initialisation du projet
This commit is contained in:
71
frontend/src/pages/Admin.jsx
Normal file
71
frontend/src/pages/Admin.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
const MOCK_STATS = {
|
||||
totalParticipants: 1248,
|
||||
gainsRemis: 342,
|
||||
gainsEnAttente: 906,
|
||||
tauxParticipation: '78%',
|
||||
}
|
||||
|
||||
const MOCK_USERS = [
|
||||
{ id: 1, name: 'Jean Dupont', email: 'jean.dupont@email.com', gains: 2 },
|
||||
{ id: 2, name: 'Marie Martin', email: 'marie.martin@email.com', gains: 1 },
|
||||
{ id: 3, name: 'Paul Bernard', email: 'paul.bernard@email.com', gains: 3 },
|
||||
]
|
||||
|
||||
export default function Admin() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 p-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h1 className="text-2xl font-bold text-gray-800 mb-6">Tableau de bord Admin</h1>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||
{[
|
||||
{ label: 'Participants', value: MOCK_STATS.totalParticipants },
|
||||
{ label: 'Gains remis', value: MOCK_STATS.gainsRemis },
|
||||
{ label: 'En attente', value: MOCK_STATS.gainsEnAttente },
|
||||
{ label: 'Taux participation', value: MOCK_STATS.tauxParticipation },
|
||||
].map((stat) => (
|
||||
<div key={stat.label} className="bg-white rounded-xl shadow p-4 text-center">
|
||||
<p className="text-2xl font-bold text-green-700">{stat.value}</p>
|
||||
<p className="text-sm text-gray-500 mt-1">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tirage au sort */}
|
||||
<div className="bg-white rounded-xl shadow p-6 mb-6">
|
||||
<h2 className="text-lg font-bold text-gray-800 mb-3">Grand tirage au sort</h2>
|
||||
<button
|
||||
onClick={() => alert('TODO: connecter au backend')}
|
||||
className="bg-green-700 hover:bg-green-800 text-white font-semibold px-6 py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
Lancer le tirage
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Liste participants */}
|
||||
<div className="bg-white rounded-xl shadow p-6">
|
||||
<h2 className="text-lg font-bold text-gray-800 mb-4">Participants</h2>
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-gray-500 border-b">
|
||||
<th className="pb-2">Nom</th>
|
||||
<th className="pb-2">Email</th>
|
||||
<th className="pb-2 text-right">Gains</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{MOCK_USERS.map((user) => (
|
||||
<tr key={user.id} className="border-b last:border-0">
|
||||
<td className="py-3 font-medium text-gray-800">{user.name}</td>
|
||||
<td className="py-3 text-gray-500">{user.email}</td>
|
||||
<td className="py-3 text-right text-green-700 font-semibold">{user.gains}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
73
frontend/src/pages/Connexion.jsx
Normal file
73
frontend/src/pages/Connexion.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function Connexion() {
|
||||
const [form, setForm] = useState({ email: '', password: '' })
|
||||
|
||||
const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value })
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
// TODO: connecter au backend
|
||||
alert(`Connexion avec ${form.email}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-green-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-2xl shadow-lg w-full max-w-md p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-green-800">Thé Tip Top</h1>
|
||||
<p className="text-gray-500 mt-1">Con
|
||||
|
||||
nexion à votre espace</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="exemple@email.com"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Mot de passe
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={form.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="••••••••"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-green-700 hover:bg-green-800 text-white font-semibold py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
Se connecter
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
Pas encore de compte ?{' '}
|
||||
<Link to="/inscription" className="text-green-700 font-medium hover:underline">
|
||||
S'inscrire
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
77
frontend/src/pages/Employe.jsx
Normal file
77
frontend/src/pages/Employe.jsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Employe() {
|
||||
const [code, setCode] = useState('')
|
||||
const [gain, setGain] = useState(null)
|
||||
|
||||
const handleSearch = (e) => {
|
||||
e.preventDefault()
|
||||
// TODO: connecter au backend
|
||||
setGain({
|
||||
code,
|
||||
lot: 'Thé signature (100g)',
|
||||
client: 'Jean Dupont',
|
||||
email: 'jean.dupont@email.com',
|
||||
status: 'PENDING',
|
||||
})
|
||||
}
|
||||
|
||||
const handleDeliver = () => {
|
||||
// TODO: connecter au backend
|
||||
setGain({ ...gain, status: 'DELIVERED' })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 p-6">
|
||||
<div className="max-w-lg mx-auto">
|
||||
<h1 className="text-2xl font-bold text-gray-800 mb-1">Espace Employé</h1>
|
||||
<p className="text-gray-500 mb-6">Vérifier et remettre un lot</p>
|
||||
|
||||
<form onSubmit={handleSearch} className="flex gap-2 mb-6">
|
||||
<input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
required
|
||||
placeholder="Code ticket (TTT-XXXXX)"
|
||||
className="flex-1 border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500 font-mono"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-green-700 hover:bg-green-800 text-white font-semibold px-5 py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
Rechercher
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{gain && (
|
||||
<div className="bg-white rounded-xl shadow p-6 space-y-3">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<p className="font-bold text-gray-800 text-lg">{gain.lot}</p>
|
||||
<p className="text-sm text-gray-500 font-mono">{gain.code}</p>
|
||||
</div>
|
||||
<span className={`text-xs font-semibold px-3 py-1 rounded-full ${gain.status === 'DELIVERED' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}`}>
|
||||
{gain.status === 'DELIVERED' ? 'Remis' : 'À remettre'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-3">
|
||||
<p className="text-sm text-gray-600"><span className="font-medium">Client :</span> {gain.client}</p>
|
||||
<p className="text-sm text-gray-600"><span className="font-medium">Email :</span> {gain.email}</p>
|
||||
</div>
|
||||
|
||||
{gain.status === 'PENDING' && (
|
||||
<button
|
||||
onClick={handleDeliver}
|
||||
className="w-full bg-green-700 hover:bg-green-800 text-white font-semibold py-2.5 rounded-lg transition-colors mt-2"
|
||||
>
|
||||
Confirmer la remise du lot
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
116
frontend/src/pages/Inscription.jsx
Normal file
116
frontend/src/pages/Inscription.jsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function Inscription() {
|
||||
const [form, setForm] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
password: '',
|
||||
acceptMarketing: false,
|
||||
})
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target
|
||||
setForm({ ...form, [name]: type === 'checkbox' ? checked : value })
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
// TODO: connecter au backend
|
||||
alert(`Inscription de ${form.firstName} ${form.lastName}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-green-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-2xl shadow-lg w-full max-w-md p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-green-800">Thé Tip Top</h1>
|
||||
<p className="text-gray-500 mt-1">Créer votre compte</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Prénom</label>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
value={form.firstName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Jean"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Nom</label>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
value={form.lastName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="Dupont"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="exemple@email.com"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Mot de passe</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
value={form.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
placeholder="••••••••"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-start gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="acceptMarketing"
|
||||
checked={form.acceptMarketing}
|
||||
onChange={handleChange}
|
||||
className="mt-1 accent-green-700"
|
||||
/>
|
||||
<span className="text-sm text-gray-600">
|
||||
J'accepte de recevoir des communications marketing de Thé Tip Top
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-green-700 hover:bg-green-800 text-white font-semibold py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
Créer mon compte
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-sm text-gray-500 mt-6">
|
||||
Déjà un compte ?{' '}
|
||||
<Link to="/connexion" className="text-green-700 font-medium hover:underline">
|
||||
Se connecter
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
35
frontend/src/pages/MesGains.jsx
Normal file
35
frontend/src/pages/MesGains.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
const MOCK_GAINS = [
|
||||
{ id: 1, code: 'TTT-00123', lot: 'Infusion découverte (30g)', status: 'PENDING' },
|
||||
{ id: 2, code: 'TTT-00456', lot: 'Thé signature (100g)', status: 'DELIVERED' },
|
||||
]
|
||||
|
||||
const statusLabel = {
|
||||
PENDING: { label: 'À récupérer', color: 'bg-yellow-100 text-yellow-800' },
|
||||
DELIVERED: { label: 'Récupéré', color: 'bg-green-100 text-green-800' },
|
||||
CANCELLED: { label: 'Annulé', color: 'bg-red-100 text-red-800' },
|
||||
}
|
||||
|
||||
export default function MesGains() {
|
||||
return (
|
||||
<div className="min-h-screen bg-green-50 p-6">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-3xl font-bold text-green-800 mb-2">Mes gains</h1>
|
||||
<p className="text-gray-500 mb-6">Historique de vos participations</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{MOCK_GAINS.map((gain) => (
|
||||
<div key={gain.id} className="bg-white rounded-xl shadow p-5 flex items-center justify-between">
|
||||
<div>
|
||||
<p className="font-semibold text-gray-800">{gain.lot}</p>
|
||||
<p className="text-sm text-gray-400 font-mono mt-1">{gain.code}</p>
|
||||
</div>
|
||||
<span className={`text-xs font-semibold px-3 py-1 rounded-full ${statusLabel[gain.status].color}`}>
|
||||
{statusLabel[gain.status].label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
frontend/src/pages/Participation.jsx
Normal file
61
frontend/src/pages/Participation.jsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function Participation() {
|
||||
const [code, setCode] = useState('')
|
||||
const [result, setResult] = useState(null)
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
// TODO: connecter au backend
|
||||
setResult({
|
||||
lot: 'Infusion découverte (30g)',
|
||||
description: 'Un assortiment de nos meilleures infusions',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-green-50 flex items-center justify-center p-4">
|
||||
<div className="bg-white rounded-2xl shadow-lg w-full max-w-md p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-green-800">Thé Tip Top</h1>
|
||||
<p className="text-gray-500 mt-1">Participer au jeu-concours</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Code ticket
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
required
|
||||
placeholder="Ex: TTT-XXXXX"
|
||||
className="w-full border border-gray-300 rounded-lg px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500 font-mono tracking-widest"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-green-700 hover:bg-green-800 text-white font-semibold py-2.5 rounded-lg transition-colors"
|
||||
>
|
||||
Valider mon ticket
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{result && (
|
||||
<div className="mt-6 bg-green-100 border border-green-300 rounded-xl p-5 text-center">
|
||||
<p className="text-green-800 font-bold text-lg">Félicitations !</p>
|
||||
<p className="text-green-700 font-medium mt-1">{result.lot}</p>
|
||||
<p className="text-gray-600 text-sm mt-1">{result.description}</p>
|
||||
<Link to="/mes-gains" className="inline-block mt-4 text-sm text-green-700 font-medium hover:underline">
|
||||
Voir tous mes gains →
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user