Merge dev into preprod: accessibility fixes and code refactoring

- Fix color contrast for WCAG compliance (text-red-700, bg-red-600)
- Extract shared DashboardSidebar component to reduce duplication
- Improve helper text contrast (text-gray-600)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
soufiane 2025-12-08 16:27:01 +01:00
commit 20258aeed5
15 changed files with 381 additions and 187 deletions

View File

@ -96,8 +96,9 @@ export default function ContactPage() {
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{/* Nom complet */} {/* Nom complet */}
<div> <div>
<label htmlFor="fullName" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="fullName" className="block text-sm font-semibold text-gray-900 mb-2">
Nom complet <span className="text-red-600" aria-hidden="true">*</span> Nom complet <span className="text-red-600" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<input <input
id="fullName" id="fullName"
@ -113,8 +114,9 @@ export default function ContactPage() {
{/* Email */} {/* Email */}
<div> <div>
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="email" className="block text-sm font-semibold text-gray-900 mb-2">
Email <span className="text-red-600" aria-hidden="true">*</span> Email <span className="text-red-600" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<input <input
id="email" id="email"
@ -130,8 +132,9 @@ export default function ContactPage() {
{/* Sujet */} {/* Sujet */}
<div> <div>
<label htmlFor="subject" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="subject" className="block text-sm font-semibold text-gray-900 mb-2">
Sujet <span className="text-red-600" aria-hidden="true">*</span> Sujet <span className="text-red-600" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<select <select
id="subject" id="subject"
@ -152,8 +155,9 @@ export default function ContactPage() {
{/* Message */} {/* Message */}
<div> <div>
<label htmlFor="message" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="message" className="block text-sm font-semibold text-gray-900 mb-2">
Message <span className="text-red-600" aria-hidden="true">*</span> Message <span className="text-red-600" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<textarea <textarea
id="message" id="message"

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import { useState } from "react"; import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card"; import { Card, CardContent, CardHeader } from "@/components/ui/Card";
import Button from "@/components/Button"; import Button from "@/components/Button";
export default function CookiesPage() { export default function CookiesPage() {
@ -43,7 +43,7 @@ export default function CookiesPage() {
return ( return (
<div className="py-12"> <div className="py-12">
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
<h1 className="text-4xl md:text-5xl font-bold text-primary-300 mb-8 text-center"> <h1 className="text-4xl md:text-5xl font-bold text-primary-600 mb-8 text-center">
Gestion des cookies Gestion des cookies
</h1> </h1>
@ -54,7 +54,7 @@ export default function CookiesPage() {
{/* Introduction */} {/* Introduction */}
<Card className="mb-8"> <Card className="mb-8">
<CardHeader> <CardHeader>
<CardTitle className="text-xl">Qu'est-ce qu'un cookie ?</CardTitle> <h2 className="text-xl font-semibold text-gray-900">Qu'est-ce qu'un cookie ?</h2>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<p className="text-gray-700"> <p className="text-gray-700">
@ -71,14 +71,14 @@ export default function CookiesPage() {
{/* Types de cookies */} {/* Types de cookies */}
<Card className="mb-8"> <Card className="mb-8">
<CardHeader> <CardHeader>
<CardTitle className="text-xl">Les cookies que nous utilisons</CardTitle> <h2 className="text-xl font-semibold text-gray-900">Les cookies que nous utilisons</h2>
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-6">
{/* Cookies essentiels */} {/* Cookies essentiels */}
<div className="border-l-4 border-primary-600 pl-4"> <div className="border-l-4 border-primary-600 pl-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h3 className="font-semibold text-gray-900">Cookies essentiels</h3> <h3 className="font-semibold text-gray-900">Cookies essentiels</h3>
<span className="text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded"> <span className="text-sm text-gray-700 bg-gray-100 px-3 py-1 rounded">
Toujours actifs Toujours actifs
</span> </span>
</div> </div>
@ -106,7 +106,9 @@ export default function CookiesPage() {
checked={preferences.analytics} checked={preferences.analytics}
onChange={() => handleToggle('analytics')} onChange={() => handleToggle('analytics')}
className="sr-only peer" className="sr-only peer"
aria-label="Activer les cookies analytiques"
/> />
<span className="sr-only">Activer les cookies analytiques</span>
<div className="w-11 h-6 bg-gray-300 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div> <div className="w-11 h-6 bg-gray-300 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div>
</label> </label>
</div> </div>
@ -135,7 +137,9 @@ export default function CookiesPage() {
checked={preferences.marketing} checked={preferences.marketing}
onChange={() => handleToggle('marketing')} onChange={() => handleToggle('marketing')}
className="sr-only peer" className="sr-only peer"
aria-label="Activer les cookies marketing"
/> />
<span className="sr-only">Activer les cookies marketing</span>
<div className="w-11 h-6 bg-gray-300 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div> <div className="w-11 h-6 bg-gray-300 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary-600"></div>
</label> </label>
</div> </div>
@ -191,7 +195,7 @@ export default function CookiesPage() {
{/* Gestion des cookies */} {/* Gestion des cookies */}
<Card className="mb-8"> <Card className="mb-8">
<CardHeader> <CardHeader>
<CardTitle className="text-xl">Comment gérer vos cookies ?</CardTitle> <h2 className="text-xl font-semibold text-gray-900">Comment gérer vos cookies ?</h2>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<p className="text-gray-700"> <p className="text-gray-700">
@ -253,7 +257,7 @@ export default function CookiesPage() {
{/* Informations complémentaires */} {/* Informations complémentaires */}
<Card className="mb-8"> <Card className="mb-8">
<CardHeader> <CardHeader>
<CardTitle className="text-xl">En savoir plus</CardTitle> <h2 className="text-xl font-semibold text-gray-900">En savoir plus</h2>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<p className="text-gray-700"> <p className="text-gray-700">
@ -285,7 +289,7 @@ export default function CookiesPage() {
{/* Contact */} {/* Contact */}
<Card className="mb-8"> <Card className="mb-8">
<CardHeader> <CardHeader>
<CardTitle className="text-xl">Contact</CardTitle> <h2 className="text-xl font-semibold text-gray-900">Contact</h2>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-gray-700"> <p className="text-gray-700">

View File

@ -6,10 +6,10 @@ import { useEffect } from "react";
import { Loading } from "@/components/ui/Loading"; import { Loading } from "@/components/ui/Loading";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Ticket, BarChart3 } from "lucide-react"; import { Ticket, BarChart3 } from "lucide-react";
import { SharedSidebar, NavItem } from "@/components/shared/SharedSidebar"; import DashboardSidebar from "@/components/ui/DashboardSidebar";
import UserDropdown from "@/components/UserDropdown"; import UserDropdown from "@/components/UserDropdown";
const EMPLOYE_NAV_ITEMS: NavItem[] = [ const navItems = [
{ {
label: "Dashboard", label: "Dashboard",
href: "/employe/dashboard", href: "/employe/dashboard",
@ -24,11 +24,6 @@ const EMPLOYE_NAV_ITEMS: NavItem[] = [
}, },
]; ];
const EMPLOYE_ACTIVE_STYLES: Record<string, string> = {
green: "bg-gradient-to-r from-green-600 to-green-700 text-white shadow-lg shadow-green-500/30",
emerald: "bg-gradient-to-r from-emerald-600 to-teal-600 text-white shadow-lg shadow-emerald-500/30",
};
export default function EmployeLayout({ export default function EmployeLayout({
children, children,
}: { }: {
@ -69,16 +64,13 @@ export default function EmployeLayout({
return ( return (
<div className="flex min-h-screen bg-gray-50"> <div className="flex min-h-screen bg-gray-50">
<SharedSidebar <DashboardSidebar
navItems={EMPLOYE_NAV_ITEMS} navItems={navItems}
title="Thé Tip Top" title="Thé Tip Top"
subtitle="Espace Employé" subtitle="Espace Employé"
activeStyles={EMPLOYE_ACTIVE_STYLES}
/> />
{/* Main Content */}
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
{/* Header */}
<header className="bg-[#faf8f5] shadow-sm border-b border-gray-200 px-6 py-3"> <header className="bg-[#faf8f5] shadow-sm border-b border-gray-200 px-6 py-3">
<div className="flex items-center justify-end"> <div className="flex items-center justify-end">
<UserDropdown <UserDropdown
@ -90,7 +82,6 @@ export default function EmployeLayout({
</div> </div>
</header> </header>
{/* Page Content */}
<main className="flex-1 overflow-auto">{children}</main> <main className="flex-1 overflow-auto">{children}</main>
</div> </div>
</div> </div>

View File

@ -7,6 +7,7 @@ interface FAQ {
category: string; category: string;
question: string; question: string;
answer: string; answer: string;
icon: string;
} }
const faqData: FAQ[] = [ const faqData: FAQ[] = [
@ -14,63 +15,73 @@ const faqData: FAQ[] = [
category: "Participation", category: "Participation",
question: "Comment participer au jeu-concours ?", question: "Comment participer au jeu-concours ?",
answer: "Pour participer, vous devez effectuer un achat de minimum 49€ dans une boutique Thé Tip Top participante, récupérer votre ticket de caisse avec le code unique, créer un compte sur notre site www.thetiptop.fr, vous connecter et saisir votre code pour découvrir instantanément votre gain.", answer: "Pour participer, vous devez effectuer un achat de minimum 49€ dans une boutique Thé Tip Top participante, récupérer votre ticket de caisse avec le code unique, créer un compte sur notre site www.thetiptop.fr, vous connecter et saisir votre code pour découvrir instantanément votre gain.",
icon: "M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z",
}, },
{ {
category: "Codes", category: "Codes",
question: "Où trouver mon code de participation ?", question: "Où trouver mon code de participation ?",
answer: "Votre code de participation se trouve sur votre ticket de caisse après chaque achat de minimum 49€ en magasin Thé Tip Top. Il est clairement indiqué et composé de 10 caractères alphanumériques.", answer: "Votre code de participation se trouve sur votre ticket de caisse après chaque achat de minimum 49€ en magasin Thé Tip Top. Il est clairement indiqué et composé de 10 caractères alphanumériques.",
icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
}, },
{ {
category: "Codes", category: "Codes",
question: "Mon code ne fonctionne pas, que faire ?", question: "Mon code ne fonctionne pas, que faire ?",
answer: "Vérifiez d'abord que vous avez bien saisi le code sans erreur (attention aux caractères similaires comme 0/O ou 1/I). Assurez-vous que le code n'a pas déjà été utilisé. Si le problème persiste, contactez notre service client via la page Contact avec une photo de votre ticket.", answer: "Vérifiez d'abord que vous avez bien saisi le code sans erreur (attention aux caractères similaires comme 0/O ou 1/I). Assurez-vous que le code n'a pas déjà été utilisé. Si le problème persiste, contactez notre service client via la page Contact avec une photo de votre ticket.",
icon: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z",
}, },
{ {
category: "Compte", category: "Compte",
question: "Puis-je créer un compte avec mes réseaux sociaux ?", question: "Puis-je créer un compte avec mes réseaux sociaux ?",
answer: "Oui, vous pouvez créer un compte et vous connecter rapidement en utilisant votre compte Google ou Facebook. Cela permet une inscription plus rapide et sécurisée.", answer: "Oui, vous pouvez créer un compte et vous connecter rapidement en utilisant votre compte Google ou Facebook. Cela permet une inscription plus rapide et sécurisée.",
icon: "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z",
}, },
{ {
category: "Délais", category: "Délais",
question: "Jusqu'à quand puis-je saisir mon code ?", question: "Jusqu'à quand puis-je saisir mon code ?",
answer: "Vous pouvez saisir votre code pendant toute la durée du jeu-concours, soit du 1er janvier 2025 au 31 janvier 2025 à 23h59. Après cette date, les codes ne seront plus acceptés.", answer: "Vous pouvez saisir votre code pendant toute la durée du jeu-concours, soit du 1er janvier 2025 au 31 janvier 2025 à 23h59. Après cette date, les codes ne seront plus acceptés.",
icon: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
}, },
{ {
category: "Retrait", category: "Retrait",
question: "Comment récupérer mon lot ?", question: "Comment récupérer mon lot ?",
answer: "Pour les petits lots (infuseurs, boîtes de thé), vous pouvez les récupérer directement en magasin en présentant votre confirmation de gain. Pour les coffrets, vous avez le choix entre le retrait en boutique ou la livraison à domicile offerte. Pour le grand prix, une livraison mensuelle sera organisée à l'adresse de votre choix.", answer: "Pour les petits lots (infuseurs, boîtes de thé), vous pouvez les récupérer directement en magasin en présentant votre confirmation de gain. Pour les coffrets, vous avez le choix entre le retrait en boutique ou la livraison à domicile offerte. Pour le grand prix, une livraison mensuelle sera organisée à l'adresse de votre choix.",
icon: "M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4",
}, },
{ {
category: "Retrait", category: "Retrait",
question: "Dans quelles boutiques puis-je récupérer mon lot ?", question: "Dans quelles boutiques puis-je récupérer mon lot ?",
answer: "Vous pouvez récupérer votre lot dans n'importe quelle boutique Thé Tip Top en France métropolitaine. Présentez simplement votre confirmation de gain (email ou capture d'écran) et une pièce d'identité.", answer: "Vous pouvez récupérer votre lot dans n'importe quelle boutique Thé Tip Top en France métropolitaine. Présentez simplement votre confirmation de gain (email ou capture d'écran) et une pièce d'identité.",
icon: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z M15 11a3 3 0 11-6 0 3 3 0 016 0z",
}, },
{ {
category: "Tirage final", category: "Tirage final",
question: "Comment fonctionne le tirage final pour le grand prix ?", question: "Comment fonctionne le tirage final pour le grand prix ?",
answer: "À l'issue de la période de participation, un tirage au sort sera organisé le 15 février 2025 sous contrôle d'huissier de justice (Maître Dupont, Office Notarial de Paris) pour désigner le grand gagnant du prix principal : 1 an de thé offert d'une valeur de 360€. Tous les participants ayant validé au moins un code sont automatiquement inscrits au tirage.", answer: "À l'issue de la période de participation, un tirage au sort sera organisé le 15 février 2025 sous contrôle d'huissier de justice (Maître Dupont, Office Notarial de Paris) pour désigner le grand gagnant du prix principal : 1 an de thé offert d'une valeur de 360€. Tous les participants ayant validé au moins un code sont automatiquement inscrits au tirage.",
icon: "M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z",
}, },
{ {
category: "Données", category: "Données",
question: "Mes données personnelles sont-elles protégées ?", question: "Mes données personnelles sont-elles protégées ?",
answer: "Absolument. Nous prenons la protection de vos données très au sérieux conformément au RGPD. Toutes les informations sont cryptées et stockées de manière sécurisée. Vous disposez d'un droit d'accès, de rectification et d'effacement de vos données. Consultez notre Politique de Confidentialité pour plus de détails.", answer: "Absolument. Nous prenons la protection de vos données très au sérieux conformément au RGPD. Toutes les informations sont cryptées et stockées de manière sécurisée. Vous disposez d'un droit d'accès, de rectification et d'effacement de vos données. Consultez notre Politique de Confidentialité pour plus de détails.",
icon: "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z",
}, },
{ {
category: "Technique", category: "Technique",
question: "Le site ne fonctionne pas, que faire ?", question: "Le site ne fonctionne pas, que faire ?",
answer: "Essayez de vider le cache de votre navigateur et de rafraîchir la page (Ctrl+F5 ou Cmd+Shift+R). Assurez-vous d'utiliser un navigateur récent (Chrome, Firefox, Safari, Edge). Vérifiez que JavaScript et les cookies sont activés. Si le problème persiste, contactez notre support technique à support@thetiptop.com.", answer: "Essayez de vider le cache de votre navigateur et de rafraîchir la page (Ctrl+F5 ou Cmd+Shift+R). Assurez-vous d'utiliser un navigateur récent (Chrome, Firefox, Safari, Edge). Vérifiez que JavaScript et les cookies sont activés. Si le problème persiste, contactez notre support technique à support@thetiptop.com.",
icon: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z",
}, },
]; ];
const categories = [ const categories = [
{ name: "Participation", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Participation", icon: "M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z" },
{ name: "Codes", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Codes", icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14" },
{ name: "Compte", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Compte", icon: "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" },
{ name: "Délais", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Délais", icon: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" },
{ name: "Retrait", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Retrait", icon: "M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" },
{ name: "Tirage final", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Tirage final", icon: "M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" },
{ name: "Données", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Données", icon: "M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" },
{ name: "Technique", color: "bg-gradient-to-r from-[#d4a574]/20 to-[#c4956a]/20 text-[#c4956a]" }, { name: "Technique", icon: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" },
]; ];
export default function FAQContent() { export default function FAQContent() {
@ -90,44 +101,46 @@ export default function FAQContent() {
return matchesSearch && matchesCategory; return matchesSearch && matchesCategory;
}); });
const getCategoryColor = (category: string) => { const getCategoryIcon = (categoryName: string) => {
const cat = categories.find(c => c.name === category); const cat = categories.find(c => c.name === categoryName);
return cat?.color || "bg-gray-100 text-gray-700"; return cat?.icon || "";
}; };
return ( return (
<div className="min-h-screen bg-gradient-to-br from-[#f5f5f0] via-[#faf9f5] to-[#f5f5f0]"> <div className="min-h-screen bg-[#f0f5f3]">
{/* Hero Section */} {/* Hero Section */}
<section className="bg-gradient-to-r from-white to-[#faf9f5] py-12 border-b-2 border-[#e5e4dc]"> <section className="relative bg-gradient-to-br from-[#2d4a3e] to-[#1a2e25] py-16 md:py-24 overflow-hidden">
<div className="container mx-auto px-4"> <div className="absolute inset-0 opacity-10">
<div className="max-w-4xl mx-auto text-center"> <div className="absolute top-10 left-10 w-32 h-32 border border-white/20 rounded-full"></div>
<h1 className="text-4xl md:text-5xl font-bold text-primary-300 mb-4"> <div className="absolute bottom-10 right-10 w-48 h-48 border border-white/20 rounded-full"></div>
<div className="absolute top-1/2 left-1/4 w-24 h-24 border border-white/20 rounded-full"></div>
</div>
<div className="container mx-auto px-4 relative z-10">
<div className="max-w-3xl mx-auto text-center">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6">
Questions fréquentes Questions fréquentes
</h1> </h1>
<p className="text-lg text-[#8a8a7a]"> <p className="text-lg md:text-xl text-white/70 mb-8">
Trouvez rapidement les réponses à vos questions sur notre jeu-concours Thé Tip Top. Trouvez rapidement les réponses à vos questions sur notre jeu-concours Thé Tip Top.
</p> </p>
</div>
</div>
</section>
{/* Search Bar */} {/* Search Bar */}
<section className="py-8"> <div className="relative max-w-xl mx-auto">
<div className="container mx-auto px-4"> <label htmlFor="faq-search" className="sr-only">Rechercher une question</label>
<div className="max-w-4xl mx-auto">
<div className="relative">
<input <input
id="faq-search"
type="text" type="text"
placeholder="Rechercher une question..." placeholder="Rechercher une question..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full px-4 py-3 pl-12 border-2 border-[#e5e4dc] rounded-lg focus:outline-none focus:ring-2 focus:ring-[#d4a574] focus:border-[#d4a574] bg-white shadow-sm" className="w-full px-6 py-4 pl-14 bg-white/10 backdrop-blur-sm border border-white/20 rounded-2xl text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-[#4a7c6f] focus:border-transparent transition-all"
/> />
<svg <svg
className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-[#8a8a7a]" className="absolute left-5 top-1/2 transform -translate-y-1/2 w-5 h-5 text-white/50"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
aria-hidden="true"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -142,22 +155,38 @@ export default function FAQContent() {
</section> </section>
{/* Category Filters */} {/* Category Filters */}
<section className="pb-8"> <section className="py-8 bg-white border-b border-gray-100 sticky top-0 z-20 shadow-sm">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="max-w-4xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap justify-center gap-2 md:gap-3">
<button
onClick={() => setSelectedCategory(null)}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-2 ${
selectedCategory === null
? 'bg-[#2d4a3e] text-white shadow-lg'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
Tout
</button>
{categories.map((category) => ( {categories.map((category) => (
<button <button
key={category.name} key={category.name}
onClick={() => setSelectedCategory( onClick={() => setSelectedCategory(
selectedCategory === category.name ? null : category.name selectedCategory === category.name ? null : category.name
)} )}
className={`px-4 py-2 rounded-full text-sm font-semibold transition-all ${ className={`px-4 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-2 ${
selectedCategory === category.name selectedCategory === category.name
? 'bg-gradient-to-r from-[#d4a574] to-[#c4956a] text-white shadow-lg ring-2 ring-offset-2 ring-[#d4a574]' ? 'bg-[#2d4a3e] text-white shadow-lg'
: category.color + ' hover:shadow-md border border-[#d4a574]/30' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`} }`}
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={category.icon} />
</svg>
{category.name} {category.name}
</button> </button>
))} ))}
@ -167,75 +196,112 @@ export default function FAQContent() {
</section> </section>
{/* FAQ Questions */} {/* FAQ Questions */}
<section className="pb-16"> <section className="py-12 md:py-16">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="max-w-4xl mx-auto space-y-3"> <div className="max-w-3xl mx-auto">
<h2 className="sr-only">Liste des questions fréquentes</h2>
<div className="space-y-4">
{filteredFAQs.length === 0 ? ( {filteredFAQs.length === 0 ? (
<div className="bg-white rounded-xl shadow-md p-8 text-center text-[#8a8a7a] border border-[#e5e4dc]"> <div className="bg-white rounded-2xl shadow-sm p-12 text-center">
Aucune question ne correspond à votre recherche. <div className="w-16 h-16 bg-[#2d4a3e]/10 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-[#2d4a3e]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">Aucun résultat</h3>
<p className="text-gray-500">Aucune question ne correspond à votre recherche.</p>
</div> </div>
) : ( ) : (
filteredFAQs.map((faq, index) => ( filteredFAQs.map((faq, index) => (
<div key={index} className="bg-white rounded-xl shadow-md overflow-hidden border border-[#e5e4dc]"> <div
key={index}
className={`bg-white rounded-2xl shadow-sm overflow-hidden transition-all duration-300 ${
openQuestion === index ? 'ring-2 ring-[#2d4a3e] shadow-md' : 'hover:shadow-md'
}`}
>
<button <button
onClick={() => toggleQuestion(index)} onClick={() => toggleQuestion(index)}
className="w-full flex items-start justify-between p-6 text-left hover:bg-gradient-to-r hover:from-[#d4a574]/5 hover:to-[#c4956a]/5 transition-colors" className="w-full flex items-center gap-4 p-6 text-left transition-colors"
aria-expanded={openQuestion === index}
> >
<div className="flex-1 pr-4"> <div className={`w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0 transition-colors ${
<div className="flex items-center gap-2 mb-2"> openQuestion === index ? 'bg-[#2d4a3e] text-white' : 'bg-[#2d4a3e]/10 text-[#2d4a3e]'
<span className={`text-xs font-bold px-2 py-1 rounded ${getCategoryColor(faq.category)}`}> }`}>
{faq.category} <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
</span> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={getCategoryIcon(faq.category)} />
</div> </svg>
<h3 className="text-lg font-semibold text-[#5a5a4e]">{faq.question}</h3> </div>
<div className="flex-1 min-w-0">
<span className="text-xs font-semibold text-[#4a7c6f] uppercase tracking-wide">
{faq.category}
</span>
<h3 className="text-lg font-semibold text-gray-900 mt-1">{faq.question}</h3>
</div>
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 transition-all ${
openQuestion === index ? 'bg-[#2d4a3e] text-white rotate-180' : 'bg-gray-100 text-gray-400'
}`}>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div> </div>
<svg
className={`w-6 h-6 text-[#8a8a7a] flex-shrink-0 transition-transform ${
openQuestion === index ? 'rotate-180' : ''
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button> </button>
{openQuestion === index && ( <div className={`overflow-hidden transition-all duration-300 ${
<div className="px-6 pb-6 text-[#5a5a4e] leading-relaxed"> openQuestion === index ? 'max-h-96' : 'max-h-0'
{faq.answer} }`}>
<div className="px-6 pb-6 pl-[88px]">
<p className="text-gray-600 leading-relaxed">
{faq.answer}
</p>
</div> </div>
)} </div>
</div> </div>
)) ))
)} )}
</div>
</div> </div>
</div> </div>
</section> </section>
{/* CTA Section */} {/* CTA Section */}
<section className="pb-16"> <section className="py-16 md:py-20">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
<div className="bg-gradient-to-r from-[#d4a574] to-[#c4956a] rounded-2xl shadow-2xl p-12 text-center text-white border-2 border-[#e5e4dc]"> <div className="relative bg-gradient-to-br from-[#2d4a3e] to-[#1a2e25] rounded-3xl p-8 md:p-12 overflow-hidden">
<h2 className="text-2xl md:text-3xl font-bold mb-4"> <div className="absolute top-0 right-0 w-64 h-64 bg-[#4a7c6f]/20 rounded-full blur-3xl"></div>
Vous ne trouvez pas votre réponse ? <div className="absolute bottom-0 left-0 w-48 h-48 bg-[#4a7c6f]/20 rounded-full blur-3xl"></div>
</h2>
<p className="text-white/90 mb-6 max-w-2xl mx-auto"> <div className="relative z-10 text-center">
Notre équipe est pour vous aider ! Contactez-nous et nous vous répondrons dans les plus brefs délais. <div className="w-16 h-16 bg-white/10 rounded-2xl flex items-center justify-center mx-auto mb-6">
</p> <svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div className="flex flex-col sm:flex-row gap-4 justify-center"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
<a </svg>
href="mailto:support@thetiptop.com" </div>
className="inline-flex items-center justify-center gap-2 bg-white text-[#d4a574] hover:bg-[#f5f5f0] font-bold px-8 py-3 rounded-lg transition-all shadow-lg hover:scale-105 duration-300" <h2 className="text-2xl md:text-3xl font-bold text-white mb-4">
> Vous ne trouvez pas votre réponse ?
support@thetiptop.com </h2>
</a> <p className="text-white/70 mb-8 max-w-xl mx-auto">
<Link Notre équipe est pour vous aider ! Contactez-nous et nous vous répondrons dans les plus brefs délais.
href="/contact" </p>
className="inline-flex items-center justify-center bg-white/10 hover:bg-white/20 backdrop-blur-sm text-white font-bold px-8 py-3 rounded-lg transition-all border-2 border-white/30 hover:border-white/50" <div className="flex flex-col sm:flex-row gap-4 justify-center">
> <a
Formulaire de contact href="mailto:support@thetiptop.com"
</Link> className="inline-flex items-center justify-center gap-3 bg-white text-[#2d4a3e] hover:bg-gray-100 font-semibold px-8 py-4 rounded-xl transition-all shadow-lg hover:shadow-xl"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
support@thetiptop.com
</a>
<Link
href="/contact"
className="inline-flex items-center justify-center gap-3 bg-white/10 hover:bg-white/20 backdrop-blur-sm text-white font-semibold px-8 py-4 rounded-xl transition-all border border-white/20 hover:border-white/40"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Formulaire de contact
</Link>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -104,6 +104,8 @@ export default function ForgotPasswordPage() {
{/* Form Container */} {/* Form Container */}
<div className="p-8"> <div className="p-8">
{/* Hidden h2 for accessibility - maintains heading hierarchy */}
<h2 className="sr-only">Formulaire de réinitialisation</h2>
{/* Error Message */} {/* Error Message */}
{error && ( {error && (
@ -117,8 +119,9 @@ export default function ForgotPasswordPage() {
{/* Email */} {/* Email */}
<div> <div>
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="email" className="block text-sm font-semibold text-gray-900 mb-2">
Email <span className="text-red-500">*</span> Email <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<input <input
id="email" id="email"

View File

@ -158,7 +158,7 @@ export default function JeuxPage() {
</Link> </Link>
</div> </div>
)} )}
<p className="mt-2 text-sm text-beige-600 text-center"> <p className="mt-2 text-sm text-gray-700 text-center">
Format: TTP2025ABC (10 caractères) Format: TTP2025ABC (10 caractères)
</p> </p>
</div> </div>

View File

@ -110,14 +110,17 @@ export default function LoginPage() {
{/* Form Container */} {/* Form Container */}
<div className="p-8"> <div className="p-8">
{/* Hidden h2 for accessibility - maintains heading hierarchy */}
<h2 className="sr-only">Formulaire de connexion</h2>
{/* Login Form */} {/* Login Form */}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5"> <form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
{/* Email */} {/* Email */}
<div> <div>
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="email" className="block text-sm font-semibold text-gray-900 mb-2">
Email <span className="text-red-500">*</span> Email <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<input <input
id="email" id="email"
@ -133,8 +136,9 @@ export default function LoginPage() {
{/* Password */} {/* Password */}
<div> <div>
<label htmlFor="password" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="password" className="block text-sm font-semibold text-gray-900 mb-2">
Mot de passe <span className="text-red-500">*</span> Mot de passe <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<div className="relative"> <div className="relative">
<input <input
@ -148,13 +152,14 @@ export default function LoginPage() {
type="button" type="button"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700" className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
aria-label={showPassword ? "Masquer le mot de passe" : "Afficher le mot de passe"}
> >
{showPassword ? ( {showPassword ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg> </svg>
) : ( ) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg> </svg>

View File

@ -128,35 +128,35 @@ export default function ProfilePage() {
{!isEditing ? ( {!isEditing ? (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-700 block">
Prénom Prénom
</label> </span>
<p className="text-lg text-gray-800">{user.firstName}</p> <p className="text-lg text-gray-800">{user.firstName}</p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-700 block">
Nom Nom
</label> </span>
<p className="text-lg text-gray-800">{user.lastName}</p> <p className="text-lg text-gray-800">{user.lastName}</p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-700 block">
Email Email
</label> </span>
<p className="text-lg text-gray-800">{user.email}</p> <p className="text-lg text-gray-800">{user.email}</p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-700 block">
Téléphone Téléphone
</label> </span>
<p className="text-lg text-gray-800"> <p className="text-lg text-gray-800">
{user.phone || "Non renseigné"} {user.phone || "Non renseigné"}
</p> </p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-700 block">
Rôle Rôle
</label> </span>
<div className="mt-1"> <div className="mt-1">
<Badge variant={getRoleBadgeVariant(user.role)}> <Badge variant={getRoleBadgeVariant(user.role)}>
{getRoleLabel(user.role)} {getRoleLabel(user.role)}
@ -172,7 +172,7 @@ export default function ProfilePage() {
</button> </button>
<button <button
onClick={() => setShowDeleteModal(true)} onClick={() => setShowDeleteModal(true)}
className="bg-red-500 hover:bg-red-600 text-white font-bold px-6 py-3 rounded-lg transition-all shadow-lg hover:scale-105 duration-300" className="bg-red-600 hover:bg-red-700 text-white font-bold px-6 py-3 rounded-lg transition-all shadow-lg hover:scale-105 duration-300"
> >
Supprimer mon compte Supprimer mon compte
</button> </button>
@ -242,9 +242,9 @@ export default function ProfilePage() {
</div> </div>
<div className="p-6 space-y-4"> <div className="p-6 space-y-4">
<div> <div>
<label className="text-sm font-medium text-gray-600"> <span className="text-sm font-medium text-gray-700 block">
Membre depuis Membre depuis
</label> </span>
<p className="text-sm text-gray-800 mt-1"> <p className="text-sm text-gray-800 mt-1">
{formatDate(user.createdAt)} {formatDate(user.createdAt)}
</p> </p>
@ -325,7 +325,7 @@ export default function ProfilePage() {
<button <button
onClick={handleDeleteAccount} onClick={handleDeleteAccount}
disabled={isDeleting} disabled={isDeleting}
className="flex-1 bg-red-500 hover:bg-red-600 disabled:bg-red-300 text-white font-bold px-6 py-3 rounded-lg transition-all" className="flex-1 bg-red-600 hover:bg-red-700 disabled:bg-red-300 text-white font-bold px-6 py-3 rounded-lg transition-all"
> >
{isDeleting ? "Suppression..." : "Supprimer"} {isDeleting ? "Suppression..." : "Supprimer"}
</button> </button>

View File

@ -129,6 +129,8 @@ export default function RegisterPage() {
{/* Form Container */} {/* Form Container */}
<div className="p-8"> <div className="p-8">
{/* Hidden h2 for accessibility - maintains heading hierarchy */}
<h2 className="sr-only">Formulaire d'inscription</h2>
{/* Registration Form */} {/* Registration Form */}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5"> <form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
@ -136,8 +138,9 @@ export default function RegisterPage() {
{/* Prénom et Nom */} {/* Prénom et Nom */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label htmlFor="firstName" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="firstName" className="block text-sm font-semibold text-gray-900 mb-2">
Prénom <span className="text-red-500">*</span> Prénom <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<input <input
id="firstName" id="firstName"
@ -152,8 +155,9 @@ export default function RegisterPage() {
</div> </div>
<div> <div>
<label htmlFor="lastName" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="lastName" className="block text-sm font-semibold text-gray-900 mb-2">
Nom <span className="text-red-500">*</span> Nom <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<input <input
id="lastName" id="lastName"
@ -170,8 +174,9 @@ export default function RegisterPage() {
{/* Email */} {/* Email */}
<div> <div>
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="email" className="block text-sm font-semibold text-gray-900 mb-2">
Email <span className="text-red-500">*</span> Email <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<div className="relative"> <div className="relative">
<input <input
@ -224,8 +229,8 @@ export default function RegisterPage() {
{/* Téléphone */} {/* Téléphone */}
<div> <div>
<label htmlFor="phone" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="phone" className="block text-sm font-semibold text-gray-900 mb-2">
Téléphone Téléphone <span className="sr-only">(optionnel)</span>
</label> </label>
<input <input
id="phone" id="phone"
@ -234,7 +239,7 @@ export default function RegisterPage() {
{...register("phone")} {...register("phone")}
className={`w-full px-4 py-3 border ${errors.phone ? 'border-red-500' : 'border-gray-300'} rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent`} className={`w-full px-4 py-3 border ${errors.phone ? 'border-red-500' : 'border-gray-300'} rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent`}
/> />
<p className="mt-1 text-xs text-gray-500">Optionnel - Format: 06 12 34 56 78</p> <p className="mt-1 text-xs text-gray-600">Optionnel - Format: 06 12 34 56 78</p>
{errors.phone && ( {errors.phone && (
<p className="mt-1 text-sm text-red-500">{errors.phone.message}</p> <p className="mt-1 text-sm text-red-500">{errors.phone.message}</p>
)} )}
@ -242,8 +247,9 @@ export default function RegisterPage() {
{/* Mot de passe */} {/* Mot de passe */}
<div> <div>
<label htmlFor="password" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="password" className="block text-sm font-semibold text-gray-900 mb-2">
Mot de passe <span className="text-red-500">*</span> Mot de passe <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<div className="relative"> <div className="relative">
<input <input
@ -257,20 +263,21 @@ export default function RegisterPage() {
type="button" type="button"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700" className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
aria-label={showPassword ? "Masquer le mot de passe" : "Afficher le mot de passe"}
> >
{showPassword ? ( {showPassword ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg> </svg>
) : ( ) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg> </svg>
)} )}
</button> </button>
</div> </div>
<p className="mt-1 text-xs text-gray-500">Min. 8 caractères, 1 majuscule, 1 minuscule, 1 chiffre</p> <p className="mt-1 text-xs text-gray-600">Min. 8 caractères, 1 majuscule, 1 minuscule, 1 chiffre</p>
{errors.password && ( {errors.password && (
<p className="mt-1 text-sm text-red-500">{errors.password.message}</p> <p className="mt-1 text-sm text-red-500">{errors.password.message}</p>
)} )}
@ -278,8 +285,9 @@ export default function RegisterPage() {
{/* Confirmer mot de passe */} {/* Confirmer mot de passe */}
<div> <div>
<label htmlFor="confirmPassword" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="confirmPassword" className="block text-sm font-semibold text-gray-900 mb-2">
Confirmer le mot de passe <span className="text-red-500">*</span> Confirmer le mot de passe <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<div className="relative"> <div className="relative">
<input <input
@ -293,13 +301,14 @@ export default function RegisterPage() {
type="button" type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)} onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700" className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
aria-label={showConfirmPassword ? "Masquer la confirmation du mot de passe" : "Afficher la confirmation du mot de passe"}
> >
{showConfirmPassword ? ( {showConfirmPassword ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg> </svg>
) : ( ) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg> </svg>
@ -328,7 +337,7 @@ export default function RegisterPage() {
<Link href="/privacy" className="text-beige-800 underline hover:text-primary-500"> <Link href="/privacy" className="text-beige-800 underline hover:text-primary-500">
politique de confidentialité politique de confidentialité
</Link>{' '} </Link>{' '}
<span className="text-red-500">*</span> <span className="text-red-700 font-bold" aria-hidden="true">*</span>
</label> </label>
</div> </div>

View File

@ -132,8 +132,9 @@ function ResetPasswordForm() {
{/* New Password */} {/* New Password */}
<div> <div>
<label htmlFor="password" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="password" className="block text-sm font-semibold text-gray-900 mb-2">
Nouveau mot de passe <span className="text-red-500">*</span> Nouveau mot de passe <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<div className="relative"> <div className="relative">
<input <input
@ -151,13 +152,14 @@ function ResetPasswordForm() {
type="button" type="button"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700" className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
aria-label={showPassword ? "Masquer le mot de passe" : "Afficher le mot de passe"}
> >
{showPassword ? ( {showPassword ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg> </svg>
) : ( ) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg> </svg>
@ -168,8 +170,9 @@ function ResetPasswordForm() {
{/* Confirm Password */} {/* Confirm Password */}
<div> <div>
<label htmlFor="confirmPassword" className="block text-sm font-semibold text-gray-700 mb-2"> <label htmlFor="confirmPassword" className="block text-sm font-semibold text-gray-900 mb-2">
Confirmer le mot de passe <span className="text-red-500">*</span> Confirmer le mot de passe <span className="text-red-700 font-bold" aria-hidden="true">*</span>
<span className="sr-only">(obligatoire)</span>
</label> </label>
<input <input
id="confirmPassword" id="confirmPassword"
@ -186,7 +189,7 @@ function ResetPasswordForm() {
{/* Password Requirements */} {/* Password Requirements */}
<div className="bg-gray-50 p-4 rounded-lg"> <div className="bg-gray-50 p-4 rounded-lg">
<p className="text-sm font-semibold text-gray-700 mb-2">Le mot de passe doit contenir :</p> <p className="text-sm font-semibold text-gray-900 mb-2">Le mot de passe doit contenir :</p>
<ul className="text-sm text-gray-600 space-y-1"> <ul className="text-sm text-gray-600 space-y-1">
<li className={`flex items-center gap-2 ${password.length >= 8 ? 'text-green-600' : ''}`}> <li className={`flex items-center gap-2 ${password.length >= 8 ? 'text-green-600' : ''}`}>
{password.length >= 8 ? ( {password.length >= 8 ? (

View File

@ -41,6 +41,8 @@ export default function Footer() {
return ( return (
<footer className="bg-gradient-to-br from-beige-100 via-beige-50 to-beige-100 text-beige-800 border-t-2 border-beige-300"> <footer className="bg-gradient-to-br from-beige-100 via-beige-50 to-beige-100 text-beige-800 border-t-2 border-beige-300">
{/* Hidden h2 for accessibility - maintains heading hierarchy */}
<h2 className="sr-only">Pied de page</h2>
{/* Main Footer */} {/* Main Footer */}
<div className="container mx-auto px-4 py-12"> <div className="container mx-auto px-4 py-12">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">

View File

@ -8,9 +8,9 @@ import {
Gift, Gift,
Trophy, Trophy,
} from "lucide-react"; } from "lucide-react";
import { SharedSidebar, NavItem } from "@/components/shared/SharedSidebar"; import DashboardSidebar from "@/components/ui/DashboardSidebar";
const ADMIN_NAV_ITEMS: NavItem[] = [ const navItems = [
{ {
label: "Dashboard", label: "Dashboard",
href: "/admin/dashboard", href: "/admin/dashboard",
@ -49,22 +49,12 @@ const ADMIN_NAV_ITEMS: NavItem[] = [
}, },
]; ];
const ADMIN_ACTIVE_STYLES: Record<string, string> = {
darkblue: "bg-gradient-to-r from-[#1e3a5f] to-[#2d5a8f] text-white shadow-lg shadow-blue-900/30",
blue: "bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-lg shadow-blue-500/30",
emerald: "bg-gradient-to-r from-emerald-600 to-teal-600 text-white shadow-lg shadow-emerald-500/30",
purple: "bg-gradient-to-r from-purple-600 to-pink-600 text-white shadow-lg shadow-purple-500/30",
indigo: "bg-gradient-to-r from-indigo-600 to-purple-600 text-white shadow-lg shadow-indigo-500/30",
amber: "bg-gradient-to-r from-amber-500 to-orange-600 text-white shadow-lg shadow-amber-500/30",
};
export default function Sidebar() { export default function Sidebar() {
return ( return (
<SharedSidebar <DashboardSidebar
navItems={ADMIN_NAV_ITEMS} navItems={navItems}
title="Thé Tip Top" title="Thé Tip Top"
subtitle="Administration" subtitle="Administration"
activeStyles={ADMIN_ACTIVE_STYLES}
/> />
); );
} }

View File

@ -0,0 +1,117 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Menu, X } from "lucide-react";
import { useState } from "react";
import Logo from "@/components/Logo";
export interface NavItem {
label: string;
href: string;
icon: React.ReactNode;
color: string;
}
interface DashboardSidebarProps {
navItems: NavItem[];
title: string;
subtitle: string;
colorStyles?: Record<string, string>;
}
const defaultColorStyles: Record<string, string> = {
darkblue: "bg-gradient-to-r from-[#1e3a5f] to-[#2d5a8f] text-white shadow-lg shadow-blue-900/30",
blue: "bg-gradient-to-r from-blue-600 to-indigo-600 text-white shadow-lg shadow-blue-500/30",
green: "bg-gradient-to-r from-green-600 to-green-700 text-white shadow-lg shadow-green-500/30",
emerald: "bg-gradient-to-r from-emerald-600 to-teal-600 text-white shadow-lg shadow-emerald-500/30",
purple: "bg-gradient-to-r from-purple-600 to-pink-600 text-white shadow-lg shadow-purple-500/30",
indigo: "bg-gradient-to-r from-indigo-600 to-purple-600 text-white shadow-lg shadow-indigo-500/30",
amber: "bg-gradient-to-r from-amber-500 to-orange-600 text-white shadow-lg shadow-amber-500/30",
};
export default function DashboardSidebar({
navItems,
title,
subtitle,
colorStyles = defaultColorStyles,
}: DashboardSidebarProps) {
const pathname = usePathname();
const [isOpen, setIsOpen] = useState(false);
const isActive = (href: string) => pathname === href;
const getActiveStyles = (color: string) => {
return colorStyles[color] || colorStyles.blue || defaultColorStyles.blue;
};
return (
<>
{/* Mobile menu button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="lg:hidden fixed top-4 left-4 z-50 p-2 rounded-xl bg-white shadow-lg hover:bg-gray-50 border border-gray-100"
>
{isOpen ? (
<X className="w-6 h-6 text-gray-600" />
) : (
<Menu className="w-6 h-6 text-gray-600" />
)}
</button>
{/* Overlay for mobile */}
{isOpen && (
<div
className="lg:hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-30"
onClick={() => setIsOpen(false)}
/>
)}
{/* Sidebar */}
<aside
className={`
fixed top-0 left-0 h-full bg-[#faf8f5] shadow-xl z-40 transition-transform duration-300 ease-in-out border-r border-gray-200
${isOpen ? "translate-x-0" : "-translate-x-full"}
lg:translate-x-0 lg:static
w-72
`}
>
<div className="flex flex-col h-full">
{/* Logo/Header */}
<div className="p-6 border-b border-gray-200">
<div className="flex items-center gap-3">
<Logo size="md" showText={false} />
<div>
<h2 className="text-xl font-bold text-[#1e3a5f]">{title}</h2>
<p className="text-sm text-gray-500">{subtitle}</p>
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 p-4 space-y-2">
<p className="text-xs font-semibold text-gray-400 uppercase tracking-wider px-4 mb-3">Menu Principal</p>
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
onClick={() => setIsOpen(false)}
className={`
flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200
${
isActive(item.href)
? getActiveStyles(item.color)
: "text-gray-600 hover:bg-gray-100 hover:text-gray-900"
}
`}
>
{item.icon}
<span className="font-medium">{item.label}</span>
</Link>
))}
</nav>
</div>
</aside>
</>
);
}

View File

@ -16,24 +16,24 @@ interface StatusBadgeProps {
} }
const roleColors: Record<string, string> = { const roleColors: Record<string, string> = {
ADMIN: 'bg-red-100 text-red-800', ADMIN: 'bg-red-100 text-red-900',
EMPLOYEE: 'bg-blue-100 text-blue-800', EMPLOYEE: 'bg-blue-100 text-blue-900',
CLIENT: 'bg-green-100 text-green-800', CLIENT: 'bg-green-100 text-green-900',
}; };
const ticketColors: Record<string, string> = { const ticketColors: Record<string, string> = {
AVAILABLE: 'bg-gray-100 text-gray-800', AVAILABLE: 'bg-gray-100 text-gray-900',
CLAIMED: 'bg-green-100 text-green-800', CLAIMED: 'bg-green-100 text-green-900',
PENDING: 'bg-yellow-100 text-yellow-800', PENDING: 'bg-yellow-100 text-yellow-900',
EXPIRED: 'bg-red-100 text-red-800', EXPIRED: 'bg-red-100 text-red-900',
REJECTED: 'bg-red-100 text-red-800', REJECTED: 'bg-red-100 text-red-900',
}; };
const statusColors: Record<string, string> = { const statusColors: Record<string, string> = {
active: 'bg-green-100 text-green-800', active: 'bg-green-100 text-green-900',
inactive: 'bg-gray-100 text-gray-800', inactive: 'bg-gray-100 text-gray-900',
verified: 'bg-green-100 text-green-800', verified: 'bg-green-100 text-green-900',
unverified: 'bg-yellow-100 text-yellow-800', unverified: 'bg-yellow-100 text-yellow-900',
}; };
const roleLabels: Record<string, string> = { const roleLabels: Record<string, string> = {
@ -74,7 +74,7 @@ export const StatusBadge: React.FC<StatusBadgeProps> = ({
className, className,
showIcon = false, showIcon = false,
}) => { }) => {
let colorClass = 'bg-gray-100 text-gray-800'; let colorClass = 'bg-gray-100 text-gray-900';
let label = value; let label = value;
let icon: React.ReactNode = null; let icon: React.ReactNode = null;
@ -115,15 +115,15 @@ export const StatusBadge: React.FC<StatusBadgeProps> = ({
// Utility functions for getting colors (for backward compatibility) // Utility functions for getting colors (for backward compatibility)
export const getRoleBadgeColor = (role: RoleType): string => { export const getRoleBadgeColor = (role: RoleType): string => {
return roleColors[role] || 'bg-gray-100 text-gray-800'; return roleColors[role] || 'bg-gray-100 text-gray-900';
}; };
export const getTicketStatusColor = (status: TicketStatusType): string => { export const getTicketStatusColor = (status: TicketStatusType): string => {
return ticketColors[status] || 'bg-gray-100 text-gray-800'; return ticketColors[status] || 'bg-gray-100 text-gray-900';
}; };
export const getStatusColor = (status: StatusType): string => { export const getStatusColor = (status: StatusType): string => {
return statusColors[status] || 'bg-gray-100 text-gray-800'; return statusColors[status] || 'bg-gray-100 text-gray-900';
}; };
export default StatusBadge; export default StatusBadge;

View File

@ -29,7 +29,7 @@ export const TicketTableRow: React.FC<TicketTableRowProps> = ({
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<StatusBadge type="ticket" value={ticket.status} /> <StatusBadge type="ticket" value={ticket.status} />
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[#8a8a7a]"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
{ticket.playedAt {ticket.playedAt
? new Date(ticket.playedAt).toLocaleDateString("fr-FR", showClaimedDate ? { ? new Date(ticket.playedAt).toLocaleDateString("fr-FR", showClaimedDate ? {
day: 'numeric', day: 'numeric',
@ -41,7 +41,7 @@ export const TicketTableRow: React.FC<TicketTableRowProps> = ({
: "-"} : "-"}
</td> </td>
{showClaimedDate && ( {showClaimedDate && (
<td className="px-6 py-4 whitespace-nowrap text-sm text-[#8a8a7a]"> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
{ticket.claimedAt {ticket.claimedAt
? new Date(ticket.claimedAt).toLocaleDateString("fr-FR", { ? new Date(ticket.claimedAt).toLocaleDateString("fr-FR", {
day: 'numeric', day: 'numeric',