Application complète SLUC Business Club (React + Express + PostgreSQL, Docker)
Implémente l'intégralité de la maquette « SLUC Business Club.dc.html » : - Site public : accueil (héro administrable, carrousel membres, agenda avec inscription en ligne, rencontres passées, demande d'adhésion), annuaire avec recherche/filtres/fiche détaillée, page association - Espace membre : connexion, édition de fiche avec aperçu direct, upload logo/photo, statut d'adhésion par saison, changement de mot de passe - Espace admin : tableau de bord, membres (validation par saison), rencontres (CRUD + inscrits + impression + export Excel), inscriptions, catégories, contenu du site Architecture : 3 conteneurs Docker Compose — PostgreSQL 16 (réseau interne, rôle applicatif restreint), API Express (non-root, read-only fs), nginx non privilégié (frontend React + reverse-proxy + CSP stricte). Sécurité : requêtes 100 % paramétrées, bcrypt + JWT httpOnly SameSite=Strict, vérification d'Origin (CSRF), validation zod, rate limiting, uploads vérifiés par octets magiques avec noms aléatoires, aucun secret committé. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
This commit is contained in:
50 files changed
+6887
No files matched your search
@@ -0,0 +1,52 @@
|
||||
async function request(path, options = {}) {
|
||||
const res = await fetch(path, {
|
||||
headers: options.body instanceof FormData ? {} : { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
...options,
|
||||
body:
|
||||
options.body instanceof FormData
|
||||
? options.body
|
||||
: options.body
|
||||
? JSON.stringify(options.body)
|
||||
: undefined,
|
||||
});
|
||||
let data = null;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch {
|
||||
/* non-JSON response */
|
||||
}
|
||||
if (!res.ok) {
|
||||
const err = new Error(data?.error || `Erreur ${res.status}`);
|
||||
err.status = res.status;
|
||||
throw err;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: (path) => request(path),
|
||||
post: (path, body) => request(path, { method: 'POST', body }),
|
||||
put: (path, body) => request(path, { method: 'PUT', body }),
|
||||
del: (path) => request(path, { method: 'DELETE' }),
|
||||
upload: (path, file) => {
|
||||
const fd = new FormData();
|
||||
fd.append('file', file);
|
||||
return request(path, { method: 'POST', body: fd });
|
||||
},
|
||||
};
|
||||
|
||||
const MOIS = ['Jan', 'Fév', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sept', 'Oct', 'Nov', 'Déc'];
|
||||
|
||||
export function dateParts(dateStr) {
|
||||
const d = new Date(dateStr);
|
||||
if (Number.isNaN(d.getTime())) return { jour: '--', mois: '' };
|
||||
return { jour: String(d.getUTCDate()).padStart(2, '0'), mois: MOIS[d.getUTCMonth()] };
|
||||
}
|
||||
|
||||
export function seasonLabel(d = new Date()) {
|
||||
const y = d.getFullYear();
|
||||
return d.getMonth() >= 8 ? `${y}–${y + 1}` : `${y - 1}–${y}`;
|
||||
}
|
||||
|
||||
export const statutLabel = (s) => (s === 'confirmee' ? 'Confirmée' : 'En attente');
|
||||
Reference in new issue
Block a user