Detect time zone change (#1254)

This commit is contained in:
Luke Vella 2024-08-10 18:17:47 +01:00 committed by GitHub
parent acb7aa88da
commit 7255130699
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 192 additions and 2 deletions

View file

@ -269,5 +269,9 @@
"outlook": "Outlook", "outlook": "Outlook",
"yahoo": "Yahoo", "yahoo": "Yahoo",
"downloadICSFile": "Download ICS File", "downloadICSFile": "Download ICS File",
"schedulateDate": "Scheduled Date" "schedulateDate": "Scheduled Date",
"timeZoneChangeDetectorTitle": "Timezone Change Detected",
"timeZoneChangeDetectorMessage": "Your timezone has changed to {currentTimeZone}. Do you want to update your preferences?",
"yesUpdateTimezone": "Yes, update my timezone",
"noKeepCurrentTimezone": "No, keep the current timezone"
} }

View file

@ -7,6 +7,7 @@ import { Viewport } from "next";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import React from "react"; import React from "react";
import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
import { Providers } from "@/app/providers"; import { Providers } from "@/app/providers";
const inter = Inter({ const inter = Inter({
@ -36,7 +37,10 @@ export default function Root({
<html lang={locale} className={inter.className}> <html lang={locale} className={inter.className}>
<body> <body>
<Toaster /> <Toaster />
<Providers>{children}</Providers> <Providers>
{children}
<TimeZoneChangeDetector />
</Providers>
</body> </body>
</html> </html>
); );

View file

@ -0,0 +1,103 @@
"use client";
import { Button } from "@rallly/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@rallly/ui/dialog";
import { usePostHog } from "posthog-js/react";
import React, { useState } from "react";
import { Trans } from "@/components/trans";
import { usePreferences } from "@/contexts/preferences";
import { getBrowserTimeZone } from "@/utils/date-time-utils";
export function TimeZoneChangeDetector() {
const { preferences, updatePreferences } = usePreferences();
const [previousTimeZone, setPreviousTimeZone] = useState(() => {
try {
const cachedPreviousTimeZone = localStorage.getItem("previousTimeZone");
if (cachedPreviousTimeZone) {
return cachedPreviousTimeZone;
}
} catch (e) {
console.error(e);
}
const timeZone = preferences.timeZone ?? getBrowserTimeZone();
try {
localStorage.setItem("previousTimeZone", timeZone);
} catch (e) {
console.error(e);
}
return timeZone;
});
const currentTimeZone = getBrowserTimeZone();
const [open, setOpen] = useState(false);
const posthog = usePostHog();
React.useEffect(() => {
if (previousTimeZone !== currentTimeZone) {
posthog?.capture("timezone change detected");
setOpen(true);
}
}, [previousTimeZone, currentTimeZone, posthog]);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans
i18nKey="timeZoneChangeDetectorTitle"
defaults="Timezone Change Detected"
/>
</DialogTitle>
</DialogHeader>
<p className="text-muted-foreground text-sm">
<Trans
i18nKey="timeZoneChangeDetectorMessage"
defaults="Your timezone has changed to {currentTimeZone}. Do you want to update your preferences?"
values={{ currentTimeZone }}
/>
</p>
<DialogFooter>
<Button
variant="primary"
onClick={() => {
localStorage.setItem("previousTimeZone", currentTimeZone);
updatePreferences({ timeZone: currentTimeZone });
setOpen(false);
posthog?.capture("timezone change accepted");
}}
>
<Trans
i18nKey="yesUpdateTimezone"
defaults="Yes, update my timezone"
/>
</Button>
<Button
onClick={() => {
setPreviousTimeZone(currentTimeZone);
localStorage.setItem("previousTimeZone", currentTimeZone);
setOpen(false);
posthog?.capture("timezone change rejected");
}}
>
<Trans
i18nKey="noKeepCurrentTimezone"
defaults="No, keep the current timezone"
/>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View file

@ -78,6 +78,7 @@ export const UserProvider = (props: { children?: React.ReactNode }) => {
email: user.email || null, email: user.email || null,
isGuest: !user.email, isGuest: !user.email,
tier, tier,
timeZone: user.timeZone ?? null,
}, },
refresh: session.update, refresh: session.update,
ownsObject: ({ userId }) => { ownsObject: ({ userId }) => {

View file

@ -0,0 +1,78 @@
import { expect, test } from "@playwright/test";
test.describe("Timezone change", () => {
test("should show a dialog when the timezone changes", async ({ page }) => {
await page.goto("/");
await page.evaluate(() => {
localStorage.setItem("previousTimeZone", "some other timezone");
});
await page.reload();
const dialog = page.locator("text=Timezone Change Detected");
await expect(dialog).toBeVisible();
});
test("should not show a dialog when the timezone does not change", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
localStorage.setItem(
"previousTimeZone",
Intl.DateTimeFormat().resolvedOptions().timeZone,
);
});
await page.reload();
const dialog = page.locator("text=Timezone Change Detected");
await expect(dialog).toBeHidden();
});
test("should not show dialog after user accepts a change", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
localStorage.setItem("previousTimeZone", "some other timezone");
});
await page.reload();
await page.waitForSelector("text=Timezone Change Detected");
await page.click("text=Yes, update my timezone");
await page.reload();
const dialog = page.locator("text=Timezone Change Detected");
await expect(dialog).toBeHidden();
});
test("should not show dialog after user declines a change", async ({
page,
}) => {
await page.goto("/");
await page.evaluate(() => {
localStorage.setItem("previousTimeZone", "some other timezone");
});
await page.reload();
await page.waitForSelector("text=Timezone Change Detected");
await page.click("text=No, keep the current timezone");
await page.reload();
const dialog = page.locator("text=Timezone Change Detected");
await expect(dialog).toBeHidden();
});
test.describe("when localStorage is not available", () => {
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
Object.defineProperty(window, "localStorage", {
value: undefined,
writable: true,
});
});
});
test("should not show a dialog when the timezone changes", async ({
page,
}) => {
await page.goto("/");
await page.reload();
const dialog = page.locator("text=Timezone Change Detected");
await expect(dialog).toBeHidden();
});
});
});