mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* refactor: move Booker hooks from packages/features to apps/web/modules Migrate the following Booker hooks: - useOverlayCalendar - useSkipConfirmStep - useInitializeWeekStart - useIsQuickAvailabilityCheckFeatureEnabled - useDecoyBooking These hooks are only imported by apps/web files, making them safe to move without creating circular dependencies. Part of the tRPC-driven UI migration from packages/features to apps/web/modules. Co-Authored-By: [email protected] <[email protected]> * fix: revert useIsQuickAvailabilityCheckFeatureEnabled move and fix import paths - Reverted useIsQuickAvailabilityCheckFeatureEnabled.ts back to packages/features since it's imported by useSlots.ts in packages/features (would create circular dependency) - Fixed import paths in useOverlayCalendar.ts to use @calcom/features paths - Fixed import paths in useSkipConfirmStep.ts to use @calcom/features paths - Updated Booker.tsx to import useIsQuickAvailabilityCheckFeatureEnabled from original location Co-Authored-By: [email protected] <[email protected]> * fix: use @calcom/web alias for hook imports to fix vitest resolution Co-Authored-By: [email protected] <[email protected]> * refactor * migrate more hooks * fix * mv types * fix * fix * fix * fix * revert: remove formatting-only changes to make PR easier to review Co-Authored-By: [email protected] <[email protected]> * fix * fix * fix * revert: restore original import ordering in moved hooks Co-Authored-By: [email protected] <[email protected]> * fix imports * fix * fix * wip * wip * wip * wip * wip * wip * fix: add isBrowser guard to useSlotsViewOnSmallScreen hook Address Cubic AI review feedback (confidence 9/10) by adding the isBrowser guard to the useSlotsViewOnSmallScreen hook to follow the file's client-only contract and avoid executing during SSR/prerendering. Co-Authored-By: unknown <> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
140 lines
4.8 KiB
TypeScript
140 lines
4.8 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
|
|
import { useBookerStoreContext } from "@calcom/features/bookings/Booker/BookerStoreProvider";
|
|
import type { BookerEvent } from "@calcom/features/bookings/types";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { parseRecurringDates } from "@calcom/lib/parse-dates";
|
|
import { getRecurringFreq } from "@calcom/lib/recurringStrings";
|
|
import { getTimeShiftFlags, getFirstShiftFlags } from "@calcom/lib/timeShift";
|
|
import { Alert } from "@calcom/ui/components/alert";
|
|
import { Badge } from "@calcom/ui/components/badge";
|
|
import { Input } from "@calcom/ui/components/form";
|
|
import { Tooltip } from "@calcom/ui/components/tooltip";
|
|
|
|
import { useBookerTime } from "@calcom/features/bookings/Booker/hooks/useBookerTime";
|
|
|
|
export const EventOccurences = ({ event }: { event: Pick<BookerEvent, "recurringEvent"> }) => {
|
|
const maxOccurences = event.recurringEvent?.count || null;
|
|
const { t, i18n } = useLocale();
|
|
const [
|
|
setRecurringEventCount,
|
|
recurringEventCount,
|
|
setRecurringEventCountQueryParam,
|
|
recurringEventCountQueryParam,
|
|
] = useBookerStoreContext((state) => [
|
|
state.setRecurringEventCount,
|
|
state.recurringEventCount,
|
|
state.setRecurringEventCountQueryParam,
|
|
state.recurringEventCountQueryParam,
|
|
]);
|
|
const selectedTimeslot = useBookerStoreContext((state) => state.selectedTimeslot);
|
|
const bookerState = useBookerStoreContext((state) => state.state);
|
|
const { timezone, timeFormat } = useBookerTime();
|
|
const [warning, setWarning] = useState(false);
|
|
|
|
const validateAndSetRecurringEventCount = (value: number | string) => {
|
|
const inputValue = parseInt(value as string);
|
|
const isValid =
|
|
!isNaN(inputValue) && inputValue >= 1 && maxOccurences !== null && inputValue <= maxOccurences;
|
|
|
|
if (isValid) {
|
|
setRecurringEventCount(inputValue);
|
|
setWarning(false);
|
|
} else {
|
|
setRecurringEventCount(maxOccurences);
|
|
setWarning(true);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!event.recurringEvent?.count) return;
|
|
|
|
if (recurringEventCountQueryParam) {
|
|
validateAndSetRecurringEventCount(recurringEventCountQueryParam);
|
|
} else {
|
|
setRecurringEventCount(maxOccurences);
|
|
setRecurringEventCountQueryParam(maxOccurences);
|
|
}
|
|
}, [setRecurringEventCount, event.recurringEvent, recurringEventCount, recurringEventCountQueryParam]);
|
|
if (!event.recurringEvent) return null;
|
|
|
|
if (bookerState === "booking" && recurringEventCount && selectedTimeslot) {
|
|
const [recurringStrings, recurringDates] = parseRecurringDates(
|
|
{
|
|
startDate: selectedTimeslot,
|
|
timeZone: timezone,
|
|
recurringEvent: event.recurringEvent,
|
|
recurringCount: recurringEventCount,
|
|
selectedTimeFormat: timeFormat,
|
|
},
|
|
i18n.language
|
|
);
|
|
const shiftFlags = getTimeShiftFlags({ dates: recurringDates, timezone });
|
|
const displayFlags = getFirstShiftFlags(shiftFlags);
|
|
return (
|
|
<div data-testid="recurring-dates">
|
|
{recurringStrings.slice(0, 5).map((timeFormatted, key) => (
|
|
<p key={key}>
|
|
{timeFormatted}
|
|
{displayFlags[key] && (
|
|
<>
|
|
{" "}
|
|
<Badge variant="orange" size="sm">
|
|
{t("time_shift")}
|
|
</Badge>
|
|
</>
|
|
)}
|
|
</p>
|
|
))}
|
|
{recurringStrings.length > 5 && (
|
|
<Tooltip
|
|
content={recurringStrings.slice(5).map((timeFormatted, key) => (
|
|
<p key={key}>
|
|
{timeFormatted}
|
|
{displayFlags[key + 5] && (
|
|
<>
|
|
{" "}
|
|
<Badge variant="orange" size="sm">
|
|
{t("time_shift")}
|
|
</Badge>
|
|
</>
|
|
)}
|
|
</p>
|
|
))}>
|
|
<p className=" text-sm">+ {t("plus_more", { count: recurringStrings.length - 5 })}</p>
|
|
</Tooltip>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{getRecurringFreq({ t, recurringEvent: event.recurringEvent })}
|
|
<br />
|
|
<Input
|
|
className="my-1 mr-3 inline-flex h-[26px] w-[46px] px-1 py-0"
|
|
type="number"
|
|
min="1"
|
|
max={event.recurringEvent.count}
|
|
defaultValue={recurringEventCountQueryParam || event.recurringEvent.count}
|
|
data-testid="occurrence-input"
|
|
onChange={(event) => {
|
|
const inputValue = parseInt(event.target.value);
|
|
setRecurringEventCountQueryParam(inputValue);
|
|
validateAndSetRecurringEventCount(event.target.value);
|
|
}}
|
|
/>
|
|
|
|
{t("occurrence", {
|
|
count: recurringEventCount || event.recurringEvent.count,
|
|
})}
|
|
{warning && (
|
|
<div className="-ml-4 mr-4 mt-2 flex">
|
|
<Alert severity="warning" title={t("enter_number_between_range", { maxOccurences })} />
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|