diff --git a/apps/web/components/booking/pages/AvailabilityPage.tsx b/apps/web/components/booking/pages/AvailabilityPage.tsx index 7a91f5e89c..ddee5ef675 100644 --- a/apps/web/components/booking/pages/AvailabilityPage.tsx +++ b/apps/web/components/booking/pages/AvailabilityPage.tsx @@ -36,7 +36,6 @@ import { yyyymmdd } from "@calcom/lib/date-fns"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { getRecurringFreq } from "@calcom/lib/recurringStrings"; import { localStorage } from "@calcom/lib/webstorage"; -import Loader from "@calcom/ui/Loader"; import DatePicker from "@calcom/ui/booker/DatePicker"; import { asStringOrNull } from "@lib/asStringOrNull"; @@ -155,7 +154,7 @@ const SlotPicker = ({ }) => { const { selectedDate, setSelectedDate } = useDateSelected({ timeZone }); - const { i18n } = useLocale(); + const { i18n, isLocaleReady } = useLocale(); const [startDate, setStartDate] = useState(new Date()); useEffect(() => { @@ -188,7 +187,7 @@ const SlotPicker = ({ ? "sm:w-1/2 sm:border-r sm:pl-4 sm:pr-6 sm:dark:border-gray-700 md:w-1/3 " : "sm:pl-4") } - locale={i18n.language} + locale={isLocaleReady ? i18n.language : "en"} includedDates={Object.keys(slots).filter((k) => slots[k].length > 0)} selected={selectedDate} onChange={setSelectedDate} @@ -304,8 +303,8 @@ const AvailabilityPage = ({ profile, eventType }: Props) => { const router = useRouter(); const isEmbed = useIsEmbed(); const { rescheduleUid } = router.query; - const { isReady, Theme } = useTheme(profile.theme); - const { t, i18n } = useLocale(); + const { Theme } = useTheme(profile.theme); + const { t } = useLocale(); const { contracts } = useContracts(); const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker"); const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left"; @@ -354,10 +353,6 @@ const AvailabilityPage = ({ profile, eventType }: Props) => { ? "max-w-4xl" : "max-w-3xl"; - if (Object.keys(i18n).length === 0) { - return ; - } - const timezoneDropdown = ( ); @@ -381,132 +376,129 @@ const AvailabilityPage = ({ profile, eventType }: Props) => { ? classNames(maxWidth) : classNames("transition-max-width mx-auto my-0 duration-500 ease-in-out md:my-24", maxWidth) )}> - {isReady && ( -
- {/* mobile: details */} -
-
- user.name !== profile.name) - .map((user) => ({ - title: user.name, - image: `${CAL_URL}/${user.username}/avatar.png`, - alt: user.name || undefined, - })), - ].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[] - } - size={9} - truncateAfter={5} - /> -
-

- {profile.name} -

-
-

- {eventType.title} -

-
- {eventType?.description && ( -

- - {eventType.description} -

- )} - {eventType?.requiresConfirmation && ( -

- - {t("requires_confirmation")} -

- )} - {eventType.locations.length === 1 && ( -

- {Object.values(AppStoreLocationType).includes( - eventType.locations[0].type as unknown as AppStoreLocationType - ) ? ( - - ) : ( - - )} +

+ {/* mobile: details */} +
+
+ user.name !== profile.name) + .map((user) => ({ + title: user.name, + image: `${CAL_URL}/${user.username}/avatar.png`, + alt: user.name || undefined, + })), + ].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[] + } + size={9} + truncateAfter={5} + /> +
+

{profile.name}

+
+

+ {eventType.title} +

+
+ {eventType?.description && ( +

+ + {eventType.description} +

+ )} + {eventType?.requiresConfirmation && ( +

+ + {t("requires_confirmation")} +

+ )} + {eventType.locations.length === 1 && ( +

+ {Object.values(AppStoreLocationType).includes( + eventType.locations[0].type as unknown as AppStoreLocationType + ) ? ( + + ) : ( + + )} - {locationKeyToString(eventType.locations[0], t)} + {locationKeyToString(eventType.locations[0], t)} +

+ )} + {eventType.locations.length > 1 && ( +
+
+ +
+

+ {eventType.locations.map((el, i, arr) => { + return ( + + {locationKeyToString(el, t)}{" "} + {arr.length - 1 !== i && ( + {t("or_lowercase")} + )} + + ); + })}

- )} - {eventType.locations.length > 1 && ( -
-
- -
-

- {eventType.locations.map((el, i, arr) => { - return ( - - {locationKeyToString(el, t)}{" "} - {arr.length - 1 !== i && ( - {t("or_lowercase")} - )} - - ); +

+ )} +

+ + {eventType.length} {t("minutes")} +

+ {eventType.price > 0 && ( +
+ + + + +
+ )} + {!rescheduleUid && eventType.recurringEvent && ( +
+ +
+

+ {getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })} +

+ { + setRecurringEventCount(parseInt(event?.target.value)); + }} + /> +

+ {t("occurrence", { + count: recurringEventCount, })}

- )} -

- - {eventType.length} {t("minutes")} -

- {eventType.price > 0 && ( -
- - - - -
- )} - {!rescheduleUid && eventType.recurringEvent && ( -
- -
-

- {getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })} -

- { - setRecurringEventCount(parseInt(event?.target.value)); - }} - /> -

- {t("occurrence", { - count: recurringEventCount, - })} -

-
-
- )} - {timezoneDropdown} +
+ )} + {timezoneDropdown} -
- {/* Temp disabled booking?.startTime && rescheduleUid && ( +
+ {/* Temp disabled booking?.startTime && rescheduleUid && (

{

)*/} -
+
-
-
- user.name !== profile.name) - .map((user) => ({ - title: user.name, - alt: user.name, - image: `${CAL_URL}/${user.username}/avatar.png`, - })), - ].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[] - } - size={10} - truncateAfter={3} - /> -

