Le back-end (Étape 9) a retiré AssignmentStatus : une affectation est valide
dès sa création, la retirer = supprimer la ligne.
- schema.ts : AssignmentRead/Create/Update sans `status` ;
MatrixShift.{confirmed, proposed} → `assigned`.
- mock : générateur sans statut (plus de rollStatus / DECLINED) ; matrice
= compte unique ; create → validation toujours exécutée ; PATCH ne porte
plus que source/locked ; runValidation compte toutes les affectations.
- lib/planning : suppression de isActive() et statusLabel().
- Grille + panneau : cellule « affectés / idéal », plus de +proposés ni de
badge confirmé/proposé ni de bouton « confirmer ».
- Vue par bénévole : une seule trame d'affectation (bleu).
- Mobile bénévole : plus de bouton « JE CONFIRME » ni de bannière.
- Mobile référent : plus de libellé de statut par équipier.
- Onglet 03 « Confirmations » supprimé (route, page, nav, ROUTE_ROLES) —
il n'a plus d'objet. Coordination : onglets 01, 02, 06.
- i18n : clés de statut / de l'écran Confirmations retirées.
- Vérifs : build tsc + vite, eslint (0 problème), npm run smoke.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
183 lines
5.9 KiB
TypeScript
183 lines
5.9 KiB
TypeScript
// Vérifs headless : flux d'auth du mock + rendu de chaque écran.
|
|
// Lancement : npx tsx --tsconfig tsconfig.app.json scripts/smoke.tsx
|
|
import { createElement, type ReactNode } from 'react';
|
|
import { renderToString } from 'react-dom/server';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { MemoryRouter, RouterProvider, createMemoryRouter } from 'react-router-dom';
|
|
|
|
import {
|
|
ApiError,
|
|
DEMO_COORDINATOR_EMAIL,
|
|
DEMO_COORDINATOR_PASSWORD,
|
|
EDITION_ID,
|
|
api,
|
|
devMagicToken,
|
|
setAuthToken,
|
|
} from '@/api';
|
|
import type { AccountRead } from '@/api/schema';
|
|
import { queryKeys } from '@/api/hooks';
|
|
import { AuthContext, AuthProvider, type AuthContextValue } from '@/auth/auth-context';
|
|
import { routes } from '@/app/router';
|
|
import { ConfigPage } from '@/pages/ConfigPage';
|
|
import { GrillePage } from '@/pages/GrillePage';
|
|
import { MobileLeadPage } from '@/pages/MobileLeadPage';
|
|
import { MobileVolunteerPage } from '@/pages/MobileVolunteerPage';
|
|
import { VolunteerViewPage } from '@/pages/VolunteerViewPage';
|
|
|
|
const e = EDITION_ID;
|
|
let failures = 0;
|
|
const fail = (label: string, err: unknown) => {
|
|
failures += 1;
|
|
console.error(`FAIL ${label}`, err);
|
|
};
|
|
|
|
let coordinator: AccountRead | null = null;
|
|
let volunteerAccount: AccountRead | null = null;
|
|
let leadAccount: AccountRead | null = null;
|
|
let coordinatorToken = '';
|
|
|
|
async function checkAuth() {
|
|
let badRejected = 'non';
|
|
try {
|
|
await api.auth.login(e, { email: DEMO_COORDINATOR_EMAIL, password: 'faux' });
|
|
} catch (err) {
|
|
badRejected = err instanceof ApiError ? err.code : String(err);
|
|
}
|
|
|
|
const session = await api.auth.login(e, {
|
|
email: DEMO_COORDINATOR_EMAIL,
|
|
password: DEMO_COORDINATOR_PASSWORD,
|
|
});
|
|
coordinator = session.account;
|
|
coordinatorToken = session.access_token;
|
|
setAuthToken(coordinatorToken);
|
|
const me = await api.auth.me();
|
|
|
|
const vols = await api.volunteers.list(e);
|
|
const volEmail = vols.find((v) => v.email)?.email ?? '';
|
|
await api.auth.requestMagicLink(e, { email: volEmail });
|
|
const raw = devMagicToken(volEmail);
|
|
volunteerAccount = raw ? (await api.auth.verifyMagicLink(e, { token: raw })).account : null;
|
|
|
|
await api.auth.requestMagicLink(e, { email: 'referent1@lessonsdautomne.fr' });
|
|
const leadRaw = devMagicToken('referent1@lessonsdautomne.fr');
|
|
leadAccount = leadRaw ? (await api.auth.verifyMagicLink(e, { token: leadRaw })).account : null;
|
|
|
|
setAuthToken(coordinatorToken);
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
badPasswordRejectedWith: badRejected,
|
|
loginRole: session.account.role,
|
|
meRole: me.role,
|
|
magicVolunteerRole: volunteerAccount?.role,
|
|
magicVolunteerLinked: Boolean(volunteerAccount?.volunteer_id),
|
|
magicLeadRole: leadAccount?.role,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
}
|
|
|
|
async function prefetchCore(client: QueryClient) {
|
|
await Promise.all([
|
|
client.prefetchQuery({ queryKey: queryKeys.volunteers, queryFn: () => api.volunteers.list(e) }),
|
|
client.prefetchQuery({ queryKey: queryKeys.positions, queryFn: () => api.positions.list(e) }),
|
|
client.prefetchQuery({ queryKey: queryKeys.shifts(), queryFn: () => api.shifts.list(e) }),
|
|
client.prefetchQuery({ queryKey: queryKeys.assignments(), queryFn: () => api.assignments.list(e) }),
|
|
client.prefetchQuery({ queryKey: queryKeys.matrix, queryFn: () => api.matrix.get(e) }),
|
|
]);
|
|
}
|
|
|
|
function authValue(account: AccountRead): AuthContextValue {
|
|
return {
|
|
status: 'authed',
|
|
account,
|
|
loginWithPassword: async () => account,
|
|
requestMagicLink: async () => {},
|
|
verifyMagicLink: async () => account,
|
|
logout: () => {},
|
|
};
|
|
}
|
|
|
|
async function renderProtected(
|
|
label: string,
|
|
account: AccountRead,
|
|
node: ReactNode,
|
|
extra?: (client: QueryClient) => Promise<void>,
|
|
) {
|
|
try {
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
await prefetchCore(client);
|
|
if (extra) await extra(client);
|
|
const html = renderToString(
|
|
createElement(
|
|
QueryClientProvider,
|
|
{ client },
|
|
createElement(
|
|
AuthContext.Provider,
|
|
{ value: authValue(account) },
|
|
createElement(MemoryRouter, null, node),
|
|
),
|
|
),
|
|
);
|
|
console.log(`ok ${label.padEnd(26)} ${html.length} octets`);
|
|
} catch (err) {
|
|
fail(label, err);
|
|
}
|
|
}
|
|
|
|
function renderPublic(path: string) {
|
|
try {
|
|
const client = new QueryClient();
|
|
const router = createMemoryRouter(routes, { initialEntries: [path] });
|
|
const html = renderToString(
|
|
createElement(
|
|
QueryClientProvider,
|
|
{ client },
|
|
createElement(AuthProvider, null, createElement(RouterProvider, { router })),
|
|
),
|
|
);
|
|
console.log(`ok ${path.padEnd(26)} ${html.length} octets`);
|
|
} catch (err) {
|
|
fail(path, err);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------- //
|
|
await checkAuth().catch((err) => fail('checkAuth', err));
|
|
|
|
renderPublic('/connexion');
|
|
renderPublic('/connexion/coordination');
|
|
|
|
if (coordinator) {
|
|
setAuthToken(coordinatorToken);
|
|
await renderProtected('/grille', coordinator, createElement(GrillePage));
|
|
await renderProtected('/benevoles', coordinator, createElement(VolunteerViewPage));
|
|
await renderProtected('/configuration', coordinator, createElement(ConfigPage));
|
|
}
|
|
if (volunteerAccount) {
|
|
const vid = volunteerAccount.volunteer_id as string;
|
|
await renderProtected(
|
|
'/mobile-benevole',
|
|
volunteerAccount,
|
|
createElement(MobileVolunteerPage),
|
|
async (client) => {
|
|
await client.prefetchQuery({
|
|
queryKey: queryKeys.assignments({ volunteerId: vid }),
|
|
queryFn: () => api.assignments.list(e, { volunteerId: vid }),
|
|
});
|
|
await client.prefetchQuery({
|
|
queryKey: ['volunteer', e, vid],
|
|
queryFn: () => api.volunteers.get(e, vid),
|
|
});
|
|
},
|
|
);
|
|
}
|
|
if (leadAccount) {
|
|
await renderProtected('/mobile-referent', leadAccount, createElement(MobileLeadPage));
|
|
}
|
|
|
|
process.exit(failures ? 1 : 0);
|