import { Button, Heading, Hint, Input, OtpInput, Text } from "@medusajs/ui" import type { AuthTypes } from "@medusajs/types" import { useState } from "react" import { useTranslation } from "react-i18next" import { Link } from "react-router-dom" import { useVerifyAuthMfaChallenge } from "../../../hooks/api" const getDefaultMethod = ( methods: AuthTypes.AuthMfaChallengeMethod[] ): AuthTypes.AuthMfaChallengeMethod => { if (methods.includes("totp")) { return "totp" } return methods[0] ?? "totp" } type MfaChallengeFormProps = { challenge: AuthTypes.AuthMfaChallengeDTO onSuccess: (token: string) => void | Promise onBack?: () => void } export const MfaChallengeForm = ({ challenge, onSuccess, onBack, }: MfaChallengeFormProps) => { const { t } = useTranslation() const [method, setMethod] = useState( getDefaultMethod(challenge.methods) ) const [code, setCode] = useState("") const [error, setError] = useState(null) const [verifiedToken, setVerifiedToken] = useState(null) const [isCompleting, setIsCompleting] = useState(false) const { mutateAsync, isPending } = useVerifyAuthMfaChallenge() const isRecoveryCode = method === "recovery_code" const canUseRecoveryCode = challenge.methods.includes("recovery_code") const canVerify = isRecoveryCode ? !!code.trim() : code.length === 6 const isLoading = isPending || isCompleting const handleVerify = async (nextCode = code) => { const verificationCode = nextCode.trim() if ( !verifiedToken && (!verificationCode || (!isRecoveryCode && verificationCode.length !== 6)) ) { return } setError(null) let token = verifiedToken if (!token) { try { token = await mutateAsync({ id: challenge.id, method, code: verificationCode, }) setVerifiedToken(token) } catch (e) { setError(e instanceof Error ? e.message : t("login.mfa.verifyError")) setCode("") return } } setIsCompleting(true) try { await onSuccess(token) } catch (e) { setError(e instanceof Error ? e.message : t("login.mfa.completeError")) } finally { setIsCompleting(false) } } const handleOtpChange = (value: string) => { setCode(value) } const handleMethodChange = (nextMethod: AuthTypes.AuthMfaChallengeMethod) => { setMethod(nextMethod) setCode("") setError(null) setVerifiedToken(null) } return (
{t("login.mfa.title")} {isRecoveryCode ? t("login.mfa.recoveryDescription") : t("login.mfa.description")}
{isRecoveryCode ? ( setCode(e.target.value)} disabled={isLoading || !!verifiedToken} /> ) : ( )} {error && (
{error}
)} {onBack && ( {t("login.mfa.backToLogin")} )}
{canUseRecoveryCode && (
{isRecoveryCode ? t("login.mfa.useAuthenticatorPrompt") : t("login.mfa.useRecoveryCodePrompt")}{" "}
)}
) }