6 Commits

Author SHA1 Message Date
6ee3dfdb87 release: promote 1.0.2 to production
All checks were successful
Tests / Backend - Tests passed: 8
THE-TIP-TOP-MULTIBRANCH/pipeline/head This commit looks good
2026-08-17 15:59:13 +00:00
ecda263518 ci: retry deployment with Jenkins SMTP secret mount
All checks were successful
Tests / Backend - Tests passed: 8
THE-TIP-TOP-MULTIBRANCH/pipeline/head This commit looks good
2026-08-17 15:30:55 +00:00
1f11a4e3dd ci: retry develop deployment after SMTP secret setup
Some checks failed
Tests / Backend - Tests passed: 8
THE-TIP-TOP-MULTIBRANCH/pipeline/head There was a failure building this commit
2026-08-17 15:24:50 +00:00
117ebac770 fix: configure SMTP and public API URLs for deployments
Some checks failed
Tests / Backend - Tests passed: 8
THE-TIP-TOP-MULTIBRANCH/pipeline/head There was a failure building this commit
2026-08-17 15:19:32 +00:00
d9d2eaba3d merge: promote validated develop to main 2026-08-17 14:55:36 +00:00
047d34f59b fix: resolve frontend lint issues
All checks were successful
Tests / Backend - Tests passed: 8
THE-TIP-TOP-MULTIBRANCH/pipeline/head This commit looks good
2026-08-17 14:54:58 +00:00
9 changed files with 97 additions and 96 deletions

View File

@@ -24,6 +24,9 @@ services:
image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG} image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG}
restart: unless-stopped restart: unless-stopped
env_file:
- /opt/furious-ducks/secrets/tiptop-mail.env
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@@ -34,6 +37,7 @@ services:
DATABASE_URL: postgresql://tiptop:tiptop_dev_password@postgres:5432/the_tip_top_db DATABASE_URL: postgresql://tiptop:tiptop_dev_password@postgres:5432/the_tip_top_db
JWT_SECRET: dev_secret_change_me JWT_SECRET: dev_secret_change_me
APP_URL: https://api-dev.dsp5-archi-o24a-4-5-g4.fr APP_URL: https://api-dev.dsp5-archi-o24a-4-5-g4.fr
API_PUBLIC_URL: https://api-dev.dsp5-archi-o24a-4-5-g4.fr
command: > command: >
sh -c " sh -c "

View File

@@ -24,6 +24,9 @@ services:
image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG} image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG}
restart: unless-stopped restart: unless-stopped
env_file:
- /opt/furious-ducks/secrets/tiptop-mail.env
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@@ -34,6 +37,7 @@ services:
DATABASE_URL: postgresql://tiptop_preprod:tiptop_preprod_password@postgres:5432/the_tip_top_preprod DATABASE_URL: postgresql://tiptop_preprod:tiptop_preprod_password@postgres:5432/the_tip_top_preprod
JWT_SECRET: preprod_secret_change_me JWT_SECRET: preprod_secret_change_me
APP_URL: https://api-preprod.dsp5-archi-o24a-4-5-g4.fr APP_URL: https://api-preprod.dsp5-archi-o24a-4-5-g4.fr
API_PUBLIC_URL: https://api-preprod.dsp5-archi-o24a-4-5-g4.fr
command: > command: >
sh -c " sh -c "

View File

@@ -24,6 +24,9 @@ services:
image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG} image: ${REGISTRY}/the-tip-top/backend:${IMAGE_TAG}
restart: unless-stopped restart: unless-stopped
env_file:
- /opt/furious-ducks/secrets/tiptop-mail.env
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@@ -34,6 +37,7 @@ services:
DATABASE_URL: postgresql://tiptop_prod:tiptop_prod_password@postgres:5432/the_tip_top_prod DATABASE_URL: postgresql://tiptop_prod:tiptop_prod_password@postgres:5432/the_tip_top_prod
JWT_SECRET: prod_secret_change_me JWT_SECRET: prod_secret_change_me
APP_URL: https://api.dsp5-archi-o24a-4-5-g4.fr APP_URL: https://api.dsp5-archi-o24a-4-5-g4.fr
API_PUBLIC_URL: https://api.dsp5-archi-o24a-4-5-g4.fr
command: > command: >
sh -c " sh -c "

