mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-05 20:21:50 +02:00
✨ Detect time zone change (#1254)
This commit is contained in:
parent
acb7aa88da
commit
7255130699
5 changed files with 192 additions and 2 deletions
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
103
apps/web/src/app/[locale]/timezone-change-detector.tsx
Normal file
103
apps/web/src/app/[locale]/timezone-change-detector.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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 }) => {
|
||||||
|
|
78
apps/web/tests/timezone-change.spec.ts
Normal file
78
apps/web/tests/timezone-change.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue