diff --git a/frontend/Design/Code gagnang.png b/frontend/Design/Code gagnang.png new file mode 100644 index 0000000..29de31f Binary files /dev/null and b/frontend/Design/Code gagnang.png differ diff --git a/frontend/Design/Inscription reussit.png b/frontend/Design/Inscription reussit.png new file mode 100644 index 0000000..01c9dae Binary files /dev/null and b/frontend/Design/Inscription reussit.png differ diff --git a/frontend/Design/Saisir_code.png b/frontend/Design/Saisir_code.png new file mode 100644 index 0000000..01eba6f Binary files /dev/null and b/frontend/Design/Saisir_code.png differ diff --git a/frontend/Design/historique des gain .png b/frontend/Design/historique des gain .png new file mode 100644 index 0000000..371c09a Binary files /dev/null and b/frontend/Design/historique des gain .png differ diff --git a/frontend/Design/profil .png b/frontend/Design/profil .png new file mode 100644 index 0000000..c0fb4a0 Binary files /dev/null and b/frontend/Design/profil .png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index fdac0a8..cbf4362 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,12 +1,13 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import Connexion from './pages/Connexion' import Inscription from './pages/Inscription' -import Participation from './pages/Participation' +//import Participation from './pages/Participation' import MesGains from './pages/MesGains' import Employe from './pages/Employe' import Admin from './pages/Admin' import Profile from './pages/Profile' import SaisirCodeUnique from './pages/SaisirCodeUnique.jsx' +import Perdu from './pages/Perdu' export default function App() { return ( @@ -15,12 +16,13 @@ export default function App() { } /> } /> } /> - } /> + {/* } /> */} } /> } /> } /> } /> } /> + } /> ) diff --git a/frontend/src/pages/Connexion.jsx b/frontend/src/pages/Connexion.jsx index 7c75dea..eb1d567 100644 --- a/frontend/src/pages/Connexion.jsx +++ b/frontend/src/pages/Connexion.jsx @@ -23,7 +23,7 @@ export default function Connexion() { localStorage.setItem('user', JSON.stringify(user)) if (user.role === 'ADMIN') navigate('/admin') else if (user.role === 'EMPLOYEE') navigate('/employe') - else navigate('/participation') + else navigate('/mes-gains') } catch (err) { setError(err.message) } finally { diff --git a/frontend/src/pages/Inscription.jsx b/frontend/src/pages/Inscription.jsx index 1f6ce7e..ae9c804 100644 --- a/frontend/src/pages/Inscription.jsx +++ b/frontend/src/pages/Inscription.jsx @@ -1,18 +1,25 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { Link, useNavigate } from 'react-router-dom' import { registerApi } from '../services/auth' export default function Inscription() { const navigate = useNavigate() - const [form, setForm] = useState({ - email: '', - lastName: '', - firstName: '', - password: '', - }) + const [form, setForm] = useState({ email: '', lastName: '', firstName: '', password: '' }) const [showPassword, setShowPassword] = useState(false) const [error, setError] = useState('') const [loading, setLoading] = useState(false) + const [successName, setSuccessName] = useState(null) + const [countdown, setCountdown] = useState(5) + + useEffect(() => { + if (successName === null) return + if (countdown === 0) { + navigate('/saisir-code-unique') + return + } + const t = setTimeout(() => setCountdown((c) => c - 1), 1000) + return () => clearTimeout(t) + }, [successName, countdown, navigate]) const handleChange = (e) => { const { name, value } = e.target @@ -30,7 +37,7 @@ export default function Inscription() { lastname: form.lastName, password: form.password, }) - navigate('/connexion', { state: { registered: true } }) + setSuccessName(form.firstName) } catch (err) { setError(err.message) } finally { @@ -53,7 +60,6 @@ export default function Inscription() { )}
-
) } diff --git a/frontend/src/pages/MesGains.jsx b/frontend/src/pages/MesGains.jsx index a0ed8d6..53a25f9 100644 --- a/frontend/src/pages/MesGains.jsx +++ b/frontend/src/pages/MesGains.jsx @@ -1,34 +1,108 @@ -const MOCK_GAINS = [ - { id: 1, code: 'TTT-00123', lot: 'Infusion découverte (30g)', status: 'PENDING' }, - { id: 2, code: 'TTT-00456', lot: 'Thé signature (100g)', status: 'DELIVERED' }, -] +import { useState, useEffect } from 'react' +import { Link } from 'react-router-dom' +import { myGainsApi } from '../services/game' -const statusLabel = { - PENDING: { label: 'À récupérer', color: 'bg-yellow-100 text-yellow-800' }, - DELIVERED: { label: 'Récupéré', color: 'bg-green-100 text-green-800' }, - CANCELLED: { label: 'Annulé', color: 'bg-red-100 text-red-800' }, +function TabNav({ active }) { + return ( +
+ + + + + Mes gains + + + + + + Profile + +
+ ) +} + +function formatDate(dateStr) { + return new Date(dateStr).toLocaleDateString('fr-FR', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }) } export default function MesGains() { - return ( -
-
-

Mes gains

-

Historique de vos participations

+ const [gains, setGains] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') -
- {MOCK_GAINS.map((gain) => ( -
-
-

{gain.lot}

-

{gain.code}

-
- - {statusLabel[gain.status].label} - -
- ))} + useEffect(() => { + myGainsApi() + .then((data) => setGains(data.gains)) + .catch((err) => setError(err.message)) + .finally(() => setLoading(false)) + }, []) + + return ( +
+ + + + + + + Saisir un nouveau code + + + {loading && ( +

Chargement...

+ )} + + {error && ( +
+ {error}
+ )} + + {!loading && !error && gains.length === 0 && ( +

Aucun gain pour le moment.

+ )} + + {/* Timeline */} +
+ {gains.map((gain, index) => ( +
+ {/* Ligne verticale + cercle */} +
+
+ {index < gains.length - 1 && ( +
+ )} +
+ + {/* Contenu */} +
+

{gain.lot}

+

{gain.description}

+

Gain obtenu le {formatDate(gain.wonAt)}

+
+
+ ))}
) diff --git a/frontend/src/pages/Perdu.jsx b/frontend/src/pages/Perdu.jsx new file mode 100644 index 0000000..ddd4b62 --- /dev/null +++ b/frontend/src/pages/Perdu.jsx @@ -0,0 +1,37 @@ +import { useLocation, useNavigate } from 'react-router-dom' + +export default function Perdu() { + const navigate = useNavigate() + const location = useLocation() + const message = location.state?.message + + return ( +
+ +
+ + + +
+ +

+ Réessayer la prochaine fois +

+ +

+ {message || 'Ce code est invalide ou a déjà été utilisé.'} +

+ +

+ Vérifiez le code présent sur votre ticket et réessayez. +

+ + +
+ ) +} diff --git a/frontend/src/pages/Profile.jsx b/frontend/src/pages/Profile.jsx index e7fa9c9..e485c39 100644 --- a/frontend/src/pages/Profile.jsx +++ b/frontend/src/pages/Profile.jsx @@ -1,94 +1,89 @@ -import { useState } from 'react' +import { Link } from 'react-router-dom' + +function TabNav({ active }) { + return ( +
+ + + + + Mes gains + + + + + + Profile + +
+ ) +} export default function Profile() { - const [form, setForm] = useState({ - firstName: '', - lastName: '', - email: '', - }) + const user = JSON.parse(localStorage.getItem('user') || '{}') - const handleChange = (e) => { - const { name, value } = e.target - setForm({ ...form, [name]: value }) - } + const initials = `${(user.firstname || '?')[0]}${(user.lastname || '?')[0]}`.toUpperCase() - const handleSubmit = (e) => { - e.preventDefault() - // TODO: appeler backend pour update profil - alert('Profil mis à jour') - } + const handleSubmit = (e) => { + e.preventDefault() + // TODO: connecter au backend pour update profil + alert('Modification du profil à venir') + } - // Initiales pour l'avatar - const initials = 'MD' + return ( +
+ - return ( -
-
-

Mon Profil

- {/* Avatar */} -
-
- {initials} -
-
- - {/* Formulaire */} - - - {/* Email */} -
- - -
- - {/* Nom */} -
- - -
- - {/* Prénom */} -
- - -
- - {/* Bouton de soumission */} - - -
+ {/* Avatar */} +
+
+ {initials}
- ) -} \ No newline at end of file +
+ +
+
+ +
+ {user.email || '—'} +
+
+ +
+ +
+ {user.lastname || '—'} +
+
+ +
+ +
+ {user.firstname || '—'} +
+
+ + +
+
+ ) +} diff --git a/frontend/src/pages/SaisirCodeUnique.jsx b/frontend/src/pages/SaisirCodeUnique.jsx index ad93d59..999e6c7 100644 --- a/frontend/src/pages/SaisirCodeUnique.jsx +++ b/frontend/src/pages/SaisirCodeUnique.jsx @@ -1,65 +1,88 @@ import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { participateApi } from '../services/game' export default function SaisirCodeUnique() { - const [form, setForm] = useState({ - codeUnique: '', - }) + const navigate = useNavigate() + const [code, setCode] = useState('') + const [loading, setLoading] = useState(false) + const [gain, setGain] = useState(null) - const handleChange = (e) => { - const { name, value } = e.target - setForm({ ...form, [name]: value.toUpperCase() }) + const handleSubmit = async (e) => { + e.preventDefault() + setLoading(true) + try { + const data = await participateApi(code.trim().toUpperCase()) + setGain(data.gain) + } catch (err) { + navigate('/perdu', { state: { message: err.message } }) + } finally { + setLoading(false) } + } - const handleSubmit = (e) => { - e.preventDefault() + return ( +
- const code = form.codeUnique.trim() - - if (!code) { - alert('Veuillez saisir un code') - return - } - if (code.length !== 10) { - alert('Le code doit contenir exactement 10 caractères') - return - } - - // TODO: appeler backend pour valider le code unique - alert('Code validé') - } +
+

Saisir mon code gagnant

+

Saisissez le code présent sur votre ticket

+
- return ( -
-
-

Saisir mon code gagnant

-

Saisissez le code présent sur votre ticket

- +
+ setCode(e.target.value.toUpperCase())} + placeholder="Votre code" + maxLength={10} + required + className="w-full border border-gray-200 rounded-lg px-4 py-3 text-sm text-gray-700 placeholder-gray-300 bg-white focus:outline-none focus:ring-1 focus:ring-[#006B6B] focus:border-[#006B6B]" + /> - {/* Formulaire */} - + +
- {/* Code unique */} -
- -
- - {/* Bouton de soumission */} - - + {/* Overlay Félicitation */} + {gain && ( +
+
+

Félicitation !

+

Vous avez obtenu une récompense

+
+ +
+ {/* Code saisi */} +
+ {code}
+
+ + {/* Icône décorative */} +
+ + + +
+ +
+

{gain.name}

+

{gain.description}

+ +
- ) -} \ No newline at end of file + )} +
+ ) +} diff --git a/frontend/src/services/game.js b/frontend/src/services/game.js new file mode 100644 index 0000000..c3fe267 --- /dev/null +++ b/frontend/src/services/game.js @@ -0,0 +1,24 @@ +export async function myGainsApi() { + const token = localStorage.getItem('token') + const res = await fetch('/api/game/my-gains', { + headers: { Authorization: `Bearer ${token}` }, + }) + const data = await res.json() + if (!res.ok) throw new Error(data.message || 'Erreur lors de la récupération des gains.') + return data +} + +export async function participateApi(code) { + const token = localStorage.getItem('token') + const res = await fetch('/api/game/participate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ code }), + }) + const data = await res.json() + if (!res.ok) throw new Error(data.message || 'Erreur lors de la participation.') + return data +}