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

121 lines
4.1 KiB
TypeScript

import React, { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { useRouter } from 'expo-router';
import { api } from '@/api';
import { useT } from '@/i18n';
import { AuthShell } from '@/components/AuthShell';
import { Button, Field, TextField } from '@/components/ui';
import { colors, fontSize, spacing } from '@/theme';
/** Enforced by the backend; keep the two in step. */
const MIN_PASSWORD = 8;
export default function RegisterScreen() {
const router = useRouter();
const t = useT();
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPass, setShowPass] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function onSubmit() {
const mail = email.trim();
if (!mail || !mail.includes('@')) return setError(t('register.badEmail'));
if (password.length < MIN_PASSWORD) return setError(t('register.shortPassword', { count: MIN_PASSWORD }));
setError(null);
setLoading(true);
try {
await api.auth.register({
email: mail,
password,
firstName: firstName.trim() || undefined,
lastName: lastName.trim() || undefined,
});
// The account exists but has no session until the code is verified.
router.replace(`/verify?email=${encodeURIComponent(mail)}`);
} catch (e: any) {
setError(e?.fields?.email?.[0] ? t('register.emailTaken') : (e?.message ?? t('common.tryAgain')));
} finally {
setLoading(false);
}
}
return (
<AuthShell title={t('register.title')} subtitle={t('register.subtitle')} back error={error}>
<View style={{ flexDirection: 'row', gap: spacing[3] }}>
<Field label={t('register.firstName')} optional style={{ flex: 1 }}>
<TextField
value={firstName}
onChangeText={setFirstName}
autoCapitalize="words"
textContentType="givenName"
/>
</Field>
<Field label={t('register.lastName')} optional style={{ flex: 1 }}>
<TextField
value={lastName}
onChangeText={setLastName}
autoCapitalize="words"
textContentType="familyName"
/>
</Field>
</View>
<Field label={t('login.email')}>
<TextField
icon="mail-outline"
value={email}
onChangeText={setEmail}
placeholder={t('login.emailPlaceholder')}
autoCapitalize="none"
keyboardType="email-address"
autoComplete="email"
textContentType="emailAddress"
/>
</Field>
<Field label={t('login.password')}>
<TextField
icon="lock-closed-outline"
value={password}
onChangeText={setPassword}
placeholder={t('register.passwordHint', { count: MIN_PASSWORD })}
secureTextEntry={!showPass}
autoCapitalize="none"
textContentType="newPassword"
/>
<Pressable onPress={() => setShowPass((v) => !v)} style={styles.showPass} hitSlop={8}>
<Text style={styles.link}>{showPass ? t('login.hide') : t('login.show')}</Text>
</Pressable>
</Field>
<Button
title={t('register.submit')}
size="lg"
block
loading={loading}
onPress={onSubmit}
style={{ marginTop: spacing[2] }}
/>
<Pressable onPress={() => router.back()} style={styles.footer}>
<Text style={styles.footerText}>
{t('register.haveAccount')} <Text style={styles.link}>{t('login.signIn')}</Text>
</Text>
</Pressable>
</AuthShell>
);
}
const styles = StyleSheet.create({
showPass: { position: 'absolute', right: 14, top: 40 },
link: { color: colors.teal700, fontWeight: '600', fontSize: fontSize.sm },
footer: { marginTop: 'auto', paddingTop: spacing[7], alignItems: 'center' },
footerText: { fontSize: fontSize.sm, color: colors.textMuted },
});