Files
SBC/web/src/components/ImageSlot.jsx
T
Claude 96dca2a802 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
2026-07-10 19:43:07 +00:00

50 lines
1.4 KiB
React

import { useRef, useState } from 'react';
import { api } from '../lib/api.js';
// Real replacement for the mock's <image-slot>: click or drop an image,
// it is uploaded to `endpoint` and the stored path is displayed.
export default function ImageSlot({ endpoint, value, onUploaded, placeholder, circle }) {
const inputRef = useRef(null);
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const send = async (file) => {
if (!file) return;
setBusy(true);
setError('');
try {
const d = await api.upload(endpoint, file);
onUploaded?.(d.path);
} catch (e) {
setError(e.message);
} finally {
setBusy(false);
}
};
return (
<div
className={`image-slot${circle ? ' circle' : ''}`}
style={{ cursor: 'pointer' }}
onClick={() => inputRef.current?.click()}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
e.preventDefault();
send(e.dataTransfer.files?.[0]);
}}
role="button"
tabIndex={0}
>
{value && <img src={value} alt="" />}
{!value && <span>{busy ? 'Envoi…' : error || placeholder}</span>}
<input
ref={inputRef}
type="file"
accept="image/jpeg,image/png,image/webp"
style={{ display: 'none' }}
onChange={(e) => send(e.target.files?.[0])}
/>
</div>
);
}