Skip to content

Commit

Permalink
fix: Impersonation flicker on timezone change (calcom#14738)
Browse files Browse the repository at this point in the history
  • Loading branch information
emrysal authored and p6l-richard committed Jul 22, 2024
1 parent 45f8013 commit eca0264
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 47 deletions.
118 changes: 72 additions & 46 deletions packages/features/settings/TimezoneChangeDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
"use client";

import { useSession } from "next-auth/react";
import { useEffect, useState } from "react";

import dayjs from "@calcom/dayjs";
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Dialog, DialogClose, DialogContent, DialogFooter, showToast } from "@calcom/ui";

export default function TimezoneChangeDialog() {
const TimezoneChangeDialogContent = ({
onAction,
browserTimezone,
}: {
browserTimezone: string;
onAction: (action?: "update" | "cancel") => void;
}) => {
const { t } = useLocale();
const { data: user, isPending } = trpc.viewer.me.useQuery();
const utils = trpc.useUtils();
const userTz = user?.timeZone;
const currentTz = dayjs.tz.guess() || "Europe/London";
const formattedCurrentTz = currentTz?.replace("_", " ");
const formattedCurrentTz = browserTimezone.replace("_", " ");

// save cookie to not show again
function onCancel(hideFor: [number, dayjs.ManipulateType], toast: boolean) {
onAction("cancel");
document.cookie = `calcom-timezone-dialog=1;max-age=${
dayjs().add(hideFor[0], hideFor[1]).unix() - dayjs().unix()
}`;
toast && showToast(t("we_wont_show_again"), "success");
}

// update user settings
const onSuccessMutation = async () => {
showToast(t("updated_timezone_to", { formattedCurrentTz }), "success");
await utils.viewer.me.invalidate();
Expand All @@ -32,55 +44,69 @@ export default function TimezoneChangeDialog() {
});

function updateTimezone() {
setOpen(false);
onAction("update");
mutation.mutate({
timeZone: currentTz,
timeZone: browserTimezone,
});
}

// check for difference in user timezone and current browser timezone
const [open, setOpen] = useState(false);
useEffect(() => {
const tzDifferent =
!isPending && dayjs.tz(undefined, currentTz).utcOffset() !== dayjs.tz(undefined, userTz).utcOffset();
const showDialog = tzDifferent && !document.cookie.includes("calcom-timezone-dialog=1");
setOpen(!IS_VISUAL_REGRESSION_TESTING && showDialog);
}, [currentTz, isPending, userTz]);

// save cookie to not show again
function onCancel(maxAge: number, toast: boolean) {
setOpen(false);
document.cookie = `calcom-timezone-dialog=1;max-age=${maxAge}`;
toast && showToast(t("we_wont_show_again"), "success");
}
return (
<DialogContent
title={t("update_timezone_question")}
description={t("update_timezone_description", { formattedCurrentTz })}
type="creation"
onInteractOutside={() => onCancel([1, "day"], false) /* 1 day expire */}>
{/* todo: save this in db and auto-update when timezone changes (be able to disable??? if yes, /settings)
<Checkbox description="Always update timezone" />
*/}
<div className="mb-8" />
<DialogFooter showDivider>
<DialogClose onClick={() => onCancel([3, "months"], true)} color="secondary">
{t("dont_update")}
</DialogClose>
<DialogClose onClick={() => updateTimezone()} color="primary">
{t("update_timezone")}
</DialogClose>
</DialogFooter>
</DialogContent>
);
};

const { data } = useSession();
export function useOpenTimezoneDialog() {
const { data: user } = trpc.viewer.me.useQuery();
const [showDialog, setShowDialog] = useState(false);
const browserTimezone = dayjs.tz.guess() || "Europe/London";
const { isLocaleReady } = useLocale();
const { data: userSession, status } = useSession();

if (data?.user.impersonatedBy) return null;
useEffect(() => {
if (
!isLocaleReady ||
!user?.timeZone ||
status !== "authenticated" ||
userSession?.user?.impersonatedBy
) {
return;
}
const cookie = document.cookie
.split(";")
.find((cookie) => cookie.trim().startsWith("calcom-timezone-dialog"));
if (
!cookie &&
dayjs.tz(undefined, browserTimezone).utcOffset() !== dayjs.tz(undefined, user.timeZone).utcOffset()
) {
setShowDialog(true);
}
}, [user, isLocaleReady, status, browserTimezone, userSession?.user?.impersonatedBy]);

const ONE_DAY = 60 * 60 * 24; // 1 day in seconds (60 seconds * 60 minutes * 24 hours)
const THREE_MONTHS = ONE_DAY * 90; // 90 days in seconds (90 days * 1 day in seconds)
return { open: showDialog, setOpen: setShowDialog, browserTimezone };
}

export default function TimezoneChangeDialog() {
const { open, setOpen, browserTimezone } = useOpenTimezoneDialog();
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent
title={t("update_timezone_question")}
description={t("update_timezone_description", { formattedCurrentTz })}
type="creation"
onInteractOutside={() => onCancel(ONE_DAY, false) /* 1 day expire */}>
{/* todo: save this in db and auto-update when timezone changes (be able to disable??? if yes, /settings)
<Checkbox description="Always update timezone" />
*/}
<div className="mb-8" />
<DialogFooter showDivider>
<DialogClose onClick={() => onCancel(THREE_MONTHS, true)} color="secondary">
{t("dont_update")}
</DialogClose>
<DialogClose onClick={() => updateTimezone()} color="primary">
{t("update_timezone")}
</DialogClose>
</DialogFooter>
</DialogContent>
<TimezoneChangeDialogContent browserTimezone={browserTimezone} onAction={() => setOpen(false)} />
</Dialog>
);
}
1 change: 0 additions & 1 deletion packages/features/shell/Shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ const Layout = (props: LayoutProps) => {
<Toaster position="bottom-right" />
</div>

{/* todo: only run this if timezone is different */}
<TimezoneChangeDialog />

<div className="flex min-h-screen flex-col">
Expand Down
18 changes: 18 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import dotEnv from "dotenv";
import * as os from "os";
import * as path from "path";

import { WEBAPP_URL } from "@calcom/lib/constants";

dotEnv.config({ path: ".env" });

const outputDir = path.join(__dirname, "test-results");
Expand Down Expand Up @@ -58,6 +60,16 @@ if (IS_EMBED_REACT_TEST) {
const DEFAULT_CHROMIUM = {
...devices["Desktop Chrome"],
timezoneId: "Europe/London",
storageState: {
cookies: [
{
url: WEBAPP_URL,
name: "calcom-timezone-dialog",
expires: -1,
value: "1",
},
],
},
locale: "en-US",
/** If navigation takes more than this, then something's wrong, let's fail fast. */
navigationTimeout: DEFAULT_NAVIGATION_TIMEOUT,
Expand Down Expand Up @@ -98,6 +110,8 @@ const config: PlaywrightTestConfig = {
expect: {
timeout: DEFAULT_EXPECT_TIMEOUT,
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS definitions for USE are wrong.
use: DEFAULT_CHROMIUM,
},
{
Expand All @@ -107,6 +121,8 @@ const config: PlaywrightTestConfig = {
expect: {
timeout: DEFAULT_EXPECT_TIMEOUT,
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS definitions for USE are wrong.
use: DEFAULT_CHROMIUM,
},
{
Expand All @@ -129,6 +145,8 @@ const config: PlaywrightTestConfig = {
timeout: DEFAULT_EXPECT_TIMEOUT,
},
testMatch: /.*\.e2e\.tsx?/,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TS definitions for USE are wrong.
use: {
...DEFAULT_CHROMIUM,
baseURL: "http://localhost:3101/",
Expand Down

0 comments on commit eca0264

Please sign in to comment.