"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { coerceToArrayBuffer, coerceToBase64Url } from "#/utils/base64"; import { Button, ButtonVariants } from "./Button"; import Alert, { AlertType } from "./Alert"; import { Spinner } from "./Spinner"; import { Checks } from "@zitadel/server"; import { useForm } from "react-hook-form"; import { TextInput } from "./Input"; import { Challenges } from "@zitadel/server"; // either loginName or sessionId must be provided type Props = { loginName?: string; sessionId?: string; authRequestId?: string; organization?: string; method: string; code?: string; }; type Inputs = { code: string; }; export default function LoginOTP({ loginName, sessionId, authRequestId, organization, method, code, }: Props) { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); const initialized = useRef(false); const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { code: code ? code : "", }, }); useEffect(() => { if (!initialized.current && ["email", "sms"].includes(method)) { initialized.current = true; setLoading(true); updateSessionForOTPChallenge() .then((response) => { setLoading(false); }) .catch((error) => { setError(error); setLoading(false); }); } }, []); async function updateSessionForOTPChallenge() { const challenges: Challenges = {}; if (method === "email") { challenges.otpEmail = ""; } if (method === "sms") { challenges.otpSms = ""; } setLoading(true); const res = await fetch("/api/session", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ loginName, sessionId, organization, challenges, authRequestId, }), }); setLoading(false); if (!res.ok) { const error = await res.json(); throw error.details.details; } return res.json(); } async function submitCode(values: Inputs, organization?: string) { setLoading(true); let body: any = { code: values.code, method, }; if (organization) { body.organization = organization; } if (authRequestId) { body.authRequestId = authRequestId; } const checks: Checks = {}; if (method === "sms") { checks.otpSms = { code: values.code }; } if (method === "email") { checks.otpEmail = { code: values.code }; } if (method === "time-based") { checks.totp = { code: values.code }; } const res = await fetch("/api/session", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ loginName, sessionId, organization, checks, authRequestId, }), }); setLoading(false); if (!res.ok) { const response = await res.json(); setError(response.details.details ?? "An internal error occurred"); return Promise.reject( response.details.details ?? "An internal error occurred" ); } return res.json(); } function setCodeAndContinue(values: Inputs, organization?: string) { return submitCode(values, organization).then((response) => { if (authRequestId && response && response.sessionId) { const params = new URLSearchParams({ sessionId: response.sessionId, authRequest: authRequestId, }); if (organization) { params.append("organization", organization); } return router.push(`/login?` + params); } else { const params = new URLSearchParams( authRequestId ? { loginName: response.factors.user.loginName, authRequestId, } : { loginName: response.factors.user.loginName, } ); if (organization) { params.append("organization", organization); } return router.push(`/signedin?` + params); } }); } const { errors } = formState; return (
{["email", "sms"].includes(method) && (
Did not get the Code?
)}
{error && (
{error}
)}
); }