"use client"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useRouter } from "next/navigation"; import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/ui/input-otp"; import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; import { AlertTriangle } from "lucide-react"; import { DeviceAuthConfirmation } from "@/components/DeviceAuthConfirmation"; import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import BrandingLogo from "./BrandingLogo"; import { useTranslations } from "next-intl"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; const createFormSchema = (t: (key: string) => string) => z.object({ code: z.string().length(8, t("deviceCodeInvalidFormat")) }); type DeviceAuthMetadata = { ip: string | null; city: string | null; deviceName: string | null; applicationName: string; createdAt: number; }; type DeviceLoginFormProps = { userEmail: string; userName?: string; initialCode?: string; }; export default function DeviceLoginForm({ userEmail, userName, initialCode = "" }: DeviceLoginFormProps) { const router = useRouter(); const { env } = useEnvContext(); const api = createApiClient({ env }); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [metadata, setMetadata] = useState(null); const [code, setCode] = useState(""); const { isUnlocked } = useLicenseStatusContext(); const t = useTranslations(); const formSchema = createFormSchema(t); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { code: initialCode.replace(/-/g, "").toUpperCase() } }); async function onSubmit(data: z.infer) { setError(null); setLoading(true); try { // split code and add dash if missing if (!data.code.includes("-") && data.code.length === 8) { data.code = data.code.slice(0, 4) + "-" + data.code.slice(4); } await new Promise((resolve) => setTimeout(resolve, 300)); // First check - get metadata const res = await api.post( "/device-web-auth/verify?forceLogin=true", { code: data.code.toUpperCase(), verify: false } ); if (res.data.success && res.data.data.metadata) { setMetadata(res.data.data.metadata); setCode(data.code.toUpperCase()); } else { setError(t("deviceCodeInvalidOrExpired")); } } catch (e: any) { const errorMessage = formatAxiosError(e); setError(errorMessage || t("deviceCodeInvalidOrExpired")); } finally { setLoading(false); } } async function onConfirm() { if (!code || !metadata) return; setError(null); setLoading(true); try { await new Promise((resolve) => setTimeout(resolve, 300)); // Final verify await api.post("/device-web-auth/verify", { code: code, verify: true }); // Redirect to success page router.push("/auth/login/device/success"); } catch (e: any) { const errorMessage = formatAxiosError(e); setError(errorMessage || t("deviceCodeVerifyFailed")); setMetadata(null); setCode(""); form.reset(); } finally { setLoading(false); } } const logoWidth = isUnlocked() ? env.branding.logo?.authPage?.width || 175 : 175; const logoHeight = isUnlocked() ? env.branding.logo?.authPage?.height || 58 : 58; function onCancel() { setMetadata(null); setCode(""); form.reset(); setError(null); } const profileLabel = (userName || userEmail || "").trim(); const profileInitial = profileLabel ? profileLabel.charAt(0).toUpperCase() : "?"; async function handleUseDifferentAccount() { try { await api.post("/auth/logout"); } catch (logoutError) { console.error( "Failed to logout before switching account", logoutError ); } finally { const currentSearch = typeof window !== "undefined" ? window.location.search : ""; const redirectTarget = `/auth/login/device${currentSearch || ""}`; router.push( `/auth/login?forceLogin=true&redirect=${encodeURIComponent(redirectTarget)}` ); router.refresh(); } } if (metadata) { return ( ); } return (

{t("deviceActivation")}

{profileInitial}

{profileLabel || userEmail}

{t( "deviceLoginDeviceRequestingAccessToAccount" )}

{t("deviceCodeEnterPrompt")}

(
{ // Strip hyphens and convert to uppercase const cleanedValue = value .replace(/-/g, "") .toUpperCase(); field.onChange( cleanedValue ); }} >
)} /> {error && ( {error} )}
); }