238 lines
5.8 KiB
TypeScript
238 lines
5.8 KiB
TypeScript
import { Link } from 'expo-router';
|
|
import { useMemo, useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
View,
|
|
} from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
|
import { Button } from '@/components/common/Button';
|
|
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 [displayName, setDisplayName] = useState('');
|
|
const [email, setEmail] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
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' && !displayName.trim()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}, [displayName, 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,
|
|
displayName,
|
|
});
|
|
}
|
|
} 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' ? (
|
|
<View style={styles.field}>
|
|
<Text style={styles.label}>Nom</Text>
|
|
<TextInput
|
|
autoCapitalize="words"
|
|
onChangeText={setDisplayName}
|
|
placeholder="Pierre"
|
|
placeholderTextColor={Palette.black}
|
|
style={styles.input}
|
|
value={displayName}
|
|
/>
|
|
</View>
|
|
) : null}
|
|
|
|
<View style={styles.field}>
|
|
<Text style={styles.label}>Email</Text>
|
|
<TextInput
|
|
autoCapitalize="none"
|
|
autoComplete="email"
|
|
keyboardType="email-address"
|
|
onChangeText={setEmail}
|
|
placeholder="pierre@email.com"
|
|
placeholderTextColor={Palette.black}
|
|
style={styles.input}
|
|
value={email}
|
|
/>
|
|
</View>
|
|
|
|
<View style={styles.field}>
|
|
<Text style={styles.label}>Mot de passe</Text>
|
|
<TextInput
|
|
autoCapitalize="none"
|
|
autoComplete={mode === 'login' ? 'password' : 'new-password'}
|
|
onChangeText={setPassword}
|
|
placeholder="••••••••"
|
|
placeholderTextColor={Palette.black}
|
|
secureTextEntry
|
|
style={styles.input}
|
|
value={password}
|
|
/>
|
|
</View>
|
|
|
|
{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: {
|
|
color: Palette.black,
|
|
fontSize: 36,
|
|
fontWeight: '800',
|
|
},
|
|
subtitle: {
|
|
color: Palette.gray,
|
|
fontSize: 16,
|
|
lineHeight: 22,
|
|
},
|
|
|
|
card: {
|
|
backgroundColor: Palette.white,
|
|
borderColor: Palette.cream,
|
|
borderRadius: Radius.xl,
|
|
borderWidth: 1,
|
|
padding: 20,
|
|
gap: Spacing.md,
|
|
},
|
|
field: {
|
|
gap: Spacing.sm,
|
|
},
|
|
label: {
|
|
color: Palette.black,
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
},
|
|
input: {
|
|
borderWidth: 1,
|
|
borderColor: Palette.gray,
|
|
borderRadius: Radius.lg,
|
|
backgroundColor: Palette.white,
|
|
color: Palette.black,
|
|
fontSize: 16,
|
|
paddingHorizontal: Spacing.md,
|
|
paddingVertical: 14,
|
|
},
|
|
error: {
|
|
color: Palette.danger,
|
|
fontSize: 14,
|
|
lineHeight: 20,
|
|
},
|
|
submitButton: {
|
|
minHeight: 52,
|
|
},
|
|
loader: {
|
|
marginTop: -4,
|
|
},
|
|
switchLink: {
|
|
color: Palette.black,
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
textAlign: 'center',
|
|
textDecorationLine: 'underline',
|
|
},
|
|
});
|