- {profile.name} -

-

- {eventType.title} -

-
- {eventType?.description && ( -
-
- -
-

{eventType.description}

+
+
+ user.name !== profile.name) + .map((user) => ({ + title: user.name, + alt: user.name, + image: `${CAL_URL}/${user.username}/avatar.png`, + })), + ].filter((item) => !!item.image) as { image: string; alt?: string; title?: string }[] + } + size={10} + truncateAfter={3} + /> +

+ {profile.name} +

+

+ {eventType.title} +

+
+ {eventType?.description && ( +
+
+
- )} - {eventType?.requiresConfirmation && ( -
-
- -
- {t("requires_confirmation")} +

{eventType.description}

+
+ )} + {eventType?.requiresConfirmation && ( +
+
+
- )} - {eventType.locations.length === 1 && ( -

- {Object.values(AppStoreLocationType).includes( - eventType.locations[0].type as unknown as AppStoreLocationType - ) ? ( - - ) : ( - - )} + {t("requires_confirmation")} +

+ )} + {eventType.locations.length === 1 && ( +

+ {Object.values(AppStoreLocationType).includes( + eventType.locations[0].type as unknown as AppStoreLocationType + ) ? ( + + ) : ( + + )} - {locationKeyToString(eventType.locations[0], t)} + {locationKeyToString(eventType.locations[0], t)} +

+ )} + {eventType.locations.length > 1 && ( +
+
+ +
+

+ {eventType.locations.map((el, i, arr) => { + return ( + + {locationKeyToString(el, t)}{" "} + {arr.length - 1 !== i && ( + {t("or_lowercase")} + )} + + ); + })}

- )} - {eventType.locations.length > 1 && ( -
-
- -
-

- {eventType.locations.map((el, i, arr) => { - return ( - - {locationKeyToString(el, t)}{" "} - {arr.length - 1 !== i && ( - {t("or_lowercase")} - )} - - ); +

+ )} +

+ + {eventType.length} {t("minutes")} +

+ {!rescheduleUid && eventType.recurringEvent && ( +
+ +
+

+ {getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })} +

+ { + setRecurringEventCount(parseInt(event?.target.value)); + }} + /> +

+ {t("occurrence", { + count: recurringEventCount, })}

- )} -

