From 2d25c91c55979877cdf9dfebfb2691627cd50f54 Mon Sep 17 00:00:00 2001 From: Emma781227 Date: Fri, 26 Jun 2026 11:26:45 +0200 Subject: [PATCH] changes --- frontend/Design/image.png | Bin 33522 -> 0 bytes frontend/public/robots.txt | 23 +++ frontend/src/App.jsx | 50 +++++- frontend/src/components/Footer.jsx | 9 + frontend/src/components/Header.jsx | 102 ++++++++--- frontend/src/components/ProtectedRoute.jsx | 17 ++ frontend/src/components/UserNav.jsx | 56 ++++++ frontend/src/pages/Admin.jsx | 187 ++++++++++++++++----- frontend/src/pages/MesGains.jsx | 59 +------ frontend/src/pages/NotFound.jsx | 26 +++ frontend/src/pages/Profile.jsx | 42 +---- frontend/src/pages/SaisirCodeUnique.jsx | 3 + frontend/src/pages/Sitemap.jsx | 96 +++++++++++ frontend/src/services/admin.js | 35 ++++ 14 files changed, 540 insertions(+), 165 deletions(-) delete mode 100644 frontend/Design/image.png create mode 100644 frontend/public/robots.txt create mode 100644 frontend/src/components/ProtectedRoute.jsx create mode 100644 frontend/src/components/UserNav.jsx create mode 100644 frontend/src/pages/NotFound.jsx create mode 100644 frontend/src/pages/Sitemap.jsx create mode 100644 frontend/src/services/admin.js diff --git a/frontend/Design/image.png b/frontend/Design/image.png deleted file mode 100644 index 41aaeb4923a634f2c0b2eb883a504fc08ba445ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33522 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 RBnAJD$*ah{JbdoP{{i9p5$6B^ diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..86a7682 --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,23 @@ +User-agent: * + +# Pages publiques autorisées +Allow: / +Allow: /accueil +Allow: /regles-jeu +Allow: /contact +Allow: /mentions-legales +Allow: /confidentialite +Allow: /sitemap +Allow: /inscription +Allow: /connexion + +# Pages privées interdites aux robots +Disallow: /admin +Disallow: /employe +Disallow: /profile +Disallow: /mes-gains +Disallow: /saisir-code-unique +Disallow: /perdu + +# Sitemap +Sitemap: https://thetiptop.fr/sitemap diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3ef0c6d..7263b3c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,6 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import Layout from './components/Layout.jsx' +import ProtectedRoute from './components/ProtectedRoute.jsx' import Connexion from './pages/Connexion' import Inscription from './pages/Inscription' import Accueil from './pages/Accueil' @@ -11,6 +12,8 @@ import Admin from './pages/Admin' import Profile from './pages/Profile' import SaisirCodeUnique from './pages/SaisirCodeUnique.jsx' import Perdu from './pages/Perdu' +import NotFound from './pages/NotFound' +import Sitemap from './pages/Sitemap' import MentionsLegales from './pages/MentionsLegales' import Confidentialite from './pages/Confidentialite' import Contact from './pages/Contact.jsx' @@ -22,18 +25,53 @@ export default function App() { } /> } /> } /> + + {/* Pages publiques */} } /> } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> } /> } /> } /> + } /> + + {/* Pages CLIENT */} + + + + } /> + + + + } /> + + + + } /> + + + + } /> + + {/* Page EMPLOYEE */} + + + + } /> + + {/* Page ADMIN */} + + + + } /> + + } /> ) diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx index 2cba567..604ba0a 100644 --- a/frontend/src/components/Footer.jsx +++ b/frontend/src/components/Footer.jsx @@ -123,6 +123,15 @@ export default function Footer() { +
  • + + Plan du site + +
  • + diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index a17a658..a9b7291 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -1,21 +1,36 @@ import { useState } from 'react' -import { Link, useLocation } from 'react-router-dom' +import { Link, useLocation, useNavigate } from 'react-router-dom' import logo from '../assets/img/logo.png' export default function Header() { const [menuOpen, setMenuOpen] = useState(false) const { pathname } = useLocation() + const navigate = useNavigate() + + const user = JSON.parse(localStorage.getItem('user') || 'null') + const isLoggedIn = !!user + + const handleLogout = () => { + localStorage.removeItem('token') + localStorage.removeItem('user') + setMenuOpen(false) + navigate('/connexion') + } const navLinks = [ { to: '/accueil', label: 'Accueil' }, { to: '/regles-jeu', label: 'Règles du jeu' }, ] + const initials = user + ? `${(user.firstname || '?')[0]}${(user.lastname || '?')[0]}`.toUpperCase() + : null + return (
    - + Thé Tip Top @@ -32,14 +47,32 @@ export default function Header() { {link.label} ))} -
    - - Connexion - - - Inscription - -
    + + {isLoggedIn ? ( +
    + +
    + {initials} +
    + {user.firstname} + + +
    + ) : ( +
    + + Connexion + + + Inscription + +
    + )} {/* Hamburger mobile */} @@ -69,21 +102,42 @@ export default function Header() { {link.label} ))} +
    - setMenuOpen(false)} - className="w-full text-center bg-green-700 text-white px-4 py-2.5 rounded-lg hover:bg-green-800 transition-colors text-sm font-medium" - > - Connexion - - setMenuOpen(false)} - className="w-full text-center border border-green-700 text-green-700 px-4 py-2.5 rounded-lg hover:bg-green-50 transition-colors text-sm font-medium" - > - Inscription - + {isLoggedIn ? ( + <> + setMenuOpen(false)} + className="w-full text-center bg-[#006B6B] text-white px-4 py-2.5 rounded-lg text-sm font-medium" + > + Mon espace ({user.firstname}) + + + + ) : ( + <> + setMenuOpen(false)} + className="w-full text-center bg-green-700 text-white px-4 py-2.5 rounded-lg hover:bg-green-800 transition-colors text-sm font-medium" + > + Connexion + + setMenuOpen(false)} + className="w-full text-center border border-green-700 text-green-700 px-4 py-2.5 rounded-lg hover:bg-green-50 transition-colors text-sm font-medium" + > + Inscription + + + )}
    )} diff --git a/frontend/src/components/ProtectedRoute.jsx b/frontend/src/components/ProtectedRoute.jsx new file mode 100644 index 0000000..968d36b --- /dev/null +++ b/frontend/src/components/ProtectedRoute.jsx @@ -0,0 +1,17 @@ +import { Navigate } from 'react-router-dom' + +export default function ProtectedRoute({ children, roles }) { + const token = localStorage.getItem('token') + const user = JSON.parse(localStorage.getItem('user') || 'null') + + if (!token || !user) { + return + } + + if (roles && !roles.includes(user.role)) { + const redirects = { ADMIN: '/admin', EMPLOYEE: '/employe', CLIENT: '/mes-gains' } + return + } + + return children +} diff --git a/frontend/src/components/UserNav.jsx b/frontend/src/components/UserNav.jsx new file mode 100644 index 0000000..1367a3b --- /dev/null +++ b/frontend/src/components/UserNav.jsx @@ -0,0 +1,56 @@ +import { Link } from 'react-router-dom' + +export default function UserNav({ active }) { + const tabs = [ + { + key: 'code', + to: '/saisir-code-unique', + label: 'Saisir un code', + icon: ( + + + + ), + }, + { + key: 'gains', + to: '/mes-gains', + label: 'Mes gains', + icon: ( + + + + ), + }, + { + key: 'profile', + to: '/profile', + label: 'Profil', + icon: ( + + + + ), + }, + ] + + return ( +
    + {tabs.map((tab) => ( + + {tab.icon} + {tab.label} + {tab.label.split(' ')[0]} + + ))} +
    + ) +} diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 94dc907..e55af6c 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -1,72 +1,179 @@ -const MOCK_STATS = { - totalParticipants: 1248, - gainsRemis: 342, - gainsEnAttente: 906, - tauxParticipation: '78%', +import { useState, useEffect } from 'react' +import { getStatsApi, getUsersApi, drawWinnerApi, getWinnerApi } from '../services/admin' + +function StatCard({ label, value, sub }) { + return ( +
    +

    {value ?? '—'}

    +

    {label}

    + {sub &&

    {sub}

    } +
    + ) } -const MOCK_USERS = [ - { id: 1, name: 'Jean Dupont', email: 'jean.dupont@email.com', gains: 2 }, - { id: 2, name: 'Marie Martin', email: 'marie.martin@email.com', gains: 1 }, - { id: 3, name: 'Paul Bernard', email: 'paul.bernard@email.com', gains: 3 }, -] - export default function Admin() { + const [stats, setStats] = useState(null) + const [users, setUsers] = useState([]) + const [winner, setWinner] = useState(undefined) + const [drawing, setDrawing] = useState(false) + const [drawError, setDrawError] = useState('') + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + + useEffect(() => { + Promise.all([getStatsApi(), getUsersApi(), getWinnerApi()]) + .then(([s, u, w]) => { + setStats(s) + setUsers(u.users) + setWinner(w) + }) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)) + }, []) + + const handleDraw = async () => { + if (!window.confirm('Lancer le tirage au sort ? Cette action est irréversible.')) return + setDrawing(true) + setDrawError('') + try { + const data = await drawWinnerApi() + setWinner(data) + } catch (e) { + setDrawError(e.message) + } finally { + setDrawing(false) + } + } + + if (loading) return ( +
    +

    Chargement...

    +
    + ) + + if (error) return ( +
    +

    {error}

    +
    + ) + return (
    -
    -

    Tableau de bord Admin

    +
    - {/* Stats */} -
    - {[ - { label: 'Participants', value: MOCK_STATS.totalParticipants }, - { label: 'Gains remis', value: MOCK_STATS.gainsRemis }, - { label: 'En attente', value: MOCK_STATS.gainsEnAttente }, - { label: 'Taux participation', value: MOCK_STATS.tauxParticipation }, - ].map((stat) => ( -
    -

    {stat.value}

    -

    {stat.label}

    -
    - ))} +

    Tableau de bord Admin

    + + {/* Stats principales */} +
    + + + +
    + {/* Stats tickets */} +
    + + + +
    + + {/* Répartition par lot */} + {stats?.lotsStats?.length > 0 && ( +
    +

    Répartition des lots

    +
    + + + + + + + + + + + {stats.lotsStats.map((lot) => ( + + + + + + + ))} + +
    Lot%TicketsParticipations
    {lot.name}{lot.percentage}%{lot._count.tickets.toLocaleString('fr-FR')}{lot._count.participations}
    +
    +
    + )} + {/* Tirage au sort */} -
    +

    Grand tirage au sort

    - + + {winner?.winner ? ( +
    +

    🏆 Gagnant tiré au sort

    +

    + {winner.winner.user.firstname} {winner.winner.user.lastname} +

    +

    {winner.winner.user.email}

    +

    + Lot : {winner.winner.prize} +

    +
    + ) : ( +
    + + {drawError &&

    {drawError}

    } +
    + )}
    {/* Liste participants */}
    -

    Participants

    +

    + Participants ({users.length}) +

    - + - + - {MOCK_USERS.map((user) => ( + {users.length === 0 && ( + + + + )} + {users.map((user) => ( - - - + + + ))}
    Nom EmailGainsParticipations
    + Aucun participant pour le moment +
    {user.name}{user.email}{user.gains} + {user.firstname} {user.lastname} + {user.email} + {user._count.participations} +
    +
    ) diff --git a/frontend/src/pages/MesGains.jsx b/frontend/src/pages/MesGains.jsx index 53a25f9..5646002 100644 --- a/frontend/src/pages/MesGains.jsx +++ b/frontend/src/pages/MesGains.jsx @@ -1,39 +1,6 @@ import { useState, useEffect } from 'react' -import { Link } from 'react-router-dom' import { myGainsApi } from '../services/game' - -function TabNav({ active }) { - return ( -
    - - - - - Mes gains - - - - - - Profile - -
    - ) -} +import UserNav from '../components/UserNav' function formatDate(dateStr) { return new Date(dateStr).toLocaleDateString('fr-FR', { @@ -57,21 +24,9 @@ export default function MesGains() { return (
    - + - - - - - Saisir un nouveau code - - - {loading && ( -

    Chargement...

    - )} + {loading &&

    Chargement...

    } {error && (
    @@ -83,19 +38,13 @@ export default function MesGains() {

    Aucun gain pour le moment.

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

    {gain.lot}

    {gain.description}

    diff --git a/frontend/src/pages/NotFound.jsx b/frontend/src/pages/NotFound.jsx new file mode 100644 index 0000000..1bca46e --- /dev/null +++ b/frontend/src/pages/NotFound.jsx @@ -0,0 +1,26 @@ +import { Link } from 'react-router-dom' + +export default function NotFound() { + return ( +
    + +

    404

    + +

    + Page introuvable +

    + +

    + La page que vous recherchez n'existe pas ou a été déplacée. +

    + + + Retour à l'accueil + + +
    + ) +} diff --git a/frontend/src/pages/Profile.jsx b/frontend/src/pages/Profile.jsx index e485c39..c61c208 100644 --- a/frontend/src/pages/Profile.jsx +++ b/frontend/src/pages/Profile.jsx @@ -1,41 +1,7 @@ -import { Link } from 'react-router-dom' - -function TabNav({ active }) { - return ( -
    - - - - - Mes gains - - - - - - Profile - -
    - ) -} +import UserNav from '../components/UserNav' export default function Profile() { const user = JSON.parse(localStorage.getItem('user') || '{}') - const initials = `${(user.firstname || '?')[0]}${(user.lastname || '?')[0]}`.toUpperCase() const handleSubmit = (e) => { @@ -46,9 +12,8 @@ export default function Profile() { return (
    - + - {/* Avatar */}
    {initials} @@ -62,21 +27,18 @@ export default function Profile() { {user.email || '—'}
    -
    {user.lastname || '—'}
    -
    {user.firstname || '—'}
    -