diff --git a/apps/web/components/booking/TimeOptions.tsx b/apps/web/components/booking/TimeOptions.tsx index ee30239b..d37f2047 100644 --- a/apps/web/components/booking/TimeOptions.tsx +++ b/apps/web/components/booking/TimeOptions.tsx @@ -13,7 +13,7 @@ type Props = { onToggle24hClock: (is24hClock: boolean) => void; }; -const TimeOptions: FC = (props) => { +const TimeOptions: FC = ({ onToggle24hClock, onSelectTimeZone }) => { const [selectedTimeZone, setSelectedTimeZone] = useState(""); const [is24hClock, setIs24hClock] = useState(false); const { t } = useLocale(); @@ -25,13 +25,12 @@ const TimeOptions: FC = (props) => { useEffect(() => { if (selectedTimeZone && timeZone() && selectedTimeZone !== timeZone()) { - props.onSelectTimeZone(timeZone(selectedTimeZone)); + onSelectTimeZone(timeZone(selectedTimeZone)); } - }, [selectedTimeZone]); - + }, [selectedTimeZone, onSelectTimeZone]); const handle24hClockToggle = (is24hClock: boolean) => { setIs24hClock(is24hClock); - props.onToggle24hClock(is24h(is24hClock)); + onToggle24hClock(is24h(is24hClock)); }; return selectedTimeZone !== "" ? ( diff --git a/apps/web/components/booking/pages/AvailabilityPage.tsx b/apps/web/components/booking/pages/AvailabilityPage.tsx index 0691917d..c8f426a3 100644 --- a/apps/web/components/booking/pages/AvailabilityPage.tsx +++ b/apps/web/components/booking/pages/AvailabilityPage.tsx @@ -15,6 +15,7 @@ import { useLocale } from "@lib/hooks/useLocale"; import useTheme from "@lib/hooks/useTheme"; import { isBrandingHidden } from "@lib/isBrandingHidden"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry"; +import { detectBrowserTimeFormat } from "@lib/timeFormat"; import CustomBranding from "@components/CustomBranding"; import AvailableTimes from "@components/booking/AvailableTimes"; @@ -62,11 +63,13 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: Props) => { }, [router.query.date]); const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false); - const [timeFormat, setTimeFormat] = useState("h:mma"); + const [timeFormat, setTimeFormat] = useState(detectBrowserTimeFormat); + const telemetry = useTelemetry(); useEffect(() => { handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true"); + telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters())); }, [telemetry]); diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx index 68e9c309..2d327161 100644 --- a/apps/web/components/booking/pages/BookingPage.tsx +++ b/apps/web/components/booking/pages/BookingPage.tsx @@ -24,6 +24,7 @@ import createBooking from "@lib/mutations/bookings/create-booking"; import { parseZone } from "@lib/parseZone"; import slugify from "@lib/slugify"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry"; +import { detectBrowserTimeFormat } from "@lib/timeFormat"; import CustomBranding from "@components/CustomBranding"; import { EmailInput, Form } from "@components/form/fields"; @@ -110,9 +111,7 @@ const BookingPage = (props: BookingPageProps) => { const rescheduleUid = router.query.rescheduleUid as string; const { isReady, Theme } = useTheme(props.profile.theme); - const date = asStringOrNull(router.query.date); - const timeFormat = asStringOrNull(router.query.clock) === "24h" ? "H:mm" : "h:mma"; const [guestToggle, setGuestToggle] = useState(props.booking && props.booking.attendees.length > 1); @@ -213,7 +212,7 @@ const BookingPage = (props: BookingPageProps) => { if (!date) return "No date"; const parsedZone = parseZone(date); if (!parsedZone?.isValid()) return "Invalid date"; - const formattedTime = parsedZone?.format(timeFormat); + const formattedTime = parsedZone?.format(detectBrowserTimeFormat); return formattedTime + ", " + dayjs(date).toDate().toLocaleString(i18n.language, { dateStyle: "full" }); }; diff --git a/apps/web/ee/components/stripe/PaymentPage.tsx b/apps/web/ee/components/stripe/PaymentPage.tsx index 0587031c..28ac7334 100644 --- a/apps/web/ee/components/stripe/PaymentPage.tsx +++ b/apps/web/ee/components/stripe/PaymentPage.tsx @@ -14,6 +14,7 @@ import { PaymentPageProps } from "@ee/pages/payment/[uid]"; import { useLocale } from "@lib/hooks/useLocale"; import useTheme from "@lib/hooks/useTheme"; +import { isBrowserLocale24h } from "@lib/timeFormat"; dayjs.extend(utc); dayjs.extend(toArray); @@ -21,7 +22,7 @@ dayjs.extend(timezone); const PaymentPage: FC = (props) => { const { t } = useLocale(); - const [is24h, setIs24h] = useState(false); + const [is24h, setIs24h] = useState(isBrowserLocale24h()); const [date, setDate] = useState(dayjs.utc(props.booking.startTime)); const { isReady, Theme } = useTheme(props.profile.theme); diff --git a/apps/web/lib/clock.ts b/apps/web/lib/clock.ts index 59fb55e7..75b5939c 100644 --- a/apps/web/lib/clock.ts +++ b/apps/web/lib/clock.ts @@ -3,6 +3,8 @@ import dayjs from "dayjs"; import timezone from "dayjs/plugin/timezone"; import utc from "dayjs/plugin/utc"; +import { isBrowserLocale24h } from "./timeFormat"; + dayjs.extend(utc); dayjs.extend(timezone); @@ -22,6 +24,8 @@ const initClock = () => { if (typeof localStorage === "undefined" || isInitialized) { return; } + // This only sets browser locale if there's no preference on localStorage. + if (!localStorage || !localStorage.getItem("timeOption.is24hClock")) set24hClock(isBrowserLocale24h()); timeOptions.is24hClock = localStorage.getItem("timeOption.is24hClock") === "true"; timeOptions.inviteeTimeZone = localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess(); }; diff --git a/apps/web/lib/timeFormat.ts b/apps/web/lib/timeFormat.ts new file mode 100644 index 00000000..6ce6408b --- /dev/null +++ b/apps/web/lib/timeFormat.ts @@ -0,0 +1,12 @@ +/* + * Detects navigator locale 24h time preference + * It works by checking whether hour output contains AM ('1 AM' or '01 h') + * based on the user's preferred language + * defaults to 'en-US' (12h) if no navigator language is found + */ +export const isBrowserLocale24h = () => { + let locale = "en-US"; + if (process.browser && navigator) locale = navigator?.language; + return !new Intl.DateTimeFormat(locale, { hour: "numeric" }).format(0).match(/AM/); +}; +export const detectBrowserTimeFormat = isBrowserLocale24h() ? "H:mm" : "h:mma"; diff --git a/apps/web/pages/cancel/[uid].tsx b/apps/web/pages/cancel/[uid].tsx index 41b1d89f..3615bb19 100644 --- a/apps/web/pages/cancel/[uid].tsx +++ b/apps/web/pages/cancel/[uid].tsx @@ -9,6 +9,7 @@ import { getSession } from "@lib/auth"; import { useLocale } from "@lib/hooks/useLocale"; import prisma from "@lib/prisma"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry"; +import { detectBrowserTimeFormat } from "@lib/timeFormat"; import { inferSSRProps } from "@lib/types/inferSSRProps"; import CustomBranding from "@components/CustomBranding"; @@ -23,7 +24,6 @@ export default function Type(props: inferSSRProps) { // Get router variables const router = useRouter(); const { uid } = router.query; - const [is24h] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(props.booking ? null : t("booking_already_cancelled")); const [cancellationReason, setCancellationReason] = useState(""); @@ -84,7 +84,7 @@ export default function Type(props: inferSSRProps) {

{dayjs(props.booking?.startTime).format( - (is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY" + detectBrowserTimeFormat + ", dddd DD MMMM YYYY" )}

diff --git a/apps/web/pages/success.tsx b/apps/web/pages/success.tsx index e374e2c0..3697cd4d 100644 --- a/apps/web/pages/success.tsx +++ b/apps/web/pages/success.tsx @@ -16,6 +16,7 @@ import { useLocale } from "@lib/hooks/useLocale"; import useTheme from "@lib/hooks/useTheme"; import { isBrandingHidden } from "@lib/isBrandingHidden"; import prisma from "@lib/prisma"; +import { isBrowserLocale24h } from "@lib/timeFormat"; import { inferSSRProps } from "@lib/types/inferSSRProps"; import CustomBranding from "@components/CustomBranding"; @@ -34,8 +35,8 @@ export default function Success(props: inferSSRProps) const router = useRouter(); const { location: _location, name, reschedule } = router.query; const location = Array.isArray(_location) ? _location[0] : _location; + const [is24h, setIs24h] = useState(isBrowserLocale24h()); - const [is24h, setIs24h] = useState(false); const [date, setDate] = useState(dayjs.utc(asStringOrThrow(router.query.date))); const { isReady, Theme } = useTheme(props.profile.theme); diff --git a/apps/web/pages/video/meeting-ended/[uid].tsx b/apps/web/pages/video/meeting-ended/[uid].tsx index 30a63905..19daaaf4 100644 --- a/apps/web/pages/video/meeting-ended/[uid].tsx +++ b/apps/web/pages/video/meeting-ended/[uid].tsx @@ -4,10 +4,10 @@ import dayjs from "dayjs"; import { NextPageContext } from "next"; import { getSession } from "next-auth/react"; import { useRouter } from "next/router"; -import { useState } from "react"; import { useEffect } from "react"; import prisma from "@lib/prisma"; +import { detectBrowserTimeFormat } from "@lib/timeFormat"; import { inferSSRProps } from "@lib/types/inferSSRProps"; import { HeadSeo } from "@components/seo/head-seo"; @@ -15,10 +15,7 @@ import Button from "@components/ui/Button"; export default function MeetingUnavailable(props: inferSSRProps) { const router = useRouter(); - - const [is24h, setIs24h] = useState(false); - - //if no booking redirectis to the 404 page + // if no booking redirectis to the 404 page const emptyBooking = props.booking === null; useEffect(() => { if (emptyBooking) { @@ -57,7 +54,7 @@ export default function MeetingUnavailable(props: inferSSRProps {dayjs(props.booking.startTime).format( - (is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY" + detectBrowserTimeFormat + ", dddd DD MMMM YYYY" )}

diff --git a/apps/web/pages/video/meeting-not-started/[uid].tsx b/apps/web/pages/video/meeting-not-started/[uid].tsx index 65dc0569..7e5268ff 100644 --- a/apps/web/pages/video/meeting-not-started/[uid].tsx +++ b/apps/web/pages/video/meeting-not-started/[uid].tsx @@ -4,10 +4,10 @@ import dayjs from "dayjs"; import { NextPageContext } from "next"; import { getSession } from "next-auth/react"; import { useRouter } from "next/router"; -import { useState } from "react"; import { useEffect } from "react"; import prisma from "@lib/prisma"; +import { detectBrowserTimeFormat } from "@lib/timeFormat"; import { inferSSRProps } from "@lib/types/inferSSRProps"; import { HeadSeo } from "@components/seo/head-seo"; @@ -23,7 +23,6 @@ export default function MeetingNotStarted(props: inferSSRProps @@ -56,7 +55,7 @@ export default function MeetingNotStarted(props: inferSSRProps {dayjs(props.booking.startTime).format( - (is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY" + detectBrowserTimeFormat + ", dddd DD MMMM YYYY" )}