211 lines
5.1 KiB
TypeScript
211 lines
5.1 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('');
|
|
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' && !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',
|
|
},
|
|
});
|