Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Impersonation flicker on timezone change #14738

Merged
merged 3 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading