feat: modern UI redesign with SVG icons and improved styling

- Update client dashboard with modern cards, SVG statistics icons, and prize icons
- Add roulette animation with colored prize icons during ticket draw
- Redesign history page with 4 statistics cards and SVG icons
- Add "Rejetés" filter button in history page
- Update profile page with modern card styling
- Redesign header with clickable user name/email button
- Add Facebook login button with green border styling
- Update game page with roulette animation and prize display
- Add prize values to constants (15€, 25€, 39€, 69€)
- Replace all emoji icons with professional SVG icons
- Apply consistent color scheme: green (#1a4d2e, #2d5a3d) and orange (#f59e0b)
- Improve button styles and hover effects across all pages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
soufiane 2025-11-19 02:29:41 +01:00
parent f822077f51
commit 70f61fca88
7 changed files with 943 additions and 643 deletions

View File

@ -78,178 +78,214 @@ export default function ClientPage() {
}; };
return ( return (
<div className="py-8"> <div className="min-h-screen bg-gray-50 py-8">
{/* Welcome Section */} <div className="container mx-auto px-4">
<div className="mb-8"> {/* Welcome Section */}
<h1 className="text-4xl font-bold text-gray-900 mb-2"> <div className="mb-8">
Bonjour {user?.firstName} ! 👋 <h1 className="text-4xl font-bold text-gray-900 mb-2">
</h1> Bonjour {user?.firstName} ! 👋
<p className="text-gray-600"> </h1>
Bienvenue dans votre espace client <p className="text-gray-600">
</p> Bienvenue dans votre espace client
</div> </p>
</div>
{/* Quick Action */} {/* Quick Action */}
<div className="mb-8"> <div className="mb-8">
<Card className="bg-gradient-to-r from-primary-500 to-primary-600 text-white"> <div className="bg-gradient-to-r from-[#1a4d2e] to-[#2d5a3d] text-white rounded-xl shadow-md p-8">
<CardContent className="py-8">
<div className="flex flex-col md:flex-row items-center justify-between gap-4"> <div className="flex flex-col md:flex-row items-center justify-between gap-4">
<div> <div>
<h2 className="text-2xl font-bold mb-2"> <h2 className="text-2xl font-bold mb-2">
Vous avez un nouveau ticket ? Vous avez un nouveau ticket ?
</h2> </h2>
<p className="text-primary-50"> <p className="text-green-50">
Entrez votre code et découvrez votre gain instantanément Entrez votre code et découvrez votre gain instantanément
</p> </p>
</div> </div>
<Link href={ROUTES.GAME}> <Link href={ROUTES.GAME}>
<Button <button className="bg-[#f59e0b] hover:bg-[#d97706] text-white font-bold px-8 py-4 rounded-lg transition-all hover:shadow-xl whitespace-nowrap">
size="lg"
className="bg-white text-black hover:bg-gray-50 border-2 border-black"
>
Jouer maintenant 🎮 Jouer maintenant 🎮
</Button> </button>
</Link> </Link>
</div> </div>
</CardContent> </div>
</Card> </div>
</div>
{/* Statistics Cards */} {/* Statistics Cards */}
<div className="grid md:grid-cols-3 gap-6 mb-8"> <div className="grid md:grid-cols-3 gap-6 mb-8">
<Card> <div className="bg-white rounded-xl shadow-md p-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600"> <p className="text-sm font-medium text-gray-600 mb-2">
Total Participations Total Participations
</p> </p>
<p className="text-3xl font-bold text-gray-900 mt-2"> <p className="text-4xl font-bold text-gray-900">
{stats.total} {stats.total}
</p> </p>
</div> </div>
<div className="text-4xl">🎫</div> <div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
</div> </div>
</CardContent> </div>
</Card>
<Card> <div className="bg-white rounded-xl shadow-md p-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600"> <p className="text-sm font-medium text-gray-600 mb-2">
Gains réclamés Gains réclamés
</p> </p>
<p className="text-3xl font-bold text-green-600 mt-2"> <p className="text-4xl font-bold text-green-600">
{stats.claimed} {stats.claimed}
</p> </p>
</div> </div>
<div className="text-4xl"></div> <div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div> </div>
</CardContent> </div>
</Card>
<Card> <div className="bg-white rounded-xl shadow-md p-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600"> <p className="text-sm font-medium text-gray-600 mb-2">
En attente En attente
</p> </p>
<p className="text-3xl font-bold text-yellow-600 mt-2"> <p className="text-4xl font-bold text-yellow-600">
{stats.pending} {stats.pending}
</p> </p>
</div> </div>
<div className="text-4xl"></div> <div className="w-16 h-16 bg-yellow-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div> </div>
</CardContent>
</Card>
</div>
{/* Recent Tickets */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>Mes derniers tickets</CardTitle>
<Link href={ROUTES.HISTORY}>
<Button variant="outline" size="sm">
Voir tout l'historique
</Button>
</Link>
</div> </div>
</CardHeader> </div>
<CardContent>
{tickets.length === 0 ? ( {/* Recent Tickets */}
<div className="text-center py-12"> <div className="bg-white rounded-xl shadow-md overflow-hidden">
<div className="text-6xl mb-4">🎲</div> <div className="px-6 py-4 border-b border-gray-200">
<p className="text-gray-600 mb-4"> <div className="flex items-center justify-between">
Vous n'avez pas encore participé au jeu <h2 className="text-xl font-bold text-gray-900">Mes derniers tickets</h2>
</p> <Link href={ROUTES.HISTORY}>
<Link href={ROUTES.GAME}> <button className="text-[#1a4d2e] hover:text-[#f59e0b] font-semibold text-sm transition-colors">
<Button className="bg-white text-black hover:bg-gray-50 border-2 border-black">Jouer maintenant</Button> Voir tout l'historique
</button>
</Link> </Link>
</div> </div>
) : ( </div>
<div className="overflow-x-auto"> <div className="p-6">
<table className="min-w-full divide-y divide-gray-200"> {tickets.length === 0 ? (
<thead className="bg-gray-50"> <div className="text-center py-12">
<tr> <div className="text-6xl mb-4">🎲</div>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <p className="text-gray-600 mb-4">
Code Ticket Vous n'avez pas encore participé au jeu
</th> </p>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <Link href={ROUTES.GAME}>
Gain <button className="bg-[#f59e0b] hover:bg-[#d97706] text-white font-bold px-6 py-3 rounded-lg transition-all">
</th> Jouer maintenant
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> </button>
Statut </Link>
</th> </div>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> ) : (
Date <div className="overflow-x-auto">
</th> <table className="min-w-full">
</tr> <thead>
</thead> <tr className="border-b border-gray-200">
<tbody className="bg-white divide-y divide-gray-200"> <th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
{tickets.slice(0, 5).map((ticket) => { Code Ticket
const prizeConfig = ticket.prize </th>
? PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG] <th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
: null; Gain
</th>
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Statut
</th>
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Date
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{tickets.slice(0, 5).map((ticket) => {
const prizeConfig = ticket.prize
? PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG]
: null;
return ( return (
<tr key={ticket.id} className="hover:bg-gray-50"> <tr key={ticket.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<span className="font-mono text-sm font-medium text-gray-900"> <span className="font-mono text-sm font-semibold text-gray-900">
{ticket.code} {ticket.code}
</span> </span>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4">
<div className="flex items-center"> <div className="flex items-center gap-3">
{prizeConfig && ( {prizeConfig && (
<> <>
<span className="text-2xl mr-2"> <div className={`w-10 h-10 rounded-full flex items-center justify-center ${prizeConfig.color}`}>
{prizeConfig.icon} {ticket.prize?.type === 'INFUSEUR' && (
</span> <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<span className="text-sm text-gray-900"> <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
{prizeConfig.name} </svg>
</span> )}
</> {ticket.prize?.type === 'THE_SIGNATURE' && (
)} <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
</div> <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3z"/>
</td> </svg>
<td className="px-6 py-4 whitespace-nowrap"> )}
{getStatusBadge(ticket.status)} {ticket.prize?.type === 'COFFRET_DECOUVERTE' && (
</td> <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"/>
{ticket.playedAt ? new Date(ticket.playedAt).toLocaleDateString("fr-FR") : "-"} </svg>
</td> )}
</tr> {ticket.prize?.type === 'COFFRET_PRESTIGE' && (
); <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
})} <path d="M12 2L4 5v6.09c0 5.05 3.41 9.76 8 10.91 4.59-1.15 8-5.86 8-10.91V5l-8-3zm6 9.09c0 4-2.55 7.7-6 8.83-3.45-1.13-6-4.82-6-8.83v-4.7l6-2.25 6 2.25v4.7zM8 10.5l1.5 1.5L15 6.5 13.5 5z"/>
</tbody> </svg>
</table> )}
</div> {ticket.prize?.type === 'THE_GRATUIT' && (
)} <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
</CardContent> <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
</Card> </svg>
)}
</div>
<div>
<p className="text-sm font-medium text-gray-900">
{prizeConfig.name}
</p>
{ticket.prize?.value && ticket.prize.value > 0 && (
<p className="text-xs text-gray-500">
{ticket.prize.value}
</p>
)}
</div>
</>
)}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getStatusBadge(ticket.status)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
{ticket.playedAt ? new Date(ticket.playedAt).toLocaleDateString("fr-FR") : "-"}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
</div>
</div>
</div> </div>
); );
} }

View File

@ -103,68 +103,77 @@ export default function HistoriquePage() {
}; };
return ( return (
<div className="py-8"> <div className="min-h-screen bg-gray-50 py-8">
<div className="mb-8"> <div className="container mx-auto px-4">
<h1 className="text-4xl font-bold text-gray-900 mb-2 flex items-center gap-3"> <div className="mb-8">
<Calendar className="w-10 h-10 text-primary-600" /> <h1 className="text-4xl font-bold text-gray-900 mb-2 flex items-center gap-3">
Historique de mes participations <Calendar className="w-10 h-10 text-[#1a4d2e]" />
</h1> Historique de mes participations
<p className="text-gray-600"> </h1>
Consultez l'historique complet de vos participations et gains <p className="text-gray-600">
</p> Consultez l'historique complet de vos participations et gains
</div> </p>
</div>
<div className="grid md:grid-cols-4 gap-6 mb-8"> <div className="grid md:grid-cols-4 gap-6 mb-8">
<Card className="bg-gradient-to-br from-blue-50 to-blue-100"> <div className="bg-white rounded-xl shadow-md p-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600">Total</p> <p className="text-sm font-medium text-gray-600 mb-2">Total</p>
<p className="text-3xl font-bold text-blue-600 mt-2">{stats.total}</p> <p className="text-4xl font-bold text-blue-600">{stats.total}</p>
</div>
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div> </div>
<div className="text-4xl">📊</div>
</div> </div>
</CardContent> </div>
</Card>
<Card className="bg-gradient-to-br from-green-50 to-green-100"> <div className="bg-white rounded-xl shadow-md p-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600">Réclamés</p> <p className="text-sm font-medium text-gray-600 mb-2">Réclamés</p>
<p className="text-3xl font-bold text-green-600 mt-2">{stats.claimed}</p> <p className="text-4xl font-bold text-green-600">{stats.claimed}</p>
</div>
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div> </div>
<div className="text-4xl"></div>
</div> </div>
</CardContent> </div>
</Card>
<Card className="bg-gradient-to-br from-yellow-50 to-yellow-100"> <div className="bg-white rounded-xl shadow-md p-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600">En attente</p> <p className="text-sm font-medium text-gray-600 mb-2">En attente</p>
<p className="text-3xl font-bold text-yellow-600 mt-2">{stats.pending}</p> <p className="text-4xl font-bold text-yellow-600">{stats.pending}</p>
</div>
<div className="w-16 h-16 bg-yellow-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div> </div>
<div className="text-4xl"></div>
</div> </div>
</CardContent> </div>
</Card>
<Card className="bg-gradient-to-br from-red-50 to-red-100"> <div className="bg-white rounded-xl shadow-md p-6">
<CardContent className="pt-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-sm font-medium text-gray-600">Rejetés</p> <p className="text-sm font-medium text-gray-600 mb-2">Rejetés</p>
<p className="text-3xl font-bold text-red-600 mt-2">{stats.rejected}</p> <p className="text-4xl font-bold text-red-600">{stats.rejected}</p>
</div>
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div> </div>
<div className="text-4xl"></div>
</div> </div>
</CardContent> </div>
</Card> </div>
</div>
<Card className="mb-6"> <div className="bg-white rounded-xl shadow-md p-6 mb-6">
<CardContent className="pt-6">
<div className="flex flex-col md:flex-row gap-4"> <div className="flex flex-col md:flex-row gap-4">
<div className="flex-1"> <div className="flex-1">
<div className="relative"> <div className="relative">
@ -174,7 +183,7 @@ export default function HistoriquePage() {
placeholder="Rechercher par code ticket..." placeholder="Rechercher par code ticket..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#1a4d2e] focus:border-transparent"
/> />
</div> </div>
</div> </div>
@ -182,9 +191,9 @@ export default function HistoriquePage() {
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => setFilter('ALL')} onClick={() => setFilter('ALL')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${ className={`px-4 py-2 rounded-lg font-semibold transition-all ${
filter === 'ALL' filter === 'ALL'
? 'bg-primary-600 text-white' ? 'bg-[#1a4d2e] text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`} }`}
> >
@ -192,7 +201,7 @@ export default function HistoriquePage() {
</button> </button>
<button <button
onClick={() => setFilter('CLAIMED')} onClick={() => setFilter('CLAIMED')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${ className={`px-4 py-2 rounded-lg font-semibold transition-all ${
filter === 'CLAIMED' filter === 'CLAIMED'
? 'bg-green-600 text-white' ? 'bg-green-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
@ -202,7 +211,7 @@ export default function HistoriquePage() {
</button> </button>
<button <button
onClick={() => setFilter('PENDING')} onClick={() => setFilter('PENDING')}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${ className={`px-4 py-2 rounded-lg font-semibold transition-all ${
filter === 'PENDING' filter === 'PENDING'
? 'bg-yellow-600 text-white' ? 'bg-yellow-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
@ -210,118 +219,152 @@ export default function HistoriquePage() {
> >
En attente ({stats.pending}) En attente ({stats.pending})
</button> </button>
<button
onClick={() => setFilter('REJECTED')}
className={`px-4 py-2 rounded-lg font-semibold transition-all ${
filter === 'REJECTED'
? 'bg-red-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
Rejetés ({stats.rejected})
</button>
</div> </div>
</div> </div>
</CardContent> </div>
</Card>
<Card> <div className="bg-white rounded-xl shadow-md overflow-hidden">
<CardHeader> <div className="px-6 py-4 border-b border-gray-200">
<CardTitle>Tous mes tickets ({filteredTickets.length})</CardTitle> <h2 className="text-xl font-bold text-gray-900">Tous mes tickets ({filteredTickets.length})</h2>
</CardHeader> </div>
<CardContent> <div className="p-6">
{filteredTickets.length === 0 ? ( {filteredTickets.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<div className="text-6xl mb-4">🎲</div> <div className="text-6xl mb-4">🎲</div>
<p className="text-gray-600 mb-4"> <p className="text-gray-600 mb-4">
{searchQuery || filter !== 'ALL' {searchQuery || filter !== 'ALL'
? 'Aucun ticket trouvé avec ces filtres' ? 'Aucun ticket trouvé avec ces filtres'
: 'Vous n\'avez pas encore participé au jeu'} : 'Vous n\'avez pas encore participé au jeu'}
</p> </p>
{!searchQuery && filter === 'ALL' && ( {!searchQuery && filter === 'ALL' && (
<Button onClick={() => router.push(ROUTES.GAME)} className="bg-white text-black hover:bg-gray-50 border-2 border-black"> <button
Jouer maintenant onClick={() => router.push(ROUTES.GAME)}
</Button> className="bg-[#f59e0b] hover:bg-[#d97706] text-white font-bold px-6 py-3 rounded-lg transition-all"
)} >
</div> Jouer maintenant
) : ( </button>
<div className="overflow-x-auto"> )}
<table className="min-w-full divide-y divide-gray-200"> </div>
<thead className="bg-gray-50"> ) : (
<tr> <div className="overflow-x-auto">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <table className="min-w-full">
Code Ticket <thead>
</th> <tr className="border-b border-gray-200">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Gain Code Ticket
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Statut Gain
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Date de participation Statut
</th> </th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Date de réclamation Date de participation
</th> </th>
</tr> <th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
</thead> Date de réclamation
<tbody className="bg-white divide-y divide-gray-200"> </th>
{filteredTickets.map((ticket) => { </tr>
const prizeConfig = ticket.prize </thead>
? PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG] <tbody className="divide-y divide-gray-200">
: null; {filteredTickets.map((ticket) => {
const prizeConfig = ticket.prize
? PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG]
: null;
return ( return (
<tr key={ticket.id} className="hover:bg-gray-50"> <tr key={ticket.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<span className="font-mono text-sm font-medium text-gray-900"> <span className="font-mono text-sm font-semibold text-gray-900">
{ticket.code} {ticket.code}
</span> </span>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4">
<div className="flex items-center"> <div className="flex items-center gap-3">
{prizeConfig && ( {prizeConfig && (
<> <>
<span className="text-2xl mr-2"> <div className={`w-10 h-10 rounded-full flex items-center justify-center ${prizeConfig.color}`}>
{prizeConfig.icon} {ticket.prize?.type === 'INFUSEUR' && (
</span> <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<div> <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
<p className="text-sm font-medium text-gray-900"> </svg>
{prizeConfig.name} )}
</p> {ticket.prize?.type === 'THE_SIGNATURE' && (
<p className="text-xs text-gray-500"> <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
{ticket.prize?.value} <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3z"/>
</p> </svg>
</div> )}
</> {ticket.prize?.type === 'COFFRET_DECOUVERTE' && (
)} <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
</div> <path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"/>
</td> </svg>
<td className="px-6 py-4 whitespace-nowrap"> )}
{getStatusBadge(ticket.status)} {ticket.prize?.type === 'COFFRET_PRESTIGE' && (
</td> <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <path d="M12 2L4 5v6.09c0 5.05 3.41 9.76 8 10.91 4.59-1.15 8-5.86 8-10.91V5l-8-3zm6 9.09c0 4-2.55 7.7-6 8.83-3.45-1.13-6-4.82-6-8.83v-4.7l6-2.25 6 2.25v4.7zM8 10.5l1.5 1.5L15 6.5 13.5 5z"/>
{ticket.playedAt </svg>
? new Date(ticket.playedAt).toLocaleDateString("fr-FR", { )}
day: 'numeric', {ticket.prize?.type === 'THE_GRATUIT' && (
month: 'long', <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
year: 'numeric', <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
hour: '2-digit', </svg>
minute: '2-digit', )}
}) </div>
: "-"} <div>
</td> <p className="text-sm font-medium text-gray-900">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {prizeConfig.name}
{ticket.claimedAt </p>
? new Date(ticket.claimedAt).toLocaleDateString("fr-FR", { </div>
day: 'numeric', </>
month: 'long', )}
year: 'numeric', </div>
hour: '2-digit', </td>
minute: '2-digit', <td className="px-6 py-4 whitespace-nowrap">
}) {getStatusBadge(ticket.status)}
: "-"} </td>
</td> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
</tr> {ticket.playedAt
); ? new Date(ticket.playedAt).toLocaleDateString("fr-FR", {
})} day: 'numeric',
</tbody> month: 'long',
</table> year: 'numeric',
</div> hour: '2-digit',
)} minute: '2-digit',
</CardContent> })
</Card> : "-"}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
{ticket.claimedAt
? new Date(ticket.claimedAt).toLocaleDateString("fr-FR", {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
: "-"}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
</div>
</div>
</div> </div>
); );
} }

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import { useState } from "react"; import { useState, useEffect } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAuth } from "@/contexts/AuthContext"; import { useAuth } from "@/contexts/AuthContext";
@ -15,13 +15,24 @@ import { useRouter } from "next/navigation";
import { ROUTES } from "@/utils/constants"; import { ROUTES } from "@/utils/constants";
import Link from "next/link"; import Link from "next/link";
const PRIZES = [
{ type: 'INFUSEUR', name: 'Infuseur à thé', color: 'bg-blue-100 text-blue-800' },
{ type: 'THE_SIGNATURE', name: 'Thé signature 100g', color: 'bg-green-100 text-green-800' },
{ type: 'COFFRET_DECOUVERTE', name: 'Coffret découverte 39€', color: 'bg-purple-100 text-purple-800' },
{ type: 'COFFRET_PRESTIGE', name: 'Coffret prestige 69€', color: 'bg-amber-100 text-amber-800' },
{ type: 'THE_GRATUIT', name: 'Thé gratuit en magasin', color: 'bg-pink-100 text-pink-800' },
];
export default function JeuxPage() { export default function JeuxPage() {
const { user, isAuthenticated } = useAuth(); const { user, isAuthenticated } = useAuth();
const { play, isPlaying } = useGame(); const { play, isPlaying } = useGame();
const router = useRouter(); const router = useRouter();
const [showResultModal, setShowResultModal] = useState(false); const [showResultModal, setShowResultModal] = useState(false);
const [showRouletteModal, setShowRouletteModal] = useState(false);
const [gameResult, setGameResult] = useState<PlayGameResponse | null>(null); const [gameResult, setGameResult] = useState<PlayGameResponse | null>(null);
const [errorMessage, setErrorMessage] = useState<string>(""); const [errorMessage, setErrorMessage] = useState<string>("");
const [currentPrizeIndex, setCurrentPrizeIndex] = useState(0);
const [isSpinning, setIsSpinning] = useState(false);
const { const {
register, register,
@ -32,6 +43,17 @@ export default function JeuxPage() {
resolver: zodResolver(ticketCodeSchema), resolver: zodResolver(ticketCodeSchema),
}); });
// Animation de la roulette
useEffect(() => {
if (isSpinning) {
const interval = setInterval(() => {
setCurrentPrizeIndex((prev) => (prev + 1) % PRIZES.length);
}, 100);
return () => clearInterval(interval);
}
}, [isSpinning]);
const onSubmit = async (data: TicketCodeFormData) => { const onSubmit = async (data: TicketCodeFormData) => {
// Réinitialiser le message d'erreur // Réinitialiser le message d'erreur
setErrorMessage(""); setErrorMessage("");
@ -42,14 +64,37 @@ export default function JeuxPage() {
return; return;
} }
// Afficher la modal de roulette et démarrer l'animation
setShowRouletteModal(true);
setIsSpinning(true);
setCurrentPrizeIndex(0);
const result = await play(data.ticketCode); const result = await play(data.ticketCode);
if (result) { if (result) {
setGameResult(result); // Trouver l'index du prix gagné
setShowResultModal(true); const winningIndex = PRIZES.findIndex(p => p.type === result.prize?.type);
setErrorMessage("");
reset(); // Continuer à tourner pendant 3 secondes
setTimeout(() => {
setIsSpinning(false);
if (winningIndex !== -1) {
setCurrentPrizeIndex(winningIndex);
}
// Afficher le résultat après 2 secondes (pour montrer le gain)
setTimeout(() => {
setShowRouletteModal(false);
setGameResult(result);
setShowResultModal(true);
setErrorMessage("");
reset();
}, 2000);
}, 3000);
} else { } else {
// En cas d'erreur, afficher un message personnalisé // En cas d'erreur, fermer la roulette et afficher l'erreur
setIsSpinning(false);
setShowRouletteModal(false);
setErrorMessage("Ce code a déjà été utilisé ou est invalide. Si vous avez déjà utilisé ce code, consultez vos tickets dans 'Mes lots'."); setErrorMessage("Ce code a déjà été utilisé ou est invalide. Si vous avez déjà utilisé ce code, consultez vos tickets dans 'Mes lots'.");
} }
}; };
@ -64,110 +109,189 @@ export default function JeuxPage() {
: null; : null;
return ( return (
<div className="py-8"> <div className="min-h-screen bg-gray-50 py-8">
{/* Formulaire Section */} <div className="container mx-auto px-4">
<section className="mb-16"> {/* Formulaire Section */}
<div className="max-w-2xl mx-auto"> <section className="mb-16">
<Card className="shadow-xl"> <div className="max-w-2xl mx-auto">
<CardHeader className="bg-gradient-to-r from-primary-50 to-green-50"> <div className="bg-white rounded-xl shadow-md overflow-hidden">
<CardTitle className="text-center text-2xl md:text-3xl text-primary-800"> <div className="bg-gradient-to-r from-[#1a4d2e] to-[#2d5a3d] px-6 py-6">
🎁 Jouez maintenant ! <h1 className="text-center text-3xl md:text-4xl font-bold text-white">
</CardTitle> 🎁 Jouez maintenant !
</CardHeader> </h1>
<CardContent className="pt-8"> </div>
<div className="mb-6 text-center"> <div className="p-8">
{isAuthenticated ? ( <div className="mb-6 text-center">
<p className="text-gray-700"> {isAuthenticated ? (
Bonjour <span className="font-bold text-primary-600">{user?.firstName}</span>, <p className="text-gray-700 text-lg">
entrez le code de 10 caractères présent sur votre ticket de caisse Bonjour <span className="font-bold text-[#1a4d2e]">{user?.firstName}</span>,
</p> entrez le code de 10 caractères présent sur votre ticket de caisse
) : (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-4">
<p className="text-yellow-800 text-sm">
💡 Vous devez être connecté pour valider votre code.
<Link href={ROUTES.LOGIN} className="font-semibold underline ml-1">
Connectez-vous
</Link>
</p> </p>
) : (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-4">
<p className="text-yellow-800 text-sm">
💡 Vous devez être connecté pour valider votre code.
<Link href={ROUTES.LOGIN} className="font-semibold underline ml-1 hover:text-yellow-900">
Connectez-vous
</Link>
</p>
</div>
)}
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div>
<label htmlFor="ticketCode" className="block text-sm font-semibold text-gray-700 mb-2">
Code du ticket
</label>
<input
id="ticketCode"
type="text"
placeholder="TTP2025ABC"
{...register("ticketCode")}
className="w-full px-6 py-4 text-center text-2xl font-mono font-bold uppercase border-2 border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#1a4d2e] focus:border-transparent tracking-widest"
maxLength={10}
/>
{errors.ticketCode && (
<p className="mt-2 text-sm text-red-600">{errors.ticketCode.message}</p>
)}
{errorMessage && (
<div className="mt-3 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-sm text-red-800 font-medium mb-2">
{errorMessage}
</p>
<Link
href={ROUTES.HISTORY}
className="text-sm text-red-600 hover:text-red-800 underline font-medium"
>
Voir vos tickets déjà utilisés
</Link>
</div>
)}
<p className="mt-2 text-sm text-gray-500 text-center">
Format: TTP2025ABC (10 caractères)
</p>
</div>
<div className="flex justify-center">
<button
type="submit"
disabled={isPlaying}
className="bg-[#f59e0b] hover:bg-[#d97706] disabled:bg-gray-400 text-white font-bold px-12 py-4 text-lg rounded-lg transition-all shadow-lg hover:shadow-xl"
>
{isPlaying ? "Vérification en cours..." : "🎲 Tenter ma chance !"}
</button>
</div>
</form>
{!isAuthenticated && (
<div className="mt-6 text-center">
<p className="text-sm text-gray-600 mb-3">
Pas encore de compte ?
</p>
<Link href={ROUTES.REGISTER}>
<button className="border-2 border-[#1a4d2e] text-[#1a4d2e] hover:bg-[#1a4d2e] hover:text-white font-bold px-6 py-2 rounded-lg transition-all">
Créer un compte gratuitement
</button>
</Link>
</div>
)}
{isAuthenticated && (
<div className="mt-6 p-4 bg-blue-50 border-l-4 border-blue-500 rounded-lg">
<p className="text-sm text-blue-800 font-semibold mb-2">
💡 Bon à savoir :
</p>
<ul className="text-sm text-blue-700 space-y-1 list-disc list-inside">
<li>Chaque code ne peut être utilisé qu'une seule fois</li>
<li>Consultez vos tickets sur la page <Link href={ROUTES.HISTORY} className="underline font-medium hover:text-blue-900">Mes gains</Link></li>
</ul>
</div> </div>
)} )}
</div> </div>
</div>
</div>
</section>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6"> {/* Roulette Modal */}
<div> <Modal
<label htmlFor="ticketCode" className="block text-sm font-medium text-gray-700 mb-2"> isOpen={showRouletteModal}
Code du ticket onClose={() => {}}
</label> title="🎰 Tirage en cours..."
<input size="md"
id="ticketCode" >
type="text" <div className="py-12">
placeholder="TTP2025ABC" <div className="flex flex-col items-center gap-8">
{...register("ticketCode")} {/* Roulette Display */}
className="w-full px-6 py-4 text-center text-2xl font-mono font-bold uppercase border-2 border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent tracking-widest" <div className="relative w-full max-w-sm">
maxLength={10} <div className="flex flex-col gap-3">
/> {PRIZES.map((prize, index) => {
{errors.ticketCode && ( const getPrizeIcon = (type: string) => {
<p className="mt-2 text-sm text-red-600">{errors.ticketCode.message}</p> switch(type) {
)} case 'INFUSEUR':
{errorMessage && ( return (
<div className="mt-3 p-4 bg-red-50 border border-red-200 rounded-lg"> <svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
<p className="text-sm text-red-800 font-medium mb-2"> <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
{errorMessage} </svg>
</p> );
<Link case 'THE_SIGNATURE':
href={ROUTES.MY_LOTS} return (
className="text-sm text-red-600 hover:text-red-800 underline font-medium" <svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3z"/>
</svg>
);
case 'COFFRET_DECOUVERTE':
return (
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"/>
</svg>
);
case 'COFFRET_PRESTIGE':
return (
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2L4 5v6.09c0 5.05 3.41 9.76 8 10.91 4.59-1.15 8-5.86 8-10.91V5l-8-3zm6 9.09c0 4-2.55 7.7-6 8.83-3.45-1.13-6-4.82-6-8.83v-4.7l6-2.25 6 2.25v4.7zM8 10.5l1.5 1.5L15 6.5 13.5 5z"/>
</svg>
);
case 'THE_GRATUIT':
return (
<svg className="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
</svg>
);
}
};
return (
<div
key={index}
className={`flex items-center gap-4 p-4 rounded-xl transition-all duration-200 ${
currentPrizeIndex === index
? `${prize.color} scale-110 shadow-xl border-4 border-current`
: 'bg-gray-100 opacity-50 scale-95'
}`}
> >
Voir vos tickets déjà utilisés <div className={`w-12 h-12 rounded-full flex items-center justify-center ${prize.color}`}>
</Link> {getPrizeIcon(prize.type)}
</div> </div>
)} <div className="flex-1">
<p className="mt-2 text-sm text-gray-500 text-center"> <p className={`font-bold ${currentPrizeIndex === index ? 'text-lg' : 'text-sm'}`}>
Format: TTP2025ABC (10 caractères) {prize.name}
</p> </p>
</div>
</div>
);
})}
</div> </div>
</div>
<div className="flex justify-center"> {/* Loading Animation */}
<Button <div className="flex items-center gap-2 text-gray-600">
type="submit" <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#1a4d2e]"></div>
isLoading={isPlaying} <span className="font-medium">Tirage en cours...</span>
disabled={isPlaying} </div>
size="lg" </div>
className="px-12 py-4 text-lg" </div>
> </Modal>
{isPlaying ? "Vérification en cours..." : "🎲 Tenter ma chance !"}
</Button>
</div>
</form>
{!isAuthenticated && (
<div className="mt-6 text-center">
<p className="text-sm text-gray-600 mb-3">
Pas encore de compte ?
</p>
<Link href={ROUTES.REGISTER}>
<Button variant="outline" size="sm">
Créer un compte gratuitement
</Button>
</Link>
</div>
)}
{isAuthenticated && (
<div className="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<p className="text-sm text-blue-800 mb-2">
💡 <strong>Bon à savoir :</strong>
</p>
<ul className="text-sm text-blue-700 space-y-1 list-disc list-inside">
<li>Chaque code ne peut être utilisé qu'une seule fois</li>
<li>Consultez vos tickets sur la page <Link href={ROUTES.MY_LOTS} className="underline font-medium">Mes lots</Link></li>
</ul>
</div>
)}
</CardContent>
</Card>
</div>
</section>
{/* Result Modal */} {/* Result Modal */}
<Modal <Modal
@ -178,8 +302,36 @@ export default function JeuxPage() {
> >
{gameResult && prizeConfig && ( {gameResult && prizeConfig && (
<div className="text-center py-6"> <div className="text-center py-6">
<div className="text-6xl mb-4">{prizeConfig.icon}</div> <div className="flex justify-center mb-4">
<h3 className="text-2xl font-bold text-gray-900 mb-2"> <div className={`w-24 h-24 rounded-full flex items-center justify-center ${prizeConfig.color}`}>
{gameResult.prize?.type === 'INFUSEUR' && (
<svg className="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
</svg>
)}
{gameResult.prize?.type === 'THE_SIGNATURE' && (
<svg className="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3z"/>
</svg>
)}
{gameResult.prize?.type === 'COFFRET_DECOUVERTE' && (
<svg className="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 11 8.76l1-1.36 1 1.36L15.38 12 17 10.83 14.92 8H20v6z"/>
</svg>
)}
{gameResult.prize?.type === 'COFFRET_PRESTIGE' && (
<svg className="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2L4 5v6.09c0 5.05 3.41 9.76 8 10.91 4.59-1.15 8-5.86 8-10.91V5l-8-3zm6 9.09c0 4-2.55 7.7-6 8.83-3.45-1.13-6-4.82-6-8.83v-4.7l6-2.25 6 2.25v4.7zM8 10.5l1.5 1.5L15 6.5 13.5 5z"/>
</svg>
)}
{gameResult.prize?.type === 'THE_GRATUIT' && (
<svg className="w-12 h-12" fill="currentColor" viewBox="0 0 24 24">
<path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.9 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM4 19h16v2H4z"/>
</svg>
)}
</div>
</div>
<h3 className="text-3xl font-bold text-gray-900 mb-2">
Félicitations ! 🎉 Félicitations ! 🎉
</h3> </h3>
<p className="text-lg text-gray-700 mb-4"> <p className="text-lg text-gray-700 mb-4">
@ -194,16 +346,23 @@ export default function JeuxPage() {
{gameResult.message || "Présentez-vous en magasin pour récupérer votre lot !"} {gameResult.message || "Présentez-vous en magasin pour récupérer votre lot !"}
</p> </p>
<div className="flex gap-3 justify-center"> <div className="flex gap-3 justify-center">
<Button onClick={closeModal} variant="outline"> <button
onClick={closeModal}
className="border-2 border-gray-300 hover:bg-gray-50 text-gray-700 font-bold px-6 py-3 rounded-lg transition-all"
>
Fermer Fermer
</Button> </button>
<Button onClick={() => router.push(ROUTES.MY_LOTS)}> <button
Voir mes lots onClick={() => router.push(ROUTES.HISTORY)}
</Button> className="bg-[#1a4d2e] hover:bg-[#2d5a3d] text-white font-bold px-6 py-3 rounded-lg transition-all"
>
Voir mes gains
</button>
</div> </div>
</div> </div>
)} )}
</Modal> </Modal>
</div>
</div> </div>
); );
} }

View File

@ -6,9 +6,7 @@ import { useAuth } from "@/contexts/AuthContext";
import { loginSchema, LoginFormData } from "@/lib/validations"; import { loginSchema, LoginFormData } from "@/lib/validations";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import Button from "@/components/Button"; import Button from "@/components/Button";
import { Card } from "@/components/ui/Card";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
import { ROUTES } from "@/utils/constants"; import { ROUTES } from "@/utils/constants";
import { GoogleLoginButton } from "@/components/GoogleLoginButton"; import { GoogleLoginButton } from "@/components/GoogleLoginButton";
import { initFacebookSDK, loginWithFacebook } from "@/lib/facebook-sdk"; import { initFacebookSDK, loginWithFacebook } from "@/lib/facebook-sdk";
@ -21,12 +19,12 @@ export default function LoginPage() {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [isFacebookLoading, setIsFacebookLoading] = useState(false); const [isFacebookLoading, setIsFacebookLoading] = useState(false);
const [isFacebookSDKLoaded, setIsFacebookSDKLoaded] = useState(false); const [isFacebookSDKLoaded, setIsFacebookSDKLoaded] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const hasGoogleAuth = !!process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; const hasGoogleAuth = !!process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
const hasFacebookAuth = !!process.env.NEXT_PUBLIC_FACEBOOK_APP_ID; const hasFacebookAuth = !!process.env.NEXT_PUBLIC_FACEBOOK_APP_ID;
useEffect(() => { useEffect(() => {
// Initialiser le SDK Facebook au chargement de la page
initFacebookSDK() initFacebookSDK()
.then(() => { .then(() => {
setIsFacebookSDKLoaded(true); setIsFacebookSDKLoaded(true);
@ -77,112 +75,153 @@ export default function LoginPage() {
}; };
return ( return (
<div className="min-h-[calc(100vh-4rem)] flex items-center justify-center py-12 px-4"> <div className="min-h-screen bg-gray-50 flex items-center justify-center py-12 px-4">
<Card className="w-full max-w-md p-8"> <div className="w-full max-w-md">
{/* Title */}
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="flex justify-center mb-6"> <h1 className="text-4xl font-bold text-gray-900 mb-2">Connexion</h1>
<Image
src="/logos/logo.svg"
alt="Thé Tip Top"
width={120}
height={120}
priority
/>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Connexion</h1>
<p className="text-gray-600"> <p className="text-gray-600">
Connectez-vous pour participer au jeu-concours Connectez-vous pour participer au jeu-concours
</p> </p>
</div> </div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> {/* Main Card */}
<Input <div className="bg-white rounded-xl shadow-md overflow-hidden">
id="email"
type="email"
label="Email"
placeholder="votre.email@example.com"
error={errors.email?.message}
{...register("email")}
required
/>
<Input {/* Tabs */}
id="password" <div className="flex border-b border-gray-200">
type="password" <button className="flex-1 py-4 px-6 text-center font-semibold text-gray-900 bg-white border-b-2 border-[#1a4d2e]">
label="Mot de passe" Connexion
placeholder="••••••••" </button>
error={errors.password?.message}
{...register("password")}
required
/>
<div className="flex items-center justify-between text-sm">
<Link <Link
href="/forgot-password" href={ROUTES.REGISTER}
className="text-blue-600 hover:text-blue-700 hover:underline" className="flex-1 py-4 px-6 text-center font-semibold text-gray-500 bg-gray-50 hover:bg-gray-100 transition-colors"
> >
Mot de passe oublié ? Inscription
</Link> </Link>
</div> </div>
<Button {/* Form Container */}
type="submit" <div className="p-8">
isLoading={isSubmitting}
disabled={isSubmitting}
fullWidth
size="lg"
>
Se connecter
</Button>
</form>
{(hasGoogleAuth || hasFacebookAuth) && ( {/* Social Login Buttons */}
<div className="mt-6"> {(hasGoogleAuth || hasFacebookAuth) && (
<div className="relative"> <div className="space-y-3 mb-6">
<div className="absolute inset-0 flex items-center"> {hasGoogleAuth && (
<div className="w-full border-t border-gray-300" /> <GoogleLoginButton fullWidth />
)}
{hasFacebookAuth && (
<button
type="button"
onClick={handleFacebookLogin}
disabled={isFacebookLoading || !isFacebookSDKLoaded}
className="w-full flex items-center justify-center gap-3 px-4 py-3 border-2 border-primary-600 rounded-lg hover:bg-primary-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-5 h-5" fill="#1877F2" viewBox="0 0 24 24">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
</svg>
<span className="font-medium text-primary-600">
{isFacebookLoading ? "Connexion..." : "Continuer avec Facebook"}
</span>
</button>
)}
</div> </div>
<div className="relative flex justify-center text-sm"> )}
<span className="px-2 bg-white text-gray-500">
Ou continuer avec {/* Divider */}
</span> {(hasGoogleAuth || hasFacebookAuth) && (
<div className="relative mb-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-4 bg-white text-gray-500">
Ou avec votre email
</span>
</div>
</div> </div>
</div> )}
<div className={`mt-6 grid ${hasGoogleAuth && hasFacebookAuth ? 'grid-cols-2' : 'grid-cols-1'} gap-3`}> {/* Login Form */}
{hasGoogleAuth && ( <form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
<GoogleLoginButton fullWidth />
)}
{hasFacebookAuth && ( {/* Email */}
<Button <div>
type="button" <label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2">
variant="outline" Email <span className="text-red-500">*</span>
onClick={handleFacebookLogin} </label>
disabled={isFacebookLoading || !isFacebookSDKLoaded} <input
isLoading={isFacebookLoading} id="email"
fullWidth type="email"
placeholder="votre@email.com"
{...register("email")}
className={`w-full px-4 py-3 border ${errors.email ? 'border-red-500' : 'border-gray-300'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[#1a4d2e] focus:border-transparent`}
/>
{errors.email && (
<p className="mt-1 text-sm text-red-500">{errors.email.message}</p>
)}
</div>
{/* Password */}
<div>
<label htmlFor="password" className="block text-sm font-semibold text-gray-700 mb-2">
Mot de passe <span className="text-red-500">*</span>
</label>
<div className="relative">
<input
id="password"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
{...register("password")}
className={`w-full px-4 py-3 border ${errors.password ? 'border-red-500' : 'border-gray-300'} rounded-lg focus:outline-none focus:ring-2 focus:ring-[#1a4d2e] focus:border-transparent pr-12`}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
>
{showPassword ? (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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 className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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" />
</svg>
)}
</button>
</div>
{errors.password && (
<p className="mt-1 text-sm text-red-500">{errors.password.message}</p>
)}
</div>
{/* Submit Button */}
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-[#1a4d2e] hover:bg-[#2d5a3d] disabled:bg-gray-400 text-white font-bold px-8 py-4 rounded-lg transition-all"
>
{isSubmitting ? "Connexion..." : "Se connecter"}
</button>
{/* Forgot Password */}
<div className="text-center">
<Link
href="/forgot-password"
className="text-sm text-[#1a4d2e] hover:text-[#f59e0b] hover:underline transition-colors"
> >
<svg className="w-5 h-5 mr-2" fill="#1877F2" viewBox="0 0 24 24"> Mot de passe oublié ?
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" /> </Link>
</svg> </div>
Facebook </form>
</Button>
)}
</div>
</div>
)}
<p className="mt-8 text-center text-sm text-gray-600"> </div>
Vous n'avez pas de compte ?{" "} </div>
<Link </div>
href={ROUTES.REGISTER}
className="font-medium text-blue-600 hover:text-blue-700 hover:underline"
>
Créer un compte
</Link>
</p>
</Card>
</div> </div>
); );
} }

View File

@ -95,124 +95,128 @@ export default function ProfilePage() {
}; };
return ( return (
<div className="py-8 max-w-4xl mx-auto"> <div className="min-h-screen bg-gray-50 py-8">
<h1 className="text-3xl font-bold text-gray-900 mb-8">Mon profil</h1> <div className="container mx-auto px-4 max-w-4xl">
<h1 className="text-4xl font-bold text-gray-900 mb-8">Mon profil</h1>
<div className="grid md:grid-cols-3 gap-6"> <div className="grid md:grid-cols-3 gap-6">
{/* Profile Info Card */} {/* Profile Info Card */}
<div className="md:col-span-2"> <div className="md:col-span-2">
<Card> <div className="bg-white rounded-xl shadow-md overflow-hidden">
<CardHeader> <div className="px-6 py-4 border-b border-gray-200">
<CardTitle>Informations personnelles</CardTitle> <h2 className="text-xl font-bold text-gray-900">Informations personnelles</h2>
</CardHeader> </div>
<CardContent> <div className="p-6">
{!isEditing ? ( {!isEditing ? (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="text-sm font-medium text-gray-500"> <label className="text-sm font-medium text-gray-500">
Prénom Prénom
</label> </label>
<p className="text-lg text-gray-900">{user.firstName}</p> <p className="text-lg text-gray-900">{user.firstName}</p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-500"> <label className="text-sm font-medium text-gray-500">
Nom Nom
</label> </label>
<p className="text-lg text-gray-900">{user.lastName}</p> <p className="text-lg text-gray-900">{user.lastName}</p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-500"> <label className="text-sm font-medium text-gray-500">
Email Email
</label> </label>
<p className="text-lg text-gray-900">{user.email}</p> <p className="text-lg text-gray-900">{user.email}</p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-500"> <label className="text-sm font-medium text-gray-500">
Téléphone Téléphone
</label> </label>
<p className="text-lg text-gray-900"> <p className="text-lg text-gray-900">
{user.phone || "Non renseigné"} {user.phone || "Non renseigné"}
</p> </p>
</div> </div>
<div> <div>
<label className="text-sm font-medium text-gray-500"> <label className="text-sm font-medium text-gray-500">
Rôle Rôle
</label> </label>
<div className="mt-1"> <div className="mt-1">
<Badge variant={getRoleBadgeVariant(user.role)}> <Badge variant={getRoleBadgeVariant(user.role)}>
{getRoleLabel(user.role)} {getRoleLabel(user.role)}
</Badge> </Badge>
</div>
</div>
<div className="pt-4">
<button
onClick={() => setIsEditing(true)}
className="bg-[#1a4d2e] hover:bg-[#2d5a3d] text-white font-bold px-6 py-3 rounded-lg transition-all"
>
Modifier mes informations
</button>
</div> </div>
</div> </div>
<div className="pt-4"> ) : (
<Button onClick={() => setIsEditing(true)}> <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
Modifier mes informations <Input
</Button> id="firstName"
</div> type="text"
</div> label="Prénom"
) : ( error={errors.firstName?.message}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> {...register("firstName")}
<Input required
id="firstName" />
type="text" <Input
label="Prénom" id="lastName"
error={errors.firstName?.message} type="text"
{...register("firstName")} label="Nom"
required error={errors.lastName?.message}
/> {...register("lastName")}
<Input required
id="lastName" />
type="text" <Input
label="Nom" id="email"
error={errors.lastName?.message} type="email"
{...register("lastName")} label="Email"
required value={user.email}
/> disabled
<Input helperText="L'email ne peut pas être modifié"
id="email" />
type="email" <Input
label="Email" id="phone"
value={user.email} type="tel"
disabled label="Téléphone"
helperText="L'email ne peut pas être modifié" error={errors.phone?.message}
/> {...register("phone")}
<Input />
id="phone" <div className="flex gap-3">
type="tel" <button
label="Téléphone" type="submit"
error={errors.phone?.message} disabled={isSubmitting}
{...register("phone")} className="bg-[#1a4d2e] hover:bg-[#2d5a3d] disabled:bg-gray-400 text-white font-bold px-6 py-3 rounded-lg transition-all"
/> >
<div className="flex gap-3"> {isSubmitting ? "Enregistrement..." : "Enregistrer"}
<Button </button>
type="submit" <button
isLoading={isSubmitting} type="button"
disabled={isSubmitting} onClick={handleCancel}
> disabled={isSubmitting}
Enregistrer className="border-2 border-gray-300 hover:bg-gray-50 text-gray-700 font-bold px-6 py-3 rounded-lg transition-all"
</Button> >
<Button Annuler
type="button" </button>
variant="outline" </div>
onClick={handleCancel} </form>
disabled={isSubmitting} )}
> </div>
Annuler </div>
</Button>
</div>
</form>
)}
</CardContent>
</Card>
</div> </div>
{/* Account Status Card */} {/* Account Status Card */}
<div> <div>
<Card> <div className="bg-white rounded-xl shadow-md overflow-hidden">
<CardHeader> <div className="px-6 py-4 border-b border-gray-200">
<CardTitle>Statut du compte</CardTitle> <h2 className="text-xl font-bold text-gray-900">Statut du compte</h2>
</CardHeader> </div>
<CardContent className="space-y-4"> <div className="p-6 space-y-4">
<div> <div>
<label className="text-sm font-medium text-gray-500"> <label className="text-sm font-medium text-gray-500">
Email vérifié Email vérifié
@ -241,55 +245,52 @@ export default function ProfilePage() {
{user.updatedAt ? formatDate(user.updatedAt) : 'N/A'} {user.updatedAt ? formatDate(user.updatedAt) : 'N/A'}
</p> </p>
</div> </div>
</CardContent> </div>
</Card> </div>
{/* Quick Actions Card */} {/* Quick Actions Card */}
<Card className="mt-6"> <div className="bg-white rounded-xl shadow-md overflow-hidden mt-6">
<CardHeader> <div className="px-6 py-4 border-b border-gray-200">
<CardTitle>Actions rapides</CardTitle> <h2 className="text-xl font-bold text-gray-900">Actions rapides</h2>
</CardHeader> </div>
<CardContent className="space-y-2"> <div className="p-6 space-y-2">
{user.role === "CLIENT" && ( {user.role === "CLIENT" && (
<> <>
<Button <button
variant="outline"
fullWidth
onClick={() => router.push(ROUTES.GAME)} onClick={() => router.push(ROUTES.GAME)}
className="w-full bg-[#f59e0b] hover:bg-[#d97706] text-white font-bold px-6 py-3 rounded-lg transition-all"
> >
Jouer Jouer
</Button> </button>
<Button <button
variant="outline"
fullWidth
onClick={() => router.push(ROUTES.HISTORY)} onClick={() => router.push(ROUTES.HISTORY)}
className="w-full border-2 border-[#1a4d2e] text-[#1a4d2e] hover:bg-[#1a4d2e] hover:text-white font-bold px-6 py-3 rounded-lg transition-all"
> >
Historique Historique
</Button> </button>
</> </>
)} )}
{user.role === "EMPLOYEE" && ( {user.role === "EMPLOYEE" && (
<Button <button
variant="outline"
fullWidth
onClick={() => router.push(ROUTES.EMPLOYEE_DASHBOARD)} onClick={() => router.push(ROUTES.EMPLOYEE_DASHBOARD)}
className="w-full border-2 border-[#1a4d2e] text-[#1a4d2e] hover:bg-[#1a4d2e] hover:text-white font-bold px-6 py-3 rounded-lg transition-all"
> >
Tableau de bord Tableau de bord
</Button> </button>
)} )}
{user.role === "ADMIN" && ( {user.role === "ADMIN" && (
<Button <button
variant="outline"
fullWidth
onClick={() => router.push(ROUTES.ADMIN_DASHBOARD)} onClick={() => router.push(ROUTES.ADMIN_DASHBOARD)}
className="w-full border-2 border-[#1a4d2e] text-[#1a4d2e] hover:bg-[#1a4d2e] hover:text-white font-bold px-6 py-3 rounded-lg transition-all"
> >
Administration Administration
</Button> </button>
)} )}
</CardContent> </div>
</Card> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
); );
} }

View File

@ -49,8 +49,9 @@ export default function Header() {
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="flex items-center justify-between h-18"> <div className="flex items-center justify-between h-18">
{/* Logo */} {/* Logo */}
<Link href={ROUTES.HOME} className="group"> <Link href={ROUTES.HOME} className="group flex items-center gap-3">
<Logo size="md" showText={true} className="group-hover:scale-105 transition-transform" /> <Logo size="md" showText={false} className="group-hover:scale-105 transition-transform" />
<span className="text-2xl font-bold text-[#f59e0b]">Thé Tip Top</span>
</Link> </Link>
{/* Desktop Navigation */} {/* Desktop Navigation */}
@ -62,13 +63,13 @@ export default function Header() {
Accueil Accueil
</Link> </Link>
<Link <Link
href={ROUTES.GAME} href={ROUTES.LOTS}
className="text-white hover:text-[#f59e0b] font-medium transition-colors" className="text-white hover:text-[#f59e0b] font-medium transition-colors"
> >
Loto à gagner Loto à gagner
</Link> </Link>
<Link <Link
href={ROUTES.LOTS} href="/rules"
className="text-white hover:text-[#f59e0b] font-medium transition-colors" className="text-white hover:text-[#f59e0b] font-medium transition-colors"
> >
Règlement Règlement
@ -80,7 +81,7 @@ export default function Header() {
FAQ FAQ
</Link> </Link>
<Link <Link
href="/about" href="/gagnants"
className="text-white hover:text-[#f59e0b] font-medium transition-colors" className="text-white hover:text-[#f59e0b] font-medium transition-colors"
> >
Gagnants Gagnants
@ -146,21 +147,30 @@ export default function Header() {
<div className="hidden md:flex items-center gap-3"> <div className="hidden md:flex items-center gap-3">
{isAuthenticated && ( {isAuthenticated && (
<> <>
<Link href={getDashboardRoute()}>
<button className="flex flex-col items-center bg-white text-[#1a4d2e] hover:bg-[#f59e0b] hover:text-white font-semibold px-4 py-2 rounded-lg transition-all">
<span className="text-sm">{user?.firstName} {user?.lastName}</span>
<span className="text-xs font-normal opacity-80">{user?.email}</span>
</button>
</Link>
<Link href={ROUTES.PROFILE}> <Link href={ROUTES.PROFILE}>
<Button variant="outline" size="sm"> <button className="flex items-center gap-2 bg-white text-[#1a4d2e] hover:bg-[#f59e0b] hover:text-white font-semibold px-4 py-2 rounded-lg transition-all">
👤 {user?.firstName} Profil
</Button> </button>
</Link> </Link>
{user?.role === 'CLIENT' && ( {user?.role === 'CLIENT' && (
<Link href={ROUTES.HISTORY}> <Link href={ROUTES.HISTORY}>
<Button variant="outline" size="sm"> <button className="flex items-center gap-2 bg-white text-[#1a4d2e] hover:bg-[#f59e0b] hover:text-white font-semibold px-4 py-2 rounded-lg transition-all">
🏆 Mes gains Mes gains
</Button> </button>
</Link> </Link>
)} )}
<Button variant="outline" size="sm" onClick={logout}> <button
onClick={logout}
className="flex items-center gap-2 bg-white text-[#1a4d2e] hover:bg-red-600 hover:text-white font-semibold px-4 py-2 rounded-lg transition-all"
>
Déconnexion Déconnexion
</Button> </button>
</> </>
)} )}
</div> </div>
@ -297,36 +307,43 @@ export default function Header() {
)} )}
{isAuthenticated && ( {isAuthenticated && (
<div className="border-t border-gray-200 pt-3 mt-3 space-y-2"> <div className="border-t border-white/20 pt-3 mt-3 space-y-2">
<Link
href={getDashboardRoute()}
onClick={() => setIsMobileMenuOpen(false)}
>
<button className="w-full flex flex-col items-center bg-white text-[#1a4d2e] hover:bg-[#f59e0b] hover:text-white font-semibold px-4 py-3 rounded-lg transition-all">
<span className="text-sm">{user?.firstName} {user?.lastName}</span>
<span className="text-xs font-normal opacity-80">{user?.email}</span>
</button>
</Link>
<Link <Link
href={ROUTES.PROFILE} href={ROUTES.PROFILE}
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
<Button variant="outline" size="sm" fullWidth> <button className="w-full flex items-center justify-center gap-2 bg-white text-[#1a4d2e] hover:bg-[#f59e0b] hover:text-white font-semibold px-4 py-3 rounded-lg transition-all">
👤 {user?.firstName} Profil
</Button> </button>
</Link> </Link>
{user?.role === 'CLIENT' && ( {user?.role === 'CLIENT' && (
<Link <Link
href={ROUTES.HISTORY} href={ROUTES.HISTORY}
onClick={() => setIsMobileMenuOpen(false)} onClick={() => setIsMobileMenuOpen(false)}
> >
<Button variant="outline" size="sm" fullWidth> <button className="w-full flex items-center justify-center gap-2 bg-white text-[#1a4d2e] hover:bg-[#f59e0b] hover:text-white font-semibold px-4 py-3 rounded-lg transition-all">
🏆 Mes gains Mes gains
</Button> </button>
</Link> </Link>
)} )}
<Button <button
variant="outline"
size="sm"
fullWidth
onClick={() => { onClick={() => {
logout(); logout();
setIsMobileMenuOpen(false); setIsMobileMenuOpen(false);
}} }}
className="w-full flex items-center justify-center gap-2 bg-white text-[#1a4d2e] hover:bg-red-600 hover:text-white font-semibold px-4 py-3 rounded-lg transition-all"
> >
Déconnexion Déconnexion
</Button> </button>
</div> </div>
)} )}
</nav> </nav>

View File

@ -53,30 +53,35 @@ export const PRIZE_CONFIG = {
description: 'Un infuseur à thé de qualité', description: 'Un infuseur à thé de qualité',
color: 'bg-blue-100 text-blue-800', color: 'bg-blue-100 text-blue-800',
icon: '🫖', icon: '🫖',
value: 15,
}, },
THE_SIGNATURE: { THE_SIGNATURE: {
name: 'Thé signature 100g', name: 'Thé signature 100g',
description: 'Notre thé signature premium 100g', description: 'Notre thé signature premium 100g',
color: 'bg-green-100 text-green-800', color: 'bg-green-100 text-green-800',
icon: '🍵', icon: '🍵',
value: 25,
}, },
COFFRET_DECOUVERTE: { COFFRET_DECOUVERTE: {
name: 'Coffret découverte 39€', name: 'Coffret découverte 39€',
description: 'Un coffret découverte de nos meilleurs thés', description: 'Un coffret découverte de nos meilleurs thés',
color: 'bg-purple-100 text-purple-800', color: 'bg-purple-100 text-purple-800',
icon: '🎁', icon: '🎁',
value: 39,
}, },
COFFRET_PRESTIGE: { COFFRET_PRESTIGE: {
name: 'Coffret prestige 69€', name: 'Coffret prestige 69€',
description: 'Un coffret prestige d\'exception', description: 'Un coffret prestige d\'exception',
color: 'bg-amber-100 text-amber-800', color: 'bg-amber-100 text-amber-800',
icon: '🏆', icon: '🏆',
value: 69,
}, },
THE_GRATUIT: { THE_GRATUIT: {
name: 'Thé gratuit en magasin', name: 'Thé gratuit en magasin',
description: 'Un thé gratuit de votre choix en magasin', description: 'Un thé gratuit de votre choix en magasin',
color: 'bg-pink-100 text-pink-800', color: 'bg-pink-100 text-pink-800',
icon: '☕', icon: '☕',
value: 0,
}, },
} as const; } as const;