'use client' import { useEffect, useState } from 'react' import { cn, Input, InputOTP, InputOTPGroup, InputOTPSlot, Label } from '@sim/emcn' import { getErrorMessage } from '@sim/utils/errors' import { normalizeEmail } from '@sim/utils/string' import { useRouter } from 'next/navigation' import { quickValidateEmail } from '@/lib/messaging/email/validation' import { AuthSubmitButton } from '@/app/(auth)/components' import { AUTH_TEXT_LINK } from '@/app/(auth)/components/auth-button-classes' import { PublicFileAuthShell } from '@/app/f/[token]/public-file-auth-shell' import { usePublicFileOtpRequest, usePublicFileOtpVerify } from '@/hooks/queries/public-shares' interface PublicFileEmailAuthProps { token: string } /** * Email-OTP gate for a protected public file share: collect an allow-listed email, * send a 6-digit code, verify it. On success the server sets the * `file_auth_{shareId}` cookie and the page re-renders the viewer. */ export function PublicFileEmailAuth({ token }: PublicFileEmailAuthProps) { const router = useRouter() const requestOtp = usePublicFileOtpRequest(token) const verifyOtp = usePublicFileOtpVerify(token) const [email, setEmail] = useState('') const [otp, setOtp] = useState('') const [sent, setSent] = useState(false) const [error, setError] = useState(null) const [countdown, setCountdown] = useState(0) useEffect(() => { if (countdown <= 0) return const timer = setTimeout(() => setCountdown((c) => c - 1), 1000) return () => clearTimeout(timer) }, [countdown]) const sendCode = async () => { if (!quickValidateEmail(normalizeEmail(email)).isValid) { setError('Please enter a valid email address.') return } setError(null) try { await requestOtp.mutateAsync({ email: normalizeEmail(email) }) setSent(true) setOtp('') } catch (err) { setError(getErrorMessage(err, 'Failed to send verification code')) } } const verifyCode = async (code: string) => { if (code.length !== 6) return setError(null) try { await verifyOtp.mutateAsync({ email: normalizeEmail(email), otp: code }) router.refresh() } catch (err) { setError(getErrorMessage(err, 'Invalid verification code')) } } const resend = async () => { setCountdown(30) try { await requestOtp.mutateAsync({ email: normalizeEmail(email) }) setOtp('') setError(null) } catch (err) { setCountdown(0) setError(getErrorMessage(err, 'Failed to resend verification code')) } } if (!sent) { return (
{ e.preventDefault() sendCode() }} className='space-y-6' >
{ setEmail(e.target.value) setError(null) }} className={cn(error && 'border-[var(--text-error)] focus:border-[var(--text-error)]')} /> {error ?

{error}

: null}
Continue
) } return (

Enter the 6-digit code to verify your access. If you don't see it in your inbox, check your spam folder.

{ setOtp(value) setError(null) if (value.length === 6) verifyCode(value) }} disabled={verifyOtp.isPending} className={cn('gap-2', error && 'otp-error')} > {[0, 1, 2, 3, 4, 5].map((i) => ( ))}
{error ?

{error}

: null} verifyCode(otp)} disabled={otp.length !== 6} loading={verifyOtp.isPending} loadingLabel='Verifying…' > Verify Email

Didn't receive a code?{' '} {countdown > 0 ? ( Resend in{' '} {countdown}s ) : ( )}

) }