Merge remote-tracking branch 'origin/Alexis' into Emmanuel
Récupère la FAQ de la page d'accueil et la refonte de la page Contact, ainsi que la chaîne CI/CD et l'infrastructure VPS HTTPS venues de main. Résolution des 13 conflits : - Footer : les deux corrections sont conservées. FooterLink sort du composant (Alexis), la grille passe en une colonne sous 640 px et l'adresse email peut se couper (audit responsive). - Admin : le BOM du CSV adopte la séquence échappée d'Alexis, plus lisible que le caractère invisible et sans règle de lint à désactiver. - Contact : version d'Alexis retenue, plus complète, avec son formulaire. - Connexion, Inscription : logos SVG conservés dans leur version nettoyée, avec aria-hidden ; les gestionnaires OAuth et les champs d'inscription restent en place. - UserNav, Profile, services/admin : versions locales conservées. - docker-compose : structure Traefik et domaines HTTPS d'Alexis, sur lesquels sont regreffés FRONTEND_URL et les identifiants OAuth par substitution. - Les deux package-lock régénérés depuis les package.json fusionnés. Aligne au passage les derniers chemins d'API restés relatifs sur VITE_API_URL, sans quoi le bouton Google et la configuration du jeu visaient le domaine du front en déploiement. Fiabilise trois tests de tirage au sort : la table GrandPrizeWinner est unique pour tout le jeu et le vivier contient de vrais comptes, donc les suites mettent désormais un éventuel tirage réel de côté et n'attendent plus de connaître le gagnant à l'avance.
This commit is contained in:
@@ -52,6 +52,67 @@ export default function Accueil() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Questions fréquentes */}
|
||||
<section className="max-w-3xl mx-auto px-4 sm:px-6 pb-16 sm:pb-24">
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-[#006B6B] mb-6 sm:mb-8 text-center">
|
||||
Questions fréquentes
|
||||
</h2>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{
|
||||
question: 'Comment obtenir un code ?',
|
||||
answer: "Achetez un produit Thé Tip Top d'une valeur supérieure à 49 € en boutique ou en ligne : un code unique à 10 caractères figure sur votre ticket de caisse ou votre facture.",
|
||||
},
|
||||
{
|
||||
question: 'Quels sont les lots à gagner ?',
|
||||
answer: (
|
||||
<>
|
||||
Tous les tickets sont gagnants ! Selon votre ticket, vous pouvez remporter :
|
||||
<ul className="list-disc list-inside space-y-1 mt-2">
|
||||
<li>Un infuseur à thé</li>
|
||||
<li>Une boîte de 100 g de thé détox ou d'infusion</li>
|
||||
<li>Une boîte de 100 g de thé signature</li>
|
||||
<li>Un coffret découverte d'une valeur de 39 €</li>
|
||||
<li>Un coffret découverte d'une valeur de 69 €</li>
|
||||
</ul>
|
||||
Un tirage au sort désigne en plus un grand gagnant qui remporte une année de thé d'une valeur de 360 €.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
question: 'Jusqu\'à quand puis-je jouer ?',
|
||||
answer: 'La participation est possible pendant toute la durée du jeu-concours, soit 30 jours à compter de son ouverture. Consultez la page Règles du jeu pour le détail des dates.',
|
||||
},
|
||||
{
|
||||
question: 'Que faire si mon code n\'est pas reconnu ?',
|
||||
answer: "Vérifiez que le code a été saisi sans espace ni erreur. Si le problème persiste, contactez-nous depuis la page Contact.",
|
||||
},
|
||||
{
|
||||
question: 'Où retirer mon lot ?',
|
||||
answer: 'En boutique, sur présentation de votre compte utilisateur validé et de la confirmation de gain affichée dans votre espace « Mes gains ».',
|
||||
},
|
||||
].map((faq) => (
|
||||
<details key={faq.question} className="group bg-white rounded-2xl shadow p-4 sm:p-6">
|
||||
<summary className="flex items-center justify-between gap-3 cursor-pointer list-none font-semibold text-gray-800 text-sm sm:text-base">
|
||||
{faq.question}
|
||||
<svg
|
||||
className="w-4 h-4 text-[#006B6B] flex-shrink-0 transition-transform group-open:rotate-180"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</summary>
|
||||
<div className="text-gray-600 text-sm sm:text-base mt-3 leading-relaxed">
|
||||
{faq.answer}
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function Admin() {
|
||||
if (!handleAuthError(e)) setError(e.message)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
}, [handleAuthError])
|
||||
|
||||
const handleSaveGameSettings = async (e) => {
|
||||
e.preventDefault()
|
||||
@@ -145,10 +145,7 @@ export default function Admin() {
|
||||
const csv = [header, ...rows]
|
||||
.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(';'))
|
||||
.join('\n')
|
||||
// Le BOM en tete du fichier est indispensable : sans lui, Excel ouvre le
|
||||
// CSV en ANSI et casse tous les accents. Ne pas le supprimer.
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
const blob = new Blob([`${csv}`], { type: 'text/csv;charset=utf-8;' })
|
||||
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
|
||||
@@ -7,6 +7,15 @@ export default function Cgu() {
|
||||
Conditions générales d'utilisation
|
||||
</h1>
|
||||
|
||||
<div className="mb-6 sm:mb-8 px-4 py-3 sm:px-5 sm:py-4 rounded-xl bg-[#006B6B]">
|
||||
<p className="text-white text-sm sm:text-base font-medium leading-relaxed">
|
||||
Projet étudiant fictif — Ce site a été conçu dans un cadre pédagogique. La société
|
||||
Thé Tip Top, l'agence Furious Ducks et l'ensemble des conditions présentées
|
||||
ci-dessous sont fictives. Aucun achat, paiement ou réservation réel ne peut être
|
||||
effectué sur ce site.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-8 sm:mb-10 text-sm sm:text-base">
|
||||
Les présentes Conditions Générales d'Utilisation (CGU) précisent les modalités d'accès et
|
||||
d'utilisation du site internet du jeu-concours « 100% Gagnant » de la société Thé Tip Top.
|
||||
|
||||
@@ -18,7 +18,8 @@ export default function Connexion() {
|
||||
// Navigation pleine page (et non fetch) : le flux OAuth passe par des
|
||||
// redirections vers Google puis retour sur /auth/callback.
|
||||
const handleGoogleLogin = () => {
|
||||
window.location.href = '/api/auth/google'
|
||||
const apiUrl = import.meta.env.VITE_API_URL || ''
|
||||
window.location.href = `${apiUrl}/api/auth/google`
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
|
||||
@@ -3,43 +3,152 @@ import imgContact from '../assets/img/img-contact.jpg'
|
||||
export default function Contact() {
|
||||
return (
|
||||
<main className="bg-[#F0F0F0] px-4 sm:px-6 py-10 sm:py-16">
|
||||
<div className="max-w-6xl mx-auto grid md:grid-cols-2 gap-6 sm:gap-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
|
||||
{/* Informations */}
|
||||
<section className="bg-white rounded-2xl shadow p-5 sm:p-8 md:p-10">
|
||||
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-[#006B6B] mb-4 sm:mb-6">
|
||||
Contact
|
||||
</h1>
|
||||
<p className="text-gray-600 leading-relaxed mb-6 sm:mb-8 text-sm sm:text-base">
|
||||
Une question concernant le jeu-concours, votre participation ou votre lot ?
|
||||
Notre équipe ThéTipTop est à votre écoute.
|
||||
</p>
|
||||
<div className="space-y-5 text-gray-700">
|
||||
<div>
|
||||
<h2 className="font-semibold text-[#006B6B] mb-1">Adresse</h2>
|
||||
<p className="text-sm sm:text-base">ThéTipTop<br />18 rue Léon Frot<br />75011 Paris, France</p>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-semibold text-[#006B6B] mb-1">Email</h2>
|
||||
<a href="mailto:contact@thetiptop.fr" className="text-sm sm:text-base hover:text-[#006B6B]">contact@thetiptop.fr</a>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-semibold text-[#006B6B] mb-1">Téléphone</h2>
|
||||
<a href="tel:+33184802034" className="text-sm sm:text-base hover:text-[#006B6B]">01 84 80 20 34</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div className="grid md:grid-cols-2 gap-6 sm:gap-8 mb-8">
|
||||
|
||||
{/* Image */}
|
||||
<section className="rounded-2xl shadow overflow-hidden h-64 sm:h-80 md:h-[468px] md:self-start">
|
||||
<img
|
||||
src={imgContact}
|
||||
alt="Une personne savourant un thé"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Informations */}
|
||||
<section className="bg-white rounded-2xl shadow p-5 sm:p-8 md:p-10">
|
||||
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-[#006B6B] mb-4 sm:mb-6">
|
||||
Contact
|
||||
</h1>
|
||||
|
||||
<p className="text-gray-600 leading-relaxed mb-6 sm:mb-8 text-sm sm:text-base">
|
||||
Une question concernant le jeu-concours, votre participation ou votre lot ?
|
||||
Notre équipe ThéTipTop est à votre écoute.
|
||||
</p>
|
||||
|
||||
<div className="space-y-5 text-gray-700">
|
||||
<div>
|
||||
<h2 className="font-semibold text-[#006B6B] mb-1">
|
||||
Adresse
|
||||
</h2>
|
||||
<p className="text-sm sm:text-base">
|
||||
ThéTipTop
|
||||
<br />
|
||||
18 rue Léon Frot
|
||||
<br />
|
||||
75011 Paris, France
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="font-semibold text-[#006B6B] mb-1">
|
||||
Email
|
||||
</h2>
|
||||
<a
|
||||
href="mailto:contact@thetiptop.fr"
|
||||
className="text-sm sm:text-base hover:text-[#006B6B]"
|
||||
>
|
||||
contact@thetiptop.fr
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="font-semibold text-[#006B6B] mb-1">
|
||||
Téléphone
|
||||
</h2>
|
||||
<a
|
||||
href="tel:+33184802034"
|
||||
className="text-sm sm:text-base hover:text-[#006B6B]"
|
||||
>
|
||||
01 84 80 20 34
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Image */}
|
||||
<section className="rounded-2xl shadow overflow-hidden min-h-80">
|
||||
<img
|
||||
src={imgContact}
|
||||
alt="Une personne savourant un thé"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Formulaire Emmanuel intégré au design Alexis */}
|
||||
<section className="bg-white rounded-2xl shadow p-5 sm:p-8 md:p-10 max-w-4xl mx-auto">
|
||||
<h2 className="text-xl sm:text-2xl font-bold text-[#006B6B] mb-4 sm:mb-6">
|
||||
Envoyez-nous un message
|
||||
</h2>
|
||||
|
||||
<form className="space-y-4 sm:space-y-5">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="contact-name"
|
||||
className="block text-gray-700 mb-1.5 text-sm sm:text-base"
|
||||
>
|
||||
Nom
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
id="contact-name"
|
||||
placeholder="Votre nom"
|
||||
className="w-full border border-gray-300 rounded-xl px-4 py-2.5 sm:py-3 text-sm sm:text-base focus:outline-none focus:ring-2 focus:ring-[#006B6B]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="contact-email"
|
||||
className="block text-gray-700 mb-1.5 text-sm sm:text-base"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="email"
|
||||
id="contact-email"
|
||||
placeholder="Votre email"
|
||||
className="w-full border border-gray-300 rounded-xl px-4 py-2.5 sm:py-3 text-sm sm:text-base focus:outline-none focus:ring-2 focus:ring-[#006B6B]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="contact-subject"
|
||||
className="block text-gray-700 mb-1.5 text-sm sm:text-base"
|
||||
>
|
||||
Sujet
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
id="contact-subject"
|
||||
placeholder="Sujet de votre demande"
|
||||
className="w-full border border-gray-300 rounded-xl px-4 py-2.5 sm:py-3 text-sm sm:text-base focus:outline-none focus:ring-2 focus:ring-[#006B6B]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="contact-message"
|
||||
className="block text-gray-700 mb-1.5 text-sm sm:text-base"
|
||||
>
|
||||
Message
|
||||
</label>
|
||||
|
||||
<textarea
|
||||
id="contact-message"
|
||||
rows="4"
|
||||
placeholder="Votre message..."
|
||||
className="w-full border border-gray-300 rounded-xl px-4 py-2.5 sm:py-3 text-sm sm:text-base focus:outline-none focus:ring-2 focus:ring-[#006B6B] resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-[#006B6B] hover:bg-[#005858] text-white font-semibold py-2.5 sm:py-3 rounded-xl transition text-sm sm:text-base"
|
||||
>
|
||||
Envoyer le message
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ export default function Inscription() {
|
||||
|
||||
// Même route que la connexion : Google crée le compte à la première venue.
|
||||
const handleGoogleSignup = () => {
|
||||
window.location.href = '/api/auth/google'
|
||||
const apiUrl = import.meta.env.VITE_API_URL || ''
|
||||
window.location.href = `${apiUrl}/api/auth/google`
|
||||
}
|
||||
|
||||
// Étape 1 — Inscription
|
||||
|
||||
@@ -7,6 +7,15 @@ export default function MentionsLegales() {
|
||||
Mentions légales
|
||||
</h1>
|
||||
|
||||
<div className="mb-6 sm:mb-8 px-4 py-3 sm:px-5 sm:py-4 rounded-xl bg-[#006B6B]">
|
||||
<p className="text-white text-sm sm:text-base font-medium leading-relaxed">
|
||||
Projet étudiant fictif — Ce site a été conçu dans un cadre pédagogique. La société
|
||||
Thé Tip Top, l'agence Furious Ducks et l'ensemble des informations légales présentées
|
||||
ci-dessous sont fictives. Aucun achat, paiement ou réservation réel ne peut être
|
||||
effectué sur ce site.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600 mb-8 sm:mb-10 text-sm sm:text-base">
|
||||
Conformément à la loi n°2004-575 du 21 juin 2004 pour la Confiance dans l'Économie
|
||||
Numérique (LCEN), les présentes mentions légales précisent l'identité des différents
|
||||
|
||||
@@ -50,7 +50,20 @@ export default function MesGains() {
|
||||
}
|
||||
}, [handleAuthError])
|
||||
|
||||
useEffect(() => { fetchGains() }, [fetchGains])
|
||||
useEffect(() => {
|
||||
const loadGains = async () => {
|
||||
try {
|
||||
const data = await myGainsApi()
|
||||
setGains(data.gains)
|
||||
} catch (err) {
|
||||
if (!handleAuthError(err)) setError(err.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadGains()
|
||||
}, [handleAuthError])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex flex-col px-8 py-12 max-w-lg mx-auto">
|
||||
|
||||
@@ -3,18 +3,11 @@ import { useNavigate } from 'react-router-dom'
|
||||
import UserNav from '../components/UserNav'
|
||||
import { changePasswordApi, deleteAccountApi, meApi, forgotPasswordApi } from '../services/auth'
|
||||
import { useHandleAuthError } from '../hooks/useHandleAuthError'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
|
||||
export default function Profile() {
|
||||
const user = JSON.parse(localStorage.getItem('user') || '{}')
|
||||
const initials = `${(user.firstname || '?')[0]}${(user.lastname || '?')[0]}`.toUpperCase()
|
||||
const handleAuthError = useHandleAuthError()
|
||||
const { logout } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const [deleteError, setDeleteError] = useState('')
|
||||
|
||||
const [pwForm, setPwForm] = useState({ currentPassword: '', newPassword: '', confirmPassword: '' })
|
||||
const [showPw, setShowPw] = useState({ current: false, new: false, confirm: false })
|
||||
|
||||
Reference in New Issue
Block a user