Files
casadoc-app/app/(auth)/verify.tsx
T
2026-08-26 23:01:07 +03:00

95 lines
3.0 KiB
TypeScript

import React, { useEffect, useRef, useState } from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
import { api } from '@/api';
import { useAuth } from '@/auth/AuthContext';
import { useT } from '@/i18n';
import { AuthShell } from '@/components/AuthShell';
import { Button, Field } from '@/components/ui';
import { CODE_LENGTH, CodeField } from '@/components/CodeField';
import { colors, fontSize, spacing } from '@/theme';
/** Matches the backend's resend throttle. */
const RESEND_COOLDOWN = 60;
export default function VerifyEmailScreen() {
const { email } = useLocalSearchParams<{ email: string }>();
const { signInWithVerification } = useAuth();
const t = useT();
const [code, setCode] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [cooldown, setCooldown] = useState(RESEND_COOLDOWN);
const submitted = useRef(false);
useEffect(() => {
if (cooldown <= 0) return;
const id = setTimeout(() => setCooldown((s) => s - 1), 1000);
return () => clearTimeout(id);
}, [cooldown]);
async function onVerify(value = code) {
if (value.length < CODE_LENGTH || submitted.current) return;
submitted.current = true;
setError(null);
setLoading(true);
try {
// The session starts here; the root layout moves us into the app.
await signInWithVerification(String(email), value);
} catch (e: any) {
// Wrong and expired are distinct, so the message can say which.
setError(e?.code === 'code_expired' ? t('verify.expired') : t('verify.invalid'));
setCode('');
} finally {
submitted.current = false;
setLoading(false);
}
}
async function onResend() {
if (cooldown > 0) return;
setError(null);
setCooldown(RESEND_COOLDOWN);
try {
await api.auth.resendVerification(String(email));
} catch {
// The endpoint always succeeds by design.
}
}
return (
<AuthShell
title={t('verify.title')}
subtitle={t('verify.subtitle', { email: String(email ?? '') })}
back
error={error}
>
<Field label={t('verify.code')}>
<CodeField value={code} onChange={setCode} onComplete={onVerify} autoFocus />
</Field>
<Button
title={t('verify.submit')}
size="lg"
block
loading={loading}
disabled={code.length < CODE_LENGTH}
onPress={() => onVerify()}
style={{ marginTop: spacing[2] }}
/>
<Pressable onPress={onResend} disabled={cooldown > 0} style={styles.resend}>
<Text style={[styles.resendText, cooldown > 0 && { color: colors.textMuted2 }]}>
{cooldown > 0 ? t('verify.resendIn', { count: cooldown }) : t('verify.resend')}
</Text>
</Pressable>
</AuthShell>
);
}
const styles = StyleSheet.create({
resend: { marginTop: spacing[5], alignItems: 'center' },
resendText: { fontSize: fontSize.sm, fontWeight: '600', color: colors.teal700 },
});