bonpoint/web/public/boutique.html
jmartin c4ebb5b2d8 Initialise le dépôt Bonpoint avec docs et prototype web.
Structure docs/, web/ (app Node existante) et mobile/ (placeholder Flutter).
2026-06-12 11:39:51 +02:00

191 lines
6.2 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#667eea">
<title>Boutique — Bons Points</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Bons Points">
<meta name="mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="/css/style.css?v=34">
</head>
<body>
<div class="container">
<a id="lien-retour" class="btn-retour">
<svg class="btn-retour-icone" width="18" height="18" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<polyline points="15 18 9 12 15 6"/>
</svg>
Retour
</a>
<div id="boutique-contenu" class="cacher">
<div class="fiche-entete">
<div id="avatar-boutique" class="avatar-cercle fiche-avatar-centre"></div>
<div id="score-bandeau" class="score-bandeau"></div>
</div>
<p class="etape-label">Choisis ta récompense</p>
<div id="liste-recompenses" class="grille-recompenses"></div>
</div>
<p id="erreur" class="erreur-page cacher">Enfant introuvable</p>
</div>
<div id="modal-confirm" class="modal">
<div class="modal-contenu">
<p id="modal-texte"></p>
<div class="modal-boutons">
<button class="btn btn-principal" id="btn-confirmer">Oui !</button>
<button class="btn btn-secondaire" id="btn-annuler">Non</button>
</div>
</div>
</div>
<div id="feedback" class="feedback"></div>
<script>
const prenomUrl = window.location.pathname.split('/').pop();
const PHOTOS = {
Ariana: '/photos/ariana.png',
Pablo: '/photos/pablo.png',
'Hélia': '/photos/helia.png',
};
function slug(prenom) {
return prenom.toLowerCase()
.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
let enfant = null;
let recompenses = [];
let recompenseChoisie = null;
const feedback = document.getElementById('feedback');
const modal = document.getElementById('modal-confirm');
function afficherFeedback(msg) {
feedback.textContent = msg;
feedback.classList.add('visible');
setTimeout(() => feedback.classList.remove('visible'), 2500);
}
function mettreAJourBandeau() {
const bandeau = document.getElementById('score-bandeau');
bandeau.innerHTML = `
<span class="score-bandeau-nom">${enfant.prenom}</span>
<span class="score-bandeau-pts" style="color:${enfant.couleur}">
${enfant.score} points
</span>
`;
bandeau.style.borderColor = enfant.couleur;
}
function afficherRecompenses() {
mettreAJourBandeau();
const cont = document.getElementById('liste-recompenses');
cont.innerHTML = recompenses.map((r) => {
const ok = enfant.score >= r.cout_points;
return `
<button class="carte-recompense ${ok ? '' : 'indisponible'}"
data-id="${r.id}" ${ok ? '' : 'disabled'}>
<span class="rec-icone">${r.icone}</span>
<span class="rec-libelle">${r.libelle}</span>
<span class="rec-cout">${r.cout_points} pts</span>
</button>
`;
}).join('');
cont.querySelectorAll('.carte-recompense:not(.indisponible)').forEach((btn) => {
btn.addEventListener('click', () => {
recompenseChoisie = recompenses.find(
(x) => x.id === parseInt(btn.dataset.id, 10),
);
document.getElementById('modal-texte').textContent =
`${enfant.prenom}, tu veux « ${recompenseChoisie.libelle} » ` +
`pour ${recompenseChoisie.cout_points} points ?`;
modal.classList.add('ouvert');
});
});
}
function fermerModal() {
modal.classList.remove('ouvert');
}
async function confirmerAchat() {
fermerModal();
const res = await fetch('/api/public/acheter', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
enfant_id: enfant.id,
recompense_id: recompenseChoisie.id,
}),
});
const data = await res.json();
if (!res.ok) {
afficherFeedback(data.erreur || 'Erreur');
return;
}
enfant = data.enfant;
afficherFeedback(
`Bravo ${data.enfant.prenom} ! ${data.recompense.icone} ` +
`${data.recompense.libelle}`,
);
afficherRecompenses();
}
async function charger() {
try {
const [resEnfant, resRecompenses] = await Promise.all([
fetch(`/api/public/enfant/${prenomUrl}`),
fetch('/api/public/recompenses'),
]);
if (!resEnfant.ok) throw new Error('introuvable');
const data = await resEnfant.json();
enfant = data.enfant;
recompenses = (await resRecompenses.json()).recompenses;
const avatar = document.getElementById('avatar-boutique');
avatar.style.borderColor = enfant.couleur;
avatar.innerHTML =
`<img src="${PHOTOS[enfant.prenom]}" alt="${enfant.prenom}">`;
mettreAJourBandeau();
document.getElementById('lien-retour').href =
`/enfant/${slug(enfant.prenom)}`;
document.title = `Boutique — ${enfant.prenom}`;
afficherRecompenses();
document.getElementById('boutique-contenu').classList.remove('cacher');
} catch {
document.getElementById('erreur').classList.remove('cacher');
}
}
document.getElementById('btn-confirmer').addEventListener('click', confirmerAchat);
document.getElementById('btn-annuler').addEventListener('click', fermerModal);
modal.addEventListener('click', (e) => {
if (e.target === modal) fermerModal();
});
charger();
</script>
<script src="/js/pwa.js" defer></script>
</body>
</html>