feat: add SonarQube integration, cookie consent, and authentication improvements
- Add SonarQube configuration for code quality analysis - sonar-project.properties with TypeScript/Next.js settings - .sonarignore to exclude build artifacts and dependencies - npm run sonar script - Jenkins pipeline stages for SonarQube analysis and quality gate - Implement cookie consent banner - New CookieConsent component with matching site colors - localStorage persistence for user choice - Accept/Reject buttons with proper styling - Link to cookies policy page - Add strict authentication protection for game page - Redirect unauthenticated users to login from /jeux - Clean up redundant auth checks and UI elements - Preserve redirect parameter for post-login navigation - Implement smart navigation with auth-aware redirects - "Jouer maintenant" button redirects based on auth status - "Participer au jeu" footer link with conditional routing - Authenticated users go to /jeux, others to /register - UI improvements and cleanup - Remove "Voir les lots" button from homepage - Remove "Gestion des cookies" from footer - Remove "À propos" from footer navigation - Consistent design across components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c8fdd59553
commit
7febb137e9
50
.sonarignore
Normal file
50
.sonarignore
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
**/node_modules/**
|
||||
|
||||
# Build outputs
|
||||
.next/
|
||||
out/
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Static files
|
||||
public/
|
||||
|
||||
# Configuration files
|
||||
*.config.js
|
||||
*.config.ts
|
||||
next.config.js
|
||||
tailwind.config.js
|
||||
postcss.config.js
|
||||
jest.config.js
|
||||
jest.setup.js
|
||||
|
||||
# Environment files
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
docker-compose*.yml
|
||||
|
||||
# CI/CD
|
||||
Jenkinsfile
|
||||
.github/
|
||||
|
||||
# Backups
|
||||
*.backup
|
||||
*.bak
|
||||
26
Jenkinsfile
vendored
26
Jenkinsfile
vendored
|
|
@ -79,6 +79,32 @@ pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
stage('SonarQube Analysis') {
|
||||
agent {
|
||||
docker {
|
||||
image 'sonarsource/sonar-scanner-cli:latest'
|
||||
args '-u root'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
echo "🔍 Analyse de la qualité du code avec SonarQube..."
|
||||
script {
|
||||
withSonarQubeEnv('SonarQube') {
|
||||
sh 'sonar-scanner'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Quality Gate') {
|
||||
steps {
|
||||
echo "🚦 Vérification du Quality Gate SonarQube..."
|
||||
timeout(time: 5, unit: 'MINUTES') {
|
||||
waitForQualityGate abortPipeline: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build Frontend') {
|
||||
agent {
|
||||
docker {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ export default function JeuxPage() {
|
|||
const [currentPrizeIndex, setCurrentPrizeIndex] = useState(0);
|
||||
const [isSpinning, setIsSpinning] = useState(false);
|
||||
|
||||
// Protection stricte: redirection si non connecté
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
router.push(`${ROUTES.LOGIN}?redirect=/jeux`);
|
||||
}
|
||||
}, [isAuthenticated, router]);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
|
|
@ -58,12 +65,6 @@ export default function JeuxPage() {
|
|||
// Réinitialiser le message d'erreur
|
||||
setErrorMessage("");
|
||||
|
||||
// Si non connecté, rediriger vers login
|
||||
if (!isAuthenticated) {
|
||||
router.push(`${ROUTES.LOGIN}?redirect=/jeux`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher la modal de roulette et démarrer l'animation
|
||||
setShowRouletteModal(true);
|
||||
setIsSpinning(true);
|
||||
|
|
@ -122,21 +123,10 @@ export default function JeuxPage() {
|
|||
</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>
|
||||
)}
|
||||
<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>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
||||
|
|
@ -184,30 +174,15 @@ export default function JeuxPage() {
|
|||
</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 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>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { usePathname } from "next/navigation";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import CookieConsent from "@/components/CookieConsent";
|
||||
|
||||
export default function LayoutClient({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
|
|
@ -23,6 +24,7 @@ export default function LayoutClient({ children }: { children: React.ReactNode }
|
|||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
<CookieConsent />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,12 +155,13 @@ export default function LoginPage() {
|
|||
|
||||
{/* 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-[#d4a574]">
|
||||
Connexion
|
||||
<button className="flex-1 py-4 px-6 text-center font-bold text-gray-900 bg-white border-b-3 border-[#d4a574] shadow-[0_3px_8px_rgba(212,165,116,0.3)] relative">
|
||||
<span className="relative z-10">Connexion</span>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-[#d4a574] to-transparent"></div>
|
||||
</button>
|
||||
<Link
|
||||
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"
|
||||
className="flex-1 py-4 px-6 text-center font-semibold text-gray-500 bg-gray-50 hover:bg-gradient-to-r hover:from-[#d4a574]/5 hover:to-[#c4956a]/5 hover:text-[#c4956a] transition-all duration-300 hover:shadow-inner"
|
||||
>
|
||||
Inscription
|
||||
</Link>
|
||||
|
|
@ -267,7 +268,7 @@ export default function LoginPage() {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full bg-[#d4a574] hover:bg-[#c4956a] disabled:bg-gray-400 text-white font-bold px-8 py-4 rounded-lg transition-all"
|
||||
className="w-full bg-gradient-to-r from-[#d4a574] to-[#c4956a] hover:from-[#e5b685] hover:to-[#d4a574] disabled:bg-gray-400 disabled:from-gray-400 disabled:to-gray-400 text-white font-bold px-8 py-4 rounded-lg transition-all duration-300 hover:shadow-[0_0_25px_rgba(212,165,116,0.7)] hover:scale-[1.02] shadow-lg transform"
|
||||
>
|
||||
{isSubmitting ? "Connexion..." : "Se connecter"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ import GamePeriod from "@/components/GamePeriod";
|
|||
import GrandPrize from "@/components/GrandPrize";
|
||||
import AboutContest from "@/components/AboutContest";
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { ROUTES } from "@/utils/constants";
|
||||
|
||||
export default function HomePage() {
|
||||
const [animationKey] = useState(Date.now());
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen -mt-[4.5rem] relative">
|
||||
|
|
@ -90,7 +93,7 @@ export default function HomePage() {
|
|||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center px-4">
|
||||
<Link href="/register">
|
||||
<Link href={isAuthenticated ? ROUTES.GAME : "/register"}>
|
||||
<button className="bg-[#d4a574] hover:bg-[#c4956a] text-white font-bold text-lg px-12 py-5 rounded-full shadow-2xl transition-all w-full sm:w-auto hover:scale-105">
|
||||
Jouer maintenant
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -114,12 +114,13 @@ export default function RegisterPage() {
|
|||
<div className="flex border-b border-gray-200">
|
||||
<Link
|
||||
href={ROUTES.LOGIN}
|
||||
className="flex-1 py-4 px-6 text-center font-semibold text-gray-500 bg-gray-50 hover:bg-gray-100 transition-colors"
|
||||
className="flex-1 py-4 px-6 text-center font-semibold text-gray-500 bg-gray-50 hover:bg-gradient-to-r hover:from-[#d4a574]/5 hover:to-[#c4956a]/5 hover:text-[#c4956a] transition-all duration-300 hover:shadow-inner"
|
||||
>
|
||||
Connexion
|
||||
</Link>
|
||||
<button className="flex-1 py-4 px-6 text-center font-semibold text-gray-900 bg-white border-b-2 border-[#d4a574]">
|
||||
Inscription
|
||||
<button className="flex-1 py-4 px-6 text-center font-bold text-gray-900 bg-white border-b-3 border-[#d4a574] shadow-[0_3px_8px_rgba(212,165,116,0.3)] relative">
|
||||
<span className="relative z-10">Inscription</span>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-[#d4a574] to-transparent"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -295,7 +296,7 @@ export default function RegisterPage() {
|
|||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full bg-[#d4a574] hover:bg-[#c4956a] disabled:bg-gray-400 text-white font-bold px-8 py-4 rounded-lg transition-all"
|
||||
className="w-full bg-gradient-to-r from-[#d4a574] to-[#c4956a] hover:from-[#e5b685] hover:to-[#d4a574] disabled:bg-gray-400 disabled:from-gray-400 disabled:to-gray-400 text-white font-bold px-8 py-4 rounded-lg transition-all duration-300 hover:shadow-[0_0_25px_rgba(212,165,116,0.7)] hover:scale-[1.02] shadow-lg transform"
|
||||
>
|
||||
{isSubmitting ? "Inscription..." : "S'inscrire"}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -24,65 +24,46 @@ export default function AboutContest({
|
|||
`;
|
||||
|
||||
return (
|
||||
<section className="py-20 bg-transparent">
|
||||
<section className="py-20 bg-white/50 backdrop-blur-sm">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||
<div className="grid md:grid-cols-2 gap-16 items-center">
|
||||
{/* Texte */}
|
||||
<div className="order-2 md:order-1">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-gray-900 mb-6 leading-tight">
|
||||
{title}
|
||||
<div className="order-2 md:order-1 space-y-6">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-[#5a5a4e] leading-tight">
|
||||
Jeu Concours Thé Tip Top
|
||||
<span className="block text-3xl md:text-4xl text-[#d4a574] mt-2">
|
||||
Boutique Premium à Nice
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<div className="prose prose-lg text-gray-700 mb-8 space-y-4">
|
||||
{(description || defaultDescription).split('\n').filter(p => p.trim()).map((paragraph, index) => (
|
||||
<p key={index} className="leading-relaxed">
|
||||
{paragraph.trim()}
|
||||
<div className="space-y-4 text-lg text-gray-700">
|
||||
<p className="leading-relaxed">
|
||||
Bienvenue dans notre grand jeu-concours <span className="font-semibold text-[#5a5a4e]">Thé Tip Top</span> organisé à l'occasion de l'ouverture de notre <span className="font-semibold text-[#d4a574]">10ème boutique</span> de thé premium à Nice.
|
||||
</p>
|
||||
<p className="leading-relaxed">
|
||||
Nous vous offrons la chance de gagner des produits exceptionnels : thés bio, accessoires exclusifs et bien plus encore.
|
||||
</p>
|
||||
<p className="leading-relaxed">
|
||||
Participez facilement en quelques clics et tentez votre chance parmi nos lots prestigieux.
|
||||
</p>
|
||||
<div className="bg-gradient-to-r from-[#d4a574]/10 to-[#c4956a]/10 border-l-4 border-[#d4a574] p-4 rounded-r-lg">
|
||||
<p className="font-semibold text-[#5a5a4e] text-xl">
|
||||
✨ 100% de tickets gagnants ! Chaque participation vous offre la garantie de repartir avec un cadeau.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Points clés */}
|
||||
<div className="space-y-3 mb-8">
|
||||
<div className="flex items-start gap-3">
|
||||
<svg className="w-6 h-6 text-[#1a4d2e] flex-shrink-0 mt-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-gray-800">
|
||||
<span className="font-semibold">100% de gagnants :</span> Chaque ticket acheté à 49€ vous fait gagner un lot
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<svg className="w-6 h-6 text-[#1a4d2e] flex-shrink-0 mt-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-gray-800">
|
||||
<span className="font-semibold">Résultat immédiat :</span> Découvrez instantanément votre lot en ligne
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<svg className="w-6 h-6 text-[#1a4d2e] flex-shrink-0 mt-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-gray-800">
|
||||
<span className="font-semibold">Thés bio et artisanaux :</span> Tous nos produits sont de qualité premium
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link href="/register">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-[#1a4d2e] hover:bg-[#2d5a3d] text-white font-bold shadow-xl"
|
||||
>
|
||||
En savoir plus
|
||||
<svg className="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||
</svg>
|
||||
</Button>
|
||||
</Link>
|
||||
<div className="flex flex-col sm:flex-row gap-4 pt-4">
|
||||
<Link href="/register">
|
||||
<button className="bg-gradient-to-r from-[#d4a574] to-[#c4956a] hover:from-[#e5b685] hover:to-[#d4a574] text-white font-bold text-lg px-8 py-4 rounded-lg transition-all duration-300 hover:shadow-[0_0_25px_rgba(212,165,116,0.7)] hover:scale-105 shadow-lg flex items-center justify-center gap-2">
|
||||
En savoir plus
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||
</svg>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
|
|
|
|||
65
components/CookieConsent.tsx
Normal file
65
components/CookieConsent.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function CookieConsent() {
|
||||
const [showBanner, setShowBanner] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Vérifier si l'utilisateur a déjà donné son consentement
|
||||
const consent = localStorage.getItem('cookieConsent');
|
||||
if (!consent) {
|
||||
setShowBanner(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAccept = () => {
|
||||
localStorage.setItem('cookieConsent', 'accepted');
|
||||
setShowBanner(false);
|
||||
};
|
||||
|
||||
const handleReject = () => {
|
||||
localStorage.setItem('cookieConsent', 'rejected');
|
||||
setShowBanner(false);
|
||||
};
|
||||
|
||||
if (!showBanner) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 bg-gradient-to-r from-[#f5f5f0] to-[#faf9f5] shadow-2xl border-t-4 border-[#d4a574]">
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
{/* Message */}
|
||||
<div className="flex-1 text-center md:text-left">
|
||||
<p className="text-sm md:text-base text-[#5a5a4e]">
|
||||
🍪 Ce site utilise des cookies pour améliorer votre expérience de navigation et analyser notre trafic.{' '}
|
||||
<Link
|
||||
href="/cookies"
|
||||
className="text-[#d4a574] hover:text-[#c4956a] underline transition-colors font-semibold"
|
||||
>
|
||||
En savoir plus
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Buttons */}
|
||||
<div className="flex gap-3 flex-shrink-0">
|
||||
<button
|
||||
onClick={handleReject}
|
||||
className="px-6 py-2.5 bg-white border-2 border-[#e5e4dc] hover:border-[#d4a574] text-[#5a5a4e] hover:text-[#d4a574] rounded-lg transition-all duration-300 text-sm font-semibold"
|
||||
>
|
||||
Refuser
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
className="px-6 py-2.5 bg-gradient-to-r from-[#d4a574] to-[#c4956a] hover:from-[#e5b685] hover:to-[#d4a574] text-white rounded-lg transition-all duration-300 shadow-lg hover:shadow-xl text-sm font-semibold"
|
||||
>
|
||||
Accepter tous les cookies
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,9 +3,11 @@
|
|||
import Link from 'next/link';
|
||||
import Logo from './Logo';
|
||||
import { ROUTES } from '@/utils/constants';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
return (
|
||||
<footer className="bg-gradient-to-br from-[#f5f5f0] via-[#faf9f5] to-[#f5f5f0] text-[#5a5a4e] border-t-2 border-[#e5e4dc]">
|
||||
|
|
@ -74,20 +76,12 @@ export default function Footer() {
|
|||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={ROUTES.GAME}
|
||||
href={isAuthenticated ? ROUTES.GAME : ROUTES.REGISTER}
|
||||
className="text-sm text-[#8a8a7a] hover:text-[#d4a574] transition-colors"
|
||||
>
|
||||
Participer au jeu
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-sm text-[#8a8a7a] hover:text-[#d4a574] transition-colors"
|
||||
>
|
||||
À propos
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/contact"
|
||||
|
|
@ -135,14 +129,6 @@ export default function Footer() {
|
|||
Règlement du jeu
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/cookies"
|
||||
className="text-sm text-[#8a8a7a] hover:text-[#d4a574] transition-colors"
|
||||
>
|
||||
Gestion des cookies
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/legal"
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export default function Header() {
|
|||
{isAuthenticated ? (
|
||||
<Link
|
||||
href={ROUTES.GAME}
|
||||
className="bg-[#d4a574] hover:bg-[#c4956a] text-white font-bold px-6 py-2 rounded-lg transition-all hover:shadow-xl"
|
||||
className="bg-gradient-to-r from-[#d4a574] to-[#c4956a] hover:from-[#e5b685] hover:to-[#d4a574] text-white font-bold px-6 py-2 rounded-lg transition-all duration-300 hover:shadow-[0_0_20px_rgba(212,165,116,0.6)] hover:scale-105 shadow-lg"
|
||||
>
|
||||
Participer
|
||||
</Link>
|
||||
|
|
@ -98,11 +98,11 @@ export default function Header() {
|
|||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setIsParticiperDropdownOpen(!isParticiperDropdownOpen)}
|
||||
className="bg-[#d4a574] hover:bg-[#c4956a] text-white font-bold px-6 py-2 rounded-lg transition-all hover:shadow-xl flex items-center gap-2"
|
||||
className="bg-gradient-to-r from-[#d4a574] to-[#c4956a] hover:from-[#e5b685] hover:to-[#d4a574] text-white font-bold px-6 py-2 rounded-lg transition-all duration-300 hover:shadow-[0_0_20px_rgba(212,165,116,0.6)] hover:scale-105 shadow-lg flex items-center gap-2"
|
||||
>
|
||||
Participer
|
||||
<svg
|
||||
className={`w-4 h-4 transition-transform ${isParticiperDropdownOpen ? 'rotate-180' : ''}`}
|
||||
className={`w-4 h-4 transition-transform duration-300 ${isParticiperDropdownOpen ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -112,22 +112,22 @@ export default function Header() {
|
|||
</button>
|
||||
|
||||
{isParticiperDropdownOpen && (
|
||||
<div className="absolute right-0 mt-2 w-56 bg-white rounded-lg shadow-xl border border-gray-200 py-2 z-50 animate-fadeIn">
|
||||
<div className="absolute right-0 mt-2 w-56 bg-white rounded-xl shadow-2xl border-2 border-[#d4a574]/30 py-2 z-50 animate-fadeIn overflow-hidden">
|
||||
<Link
|
||||
href={ROUTES.LOGIN}
|
||||
onClick={() => setIsParticiperDropdownOpen(false)}
|
||||
className="block px-4 py-3 text-gray-700 hover:bg-green-50 hover:text-green-700 transition-colors"
|
||||
className="block px-4 py-3 text-gray-700 hover:bg-gradient-to-r hover:from-[#d4a574]/10 hover:to-[#c4956a]/10 hover:text-[#c4956a] transition-all duration-300 hover:pl-6 hover:shadow-inner"
|
||||
>
|
||||
<span className="font-medium block">Connexion</span>
|
||||
<span className="font-bold block">Connexion</span>
|
||||
<p className="text-xs text-gray-500 mt-0.5">J'ai déjà un compte</p>
|
||||
</Link>
|
||||
<div className="border-t border-gray-100 my-1"></div>
|
||||
<div className="border-t border-[#d4a574]/20 my-1"></div>
|
||||
<Link
|
||||
href={ROUTES.REGISTER}
|
||||
onClick={() => setIsParticiperDropdownOpen(false)}
|
||||
className="block px-4 py-3 text-gray-700 hover:bg-green-50 hover:text-green-700 transition-colors"
|
||||
className="block px-4 py-3 text-gray-700 hover:bg-gradient-to-r hover:from-[#d4a574]/10 hover:to-[#c4956a]/10 hover:text-[#c4956a] transition-all duration-300 hover:pl-6 hover:shadow-inner"
|
||||
>
|
||||
<span className="font-medium block">Inscription</span>
|
||||
<span className="font-bold block">Inscription</span>
|
||||
<p className="text-xs text-gray-500 mt-0.5">Créer un nouveau compte</p>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"test": "echo \"No tests configured\" && exit 0"
|
||||
"test": "echo \"No tests configured\" && exit 0",
|
||||
"sonar": "sonar-scanner"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
|
|
|
|||
33
sonar-project.properties
Normal file
33
sonar-project.properties
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Informations du projet
|
||||
sonar.projectKey=the-tip-top-frontend
|
||||
sonar.projectName=Thé Tip Top - Frontend
|
||||
sonar.projectVersion=1.0.0
|
||||
|
||||
# Chemin des sources
|
||||
sonar.sources=app,components,contexts,hooks,lib,services,types,utils
|
||||
sonar.tests=__tests__
|
||||
|
||||
# Exclusions
|
||||
sonar.exclusions=**/node_modules/**,**/*.spec.ts,**/*.test.ts,**/*.spec.tsx,**/*.test.tsx,**/coverage/**,**/.next/**,**/public/**,**/*.config.js,**/*.config.ts,**/dist/**,**/build/**
|
||||
|
||||
# Encodage des fichiers
|
||||
sonar.sourceEncoding=UTF-8
|
||||
|
||||
# Langage du projet
|
||||
sonar.language=ts
|
||||
|
||||
# Chemins de couverture de code (si tests configurés)
|
||||
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||
sonar.typescript.lcov.reportPaths=coverage/lcov.info
|
||||
|
||||
# Paramètres TypeScript
|
||||
sonar.typescript.tsconfigPath=tsconfig.json
|
||||
|
||||
# Niveau de logs
|
||||
sonar.log.level=INFO
|
||||
|
||||
# URL du serveur SonarQube (à adapter selon votre configuration)
|
||||
# sonar.host.url=http://localhost:9000
|
||||
|
||||
# Token d'authentification (à configurer via variable d'environnement)
|
||||
# sonar.login=${SONAR_TOKEN}
|
||||
Loading…
Reference in New Issue
Block a user