From 471a6eff0d99be68e9cc95798268338a97e935aa Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 18 Jul 2024 20:31:12 +0530 Subject: [PATCH 1/4] [web] Add a hook for detecting mobile sized screens --- web/packages/base/hooks.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 web/packages/base/hooks.ts diff --git a/web/packages/base/hooks.ts b/web/packages/base/hooks.ts new file mode 100644 index 0000000000..1fd954377c --- /dev/null +++ b/web/packages/base/hooks.ts @@ -0,0 +1,10 @@ +import { useMediaQuery, useTheme } from "@mui/material"; + +/** + * Return true if the screen width is classified as a "mobile" size. + * + * We use the MUI "sm" (small, 600px) breakpoint as the cutoff. This hook will + * return true if the size of the window's width is less than 600px. + */ +export const useIsMobileWidth = () => + useMediaQuery(useTheme().breakpoints.down("sm")); From 5fa65da58e1182fbb985fa4337047c9ec9ca0bac Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 18 Jul 2024 20:39:57 +0530 Subject: [PATCH 2/4] Use --- web/packages/new/photos/components/DevSettings.tsx | 4 ++-- web/packages/new/photos/components/WhatsNew.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 36afc370d8..9327436b09 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,3 +1,4 @@ +import { useIsMobileWidth } from "@/base/hooks"; import { ensureOk } from "@/base/http"; import { getKV, removeKV, setKV } from "@/base/kv"; import log from "@/base/log"; @@ -11,7 +12,6 @@ import { InputAdornment, Link, TextField, - useMediaQuery, type ModalProps, } from "@mui/material"; import { useFormik } from "formik"; @@ -33,7 +33,7 @@ interface DevSettingsProps { * See: [Note: Configuring custom server]. */ export const DevSettings: React.FC = ({ open, onClose }) => { - const fullScreen = useMediaQuery("(max-width: 428px)"); + const fullScreen = useIsMobileWidth(); const handleDialogClose: ModalProps["onClose"] = (_, reason: string) => { // Don't close on backdrop clicks. diff --git a/web/packages/new/photos/components/WhatsNew.tsx b/web/packages/new/photos/components/WhatsNew.tsx index 693c409e3d..a43e1b9bd6 100644 --- a/web/packages/new/photos/components/WhatsNew.tsx +++ b/web/packages/new/photos/components/WhatsNew.tsx @@ -1,4 +1,5 @@ import { ensureElectron } from "@/base/electron"; +import { useIsMobileWidth } from "@/base/hooks"; import { ut } from "@/base/i18n"; import ArrowForward from "@mui/icons-material/ArrowForward"; import { @@ -10,7 +11,6 @@ import { DialogTitle, Typography, styled, - useMediaQuery, } from "@mui/material"; import React, { useEffect } from "react"; import { didShowWhatsNew } from "../services/changelog"; @@ -29,7 +29,7 @@ interface WhatsNewProps { * last time this dialog was shown. */ export const WhatsNew: React.FC = ({ open, onClose }) => { - const fullScreen = useMediaQuery("(max-width: 428px)"); + const fullScreen = useIsMobileWidth(); useEffect(() => { if (open) void didShowWhatsNew(ensureElectron()); From cf7020ab782703cb0664b121bf8db38c2ec3f5e2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 18 Jul 2024 21:08:25 +0530 Subject: [PATCH 3/4] Use prebuilt dialog box --- .../accounts/src/pages/passkeys/index.tsx | 115 +++++------------- .../shared/components/DialogBoxV2/types.ts | 28 +++++ 2 files changed, 57 insertions(+), 86 deletions(-) diff --git a/web/apps/accounts/src/pages/passkeys/index.tsx b/web/apps/accounts/src/pages/passkeys/index.tsx index fc45effc8c..09a7cd6ee4 100644 --- a/web/apps/accounts/src/pages/passkeys/index.tsx +++ b/web/apps/accounts/src/pages/passkeys/index.tsx @@ -5,7 +5,6 @@ import log from "@/base/log"; import { ensure } from "@/utils/ensure"; import { CenteredFlex } from "@ente/shared/components/Container"; import DialogBoxV2 from "@ente/shared/components/DialogBoxV2"; -import EnteButton from "@ente/shared/components/EnteButton"; import FormPaper from "@ente/shared/components/Form/FormPaper"; import InfoItem from "@ente/shared/components/Info/InfoItem"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; @@ -16,14 +15,7 @@ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; import KeyIcon from "@mui/icons-material/Key"; -import { - Box, - Button, - Stack, - Typography, - styled, - useMediaQuery, -} from "@mui/material"; +import { Box, Stack, Typography, styled, useMediaQuery } from "@mui/material"; import { t } from "i18next"; import React, { useCallback, useEffect, useState } from "react"; import { @@ -265,8 +257,34 @@ const ManagePasskeyDrawer: React.FC = ({ passkey, onUpdateOrDeletePasskey, }) => { + const { setDialogBoxAttributesV2 } = useAppContext(); + const [showRenameDialog, setShowRenameDialog] = useState(false); - const [showDeleteDialog, setShowDeleteDialog] = useState(false); + + const showDeleteConfirmationDialog = useCallback(() => { + const handleDelete = async (setLoading?: (value: boolean) => void) => { + setLoading && setLoading(true); + try { + await deletePasskey(ensure(token), ensure(passkey).id); + onUpdateOrDeletePasskey(); + } catch (e) { + log.error("Failed to delete passkey", e); + } finally { + setLoading && setLoading(false); + } + }; + + setDialogBoxAttributesV2({ + title: t("delete_passkey"), + content: t("delete_passkey_confirmation"), + proceed: { + text: t("DELETE"), + action: handleDelete, + variant: "critical", + }, + close: { text: t("CANCEL") }, + }); + }, [token, onUpdateOrDeletePasskey, passkey, setDialogBoxAttributesV2]); return ( <> @@ -297,9 +315,7 @@ const ManagePasskeyDrawer: React.FC = ({ /> { - setShowDeleteDialog(true); - }} + onClick={showDeleteConfirmationDialog} startIcon={} label={t("delete_passkey")} color="critical" @@ -321,19 +337,6 @@ const ManagePasskeyDrawer: React.FC = ({ }} /> )} - - {token && passkey && ( - setShowDeleteDialog(false)} - token={token} - passkey={passkey} - onDeletePasskey={() => { - setShowDeleteDialog(false); - onUpdateOrDeletePasskey(); - }} - /> - )} ); }; @@ -387,63 +390,3 @@ const RenamePasskeyDialog: React.FC = ({ ); }; - -interface DeletePasskeyDialogProps { - /** If `true`, then the dialog is shown. */ - open: boolean; - /** Callback to invoke when the dialog wants to be closed. */ - onClose: () => void; - /** Auth token for API requests. */ - token: string; - /** The {@link Passkey} to delete. */ - passkey: Passkey; - /** Callback to invoke when the passkey is deleted. */ - onDeletePasskey: () => void; -} - -const DeletePasskeyDialog: React.FC = ({ - open, - onClose, - token, - passkey, - onDeletePasskey, -}) => { - const [isDeleting, setIsDeleting] = useState(false); - const fullScreen = useMediaQuery("(max-width: 428px)"); - - const handleConfirm = async () => { - setIsDeleting(true); - try { - await deletePasskey(token, passkey.id); - onDeletePasskey(); - } catch (e) { - log.error("Failed to delete passkey", e); - } finally { - setIsDeleting(false); - } - }; - - return ( - - - {t("delete_passkey_confirmation")} - - {t("DELETE")} - - - - - ); -}; diff --git a/web/packages/shared/components/DialogBoxV2/types.ts b/web/packages/shared/components/DialogBoxV2/types.ts index d7db388600..1f4ae3f3ab 100644 --- a/web/packages/shared/components/DialogBoxV2/types.ts +++ b/web/packages/shared/components/DialogBoxV2/types.ts @@ -1,5 +1,12 @@ import type { ButtonProps } from "@mui/material"; +/** + * Customize the properties of the dialog box. + * + * Our custom dialog box helpers are meant for small message boxes, usually + * meant to confirm some user action. If more customization is needed, it might + * be a better idea to reach out for a bespoke MUI {@link DialogBox} instead. + */ export interface DialogBoxAttributesV2 { icon?: React.ReactNode; /** @@ -13,14 +20,35 @@ export interface DialogBoxAttributesV2 { title?: React.ReactNode; staticBackdrop?: boolean; nonClosable?: boolean; + /** + * The dialog's content. + */ content?: React.ReactNode; + /** + * Customize the cancel (dismiss) action button offered by the dialog box. + * + * Usually dialog boxes should have a cancel action, but this can be skipped + * to only show one of the other types of buttons. + */ close?: { + /** The string to use as the label for the cancel button. */ text?: string; + /** The color of the button. */ variant?: ButtonProps["color"]; + /** + * The function to call when the user cancels. + * + * If provided, this callback is invoked before closing the dialog. + */ action?: () => void; }; + /** + * Customize the primary action button offered by the dialog box. + */ proceed?: { + /** The string to use as the label for the primary action. */ text: string; + /** The function to call when the user presses the primary action button. */ action: (setLoading?: (value: boolean) => void) => void | Promise; variant?: ButtonProps["color"]; disabled?: boolean; From 82b12fcb370c1895e9375a81da66c2a5f9801277 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 18 Jul 2024 21:15:28 +0530 Subject: [PATCH 4/4] Type --- web/apps/accounts/src/pages/passkeys/index.tsx | 8 ++++---- web/packages/shared/components/DialogBoxV2/types.ts | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/web/apps/accounts/src/pages/passkeys/index.tsx b/web/apps/accounts/src/pages/passkeys/index.tsx index 09a7cd6ee4..b20c76ac1d 100644 --- a/web/apps/accounts/src/pages/passkeys/index.tsx +++ b/web/apps/accounts/src/pages/passkeys/index.tsx @@ -262,15 +262,15 @@ const ManagePasskeyDrawer: React.FC = ({ const [showRenameDialog, setShowRenameDialog] = useState(false); const showDeleteConfirmationDialog = useCallback(() => { - const handleDelete = async (setLoading?: (value: boolean) => void) => { - setLoading && setLoading(true); + const handleDelete = async (setLoading: (value: boolean) => void) => { + setLoading(true); try { await deletePasskey(ensure(token), ensure(passkey).id); onUpdateOrDeletePasskey(); } catch (e) { log.error("Failed to delete passkey", e); } finally { - setLoading && setLoading(false); + setLoading(false); } }; @@ -284,7 +284,7 @@ const ManagePasskeyDrawer: React.FC = ({ }, close: { text: t("CANCEL") }, }); - }, [token, onUpdateOrDeletePasskey, passkey, setDialogBoxAttributesV2]); + }, [token, passkey, onUpdateOrDeletePasskey, setDialogBoxAttributesV2]); return ( <> diff --git a/web/packages/shared/components/DialogBoxV2/types.ts b/web/packages/shared/components/DialogBoxV2/types.ts index 1f4ae3f3ab..cb89cb48c2 100644 --- a/web/packages/shared/components/DialogBoxV2/types.ts +++ b/web/packages/shared/components/DialogBoxV2/types.ts @@ -48,8 +48,15 @@ export interface DialogBoxAttributesV2 { proceed?: { /** The string to use as the label for the primary action. */ text: string; - /** The function to call when the user presses the primary action button. */ - action: (setLoading?: (value: boolean) => void) => void | Promise; + /** + * The function to call when the user presses the primary action button. + * + * It is passed a {@link setLoading} function that can be used to show + * or hide loading indicator or the primary action button. + */ + action: + | (() => void | Promise) + | ((setLoading: (value: boolean) => void) => void | Promise); variant?: ButtonProps["color"]; disabled?: boolean; };