expo-starter/components/auth/AuthForm.tsx

211 lines
5.2 KiB
TypeScript

import { Link } from 'expo-router';
import { useMemo, useState } from 'react';
import {
ActivityIndicator,
KeyboardAvoidingView,
Platform,
StyleSheet,
Text,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Button } from '@/components/common/Button';
import { Catamaran, FunnelSans } from '@/constants/fonts';
import { Input } from '@/components/common/Input';
import { Palette } from '@/constants/palette';
import { Radius, Spacing } from '@/constants/styles';
import { mapFirebaseAuthError } from '@/lib/firebase-auth-errors';
import { loginWithEmail, registerWithEmail } from '@/services/auth';
type AuthMode = 'login' | 'register';
type AuthFormProps = {
mode: AuthMode;
};
const copyByMode = {
login: {
title: 'Connexion',
submitLabel: 'Se connecter',
switchLabel: "Pas encore de compte ? S'inscrire",
switchHref: '/register' as const,
},
register: {
title: 'Inscription',
submitLabel: "S'inscrire",
switchLabel: 'Déjà un compte ? Se connecter',
switchHref: '/login' as const,
},
} as const;
export function AuthForm({ mode }: AuthFormProps) {
const copy = copyByMode[mode];
const [username, setUsername] = useState('');
const [email, setEmail] = useState('leon.morival@gmail.com');
const [password, setPassword] = useState('Furioza98*');
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const isFormValid = useMemo(() => {
if (!email.trim() || !password.trim()) {
return false;
}
if (mode === 'register' && !username.trim()) {
return false;
}
return true;
}, [username, email, mode, password]);
const handleSubmit = async () => {
if (!isFormValid || isSubmitting) {
return;
}
setError(null);
setIsSubmitting(true);
try {
if (mode === 'login') {
await loginWithEmail(email, password);
} else {
await registerWithEmail({
email,
password,
username,
});
}
} catch (submitError) {
setError(mapFirebaseAuthError(submitError));
} finally {
setIsSubmitting(false);
}
};
return (
<SafeAreaView style={styles.safeArea}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.keyboardView}
>
<View style={styles.container}>
<View style={styles.hero}>
<Text style={styles.title}>{copy.title}</Text>
</View>
<View style={styles.card}>
{mode === 'register' ? (
<Input
autoCapitalize="words"
containerStyle={styles.field}
label="Nom"
onChangeText={setUsername}
placeholder="Pierre"
value={username}
/>
) : null}
<Input
autoCapitalize="none"
autoComplete="email"
containerStyle={styles.field}
keyboardType="email-address"
label="Email"
onChangeText={setEmail}
placeholder="pierre@email.com"
value={email}
/>
<Input
autoCapitalize="none"
autoComplete={mode === 'login' ? 'password' : 'new-password'}
containerStyle={styles.field}
label="Mot de passe"
onChangeText={setPassword}
placeholder="••••••••"
secureTextEntry
value={password}
/>
{error ? <Text style={styles.error}>{error}</Text> : null}
<Button
disabled={!isFormValid || isSubmitting}
onPress={handleSubmit}
style={styles.submitButton}
title={isSubmitting ? 'Chargement...' : copy.submitLabel}
/>
{isSubmitting ? (
<ActivityIndicator color={Palette.black} style={styles.loader} />
) : null}
<Link href={copy.switchHref} style={styles.switchLink}>
{copy.switchLabel}
</Link>
</View>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: Palette.cream,
},
keyboardView: {
flex: 1,
},
container: {
flex: 1,
justifyContent: 'center',
paddingHorizontal: Spacing.lg,
gap: Spacing.lg,
backgroundColor: Palette.cream,
},
hero: {
gap: Spacing.sm,
},
title: {
...Catamaran.extraBold36,
color: Palette.black,
},
subtitle: {
...FunnelSans.regular16,
color: Palette.gray,
},
card: {
backgroundColor: Palette.white,
borderColor: Palette.cream,
borderRadius: Radius.xl,
borderWidth: 1,
padding: 20,
gap: Spacing.md,
},
field: {
width: '100%',
},
error: {
...FunnelSans.semiBold14,
color: Palette.danger,
},
submitButton: {
minHeight: 52,
},
loader: {
marginTop: -4,
},
switchLink: {
...FunnelSans.semiBold14,
color: Palette.black,
textAlign: 'center',
textDecorationLine: 'underline',
},
});