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 (
<div className="py-8">
{/* Welcome Section */}
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
Bonjour {user?.firstName} ! 👋
</h1>
<p className="text-gray-600">
Bienvenue dans votre espace client
</p>
</div>
<div className="min-h-screen bg-gray-50 py-8">
<div className="container mx-auto px-4">
{/* Welcome Section */}
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
Bonjour {user?.firstName} ! 👋
</h1>
<p className="text-gray-600">
Bienvenue dans votre espace client
</p>
</div>
{/* Quick Action */}
<div className="mb-8">
<Card className="bg-gradient-to-r from-primary-500 to-primary-600 text-white">
<CardContent className="py-8">
{/* Quick Action */}
<div className="mb-8">
<div className="bg-gradient-to-r from-[#1a4d2e] to-[#2d5a3d] text-white rounded-xl shadow-md p-8">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold mb-2">
Vous avez un nouveau ticket ?
</h2>
<p className="text-primary-50">
<p className="text-green-50">
Entrez votre code et découvrez votre gain instantanément
</p>
</div>
<Link href={ROUTES.GAME}>
<Button
size="lg"
className="bg-white text-black hover:bg-gray-50 border-2 border-black"
>
<button className="bg-[#f59e0b] hover:bg-[#d97706] text-white font-bold px-8 py-4 rounded-lg transition-all hover:shadow-xl whitespace-nowrap">
Jouer maintenant 🎮
</Button>
</button>
</Link>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
{/* Statistics Cards */}
<div className="grid md:grid-cols-3 gap-6 mb-8">
<Card>
<CardContent className="pt-6">
{/* Statistics Cards */}
<div className="grid md:grid-cols-3 gap-6 mb-8">
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
<p className="text-sm font-medium text-gray-600 mb-2">
Total Participations
</p>
<p className="text-3xl font-bold text-gray-900 mt-2">
<p className="text-4xl font-bold text-gray-900">
{stats.total}
</p>
</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>
</CardContent>
</Card>
</div>
<Card>
<CardContent className="pt-6">
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between">
<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
</p>
<p className="text-3xl font-bold text-green-600 mt-2">
<p className="text-4xl font-bold text-green-600">
{stats.claimed}
</p>
</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>
</CardContent>
</Card>
</div>
<Card>
<CardContent className="pt-6">
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">
<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">
<p className="text-4xl font-bold text-yellow-600">
{stats.pending}
</p>
</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>
</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>
</CardHeader>
<CardContent>
{tickets.length === 0 ? (
<div className="text-center py-12">
<div className="text-6xl mb-4">🎲</div>
<p className="text-gray-600 mb-4">
Vous n'avez pas encore participé au jeu
</p>
<Link href={ROUTES.GAME}>
<Button className="bg-white text-black hover:bg-gray-50 border-2 border-black">Jouer maintenant</Button>
</div>
{/* Recent Tickets */}
<div className="bg-white rounded-xl shadow-md overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold text-gray-900">Mes derniers tickets</h2>
<Link href={ROUTES.HISTORY}>
<button className="text-[#1a4d2e] hover:text-[#f59e0b] font-semibold text-sm transition-colors">
Voir tout l'historique
</button>
</Link>
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Code Ticket
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Gain
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Statut
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date
</th>
</tr>
</thead>
<tbody className="bg-white 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;
</div>
<div className="p-6">
{tickets.length === 0 ? (
<div className="text-center py-12">
<div className="text-6xl mb-4">🎲</div>
<p className="text-gray-600 mb-4">
Vous n'avez pas encore participé au jeu
</p>
<Link href={ROUTES.GAME}>
<button className="bg-[#f59e0b] hover:bg-[#d97706] text-white font-bold px-6 py-3 rounded-lg transition-all">
Jouer maintenant
</button>
</Link>
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Code Ticket
</th>
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
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 (
<tr key={ticket.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<span className="font-mono text-sm font-medium text-gray-900">
{ticket.code}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
{prizeConfig && (
<>
<span className="text-2xl mr-2">
{prizeConfig.icon}
</span>
<span className="text-sm text-gray-900">
{prizeConfig.name}
</span>
</>
)}
</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-500">
{ticket.playedAt ? new Date(ticket.playedAt).toLocaleDateString("fr-FR") : "-"}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</CardContent>
</Card>
return (
<tr key={ticket.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4 whitespace-nowrap">
<span className="font-mono text-sm font-semibold text-gray-900">
{ticket.code}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
{prizeConfig && (
<>
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${prizeConfig.color}`}>
{ticket.prize?.type === 'INFUSEUR' && (
<svg className="w-6 h-6" 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>
)}
{ticket.prize?.type === 'THE_SIGNATURE' && (
<svg className="w-6 h-6" 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>
)}
{ticket.prize?.type === 'COFFRET_DECOUVERTE' && (
<svg className="w-6 h-6" 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>
)}
{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"/>
</svg>
)}
{ticket.prize?.type === 'THE_GRATUIT' && (
<svg className="w-6 h-6" 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>
<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>
);
}

View File

@ -103,68 +103,77 @@ export default function HistoriquePage() {
};
return (
<div className="py-8">
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<Calendar className="w-10 h-10 text-primary-600" />
Historique de mes participations
</h1>
<p className="text-gray-600">
Consultez l'historique complet de vos participations et gains
</p>
</div>
<div className="min-h-screen bg-gray-50 py-8">
<div className="container mx-auto px-4">
<div className="mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<Calendar className="w-10 h-10 text-[#1a4d2e]" />
Historique de mes participations
</h1>
<p className="text-gray-600">
Consultez l'historique complet de vos participations et gains
</p>
</div>
<div className="grid md:grid-cols-4 gap-6 mb-8">
<Card className="bg-gradient-to-br from-blue-50 to-blue-100">
<CardContent className="pt-6">
<div className="grid md:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total</p>
<p className="text-3xl font-bold text-blue-600 mt-2">{stats.total}</p>
<p className="text-sm font-medium text-gray-600 mb-2">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 className="text-4xl">📊</div>
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-green-50 to-green-100">
<CardContent className="pt-6">
</div>
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Réclamés</p>
<p className="text-3xl font-bold text-green-600 mt-2">{stats.claimed}</p>
<p className="text-sm font-medium text-gray-600 mb-2">Réclamés</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 className="text-4xl"></div>
</div>
</CardContent>
</Card>
</div>
<Card className="bg-gradient-to-br from-yellow-50 to-yellow-100">
<CardContent className="pt-6">
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">En attente</p>
<p className="text-3xl font-bold text-yellow-600 mt-2">{stats.pending}</p>
<p className="text-sm font-medium text-gray-600 mb-2">En attente</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 className="text-4xl"></div>
</div>
</CardContent>
</Card>
</div>
<Card className="bg-gradient-to-br from-red-50 to-red-100">
<CardContent className="pt-6">
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Rejetés</p>
<p className="text-3xl font-bold text-red-600 mt-2">{stats.rejected}</p>
<p className="text-sm font-medium text-gray-600 mb-2">Rejetés</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 className="text-4xl"></div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
<Card className="mb-6">
<CardContent className="pt-6">
<div className="bg-white rounded-xl shadow-md p-6 mb-6">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1">
<div className="relative">
@ -174,7 +183,7 @@ export default function HistoriquePage() {
placeholder="Rechercher par code ticket..."
value={searchQuery}
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>
@ -182,9 +191,9 @@ export default function HistoriquePage() {
<div className="flex gap-2">
<button
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'
? 'bg-primary-600 text-white'
? 'bg-[#1a4d2e] text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
@ -192,7 +201,7 @@ export default function HistoriquePage() {
</button>
<button
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'
? 'bg-green-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
@ -202,7 +211,7 @@ export default function HistoriquePage() {
</button>
<button
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'
? 'bg-yellow-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
@ -210,118 +219,152 @@ export default function HistoriquePage() {
>
En attente ({stats.pending})
</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>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle>Tous mes tickets ({filteredTickets.length})</CardTitle>
</CardHeader>
<CardContent>
{filteredTickets.length === 0 ? (
<div className="text-center py-12">
<div className="text-6xl mb-4">🎲</div>
<p className="text-gray-600 mb-4">
{searchQuery || filter !== 'ALL'
? 'Aucun ticket trouvé avec ces filtres'
: 'Vous n\'avez pas encore participé au jeu'}
</p>
{!searchQuery && filter === 'ALL' && (
<Button onClick={() => router.push(ROUTES.GAME)} className="bg-white text-black hover:bg-gray-50 border-2 border-black">
Jouer maintenant
</Button>
)}
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Code Ticket
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Gain
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Statut
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date de participation
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date de réclamation
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredTickets.map((ticket) => {
const prizeConfig = ticket.prize
? PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG]
: null;
<div className="bg-white rounded-xl shadow-md overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Tous mes tickets ({filteredTickets.length})</h2>
</div>
<div className="p-6">
{filteredTickets.length === 0 ? (
<div className="text-center py-12">
<div className="text-6xl mb-4">🎲</div>
<p className="text-gray-600 mb-4">
{searchQuery || filter !== 'ALL'
? 'Aucun ticket trouvé avec ces filtres'
: 'Vous n\'avez pas encore participé au jeu'}
</p>
{!searchQuery && filter === 'ALL' && (
<button
onClick={() => router.push(ROUTES.GAME)}
className="bg-[#f59e0b] hover:bg-[#d97706] text-white font-bold px-6 py-3 rounded-lg transition-all"
>
Jouer maintenant
</button>
)}
</div>
) : (
<div className="overflow-x-auto">
<table className="min-w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Code Ticket
</th>
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
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 de participation
</th>
<th className="px-6 py-3 text-left text-xs font-semibold text-gray-700 uppercase tracking-wider">
Date de réclamation
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{filteredTickets.map((ticket) => {
const prizeConfig = ticket.prize
? PRIZE_CONFIG[ticket.prize.type as keyof typeof PRIZE_CONFIG]
: null;
return (
<tr key={ticket.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<span className="font-mono text-sm font-medium text-gray-900">
{ticket.code}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
{prizeConfig && (
<>
<span className="text-2xl mr-2">
{prizeConfig.icon}
</span>
<div>
<p className="text-sm font-medium text-gray-900">
{prizeConfig.name}
</p>
<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-500">
{ticket.playedAt
? new Date(ticket.playedAt).toLocaleDateString("fr-FR", {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
: "-"}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{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>
)}
</CardContent>
</Card>
return (
<tr key={ticket.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4 whitespace-nowrap">
<span className="font-mono text-sm font-semibold text-gray-900">
{ticket.code}
</span>
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-3">
{prizeConfig && (
<>
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${prizeConfig.color}`}>
{ticket.prize?.type === 'INFUSEUR' && (
<svg className="w-6 h-6" 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>
)}
{ticket.prize?.type === 'THE_SIGNATURE' && (
<svg className="w-6 h-6" 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>
)}
{ticket.prize?.type === 'COFFRET_DECOUVERTE' && (
<svg className="w-6 h-6" 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>
)}
{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"/>
</svg>
)}
{ticket.prize?.type === 'THE_GRATUIT' && (
<svg className="w-6 h-6" 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>
<p className="text-sm font-medium text-gray-900">
{prizeConfig.name}
</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", {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
: "-"}
</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>
);
}

View File

@ -1,5 +1,5 @@
"use client";
import { useState } from "react";
import { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useAuth } from "@/contexts/AuthContext";
@ -15,13 +15,24 @@ import { useRouter } from "next/navigation";
import { ROUTES } from "@/utils/constants";
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() {
const { user, isAuthenticated } = useAuth();
const { play, isPlaying } = useGame();
const router = useRouter();
const [showResultModal, setShowResultModal] = useState(false);
const [showRouletteModal, setShowRouletteModal] = useState(false);
const [gameResult, setGameResult] = useState<PlayGameResponse | null>(null);
const [errorMessage, setErrorMessage] = useState<string>("");
const [currentPrizeIndex, setCurrentPrizeIndex] = useState(0);
const [isSpinning, setIsSpinning] = useState(false);
const {
register,
@ -32,6 +43,17 @@ export default function JeuxPage() {
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) => {
// Réinitialiser le message d'erreur
setErrorMessage("");
@ -42,14 +64,37 @@ export default function JeuxPage() {
return;
}
// Afficher la modal de roulette et démarrer l'animation
setShowRouletteModal(true);
setIsSpinning(true);
setCurrentPrizeIndex(0);
const result = await play(data.ticketCode);
if (result) {
setGameResult(result);
setShowResultModal(true);
setErrorMessage("");
reset();
// Trouver l'index du prix gagné
const winningIndex = PRIZES.findIndex(p => p.type === result.prize?.type);
// 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 {
// 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'.");
}
};
@ -64,110 +109,189 @@ export default function JeuxPage() {
: null;
return (
<div className="py-8">
{/* Formulaire Section */}
<section className="mb-16">
<div className="max-w-2xl mx-auto">
<Card className="shadow-xl">
<CardHeader className="bg-gradient-to-r from-primary-50 to-green-50">
<CardTitle className="text-center text-2xl md:text-3xl text-primary-800">
🎁 Jouez maintenant !
</CardTitle>
</CardHeader>
<CardContent className="pt-8">
<div className="mb-6 text-center">
{isAuthenticated ? (
<p className="text-gray-700">
Bonjour <span className="font-bold text-primary-600">{user?.firstName}</span>,
entrez le code de 10 caractères présent sur votre ticket de caisse
</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">
Connectez-vous
</Link>
<div className="min-h-screen bg-gray-50 py-8">
<div className="container mx-auto px-4">
{/* Formulaire Section */}
<section className="mb-16">
<div className="max-w-2xl mx-auto">
<div className="bg-white rounded-xl shadow-md overflow-hidden">
<div className="bg-gradient-to-r from-[#1a4d2e] to-[#2d5a3d] px-6 py-6">
<h1 className="text-center text-3xl md:text-4xl font-bold text-white">
🎁 Jouez maintenant !
</h1>
</div>
<div className="p-8">
<div className="mb-6 text-center">
{isAuthenticated ? (
<p className="text-gray-700 text-lg">
Bonjour <span className="font-bold text-[#1a4d2e]">{user?.firstName}</span>,
entrez le code de 10 caractères présent sur votre ticket de caisse
</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>
</section>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div>
<label htmlFor="ticketCode" className="block text-sm font-medium 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-primary-500 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.MY_LOTS}
className="text-sm text-red-600 hover:text-red-800 underline font-medium"
{/* Roulette Modal */}
<Modal
isOpen={showRouletteModal}
onClose={() => {}}
title="🎰 Tirage en cours..."
size="md"
>
<div className="py-12">
<div className="flex flex-col items-center gap-8">
{/* Roulette Display */}
<div className="relative w-full max-w-sm">
<div className="flex flex-col gap-3">
{PRIZES.map((prize, index) => {
const getPrizeIcon = (type: string) => {
switch(type) {
case 'INFUSEUR':
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>
);
case 'THE_SIGNATURE':
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-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
</Link>
</div>
)}
<p className="mt-2 text-sm text-gray-500 text-center">
Format: TTP2025ABC (10 caractères)
</p>
<div className={`w-12 h-12 rounded-full flex items-center justify-center ${prize.color}`}>
{getPrizeIcon(prize.type)}
</div>
<div className="flex-1">
<p className={`font-bold ${currentPrizeIndex === index ? 'text-lg' : 'text-sm'}`}>
{prize.name}
</p>
</div>
</div>
);
})}
</div>
</div>
<div className="flex justify-center">
<Button
type="submit"
isLoading={isPlaying}
disabled={isPlaying}
size="lg"
className="px-12 py-4 text-lg"
>
{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>
{/* Loading Animation */}
<div className="flex items-center gap-2 text-gray-600">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#1a4d2e]"></div>
<span className="font-medium">Tirage en cours...</span>
</div>
</div>
</div>
</Modal>
{/* Result Modal */}
<Modal
@ -178,8 +302,36 @@ export default function JeuxPage() {
>
{gameResult && prizeConfig && (
<div className="text-center py-6">
<div className="text-6xl mb-4">{prizeConfig.icon}</div>
<h3 className="text-2xl font-bold text-gray-900 mb-2">
<div className="flex justify-center mb-4">
<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 ! 🎉
</h3>
<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 !"}
</p>
<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
</Button>
<Button onClick={() => router.push(ROUTES.MY_LOTS)}>
Voir mes lots
</Button>
</button>
<button
onClick={() => router.push(ROUTES.HISTORY)}
className="bg-[#1a4d2e] hover:bg-[#2d5a3d] text-white font-bold px-6 py-3 rounded-lg transition-all"
>
Voir mes gains
</button>
</div>
</div>
)}
</Modal>
</div>
</div>
);
}

View File

@ -6,9 +6,7 @@ import { useAuth } from "@/contexts/AuthContext";
import { loginSchema, LoginFormData } from "@/lib/validations";
import { Input } from "@/components/ui/Input";
import Button from "@/components/Button";
import { Card } from "@/components/ui/Card";
import Link from "next/link";
import Image from "next/image";
import { ROUTES } from "@/utils/constants";
import { GoogleLoginButton } from "@/components/GoogleLoginButton";
import { initFacebookSDK, loginWithFacebook } from "@/lib/facebook-sdk";
@ -21,12 +19,12 @@ export default function LoginPage() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isFacebookLoading, setIsFacebookLoading] = useState(false);
const [isFacebookSDKLoaded, setIsFacebookSDKLoaded] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const hasGoogleAuth = !!process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
const hasFacebookAuth = !!process.env.NEXT_PUBLIC_FACEBOOK_APP_ID;
useEffect(() => {
// Initialiser le SDK Facebook au chargement de la page
initFacebookSDK()
.then(() => {
setIsFacebookSDKLoaded(true);
@ -77,112 +75,153 @@ export default function LoginPage() {
};
return (
<div className="min-h-[calc(100vh-4rem)] flex items-center justify-center py-12 px-4">
<Card className="w-full max-w-md p-8">
<div className="min-h-screen bg-gray-50 flex items-center justify-center py-12 px-4">
<div className="w-full max-w-md">
{/* Title */}
<div className="text-center mb-8">
<div className="flex justify-center mb-6">
<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>
<h1 className="text-4xl font-bold text-gray-900 mb-2">Connexion</h1>
<p className="text-gray-600">
Connectez-vous pour participer au jeu-concours
</p>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<Input
id="email"
type="email"
label="Email"
placeholder="votre.email@example.com"
error={errors.email?.message}
{...register("email")}
required
/>
{/* Main Card */}
<div className="bg-white rounded-xl shadow-md overflow-hidden">
<Input
id="password"
type="password"
label="Mot de passe"
placeholder="••••••••"
error={errors.password?.message}
{...register("password")}
required
/>
<div className="flex items-center justify-between text-sm">
{/* Tabs */}
<div className="flex border-b border-gray-200">
<button className="flex-1 py-4 px-6 text-center font-semibold text-gray-900 bg-white border-b-2 border-[#1a4d2e]">
Connexion
</button>
<Link
href="/forgot-password"
className="text-blue-600 hover:text-blue-700 hover:underline"
href={ROUTES.REGISTER}
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>
</div>
<Button
type="submit"
isLoading={isSubmitting}
disabled={isSubmitting}
fullWidth
size="lg"
>
Se connecter
</Button>
</form>
{/* Form Container */}
<div className="p-8">
{(hasGoogleAuth || hasFacebookAuth) && (
<div className="mt-6">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
{/* Social Login Buttons */}
{(hasGoogleAuth || hasFacebookAuth) && (
<div className="space-y-3 mb-6">
{hasGoogleAuth && (
<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 className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">
Ou continuer avec
</span>
)}
{/* Divider */}
{(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 className={`mt-6 grid ${hasGoogleAuth && hasFacebookAuth ? 'grid-cols-2' : 'grid-cols-1'} gap-3`}>
{hasGoogleAuth && (
<GoogleLoginButton fullWidth />
)}
{/* Login Form */}
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
{hasFacebookAuth && (
<Button
type="button"
variant="outline"
onClick={handleFacebookLogin}
disabled={isFacebookLoading || !isFacebookSDKLoaded}
isLoading={isFacebookLoading}
fullWidth
{/* Email */}
<div>
<label htmlFor="email" className="block text-sm font-semibold text-gray-700 mb-2">
Email <span className="text-red-500">*</span>
</label>
<input
id="email"
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">
<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>
Facebook
</Button>
)}
</div>
</div>
)}
Mot de passe oublié ?
</Link>
</div>
</form>
<p className="mt-8 text-center text-sm text-gray-600">
Vous n'avez pas de compte ?{" "}
<Link
href={ROUTES.REGISTER}
className="font-medium text-blue-600 hover:text-blue-700 hover:underline"
>
Créer un compte
</Link>
</p>
</Card>
</div>
</div>
</div>
</div>
);
}

View File

@ -95,124 +95,128 @@ export default function ProfilePage() {
};
return (
<div className="py-8 max-w-4xl mx-auto">
<h1 className="text-3xl font-bold text-gray-900 mb-8">Mon profil</h1>
<div className="min-h-screen bg-gray-50 py-8">
<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">
{/* Profile Info Card */}
<div className="md:col-span-2">
<Card>
<CardHeader>
<CardTitle>Informations personnelles</CardTitle>
</CardHeader>
<CardContent>
{!isEditing ? (
<div className="space-y-4">
<div>
<label className="text-sm font-medium text-gray-500">
Prénom
</label>
<p className="text-lg text-gray-900">{user.firstName}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Nom
</label>
<p className="text-lg text-gray-900">{user.lastName}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Email
</label>
<p className="text-lg text-gray-900">{user.email}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Téléphone
</label>
<p className="text-lg text-gray-900">
{user.phone || "Non renseigné"}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Rôle
</label>
<div className="mt-1">
<Badge variant={getRoleBadgeVariant(user.role)}>
{getRoleLabel(user.role)}
</Badge>
<div className="grid md:grid-cols-3 gap-6">
{/* Profile Info Card */}
<div className="md:col-span-2">
<div className="bg-white rounded-xl shadow-md overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Informations personnelles</h2>
</div>
<div className="p-6">
{!isEditing ? (
<div className="space-y-4">
<div>
<label className="text-sm font-medium text-gray-500">
Prénom
</label>
<p className="text-lg text-gray-900">{user.firstName}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Nom
</label>
<p className="text-lg text-gray-900">{user.lastName}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Email
</label>
<p className="text-lg text-gray-900">{user.email}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Téléphone
</label>
<p className="text-lg text-gray-900">
{user.phone || "Non renseigné"}
</p>
</div>
<div>
<label className="text-sm font-medium text-gray-500">
Rôle
</label>
<div className="mt-1">
<Badge variant={getRoleBadgeVariant(user.role)}>
{getRoleLabel(user.role)}
</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 className="pt-4">
<Button onClick={() => setIsEditing(true)}>
Modifier mes informations
</Button>
</div>
</div>
) : (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<Input
id="firstName"
type="text"
label="Prénom"
error={errors.firstName?.message}
{...register("firstName")}
required
/>
<Input
id="lastName"
type="text"
label="Nom"
error={errors.lastName?.message}
{...register("lastName")}
required
/>
<Input
id="email"
type="email"
label="Email"
value={user.email}
disabled
helperText="L'email ne peut pas être modifié"
/>
<Input
id="phone"
type="tel"
label="Téléphone"
error={errors.phone?.message}
{...register("phone")}
/>
<div className="flex gap-3">
<Button
type="submit"
isLoading={isSubmitting}
disabled={isSubmitting}
>
Enregistrer
</Button>
<Button
type="button"
variant="outline"
onClick={handleCancel}
disabled={isSubmitting}
>
Annuler
</Button>
</div>
</form>
)}
</CardContent>
</Card>
) : (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<Input
id="firstName"
type="text"
label="Prénom"
error={errors.firstName?.message}
{...register("firstName")}
required
/>
<Input
id="lastName"
type="text"
label="Nom"
error={errors.lastName?.message}
{...register("lastName")}
required
/>
<Input
id="email"
type="email"
label="Email"
value={user.email}
disabled
helperText="L'email ne peut pas être modifié"
/>
<Input
id="phone"
type="tel"
label="Téléphone"
error={errors.phone?.message}
{...register("phone")}
/>
<div className="flex gap-3">
<button
type="submit"
disabled={isSubmitting}
className="bg-[#1a4d2e] hover:bg-[#2d5a3d] disabled:bg-gray-400 text-white font-bold px-6 py-3 rounded-lg transition-all"
>
{isSubmitting ? "Enregistrement..." : "Enregistrer"}
</button>
<button
type="button"
onClick={handleCancel}
disabled={isSubmitting}
className="border-2 border-gray-300 hover:bg-gray-50 text-gray-700 font-bold px-6 py-3 rounded-lg transition-all"
>
Annuler
</button>
</div>
</form>
)}
</div>
</div>
</div>
{/* Account Status Card */}
<div>
<Card>
<CardHeader>
<CardTitle>Statut du compte</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="bg-white rounded-xl shadow-md overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Statut du compte</h2>
</div>
<div className="p-6 space-y-4">
<div>
<label className="text-sm font-medium text-gray-500">
Email vérifié
@ -241,55 +245,52 @@ export default function ProfilePage() {
{user.updatedAt ? formatDate(user.updatedAt) : 'N/A'}
</p>
</div>
</CardContent>
</Card>
</div>
</div>
{/* Quick Actions Card */}
<Card className="mt-6">
<CardHeader>
<CardTitle>Actions rapides</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="bg-white rounded-xl shadow-md overflow-hidden mt-6">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Actions rapides</h2>
</div>
<div className="p-6 space-y-2">
{user.role === "CLIENT" && (
<>
<Button
variant="outline"
fullWidth
<button
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
</Button>
<Button
variant="outline"
fullWidth
</button>
<button
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
</Button>
</button>
</>
)}
{user.role === "EMPLOYEE" && (
<Button
variant="outline"
fullWidth
<button
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
</Button>
</button>
)}
{user.role === "ADMIN" && (
<Button
variant="outline"
fullWidth
<button
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
</Button>
</button>
)}
</CardContent>
</Card>
</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="flex items-center justify-between h-18">
{/* Logo */}
<Link href={ROUTES.HOME} className="group">
<Logo size="md" showText={true} className="group-hover:scale-105 transition-transform" />
<Link href={ROUTES.HOME} className="group flex items-center gap-3">
<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>
{/* Desktop Navigation */}
@ -62,13 +63,13 @@ export default function Header() {
Accueil
</Link>
<Link
href={ROUTES.GAME}
href={ROUTES.LOTS}
className="text-white hover:text-[#f59e0b] font-medium transition-colors"
>
Loto à gagner
</Link>
<Link
href={ROUTES.LOTS}
href="/rules"
className="text-white hover:text-[#f59e0b] font-medium transition-colors"
>
Règlement
@ -80,7 +81,7 @@ export default function Header() {
FAQ
</Link>
<Link
href="/about"
href="/gagnants"
className="text-white hover:text-[#f59e0b] font-medium transition-colors"
>
Gagnants
@ -146,21 +147,30 @@ export default function Header() {
<div className="hidden md:flex items-center gap-3">
{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}>
<Button variant="outline" size="sm">
👤 {user?.firstName}
</Button>
<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">
Profil
</button>
</Link>
{user?.role === 'CLIENT' && (
<Link href={ROUTES.HISTORY}>
<Button variant="outline" size="sm">
🏆 Mes gains
</Button>
<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
</button>
</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
</Button>
</button>
</>
)}
</div>
@ -297,36 +307,43 @@ export default function Header() {
)}
{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
href={ROUTES.PROFILE}
onClick={() => setIsMobileMenuOpen(false)}
>
<Button variant="outline" size="sm" fullWidth>
👤 {user?.firstName}
</Button>
<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">
Profil
</button>
</Link>
{user?.role === 'CLIENT' && (
<Link
href={ROUTES.HISTORY}
onClick={() => setIsMobileMenuOpen(false)}
>
<Button variant="outline" size="sm" fullWidth>
🏆 Mes gains
</Button>
<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
</button>
</Link>
)}
<Button
variant="outline"
size="sm"
fullWidth
<button
onClick={() => {
logout();
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
</Button>
</button>
</div>
)}
</nav>

View File

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