mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
connexion.tsx faisait router.replace('/(tabs)/journee') en dur — trouvé
en recette : Karim (Demandeur) atterrissait sur l'écran du Technicien
après avoir basculé de compte démo. La redirection par rôle
(ongletAccueil) n'était branchée qu'à l'aiguillage initial (app/index.tsx,
R6.1), pas au retour de connexion — le chemin réellement emprunté à
chaque bascule de compte (pas de sélecteur de rôle en direct sur mobile,
changer de compte = se déconnecter puis se reconnecter).
useLogin()/useDemoLogin() (auth/session.tsx) renvoient maintenant la
réponse complète (contient user.role.name) au lieu de la jeter après en
avoir extrait le jeton. connexion.tsx : entrer(role) route vers
/(tabs)/${ongletAccueil(role)} pour les deux chemins (connexion classique
et sélecteur démo).
Au passage : sous-titre "Technicien" et accroche "sur le terrain — même
sans réseau" (reste de R4, mobile alors réservé au Technicien) devenus
trompeurs pour les autres rôles depuis R6 — généricisés.
Typecheck/tests/lint verts sur les 4 paquets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
159 lines
5.6 KiB
TypeScript
159 lines
5.6 KiB
TypeScript
import { router } from 'expo-router';
|
|
import { useState } from 'react';
|
|
import {
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
Pressable,
|
|
ScrollView,
|
|
Text,
|
|
TextInput,
|
|
View,
|
|
} from 'react-native';
|
|
import type { RoleName } from '@siop/shared';
|
|
import { ongletAccueil } from '@/auth/roles';
|
|
import { useDemoAccounts, useDemoLogin, useLogin } from '@/auth/session';
|
|
import { useTokens } from '@/theme/tokens';
|
|
|
|
/** Connexion mobile — mêmes règles que le web : formulaire e-mail/mot de
|
|
* passe, et le sélecteur démo (< 3 s pour changer de rôle, ADR-002)
|
|
* UNIQUEMENT si l'API l'expose. Depuis R6, l'app n'est plus réservée au
|
|
* Technicien : on route vers l'onglet d'accueil du RÔLE réel (« Ma
|
|
* journée » pour le terrain, « Accueil » sinon) — jamais un onglet figé
|
|
* (trouvé en recette : un Demandeur atterrissait sur « Ma journée »,
|
|
* l'écran du Technicien, faute de lire le rôle retourné par la connexion). */
|
|
export default function PageConnexion() {
|
|
const t = useTokens();
|
|
const [email, setEmail] = useState('');
|
|
const [motDePasse, setMotDePasse] = useState('');
|
|
const { data: comptes } = useDemoAccounts();
|
|
const login = useLogin();
|
|
const demo = useDemoLogin();
|
|
|
|
const entrer = (role: RoleName) => router.replace(`/(tabs)/${ongletAccueil(role)}`);
|
|
|
|
return (
|
|
<KeyboardAvoidingView
|
|
style={{ flex: 1, backgroundColor: t.fond }}
|
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
>
|
|
<ScrollView contentContainerStyle={{ padding: 24, paddingTop: 80, gap: 12 }}>
|
|
<View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 8 }}>
|
|
<Text style={{ color: t.safran, fontSize: 22 }}>♦</Text>
|
|
<Text style={{ fontFamily: 'Manrope_800ExtraBold', fontSize: 26, color: t.encre }}>
|
|
SIOP
|
|
</Text>
|
|
</View>
|
|
<Text style={{ fontFamily: 'Manrope_400Regular', color: t.encre2, marginBottom: 8 }}>
|
|
Vos interventions et votre suivi, où que vous soyez.
|
|
</Text>
|
|
|
|
<View style={{ gap: 10 }}>
|
|
<TextInput
|
|
accessibilityLabel="E-mail"
|
|
placeholder="E-mail"
|
|
placeholderTextColor={t.encre3}
|
|
autoCapitalize="none"
|
|
keyboardType="email-address"
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
style={{
|
|
backgroundColor: t.surface,
|
|
borderColor: t.bordureForte,
|
|
borderWidth: 1.5,
|
|
borderRadius: 10,
|
|
padding: 13,
|
|
color: t.encre,
|
|
fontFamily: 'Manrope_600SemiBold',
|
|
}}
|
|
/>
|
|
<TextInput
|
|
accessibilityLabel="Mot de passe"
|
|
placeholder="Mot de passe"
|
|
placeholderTextColor={t.encre3}
|
|
secureTextEntry
|
|
value={motDePasse}
|
|
onChangeText={setMotDePasse}
|
|
style={{
|
|
backgroundColor: t.surface,
|
|
borderColor: t.bordureForte,
|
|
borderWidth: 1.5,
|
|
borderRadius: 10,
|
|
padding: 13,
|
|
color: t.encre,
|
|
fontFamily: 'Manrope_600SemiBold',
|
|
}}
|
|
/>
|
|
<Pressable
|
|
accessibilityRole="button"
|
|
disabled={!email || !motDePasse || login.isPending}
|
|
onPress={() =>
|
|
login.mutate(
|
|
{ email, password: motDePasse },
|
|
{ onSuccess: (res) => entrer(res.user.role.name) },
|
|
)
|
|
}
|
|
style={{
|
|
backgroundColor: !email || !motDePasse ? t.bordureForte : t.primaire,
|
|
borderRadius: 10,
|
|
padding: 14,
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<Text style={{ color: '#fff', fontFamily: 'Manrope_700Bold', fontSize: 15 }}>
|
|
{login.isPending ? 'Connexion…' : 'Se connecter'}
|
|
</Text>
|
|
</Pressable>
|
|
{login.isError ? (
|
|
<Text style={{ color: t.danger, fontFamily: 'Manrope_600SemiBold', fontSize: 13 }}>
|
|
{login.error.message}
|
|
</Text>
|
|
) : null}
|
|
</View>
|
|
|
|
{comptes?.length ? (
|
|
<View style={{ marginTop: 18, gap: 8 }}>
|
|
<Text
|
|
style={{
|
|
fontFamily: 'Manrope_700Bold',
|
|
fontSize: 11,
|
|
letterSpacing: 1,
|
|
textTransform: 'uppercase',
|
|
color: t.encre3,
|
|
}}
|
|
>
|
|
Mode démo — connexion 1 clic
|
|
</Text>
|
|
{comptes.map((c) => (
|
|
<Pressable
|
|
key={c.id}
|
|
accessibilityRole="button"
|
|
disabled={demo.isPending}
|
|
onPress={() =>
|
|
demo.mutate(c.id, { onSuccess: (res) => entrer(res.user.role.name) })
|
|
}
|
|
style={{
|
|
backgroundColor: t.surface,
|
|
borderColor: t.bordure,
|
|
borderWidth: 1,
|
|
borderRadius: 10,
|
|
padding: 12,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<Text style={{ fontFamily: 'Manrope_700Bold', color: t.encre, fontSize: 14 }}>
|
|
{c.displayName}
|
|
</Text>
|
|
<Text style={{ fontFamily: 'Manrope_600SemiBold', color: t.encre3, fontSize: 12 }}>
|
|
{c.roleName}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
) : null}
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
}
|