Detect users browser locale for time format 12/24 hours (#1900)
* fix: adds new isBrowserLocal24h timeFormat util, uses in BookingPage * fix: adds new time format locale detector in available times * fix: removes 24h clock from availabletimes * chore: move timeFormat to lib util, add to payment page * chore: remove commented out is24h * fix: adds timeFormat to success page * fix: adds timeFormat to cancel page * fix: adds timeFormat to video meeting ended/not started pages * fix: removes added typo in success page * fix: reverts back to use of is24h Switch in available times / time options, renames timeFormat to detectBrowserTimeFormat to avoid collisions * fix: moves use uf isBrowserLocal24h() to clock util initClock() itself, by calling it only if no localStorage settings are set * chore: move back timeFormat props to line it was so no change * chore: remove empty line in timeOptions * chore: move back timeFormat where it was in TimeOptions props * chore: add back empty line before selectedTimeZone return * fix: reverts back to use of is24h in payments page * feat: adds browser locale as default in payment page in case there's no settings set by the customer * feat: adds browser locale as default in success page * fix: deconstruct props so eslint passes * fix: lint issue for extra empty line in meeting-ended uid page Co-authored-by: Agusti Fernandez <git@agusti.me> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
5eca42bb45
commit
b860a79d59
10 changed files with 37 additions and 22 deletions
|
@ -13,7 +13,7 @@ type Props = {
|
|||
onToggle24hClock: (is24hClock: boolean) => void;
|
||||
};
|
||||
|
||||
const TimeOptions: FC<Props> = (props) => {
|
||||
const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone }) => {
|
||||
const [selectedTimeZone, setSelectedTimeZone] = useState("");
|
||||
const [is24hClock, setIs24hClock] = useState(false);
|
||||
const { t } = useLocale();
|
||||
|
@ -25,13 +25,12 @@ const TimeOptions: FC<Props> = (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 !== "" ? (
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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" });
|
||||
};
|
||||
|
||||
|
|
|
@ -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<PaymentPageProps> = (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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
12
apps/web/lib/timeFormat.ts
Normal file
12
apps/web/lib/timeFormat.ts
Normal file
|
@ -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";
|
|
@ -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<typeof getServerSideProps>) {
|
|||
// Get router variables
|
||||
const router = useRouter();
|
||||
const { uid } = router.query;
|
||||
const [is24h] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(props.booking ? null : t("booking_already_cancelled"));
|
||||
const [cancellationReason, setCancellationReason] = useState<string>("");
|
||||
|
@ -84,7 +84,7 @@ export default function Type(props: inferSSRProps<typeof getServerSideProps>) {
|
|||
<p className="text-gray-500">
|
||||
<CalendarIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
|
||||
{dayjs(props.booking?.startTime).format(
|
||||
(is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY"
|
||||
detectBrowserTimeFormat + ", dddd DD MMMM YYYY"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -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<typeof getServerSideProps>)
|
|||
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);
|
||||
|
||||
|
|
|
@ -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<typeof getServerSideProps>) {
|
||||
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<typeof getServer
|
|||
<p className="text-center text-gray-500">
|
||||
<CalendarIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
|
||||
{dayjs(props.booking.startTime).format(
|
||||
(is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY"
|
||||
detectBrowserTimeFormat + ", dddd DD MMMM YYYY"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -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<typeof getServerS
|
|||
router.push("/video/no-meeting-found");
|
||||
}
|
||||
});
|
||||
const [is24h, setIs24h] = useState(false);
|
||||
if (!emptyBooking) {
|
||||
return (
|
||||
<div>
|
||||
|
@ -56,7 +55,7 @@ export default function MeetingNotStarted(props: inferSSRProps<typeof getServerS
|
|||
<p className="text-center text-gray-500">
|
||||
<CalendarIcon className="mr-1 -mt-1 inline-block h-4 w-4" />
|
||||
{dayjs(props.booking.startTime).format(
|
||||
(is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY"
|
||||
detectBrowserTimeFormat + ", dddd DD MMMM YYYY"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue