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"
)}