feat(mobile): refonte design DSFR complète + partie admin

- écrans auth/dashboard/journal/info/urgence/profil refondus sur le design system
- AppBar institutionnel République Française sur tous les écrans
- partie admin (utilisateurs, contenus, émotions) avec AdminGuard
- entrée Espace administration dans le profil (admins uniquement)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Azmog
2026-06-19 15:22:15 +02:00
parent 2e96e07c7e
commit 7fd27d8dc2
22 changed files with 2102 additions and 910 deletions
+109 -59
View File
@@ -3,11 +3,15 @@ import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { useAuthStore } from '@/lib/auth-store';
import { Colors } from '@/lib/colors';
import { FontSize, FontWeight, MIN_TOUCH, Radius, Spacing } from '@/lib/theme';
import { AppBar, Badge, Button, Card, SectionTitle } from '@/components/ui';
export default function ProfileScreen() {
const router = useRouter();
const { user, logout } = useAuthStore();
const isAdmin = user?.role?.nom === 'administrateur';
const handleLogout = () => {
Alert.alert('Déconnexion', 'Voulez-vous vous déconnecter ?', [
{ text: 'Annuler', style: 'cancel' },
@@ -22,86 +26,132 @@ export default function ProfileScreen() {
]);
};
const initiales = `${user?.prenom?.[0] ?? ''}${user?.nom?.[0] ?? ''}`.toUpperCase();
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
{/* Avatar & Name */}
<View style={styles.profileHeader}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>
{user?.prenom?.[0]}{user?.nom?.[0]}
<View style={styles.screen}>
<AppBar title="Profil" />
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
{/* Avatar & identité */}
<Card style={styles.profileCard}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>{initiales}</Text>
</View>
<Text style={styles.name}>
{user?.prenom} {user?.nom}
</Text>
</View>
<Text style={styles.name}>{user?.prenom} {user?.nom}</Text>
<Text style={styles.email}>{user?.email}</Text>
<View style={styles.roleBadge}>
<Text style={styles.roleText}>{user?.role?.nom || 'Membre'}</Text>
</View>
</View>
<Text style={styles.email}>{user?.email}</Text>
<View style={styles.roleBadge}>
<Badge label={user?.role?.nom ?? 'Membre'} tone="primary" />
</View>
</Card>
{/* Menu items */}
<View style={styles.menuSection}>
<Text style={styles.sectionTitle}>Compte</Text>
<View style={styles.menuCard}>
<MenuItem icon="person-outline" label="Informations personnelles" />
<MenuItem icon="lock-closed-outline" label="Changer le mot de passe" />
<MenuItem icon="shield-checkmark-outline" label="Données personnelles (RGPD)" />
{/* Entrée admin — visible uniquement pour les administrateurs */}
{isAdmin ? (
<View style={styles.section}>
<SectionTitle>Administration</SectionTitle>
<Card onPress={() => router.push('/admin')} style={styles.adminCard}>
<View style={styles.adminIcon}>
<Ionicons name="shield-checkmark" size={24} color={Colors.primary} />
</View>
<View style={styles.adminContent}>
<Text style={styles.adminTitle}>Espace administration</Text>
<Text style={styles.adminSubtitle}>Gérer les utilisateurs, contenus et émotions</Text>
</View>
<Ionicons name="chevron-forward" size={20} color={Colors.gray[400]} />
</Card>
</View>
) : null}
{/* Menu compte */}
<View style={styles.section}>
<SectionTitle>Compte</SectionTitle>
<Card padding={0} style={styles.menuCard}>
<MenuItem icon="person-outline" label="Informations personnelles" />
<MenuItem icon="lock-closed-outline" label="Changer le mot de passe" />
<MenuItem icon="shield-checkmark-outline" label="Données personnelles (RGPD)" last />
</Card>
</View>
</View>
<View style={styles.menuSection}>
<Text style={styles.sectionTitle}>Application</Text>
<View style={styles.menuCard}>
<MenuItem icon="information-circle-outline" label="À propos de CESIZen" />
<MenuItem icon="document-text-outline" label="Conditions d'utilisation" />
{/* Menu application */}
<View style={styles.section}>
<SectionTitle>Application</SectionTitle>
<Card padding={0} style={styles.menuCard}>
<MenuItem icon="information-circle-outline" label="À propos de CESIZen" />
<MenuItem icon="document-text-outline" label="Conditions d'utilisation" last />
</Card>
</View>
</View>
{/* Logout */}
<TouchableOpacity style={styles.logoutBtn} onPress={handleLogout}>
<Ionicons name="log-out-outline" size={20} color={Colors.error} />
<Text style={styles.logoutText}>Se déconnecter</Text>
</TouchableOpacity>
{/* Déconnexion */}
<Button title="Se déconnecter" variant="danger" icon="log-out-outline" fullWidth onPress={handleLogout} />
<Text style={styles.version}>CESIZen v1.0.0</Text>
</ScrollView>
<Text style={styles.version}>CESIZen v1.0.0</Text>
</ScrollView>
</View>
);
}
function MenuItem({ icon, label }: { icon: keyof typeof Ionicons.glyphMap; label: string }) {
function MenuItem({
icon,
label,
last = false,
}: {
icon: keyof typeof Ionicons.glyphMap;
label: string;
last?: boolean;
}) {
return (
<TouchableOpacity style={styles.menuItem}>
<TouchableOpacity style={[styles.menuItem, last && styles.menuItemLast]} accessibilityRole="button">
<Ionicons name={icon} size={20} color={Colors.gray[600]} />
<Text style={styles.menuLabel}>{label}</Text>
<Ionicons name="chevron-forward" size={18} color={Colors.gray[300]} />
<Ionicons name="chevron-forward" size={18} color={Colors.gray[400]} />
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: Colors.white },
content: { padding: 20, paddingBottom: 40 },
profileHeader: { alignItems: 'center', marginBottom: 32, marginTop: 12 },
screen: { flex: 1, backgroundColor: Colors.gray[50] },
container: { flex: 1 },
content: { padding: Spacing.lg, paddingBottom: Spacing.xxxl, gap: Spacing.xl },
profileCard: { alignItems: 'center', paddingVertical: Spacing.xl },
avatar: {
width: 80, height: 80, borderRadius: 4, backgroundColor: Colors.primary,
justifyContent: 'center', alignItems: 'center', marginBottom: 12,
width: 80,
height: 80,
borderRadius: Radius.sm,
backgroundColor: Colors.primary,
justifyContent: 'center',
alignItems: 'center',
marginBottom: Spacing.md,
},
avatarText: { fontSize: 28, fontWeight: '800', color: Colors.white },
name: { fontSize: 22, fontWeight: '700', color: Colors.black },
email: { fontSize: 14, color: Colors.gray[500], marginTop: 4 },
roleBadge: { backgroundColor: `${Colors.primary}15`, paddingHorizontal: 12, paddingVertical: 4, borderRadius: 4, marginTop: 8 },
roleText: { fontSize: 12, fontWeight: '600', color: Colors.primary, textTransform: 'capitalize' },
menuSection: { marginBottom: 24 },
sectionTitle: { fontSize: 13, fontWeight: '700', color: Colors.gray[500], textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 },
menuCard: { backgroundColor: Colors.white, borderRadius: 4, overflow: 'hidden', borderWidth: 1, borderColor: Colors.gray[200] },
avatarText: { fontSize: FontSize.display, fontWeight: FontWeight.heavy, color: Colors.white },
name: { fontSize: FontSize.xl, fontWeight: FontWeight.bold, color: Colors.black },
email: { fontSize: FontSize.sm, color: Colors.gray[500], marginTop: Spacing.xs },
roleBadge: { marginTop: Spacing.sm },
section: { gap: Spacing.sm },
adminCard: { flexDirection: 'row', alignItems: 'center', gap: Spacing.md, padding: Spacing.lg },
adminIcon: {
width: 48,
height: 48,
borderRadius: Radius.sm,
backgroundColor: '#E3E3FD',
justifyContent: 'center',
alignItems: 'center',
},
adminContent: { flex: 1 },
adminTitle: { fontSize: FontSize.body, fontWeight: FontWeight.bold, color: Colors.black, marginBottom: 2 },
adminSubtitle: { fontSize: FontSize.xs, color: Colors.gray[500], lineHeight: 18 },
menuCard: { overflow: 'hidden' },
menuItem: {
flexDirection: 'row', alignItems: 'center', gap: 12, paddingVertical: 15, paddingHorizontal: 16,
borderBottomWidth: 1, borderBottomColor: Colors.gray[200],
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.md,
minHeight: MIN_TOUCH,
paddingVertical: Spacing.md,
paddingHorizontal: Spacing.lg,
borderBottomWidth: 1,
borderBottomColor: Colors.gray[200],
},
menuLabel: { flex: 1, fontSize: 15, color: Colors.black },
logoutBtn: {
flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8,
paddingVertical: 16, borderRadius: 4, backgroundColor: Colors.errorBg,
},
logoutText: { fontSize: 15, fontWeight: '600', color: Colors.error },
version: { textAlign: 'center', fontSize: 12, color: Colors.gray[400], marginTop: 20 },
menuItemLast: { borderBottomWidth: 0 },
menuLabel: { flex: 1, fontSize: FontSize.body, color: Colors.black },
version: { textAlign: 'center', fontSize: FontSize.xs, color: Colors.gray[400] },
});