View File

@@ -1,31 +1,33 @@
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import { useAuth } from '../context/AuthContext' import { useAuth } from '../context/AuthContext'
function FooterLink({ to, children, className = '', pathname }) {
return pathname === to ? (
<span className={className}>{children}</span>
) : (
<Link to={to} className={className}>
{children}
</Link>
)
}
export default function Footer() { export default function Footer() {
const { pathname } = useLocation() const { pathname } = useLocation()
const { user } = useAuth() const { user } = useAuth()
const isLoggedIn = !!user const isLoggedIn = !!user
function FooterLink({ to, children, className=''}) {
return pathname === to ? (
<span className={className}>{children}</span>
) : (
<Link to={to} className={className}>
{children}
</Link>
)
}
return ( return (
<footer className="bg-[#006B6B] text-white"> <footer className="bg-[#006B6B] text-white">
{/* Partie principale */} {/* Partie principale */}
<div className="max-w-7xl mx-auto px-6 py-10 grid grid-cols-2 md:grid-cols-4 gap-8"> <div className="max-w-7xl mx-auto px-6 py-10 grid grid-cols-2 md:grid-cols-4 gap-8">
{/* Logo */} {/* Logo */}
<div> <div>
<FooterLink to='/accueil'> <FooterLink
to="/accueil"
pathname={pathname}
>
<h3 className="text-xl font-bold mb-3"> <h3 className="text-xl font-bold mb-3">
Thé Tip Top Thé Tip Top
</h3> </h3>
@@ -35,54 +37,51 @@ export default function Footer() {
Participez au jeu-concours Thé Tip Top et tentez de gagner Participez au jeu-concours Thé Tip Top et tentez de gagner
des lots gourmands autour du thé. des lots gourmands autour du thé.
</p> </p>
</div> </div>
{/* Navigation */} {/* Navigation */}
<div> <div>
<h3 className="text-lg font-semibold mb-5"> <h3 className="text-lg font-semibold mb-5">
Navigation Navigation
</h3> </h3>
<ul className="space-y-3 text-teal-100"> <ul className="space-y-3 text-teal-100">
<li> <li>
<FooterLink <FooterLink
to="/accueil" to="/accueil"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Accueil Accueil
</FooterLink> </FooterLink>
</li> </li>
<li> <li>
<FooterLink <FooterLink
to="/regles-jeu" to="/regles-jeu"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Règles du jeu Règles du jeu
</FooterLink> </FooterLink>
</li> </li>
<li> <li>
<FooterLink <FooterLink
to="/saisir-code-unique" to="/saisir-code-unique"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Tester mon code Tester mon code
</FooterLink> </FooterLink>
</li> </li>
{ !isLoggedIn && ( {!isLoggedIn && (
<li> <li>
<FooterLink <FooterLink
to="/inscription" to="/inscription"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Créer un compte Créer un compte
@@ -91,35 +90,30 @@ export default function Footer() {
)} )}
</ul> </ul>
</div> </div>
{/* Informations légales */} {/* Informations légales */}
<div> <div>
<h3 className="text-lg font-semibold mb-5"> <h3 className="text-lg font-semibold mb-5">
Informations Informations
</h3> </h3>
<ul className="space-y-3 text-teal-100"> <ul className="space-y-3 text-teal-100">
<li> <li>
<FooterLink <FooterLink
to="/mentions-legales" to="/mentions-legales"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Mentions légales Mentions légales
</FooterLink> </FooterLink>
</li> </li>
<li> <li>
<FooterLink <FooterLink
to="/confidentialite" to="/confidentialite"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Politique de confidentialité Politique de confidentialité
@@ -129,6 +123,7 @@ export default function Footer() {
<li> <li>
<FooterLink <FooterLink
to="/accessibilite" to="/accessibilite"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Accessibilité Accessibilité
@@ -138,16 +133,17 @@ export default function Footer() {
<li> <li>
<FooterLink <FooterLink
to="/cgu" to="/cgu"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
CGU CGU
</FooterLink> </FooterLink>
</li> </li>
<li> <li>
<FooterLink <FooterLink
to="/contact" to="/contact"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Contact Contact
@@ -157,6 +153,7 @@ export default function Footer() {
<li> <li>
<FooterLink <FooterLink
to="/sitemap" to="/sitemap"
pathname={pathname}
className="hover:text-white transition" className="hover:text-white transition"
> >
Plan du site Plan du site
@@ -164,15 +161,10 @@ export default function Footer() {
</li> </li>
</ul> </ul>
</div> </div>
{/* Contact */} {/* Contact */}
<div> <div>
<h3 className="text-lg font-semibold mb-5"> <h3 className="text-lg font-semibold mb-5">
Contact Contact
</h3> </h3>
@@ -181,11 +173,17 @@ export default function Footer() {
Une question sur le jeu ? Une question sur le jeu ?
</p> </p>
<a href="tel:+33184802034" className="block mt-2 font-medium hover:text-teal-300"> <a
href="tel:+33184802034"
className="block mt-2 font-medium hover:text-teal-300"
>
01 84 80 20 34 01 84 80 20 34
</a> </a>
<a href="mailto:contact@thetiptop.fr" className="inline-block mt-3 font-medium hover:text-teal-300"> <a
href="mailto:contact@thetiptop.fr"
className="inline-block mt-3 font-medium hover:text-teal-300"
>
contact@thetiptop.fr contact@thetiptop.fr
</a> </a>
@@ -194,22 +192,14 @@ export default function Footer() {
<br /> <br />
75011 Paris, France 75011 Paris, France
</p> </p>
</div> </div>
</div> </div>
{/* Barre basse */} {/* Barre basse */}
<div className="border-t border-teal-800"> <div className="border-t border-teal-800">
<div className="max-w-7xl mx-auto px-6 py-5 flex flex-col md:flex-row justify-between items-center gap-3"> <div className="max-w-7xl mx-auto px-6 py-5 flex flex-col md:flex-row justify-between items-center gap-3">
<p className="text-teal-200 text-sm text-center md:text-left"> <p className="text-teal-200 text-sm text-center md:text-left">
© {new Date().getFullYear()} Thé Tip Top. Tous droits réservés. © {new Date().getFullYear()} Thé Tip Top. Tous droits réservés.
<br className="md:hidden" /> <br className="md:hidden" />
@@ -219,6 +209,7 @@ export default function Footer() {
</p> </p>
<div className="flex gap-5 text-sm text-teal-200"> <div className="flex gap-5 text-sm text-teal-200">
<a <a
href="https://www.facebook.com/profile.php?id=61590449280854" href="https://www.facebook.com/profile.php?id=61590449280854"
target="_blank" target="_blank"
@@ -228,7 +219,6 @@ export default function Footer() {
Facebook Facebook
</a> </a>
<a <a
href="https://www.instagram.com/thetiptop.gp4?igsh=MXZ3YzZ0d3dkazBpeg%3D%3D&utm_source=qr" href="https://www.instagram.com/thetiptop.gp4?igsh=MXZ3YzZ0d3dkazBpeg%3D%3D&utm_source=qr"
target="_blank" target="_blank"
@@ -238,7 +228,6 @@ export default function Footer() {
Instagram Instagram
</a> </a>
<a <a
href="https://www.tiktok.com/@thetiptop4" href="https://www.tiktok.com/@thetiptop4"
target="_blank" target="_blank"
@@ -249,13 +238,9 @@ export default function Footer() {
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>
) )
} }

View File

@@ -1,3 +1,5 @@
/* eslint-disable react-refresh/only-export-components */
import { createContext, useContext, useState } from 'react' import { createContext, useContext, useState } from 'react'
const AuthContext = createContext(null) const AuthContext = createContext(null)

View File

@@ -56,7 +56,7 @@ export default function Admin() {
if (!handleAuthError(e)) setError(e.message) if (!handleAuthError(e)) setError(e.message)
}) })
.finally(() => setLoading(false)) .finally(() => setLoading(false))
}, []) }, [handleAuthError])
const handleDraw = async () => { const handleDraw = async () => {
setShowConfirm(false) setShowConfirm(false)
@@ -94,7 +94,7 @@ export default function Admin() {
const csv = [header, ...rows] const csv = [header, ...rows]
.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(';')) .map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(';'))
.join('\n') .join('\n')
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 url = URL.createObjectURL(blob)
const link = document.createElement('a') const link = document.createElement('a')
link.href = url link.href = url

View File

@@ -50,7 +50,20 @@ export default function MesGains() {
} }
}, [handleAuthError]) }, [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 ( return (
<div className="min-h-screen bg-gray-50 flex flex-col px-8 py-12 max-w-lg mx-auto"> <div className="min-h-screen bg-gray-50 flex flex-col px-8 py-12 max-w-lg mx-auto">

View File

@@ -1,20 +1,12 @@
import { useState } from 'react' import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import UserNav from '../components/UserNav' import UserNav from '../components/UserNav'
import { changePasswordApi, deleteAccountApi } from '../services/auth' import { changePasswordApi} from '../services/auth'
import { useHandleAuthError } from '../hooks/useHandleAuthError' import { useHandleAuthError } from '../hooks/useHandleAuthError'
import { useAuth } from '../context/AuthContext'
export default function Profile() { export default function Profile() {
const user = JSON.parse(localStorage.getItem('user') || '{}') const user = JSON.parse(localStorage.getItem('user') || '{}')
const initials = `${(user.firstname || '?')[0]}${(user.lastname || '?')[0]}`.toUpperCase() const initials = `${(user.firstname || '?')[0]}${(user.lastname || '?')[0]}`.toUpperCase()
const handleAuthError = useHandleAuthError() 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 [pwForm, setPwForm] = useState({ currentPassword: '', newPassword: '', confirmPassword: '' })
const [showPw, setShowPw] = useState({ current: false, new: false, confirm: false }) const [showPw, setShowPw] = useState({ current: false, new: false, confirm: false })

View File

@@ -17,7 +17,6 @@ export default function SaisirCodeUnique() {
const [code, setCode] = useState('') const [code, setCode] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [gain, setGain] = useState(null) const [gain, setGain] = useState(null)
const [claim, setClaim] = useState(null)
const [wonAt, setWonAt] = useState(null) const [wonAt, setWonAt] = useState(null)
const [showDetail, setShowDetail] = useState(false) const [showDetail, setShowDetail] = useState(false)
@@ -27,7 +26,6 @@ export default function SaisirCodeUnique() {
try { try {
const data = await participateApi(code.trim().toUpperCase()) const data = await participateApi(code.trim().toUpperCase())
setGain(data.gain) setGain(data.gain)
setClaim(data.claim)
setWonAt(new Date()) setWonAt(new Date())
} catch (err) { } catch (err) {
if (!handleAuthError(err)) { if (!handleAuthError(err)) {
@@ -40,7 +38,6 @@ export default function SaisirCodeUnique() {
const handleClose = () => { const handleClose = () => {
setGain(null) setGain(null)
setClaim(null)
setWonAt(null) setWonAt(null)
setShowDetail(false) setShowDetail(false)
setCode('') setCode('')