diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx index a0873bc9e1..6ebce26f45 100644 --- a/apps/web/components/booking/pages/BookingPage.tsx +++ b/apps/web/components/booking/pages/BookingPage.tsx @@ -4,7 +4,7 @@ import { useSession } from "next-auth/react"; import dynamic from "next/dynamic"; import Head from "next/head"; import { useRouter } from "next/router"; -import { useEffect, useMemo, useReducer, useState } from "react"; +import { useCallback, useEffect, useMemo, useReducer, useState } from "react"; import { useForm, useFormContext } from "react-hook-form"; import { v4 as uuidv4 } from "uuid"; import { z } from "zod"; @@ -15,6 +15,7 @@ import { getEventLocationType, locationKeyToString } from "@calcom/app-store/loc import { createPaymentLink } from "@calcom/app-store/stripepayment/lib/client"; import { getEventTypeAppData } from "@calcom/app-store/utils"; import type { LocationObject } from "@calcom/core/location"; +import type { Dayjs } from "@calcom/dayjs"; import dayjs from "@calcom/dayjs"; import { useEmbedNonStylesConfig, @@ -38,6 +39,7 @@ import useTheme from "@calcom/lib/hooks/useTheme"; import { HttpError } from "@calcom/lib/http-error"; import { getEveryFreqFor } from "@calcom/lib/recurringStrings"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; +import type { RecurringEvent } from "@calcom/types/Calendar"; import { Button, Form, Tooltip } from "@calcom/ui"; import { FiAlertTriangle, FiCalendar, FiRefreshCw, FiUser } from "@calcom/ui/components/icon"; @@ -213,6 +215,20 @@ const BookingPage = ({ duration = Number(queryDuration); } + // This is a workaround for forcing the same time format for both server side rendering and client side rendering + // At initial render, we use the default time format which is 12H + const [withDefaultTimeFormat, setWithDefaultTimeFormat] = useState(true); + const parseDateFunc = useCallback( + (date: string | null | Dayjs) => { + return parseDate(date, i18n, withDefaultTimeFormat); + }, + [withDefaultTimeFormat] + ); + // After intial render on client side, we let parseDateFunc to use the time format from the localStorage + useEffect(() => { + setWithDefaultTimeFormat(false); + }, []); + useEffect(() => { if (top !== window) { //page_view will be collected automatically by _middleware.ts @@ -370,15 +386,26 @@ const BookingPage = ({ // Calculate the booking date(s) let recurringStrings: string[] = [], recurringDates: Date[] = []; + const parseRecurringDatesFunc = useCallback( + (date: string | null | Dayjs, recurringEvent: RecurringEvent, recurringCount: number) => { + return parseRecurringDates( + { + startDate: date, + timeZone: timeZone(), + recurringEvent: recurringEvent, + recurringCount: recurringCount, + withDefaultTimeFormat: withDefaultTimeFormat, + }, + i18n + ); + }, + [withDefaultTimeFormat, date, eventType.recurringEvent, recurringEventCount] + ); if (eventType.recurringEvent?.freq && recurringEventCount !== null) { - [recurringStrings, recurringDates] = parseRecurringDates( - { - startDate: date, - timeZone: timeZone(), - recurringEvent: eventType.recurringEvent, - recurringCount: parseInt(recurringEventCount.toString()), - }, - i18n + [recurringStrings, recurringDates] = parseRecurringDatesFunc( + date, + eventType.recurringEvent, + parseInt(recurringEventCount.toString()) ); } @@ -507,7 +534,7 @@ const BookingPage = ({
{isClientTimezoneAvailable && (rescheduleUid || !eventType.recurringEvent?.freq) && - `${parseDate(date, i18n)}`} + `${parseDateFunc(date)}`} {isClientTimezoneAvailable && !rescheduleUid && eventType.recurringEvent?.freq && @@ -537,7 +564,7 @@ const BookingPage = ({ {isClientTimezoneAvailable && typeof booking.startTime === "string" && - parseDate(dayjs(booking.startTime), i18n)} + parseDateFunc(dayjs(booking.startTime))}

)} diff --git a/apps/web/lib/parseDate.ts b/apps/web/lib/parseDate.ts index 5da576358e..54b34fbbaf 100644 --- a/apps/web/lib/parseDate.ts +++ b/apps/web/lib/parseDate.ts @@ -3,21 +3,23 @@ import { RRule } from "rrule"; import type { Dayjs } from "@calcom/dayjs"; import dayjs from "@calcom/dayjs"; -import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat"; +import { detectBrowserTimeFormat, TimeFormat } from "@calcom/lib/timeFormat"; import type { RecurringEvent } from "@calcom/types/Calendar"; import { parseZone } from "./parseZone"; -const processDate = (date: string | null | Dayjs, i18n: I18n) => { +const processDate = (date: string | null | Dayjs, i18n: I18n, withDefaultTimeFormat: boolean) => { const parsedZone = parseZone(date); if (!parsedZone?.isValid()) return "Invalid date"; - const formattedTime = parsedZone?.format(detectBrowserTimeFormat); + const formattedTime = parsedZone?.format( + withDefaultTimeFormat ? TimeFormat.TWELVE_HOUR : detectBrowserTimeFormat + ); return formattedTime + ", " + dayjs(date).toDate().toLocaleString(i18n.language, { dateStyle: "full" }); }; -export const parseDate = (date: string | null | Dayjs, i18n: I18n) => { +export const parseDate = (date: string | null | Dayjs, i18n: I18n, withDefaultTimeFormat: boolean) => { if (!date) return ["No date"]; - return processDate(date, i18n); + return processDate(date, i18n, withDefaultTimeFormat); }; export const parseRecurringDates = ( @@ -26,11 +28,13 @@ export const parseRecurringDates = ( timeZone, recurringEvent, recurringCount, + withDefaultTimeFormat, }: { startDate: string | null | Dayjs; timeZone?: string; recurringEvent: RecurringEvent | null; recurringCount: number; + withDefaultTimeFormat: boolean; }, i18n: I18n ): [string[], Date[]] => { @@ -50,7 +54,7 @@ export const parseRecurringDates = ( }); const dateStrings = times.map((t) => { // finally; show in local timeZone again - return processDate(t.tz(timeZone), i18n); + return processDate(t.tz(timeZone), i18n, withDefaultTimeFormat); }); return [dateStrings, times.map((t) => t.toDate())];