import { checkPasskeyVerificationStatus, passkeySessionExpiredErrorMessage, saveCredentialsAndNavigateTo, } from "@/accounts/services/passkey"; import type { MiniDialogAttributes } from "@/base/components/MiniDialog"; import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton"; import log from "@/base/log"; import { customAPIHost } from "@/base/origins"; import { VerticallyCentered } from "@ente/shared/components/Container"; import FormPaper from "@ente/shared/components/Form/FormPaper"; import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer"; import LinkButton from "@ente/shared/components/LinkButton"; import { CircularProgress, Stack, Typography, styled } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; export const PasswordHeader: React.FC = ({ children, }) => { return ( {t("password")} {children} ); }; const PasskeyHeader: React.FC = ({ children }) => { return ( {"Passkey"} {children} ); }; const Header_ = styled("div")` margin-block-end: 4rem; display: flex; flex-direction: column; gap: 8px; `; export const LoginFlowFormFooter: React.FC = ({ children, }) => { const [host, setHost] = useState(); useEffect(() => void customAPIHost().then(setHost), []); return ( {children} {host && ( {host} )} ); }; interface VerifyingPasskeyProps { /** ID of the current passkey verification session. */ passkeySessionID: string; /** The email of the user whose passkey we're verifying. */ email: string | undefined; /** Called when the user wants to redirect again. */ onRetry: () => void; /** Perform the (possibly app specific) logout sequence. */ logout: () => void; setDialogBoxAttributesV2: (attrs: MiniDialogAttributes) => void; } export const VerifyingPasskey: React.FC = ({ passkeySessionID, email, onRetry, logout, setDialogBoxAttributesV2, }) => { type VerificationStatus = "waiting" | "checking" | "pending"; const [verificationStatus, setVerificationStatus] = useState("waiting"); const router = useRouter(); const handleRetry = () => { setVerificationStatus("waiting"); onRetry(); }; const handleCheckStatus = async () => { setVerificationStatus("checking"); try { const response = await checkPasskeyVerificationStatus(passkeySessionID); if (!response) setVerificationStatus("pending"); else router.push(await saveCredentialsAndNavigateTo(response)); } catch (e) { log.error("Passkey verification status check failed", e); setDialogBoxAttributesV2( e instanceof Error && e.message == passkeySessionExpiredErrorMessage ? sessionExpiredDialogAttributes(logout) : genericErrorAttributes(), ); setVerificationStatus("waiting"); } }; const handleRecover = () => { router.push("/passkeys/recover"); }; return ( {email ?? ""} {verificationStatus == "checking" ? ( ) : ( {verificationStatus == "waiting" ? t("waiting_for_verification") : t("verification_still_pending")} )} {t("try_again")} {t("check_status")} {t("RECOVER_ACCOUNT")} {t("CHANGE_EMAIL")} ); }; const VerifyingPasskeyMiddle = styled("div")` display: flex; flex-direction: column; padding-block: 1rem; gap: 4rem; `; const VerifyingPasskeyStatus = styled("div")` text-align: center; /* Size of the CircularProgress (+ some margin) so that there is no layout shift when it is shown */ min-height: 2em; `; const ButtonStack = styled("div")` display: flex; flex-direction: column; gap: 1rem; `; /** * {@link DialogBoxAttributesV2} for showing the error when the user's session * has expired. * * It asks them to login again. There is one button, which allows them to * logout. * * @param onLogin Called when the user presses the "Login" button on the error * dialog. */ export const sessionExpiredDialogAttributes = ( onLogin: () => void, ): MiniDialogAttributes => ({ title: t("SESSION_EXPIRED"), message: t("SESSION_EXPIRED_MESSAGE"), nonClosable: true, continue: { text: t("login"), action: onLogin, }, cancel: false, }); /** * {@link DialogBoxAttributesV2} for showing a generic error. */ const genericErrorAttributes = (): MiniDialogAttributes => ({ title: t("error"), close: { variant: "critical" }, message: t("generic_error_retry"), });