Frontend : connexion (mot de passe + lien magique) et onglets par rôle
Authentification alignée sur le back-end (Étapes 7/8) : - src/api : schema auth (AccountRead, TokenResponse…), session.ts (jeton Bearer partagé + callback 401), méthodes auth du client HTTP (POST .../auth/login, .../auth/magic-link, .../auth/verify, GET /auth/me) et du mock (comptes coordination + référents + bénévoles, tokens magiques, sessions). - src/auth : AuthProvider/useAuth (jeton en localStorage, réhydratation via /auth/me, déconnexion sur 401), RequireAuth (garde + contrôle de rôle), HomeRedirect, table ROUTE_ROLES. - Pages : /connexion (lien magique, avec lien de démo affiché en mode mock), /connexion/coordination (email + mot de passe). - Router : toutes les routes métier passées derrière <RequireAuth> ; redirection vers l'accueil du rôle. - AppShell : onglets filtrés par rôle (01/02/03/06 coordination, 04 bénévole, 05 référent), email + rôle + bouton Déconnexion. - Mobile bénévole : cible le bénévole du compte connecté ; dégradation propre quand l'API refuse le détail des créneaux à un bénévole. - Mobile référent : cible le poste dont le compte est référent. - i18n : clés auth / rôles. - Vérifs : build tsc + vite, eslint (0 problème), npm run smoke (flux login / lien magique + rendu des 6 écrans par rôle et des pages publiques). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
464635656a
commit
6e16db4b7e
@@ -1,6 +1,7 @@
|
||||
import { hoursBetween } from '@/lib/time';
|
||||
|
||||
import type {
|
||||
AccountRead,
|
||||
AssignmentRead,
|
||||
AssignmentStatus,
|
||||
PositionCategory,
|
||||
@@ -14,11 +15,18 @@ import type {
|
||||
|
||||
export const MOCK_EDITION_ID = '00000000-0000-4000-8000-000000000001';
|
||||
|
||||
/** Identifiants de démonstration (mode mock uniquement). */
|
||||
export const DEMO_COORDINATOR_EMAIL = 'coordination@lessonsdautomne.fr';
|
||||
export const DEMO_COORDINATOR_PASSWORD = 'demo';
|
||||
|
||||
export interface MockState {
|
||||
volunteers: VolunteerRead[];
|
||||
positions: PositionRead[];
|
||||
shifts: ShiftRead[];
|
||||
assignments: AssignmentRead[];
|
||||
accounts: AccountRead[];
|
||||
/** accountId -> mot de passe en clair (mock uniquement). */
|
||||
passwords: Map<string, string>;
|
||||
/** volunteerId -> clés « YYYY-MM-DD|HH:MM » déclarées disponibles (privé au mock). */
|
||||
availability: Map<string, Set<string>>;
|
||||
seq: number;
|
||||
@@ -181,6 +189,7 @@ export function buildMockState(): MockState {
|
||||
}
|
||||
|
||||
const me = volunteers[0];
|
||||
me.email = demoEmail(me.first_name, me.last_name); // toujours un compte pour la démo
|
||||
availability.set(
|
||||
me.id,
|
||||
new Set(
|
||||
@@ -284,7 +293,57 @@ export function buildMockState(): MockState {
|
||||
seq += 1;
|
||||
});
|
||||
|
||||
return { volunteers, positions, shifts, assignments, availability, seq };
|
||||
// ---- Comptes ----
|
||||
const accounts: AccountRead[] = [];
|
||||
const passwords = new Map<string, string>();
|
||||
|
||||
const coordinator: AccountRead = {
|
||||
id: `acc${seq++}`,
|
||||
edition_id,
|
||||
email: DEMO_COORDINATOR_EMAIL,
|
||||
role: 'COORDINATOR',
|
||||
volunteer_id: null,
|
||||
auth_method: 'password',
|
||||
created_at: NOW,
|
||||
updated_at: NOW,
|
||||
};
|
||||
accounts.push(coordinator);
|
||||
passwords.set(coordinator.id, DEMO_COORDINATOR_PASSWORD);
|
||||
|
||||
// Trois référents, rattachés à trois postes (lien magique, sans mot de passe).
|
||||
['MONS', 'MONI', 'REG'].forEach((positionId, i) => {
|
||||
const account: AccountRead = {
|
||||
id: `acc${seq++}`,
|
||||
edition_id,
|
||||
email: `referent${i + 1}@lessonsdautomne.fr`,
|
||||
role: 'LEAD',
|
||||
volunteer_id: null,
|
||||
auth_method: 'magic_link',
|
||||
created_at: NOW,
|
||||
updated_at: NOW,
|
||||
};
|
||||
accounts.push(account);
|
||||
const position = positions.find((p) => p.id === positionId);
|
||||
if (position) position.lead_account_id = account.id;
|
||||
});
|
||||
|
||||
// Bénévoles connectés : tous ceux qui ont une adresse email.
|
||||
volunteers
|
||||
.filter((v) => v.email)
|
||||
.forEach((v) => {
|
||||
accounts.push({
|
||||
id: `acc${seq++}`,
|
||||
edition_id,
|
||||
email: v.email as string,
|
||||
role: 'VOLUNTEER',
|
||||
volunteer_id: v.id,
|
||||
auth_method: 'magic_link',
|
||||
created_at: NOW,
|
||||
updated_at: NOW,
|
||||
});
|
||||
});
|
||||
|
||||
return { volunteers, positions, shifts, assignments, accounts, passwords, availability, seq };
|
||||
}
|
||||
|
||||
function shiftsOverlap(a: ShiftRead | undefined, b: ShiftRead): boolean {
|
||||
|
||||
Reference in New Issue
Block a user