From 7aa131f349c8c90e3227348ab513a9d2dc28030b Mon Sep 17 00:00:00 2001 From: Peer Richelsen Date: Wed, 5 Oct 2022 15:33:29 +0100 Subject: [PATCH] added TimezoneChangeDialog (#4818) * nit * added timezoneChangeDialog * prevent interactOutside * nit * fixed title prop clash * nit * remind in one day if clicked outside * Use utcOffset instead of tz string for checking diff Co-authored-by: Alex van Andel --- apps/web/pages/event-types/index.tsx | 1 + apps/web/playwright/fixtures/users.ts | 3 +- apps/web/public/static/locales/en/common.json | 9 +- packages/ui/TimezoneChangeDialog.tsx | 73 ++++++++++ packages/ui/v2/core/Dialog.tsx | 131 +++++++++--------- packages/ui/v2/core/Shell.tsx | 5 +- 6 files changed, 156 insertions(+), 66 deletions(-) create mode 100644 packages/ui/TimezoneChangeDialog.tsx diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index cc9c2e4535..935ed4910a 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -542,6 +542,7 @@ const EventTypesPage = () => { Home | Cal.com + try again or reach out to customer support.", "calendar_connection_fail": "Calendar connection failed", "booking_confirmation_success": "Booking confirmation succeeded", - "booking_confirmation_fail": "Booking confirmation failed" + "booking_confirmation_fail": "Booking confirmation failed", + "we_wont_show_again": "We won't show this again", + "couldnt_update_timezone": "We couldn't update the timezone", + "updated_timezone_to": "Updated timezone to {{formattedCurrentTz}}", + "update_timezone": "Update timezone", + "update_timezone_question": "Update Timezone?", + "update_timezone_description": "It seems like your local timezone has changed to {{formattedCurrentTz}}. It's very important to have the correct timezone to prevent bookings at undesired times. Do you want to update it?", + "dont_update": "Don't update" } diff --git a/packages/ui/TimezoneChangeDialog.tsx b/packages/ui/TimezoneChangeDialog.tsx new file mode 100644 index 0000000000..1caa49f1de --- /dev/null +++ b/packages/ui/TimezoneChangeDialog.tsx @@ -0,0 +1,73 @@ +import { useState, useEffect } from "react"; + +import dayjs from "@calcom/dayjs"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { trpc } from "@calcom/trpc/react"; +import { DialogContent, showToast } from "@calcom/ui/v2"; +import { Dialog } from "@calcom/ui/v2/core/Dialog"; + +export default function TimezoneChangeDialog() { + const { t } = useLocale(); + const { data: user, isLoading } = trpc.useQuery(["viewer.me"]); + const utils = trpc.useContext(); + const userTz = user?.timeZone; + const currentTz = dayjs.tz.guess(); + const formattedCurrentTz = currentTz?.replace("_", " "); + + // update user settings + const onSuccessMutation = async () => { + showToast(t("updated_timezone_to", { formattedCurrentTz }), "success"); + await utils.invalidateQueries(["viewer.me"]); + }; + + const onErrorMutation = () => { + showToast(t("couldnt_update_timezone"), "error"); + }; + + // update timezone in db + const mutation = trpc.useMutation("viewer.updateProfile", { + onSuccess: onSuccessMutation, + onError: onErrorMutation, + }); + + function updateTimezone() { + setOpen(false); + mutation.mutate({ + timeZone: currentTz, + }); + } + + // check for difference in user timezone and current browser timezone + const [open, setOpen] = useState(false); + useEffect(() => { + const tzDifferent = + !isLoading && dayjs.tz(undefined, currentTz).utcOffset() !== dayjs.tz(undefined, userTz).utcOffset(); + const showDialog = tzDifferent && !document.cookie.includes("calcom-timezone-dialog=1"); + setOpen(showDialog); + }, [currentTz, isLoading, 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 ( + + updateTimezone()} + closeText={t("dont_update")} + onInteractOutside={() => onCancel(86400, false) /* 1 day expire */} + actionOnClose={() => onCancel(7776000, true) /* 3 months expire */}> + {/* todo: save this in db and auto-update when timezone changes (be able to disable??? if yes, /settings) + + */} + + + ); +} diff --git a/packages/ui/v2/core/Dialog.tsx b/packages/ui/v2/core/Dialog.tsx index e2201424ad..daa9340f23 100644 --- a/packages/ui/v2/core/Dialog.tsx +++ b/packages/ui/v2/core/Dialog.tsx @@ -4,6 +4,7 @@ import React, { ReactNode, useState } from "react"; import { Icon } from "react-feather"; import classNames from "@calcom/lib/classNames"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; import Button from "./Button"; @@ -79,73 +80,77 @@ type DialogContentProps = React.ComponentProps( - ({ children, Icon, actionProps, ...props }, forwardedRef) => ( - - - {/*zIndex one less than Toast */} - - {props.type === "creation" && ( -
- {props.title && } - {props.description &&

{props.description}

} -
{children}
-
- )} - {props.type === "confirmation" && ( -
- {Icon && ( -
- -
- )} + ({ children, title, Icon, actionProps, ...props }, forwardedRef) => { + const { t } = useLocale(); + + return ( + + + {/*zIndex one less than Toast */} + + {props.type === "creation" && (
- {props.title && } - {props.description &&

{props.description}

} + {title && } + {props.description &&

{props.description}

} +
{children}
-
- )} - {!props.useOwnActionButtons && ( - -
- - {/* This will require the i18n string passed in */} - - - {props.actionOnClick ? ( - - ) : ( - + )} + {props.type === "confirmation" && ( +
+ {Icon && ( +
+ +
)} +
+ {title && } + {props.description &&

{props.description}

} +
- - )} - - - ) + )} + {!props.useOwnActionButtons && ( + +
+ + {/* This will require the i18n string passed in */} + + + {props.actionOnClick ? ( + + ) : ( + + )} +
+
+ )} + + + ); + } ); type DialogHeaderProps = { diff --git a/packages/ui/v2/core/Shell.tsx b/packages/ui/v2/core/Shell.tsx index ec9ad23a71..6c750303c3 100644 --- a/packages/ui/v2/core/Shell.tsx +++ b/packages/ui/v2/core/Shell.tsx @@ -10,7 +10,6 @@ import dayjs from "@calcom/dayjs"; import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner"; import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem"; -import UserV2OptInBanner from "@calcom/features/users/components/UserV2OptInBanner"; import CustomBranding from "@calcom/lib/CustomBranding"; import classNames from "@calcom/lib/classNames"; import { JOIN_SLACK, ROADMAP, DESKTOP_APP_LINK, WEBAPP_URL } from "@calcom/lib/constants"; @@ -27,6 +26,7 @@ import Dropdown, { DropdownMenuPortal, } from "@calcom/ui/Dropdown"; import { Icon } from "@calcom/ui/Icon"; +import TimezoneChangeDialog from "@calcom/ui/TimezoneChangeDialog"; import Button from "@calcom/ui/v2/core/Button"; /* TODO: Get this from endpoint */ @@ -136,6 +136,9 @@ const Layout = (props: LayoutProps) => {
+ {/* todo: only run this if timezone is different */} + +
{props.SidebarContainer || }