- - {eventType.length} {t("minutes")} +

+ )} + {eventType.price > 0 && ( +

+ + + +

- {!rescheduleUid && eventType.recurringEvent && ( -
- -
-

- {getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })} -

- { - setRecurringEventCount(parseInt(event?.target.value)); - }} - /> -

- {t("occurrence", { - count: recurringEventCount, - })} -

-
-
- )} - {eventType.price > 0 && ( -

- - - - -

- )} - {timezoneDropdown} -
+ )} + {timezoneDropdown} +
- + - {/* Temporarily disabled - booking?.startTime && rescheduleUid && ( + {/* Temporarily disabled - booking?.startTime && rescheduleUid && (

{

)*/} -
-
+
- )} +
{(!eventType.users[0] || !isBrandingHidden(eventType.users[0])) && !isEmbed && }
diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index 99efce59c3..95426061fb 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -61,66 +61,73 @@ async function getUserPageProps({ username, slug }: { username: string; slug: st }, select: { id: true, + username: true, away: true, plan: true, + name: true, + hideBranding: true, + timeZone: true, eventTypes: { - // Order is important to ensure that given a slug if there are duplicates, we choose the same event type consistently when showing in event-types list UI(in terms of ordering and disabled event types) - // TODO: If we can ensure that there are no duplicates for a [slug, userId] combination in existing data, this requirement might be avoided. - orderBy: [ - { - position: "desc", - }, - { - id: "asc", - }, - ], + select: { id: true }, + }, + }, + }); + + if (!user) return { notFound: true }; + + const eventTypeIds = user.eventTypes.map((e) => e.id); + const eventTypes = await prisma.eventType.findMany({ + where: { + slug, + /* Free users can only display their first eventType */ + id: user.plan === UserPlan.PRO ? undefined : eventTypeIds[0], + AND: [{ OR: [{ userId: user.id }, { users: { some: { id: user.id } } }] }], + }, + // Order is important to ensure that given a slug if there are duplicates, we choose the same event type consistently when showing in event-types list UI(in terms of ordering and disabled event types) + // TODO: If we can ensure that there are no duplicates for a [slug, userId] combination in existing data, this requirement might be avoided. + orderBy: [ + { + position: "desc", + }, + { + id: "asc", + }, + ], + select: { + title: true, + slug: true, + recurringEvent: true, + length: true, + locations: true, + id: true, + description: true, + price: true, + currency: true, + requiresConfirmation: true, + schedulingType: true, + metadata: true, + seatsPerTimeSlot: true, + users: { select: { - title: true, - slug: true, - recurringEvent: true, - length: true, - locations: true, - id: true, - description: true, - price: true, - currency: true, - requiresConfirmation: true, - schedulingType: true, - metadata: true, - seatsPerTimeSlot: true, - users: { - select: { - name: true, - username: true, - hideBranding: true, - brandColor: true, - darkBrandColor: true, - theme: true, - plan: true, - allowDynamicBooking: true, - timeZone: true, - }, - }, + name: true, + username: true, + hideBranding: true, + brandColor: true, + darkBrandColor: true, + theme: true, + plan: true, + allowDynamicBooking: true, + timeZone: true, }, }, }, }); - if (!user) { - return { - notFound: true, - }; - } + if (!eventTypes) return { notFound: true }; - const eventType = user.eventTypes.find((et, i) => - user.plan === UserPlan.FREE ? i === 0 && et.slug === slug : et.slug === slug - ); + const [eventType] = eventTypes; - if (!eventType) { - return { - notFound: true, - }; - } + if (!eventType) return { notFound: true }; const locations = eventType.locations ? (eventType.locations as LocationObject[]) : []; @@ -128,24 +135,28 @@ async function getUserPageProps({ username, slug }: { username: string; slug: st metadata: (eventType.metadata || {}) as JSONObject, recurringEvent: parseRecurringEvent(eventType.recurringEvent), locations: locationHiddenFilter(locations), - users: eventType.users.map((user) => { - return { - name: user.name, - username: user.username, - hideBranding: user.hideBranding, - plan: user.plan, - timeZone: user.timeZone, - }; - }), + users: eventType.users.map((user) => ({ + name: user.name, + username: user.username, + hideBranding: user.hideBranding, + plan: user.plan, + timeZone: user.timeZone, + })), }); + const profile = eventType.users[0] || user; + return { props: { eventType: eventTypeObject, profile: { - ...eventType.users[0], - slug: `${eventType.users[0].username}/${eventType.slug}`, - image: `${WEBAPP_URL}/${eventType.users[0].username}/avatar.png`, + name: user.name, + username: user.username, + hideBranding: user.hideBranding, + plan: user.plan, + timeZone: user.timeZone, + slug: `${profile.username}/${eventType.slug}`, + image: `${WEBAPP_URL}/${profile.username}/avatar.png`, }, away: user?.away, isDynamic: false, diff --git a/apps/web/server/routers/viewer/slots.tsx b/apps/web/server/routers/viewer/slots.tsx index e9469d713a..ea61e29a3e 100644 --- a/apps/web/server/routers/viewer/slots.tsx +++ b/apps/web/server/routers/viewer/slots.tsx @@ -194,11 +194,7 @@ export const slotsRouter = createRouter().query("getSchedule", { }) ); const workingHours = userSchedules.flatMap((s) => s.workingHours); - console.log("workingHours", workingHours); - console.log("currentSeats", currentSeats); - const slots: Record = {}; - const availabilityCheckProps = { eventLength: eventType.length, beforeBufferTime: eventType.beforeEventBuffer, diff --git a/packages/core/getBusyTimes.ts b/packages/core/getBusyTimes.ts index b29178f2c5..d75a1b5922 100644 --- a/packages/core/getBusyTimes.ts +++ b/packages/core/getBusyTimes.ts @@ -1,8 +1,8 @@ import { BookingStatus, Credential, SelectedCalendar } from "@prisma/client"; import { getBusyCalendarTimes } from "@calcom/core/CalendarManager"; -import { getBusyVideoTimes } from "@calcom/core/videoClient"; -import notEmpty from "@calcom/lib/notEmpty"; +// import { getBusyVideoTimes } from "@calcom/core/videoClient"; +// import notEmpty from "@calcom/lib/notEmpty"; import prisma from "@calcom/prisma"; import type { EventBusyDate } from "@calcom/types/Calendar"; @@ -35,7 +35,7 @@ export async function getBusyTimes(params: { if (credentials.length > 0) { const calendarBusyTimes = await getBusyCalendarTimes(credentials, startTime, endTime, selectedCalendars); - console.log("calendarBusyTimes", calendarBusyTimes); + // console.log("calendarBusyTimes", calendarBusyTimes); busyTimes.push(...calendarBusyTimes); /* // TODO: Disabled until we can filter Zoom events by date. Also this is adding too much latency. const videoBusyTimes = (await getBusyVideoTimes(credentials)).filter(notEmpty); diff --git a/packages/lib/hooks/useLocale.ts b/packages/lib/hooks/useLocale.ts index f0611553d9..883e0a7934 100644 --- a/packages/lib/hooks/useLocale.ts +++ b/packages/lib/hooks/useLocale.ts @@ -2,9 +2,11 @@ import { useTranslation } from "next-i18next"; export const useLocale = (namespace: Parameters[0] = "common") => { const { i18n, t } = useTranslation(namespace); + const isLocaleReady = Object.keys(i18n).length > 0; return { i18n, t, + isLocaleReady, }; };