mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-06 20:51:48 +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",
|
||||
"yahoo": "Yahoo",
|
||||
"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 React from "react";
|
||||
|
||||
import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
|
||||
import { Providers } from "@/app/providers";
|
||||
|
||||
const inter = Inter({
|
||||
|
@ -36,7 +37,10 @@ export default function Root({
|
|||
<html lang={locale} className={inter.className}>
|
||||
<body>
|
||||
<Toaster />
|
||||
<Providers>{children}</Providers>
|
||||
<Providers>
|
||||
{children}
|
||||
<TimeZoneChangeDetector />
|
||||
</Providers>
|
||||
</body>
|
||||
</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,
|
||||
isGuest: !user.email,
|
||||
tier,
|
||||
timeZone: user.timeZone ?? null,
|
||||
},
|
||||
refresh: session.update,
|
||||
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