fix(a11y): améliorer l'accessibilité WAVE - corrections majeures
- Corriger les erreurs de contraste (text-primary-300 → text-primary-600) - Corriger la hiérarchie des titres (h3 → h2 pour GamePeriod et GrandPrize) - Supprimer les alt redondants sur les images décoratives (PrizeCard) - Ajouter aria-hidden sur les images décoratives - Ajouter aria-label descriptif sur le lien logo du Header - Utiliser les balises <time> pour les dates sémantiques - Convertir les textes importants en titres appropriés (h3, h4) - Mettre à jour les tests PrizeCard 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5b57d941b9
commit
6b03ad8053
|
|
@ -19,7 +19,6 @@ jest.mock('next/image', () => ({
|
|||
describe('PrizeCard', () => {
|
||||
const defaultProps = {
|
||||
imageSrc: '/images/lots/infuseur.png',
|
||||
imageAlt: 'Infuseur à thé premium',
|
||||
badge: '60%',
|
||||
title: 'Infuseur à thé premium',
|
||||
description: 'Un infuseur en acier inoxydable de haute qualité',
|
||||
|
|
@ -31,14 +30,14 @@ describe('PrizeCard', () => {
|
|||
expect(screen.getByText('Infuseur à thé premium')).toBeInTheDocument();
|
||||
expect(screen.getByText('Un infuseur en acier inoxydable de haute qualité')).toBeInTheDocument();
|
||||
expect(screen.getByText('60%')).toBeInTheDocument();
|
||||
expect(screen.getByAltText('Infuseur à thé premium')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render image with correct src', () => {
|
||||
render(<PrizeCard {...defaultProps} />);
|
||||
it('should render image with correct src and empty alt for decorative image', () => {
|
||||
const { container } = render(<PrizeCard {...defaultProps} />);
|
||||
|
||||
const image = screen.getByAltText('Infuseur à thé premium');
|
||||
const image = container.querySelector('img');
|
||||
expect(image).toHaveAttribute('src', '/images/lots/infuseur.png');
|
||||
expect(image).toHaveAttribute('alt', '');
|
||||
});
|
||||
|
||||
it('should render with default styling for non-grand prix', () => {
|
||||
|
|
@ -72,11 +71,13 @@ describe('PrizeCard', () => {
|
|||
it('should have correct structure with image container and content', () => {
|
||||
const { container } = render(<PrizeCard {...defaultProps} />);
|
||||
|
||||
// Check image container exists
|
||||
expect(container.querySelector('.aspect-square')).toBeInTheDocument();
|
||||
// Check image container exists with aria-hidden for decorative image
|
||||
const imageContainer = container.querySelector('.aspect-square');
|
||||
expect(imageContainer).toBeInTheDocument();
|
||||
expect(imageContainer).toHaveAttribute('aria-hidden', 'true');
|
||||
|
||||
// Check title is h3
|
||||
const title = screen.getByText('Infuseur à thé premium');
|
||||
expect(title.tagName).toBe('H3');
|
||||
const title = screen.getByRole('heading', { level: 3 });
|
||||
expect(title).toHaveTextContent('Infuseur à thé premium');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ export const metadata: Metadata = {
|
|||
};
|
||||
|
||||
const PRIZES = [
|
||||
{ imageSrc: "/images/lots/infuseur.png", imageAlt: "Infuseur à thé premium", badge: "60% des lots", title: "Infuseur à thé premium", description: "Un infuseur en acier inoxydable de haute qualité pour ressortir les arômes de vos thés en vrac" },
|
||||
{ imageSrc: "/images/lots/the-detox.png", imageAlt: "Boîte 100g thé détox", badge: "20% des lots", title: "Boîte 100g thé détox", description: "Mélange détox aux plantes bio : menthe, citronnelle, fenouil et gingembre" },
|
||||
{ imageSrc: "/images/lots/the-signature.png", imageAlt: "Boîte 100g thé signature", badge: "10% des lots", title: "Boîte 100g thé signature", description: "Notre mélange signature exclusif : Earl Grey aux agrumes et pétales de fleurs" },
|
||||
{ imageSrc: "/images/lots/coffret-39.png", imageAlt: "Coffret découverte 39€", badge: "6% des lots", title: "Coffret découverte 39€", description: "Sélection de nos 3 thés premium dans un élégant coffret cadeau" },
|
||||
{ imageSrc: "/images/lots/coffret-69.jpg", imageAlt: "Coffret prestige 69€", badge: "4% des lots", title: "Coffret prestige 69€", description: "Collection premium : 5 thés d'exception avec accessoires dans un coffret luxe" },
|
||||
{ imageSrc: "/images/lots/grand-prix.png", imageAlt: "Grand prix - 1 an de thé", badge: "1 an de THÉ", title: "Tirage Final", description: "Livraison mensuelle pendant 12 mois", isGrandPrix: true },
|
||||
{ imageSrc: "/images/lots/infuseur.png", imageAlt: "", badge: "60% des lots", title: "Infuseur à thé premium", description: "Un infuseur en acier inoxydable de haute qualité pour ressortir les arômes de vos thés en vrac" },
|
||||
{ imageSrc: "/images/lots/the-detox.png", imageAlt: "", badge: "20% des lots", title: "Boîte 100g thé détox", description: "Mélange détox aux plantes bio : menthe, citronnelle, fenouil et gingembre" },
|
||||
{ imageSrc: "/images/lots/the-signature.png", imageAlt: "", badge: "10% des lots", title: "Boîte 100g thé signature", description: "Notre mélange signature exclusif : Earl Grey aux agrumes et pétales de fleurs" },
|
||||
{ imageSrc: "/images/lots/coffret-39.png", imageAlt: "", badge: "6% des lots", title: "Coffret découverte 39€", description: "Sélection de nos 3 thés premium dans un élégant coffret cadeau" },
|
||||
{ imageSrc: "/images/lots/coffret-69.jpg", imageAlt: "", badge: "4% des lots", title: "Coffret prestige 69€", description: "Collection premium : 5 thés d'exception avec accessoires dans un coffret luxe" },
|
||||
{ imageSrc: "/images/lots/grand-prix.png", imageAlt: "", badge: "1 an de THÉ", title: "Tirage Final", description: "Livraison mensuelle pendant 12 mois", isGrandPrix: true },
|
||||
];
|
||||
|
||||
export default function LotsPage() {
|
||||
|
|
@ -24,7 +24,7 @@ export default function LotsPage() {
|
|||
<section className="bg-gradient-to-r from-white to-beige-50 py-12 border-b-2 border-beige-300">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-primary-300 mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-primary-600 mb-4">
|
||||
Lots à gagner
|
||||
</h1>
|
||||
<p className="text-lg text-gray-600">
|
||||
|
|
@ -41,11 +41,11 @@ export default function LotsPage() {
|
|||
<div className="max-w-5xl mx-auto">
|
||||
<div className="bg-gradient-to-r from-primary-500 to-primary-600 rounded-2xl p-8 shadow-2xl border-2 border-primary-400">
|
||||
<div className="flex flex-col md:flex-row items-center gap-6">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="flex-shrink-0" aria-hidden="true">
|
||||
<div className="w-32 h-32 bg-white rounded-lg flex items-center justify-center p-3 shadow-md">
|
||||
<Image
|
||||
src="/images/lots/grand-prix.png"
|
||||
alt="Grand prix"
|
||||
alt=""
|
||||
width={128}
|
||||
height={128}
|
||||
className="w-full h-full object-contain object-center"
|
||||
|
|
|
|||
20
app/page.tsx
20
app/page.tsx
|
|
@ -13,12 +13,12 @@ import { ROUTES } from "@/utils/constants";
|
|||
import { useRouter } from "next/navigation";
|
||||
|
||||
const HOME_PRIZES = [
|
||||
{ imageSrc: "/images/lots/infuseur.png", imageAlt: "Infuseur à thé premium", badge: "60%", title: "Infuseur à thé premium", description: "Un infuseur en acier inoxydable de haute qualité pour ressortir les arômes de vos thés en vrac" },
|
||||
{ imageSrc: "/images/lots/the-detox.png", imageAlt: "Boîte 100g thé détox", badge: "20%", title: "Boîte 100g thé détox", description: "Mélange détox aux plantes bio" },
|
||||
{ imageSrc: "/images/lots/the-signature.png", imageAlt: "Boîte 100g thé signature", badge: "10%", title: "Boîte 100g thé signature", description: "Notre mélange signature exclusif" },
|
||||
{ imageSrc: "/images/lots/coffret-39.png", imageAlt: "Coffret découverte 39€", badge: "6%", title: "Coffret découverte 39€", description: "Sélection de nos meilleurs thés" },
|
||||
{ imageSrc: "/images/lots/coffret-69.jpg", imageAlt: "Coffret prestige 69€", badge: "4%", title: "Coffret prestige 69€", description: "Une expérience complète" },
|
||||
{ imageSrc: "/images/lots/grand-prix.png", imageAlt: "Grand prix - 1 an de thé", badge: "1 an de THÉ", title: "Tirage Final", description: "Livraison mensuelle pendant 12 mois", isGrandPrix: true },
|
||||
{ imageSrc: "/images/lots/infuseur.png", imageAlt: "", badge: "60%", title: "Infuseur à thé premium", description: "Un infuseur en acier inoxydable de haute qualité pour ressortir les arômes de vos thés en vrac" },
|
||||
{ imageSrc: "/images/lots/the-detox.png", imageAlt: "", badge: "20%", title: "Boîte 100g thé détox", description: "Mélange détox aux plantes bio" },
|
||||
{ imageSrc: "/images/lots/the-signature.png", imageAlt: "", badge: "10%", title: "Boîte 100g thé signature", description: "Notre mélange signature exclusif" },
|
||||
{ imageSrc: "/images/lots/coffret-39.png", imageAlt: "", badge: "6%", title: "Coffret découverte 39€", description: "Sélection de nos meilleurs thés" },
|
||||
{ imageSrc: "/images/lots/coffret-69.jpg", imageAlt: "", badge: "4%", title: "Coffret prestige 69€", description: "Une expérience complète" },
|
||||
{ imageSrc: "/images/lots/grand-prix.png", imageAlt: "", badge: "1 an de THÉ", title: "Tirage Final", description: "Livraison mensuelle pendant 12 mois", isGrandPrix: true },
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
|
|
@ -58,8 +58,8 @@ export default function HomePage() {
|
|||
<h1 className="text-3xl sm:text-4xl md:text-6xl lg:text-7xl font-bold text-primary-500 mb-4 md:mb-6 leading-tight drop-shadow-sm tracking-wide">
|
||||
Jeu Concours - Thé Tip Top
|
||||
</h1>
|
||||
<p className="text-xl sm:text-2xl md:text-5xl lg:text-6xl font-semibold text-primary-300 mb-4 md:mb-6 leading-snug md:leading-relaxed tracking-wide">
|
||||
Célébrons l'ouverture de notre 10<sup>ème</sup> boutique à <span className="text-primary-500 font-bold">Nice</span>
|
||||
<p className="text-xl sm:text-2xl md:text-5xl lg:text-6xl font-semibold text-primary-600 mb-4 md:mb-6 leading-snug md:leading-relaxed tracking-wide">
|
||||
Célébrons l'ouverture de notre 10<sup>ème</sup> boutique à <span className="text-primary-700 font-bold">Nice</span>
|
||||
</p>
|
||||
<p className="text-base sm:text-lg md:text-2xl lg:text-3xl text-gray-500 mb-8 md:mb-10 leading-relaxed tracking-wide px-2">
|
||||
Participez à notre concours - 100% des participants gagnent un lot !
|
||||
|
|
@ -106,7 +106,7 @@ export default function HomePage() {
|
|||
<section className="py-20 bg-white/60 backdrop-blur-sm">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-center text-primary-300 mb-4">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-center text-primary-600 mb-4">
|
||||
Comment participer?
|
||||
</h2>
|
||||
<p className="text-center text-gray-600 mb-16 text-lg max-w-2xl mx-auto">
|
||||
|
|
@ -170,7 +170,7 @@ export default function HomePage() {
|
|||
<section className="py-20 bg-transparent">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-center text-primary-300 mb-4">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-center text-primary-600 mb-4">
|
||||
Lots à gagner
|
||||
</h2>
|
||||
<p className="text-center text-gray-600 mb-16 text-lg max-w-3xl mx-auto">
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default function Footer() {
|
|||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{/* Company Info */}
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<div className="mb-4" aria-hidden="true">
|
||||
<Logo variant="default" size="md" showText={true} />
|
||||
</div>
|
||||
<p className="text-sm text-beige-700 mb-4">
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ export default function GamePeriod() {
|
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-gray-900 mb-4">Période de validation des tickets</h3>
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-gray-900 mb-4">Période de validation des tickets</h2>
|
||||
<p className="text-lg text-gray-600 mb-6">
|
||||
Achetez et validez votre code de participation
|
||||
</p>
|
||||
<div className="bg-secondary-100 rounded-2xl px-6 py-4 w-full">
|
||||
<p className="text-lg font-bold text-secondary-700">
|
||||
Du 1 décembre 2025 au 31 décembre 2025
|
||||
</p>
|
||||
<p className="text-secondary-500 font-semibold mt-1">30 jours</p>
|
||||
<h3 className="text-lg font-bold text-secondary-700">
|
||||
Du <time dateTime="2025-12-01">1 décembre 2025</time> au <time dateTime="2025-12-31">31 décembre 2025</time>
|
||||
</h3>
|
||||
<p className="text-secondary-700 font-semibold mt-1">30 jours</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -32,15 +32,15 @@ export default function GamePeriod() {
|
|||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-gray-900 mb-4">Période de récupération des lots</h3>
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-gray-900 mb-4">Période de récupération des lots</h2>
|
||||
<p className="text-lg text-gray-600 mb-6">
|
||||
Récupérez vos lots gagnés en boutique
|
||||
</p>
|
||||
<div className="bg-primary-50 rounded-2xl px-6 py-4 w-full">
|
||||
<p className="text-lg font-bold text-primary-700">
|
||||
Du 1 décembre 2025 au 31 janvier 2026
|
||||
</p>
|
||||
<p className="text-primary-500 font-semibold mt-1">60 jours</p>
|
||||
<h3 className="text-lg font-bold text-primary-700">
|
||||
Du <time dateTime="2025-12-01">1 décembre 2025</time> au <time dateTime="2026-01-31">31 janvier 2026</time>
|
||||
</h3>
|
||||
<p className="text-primary-600 font-semibold mt-1">60 jours</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,15 +28,15 @@ export default function GrandPrize({
|
|||
</div>
|
||||
|
||||
{/* Titre */}
|
||||
<h3 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
|
||||
Grand Prix à gagner !
|
||||
</h3>
|
||||
</h2>
|
||||
|
||||
{/* Prix principal */}
|
||||
<div className="bg-white/80 backdrop-blur-sm rounded-2xl px-8 py-6 mb-6 shadow-lg">
|
||||
<p className="text-2xl md:text-3xl font-bold text-primary-500 mb-2">
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-primary-500 mb-2">
|
||||
1 an de thé offert
|
||||
</p>
|
||||
</h3>
|
||||
<p className="text-xl text-secondary-600 font-semibold">
|
||||
d'une valeur de {prizeAmount}
|
||||
</p>
|
||||
|
|
@ -52,25 +52,25 @@ export default function GrandPrize({
|
|||
{drawDate && (
|
||||
<div className="bg-white/70 backdrop-blur-sm rounded-2xl p-5 flex items-center gap-4 shadow-md">
|
||||
<div className="bg-primary-100 rounded-full p-3">
|
||||
<svg className="w-6 h-6 text-primary-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg className="w-6 h-6 text-primary-500" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
||||
<path fillRule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-sm text-gray-500 font-medium">Tirage au sort</p>
|
||||
<p className="text-lg font-bold text-gray-700">{formatDate(drawDate)}</p>
|
||||
<h4 className="text-sm text-gray-500 font-medium">Tirage au sort</h4>
|
||||
<p className="text-lg font-bold text-gray-700"><time dateTime={drawDate.toISOString().split('T')[0]}>{formatDate(drawDate)}</time></p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-white/70 backdrop-blur-sm rounded-2xl p-5 flex items-center gap-4 shadow-md">
|
||||
<div className="bg-primary-100 rounded-full p-3">
|
||||
<svg className="w-6 h-6 text-primary-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg className="w-6 h-6 text-primary-500" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
||||
<path fillRule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 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>
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-sm text-gray-500 font-medium">Certification</p>
|
||||
<h4 className="text-sm text-gray-500 font-medium">Certification</h4>
|
||||
<p className="text-lg font-bold text-gray-700">Contrôle d'huissier</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export default function Header() {
|
|||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-between h-18 gap-4">
|
||||
{/* Logo */}
|
||||
<Link href={ROUTES.HOME} className="group flex items-center gap-3 flex-shrink-0">
|
||||
<Link href={ROUTES.HOME} className="group flex items-center gap-3 flex-shrink-0" aria-label="Thé Tip Top - Retour à l'accueil">
|
||||
<Logo size="md" showText={false} className="group-hover:scale-105 transition-transform" />
|
||||
</Link>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { cn } from '@/utils/helpers';
|
|||
|
||||
interface PrizeCardProps {
|
||||
imageSrc: string;
|
||||
imageAlt: string;
|
||||
imageAlt?: string;
|
||||
badge: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
|
@ -16,7 +16,6 @@ interface PrizeCardProps {
|
|||
|
||||
export const PrizeCard: React.FC<PrizeCardProps> = ({
|
||||
imageSrc,
|
||||
imageAlt,
|
||||
badge,
|
||||
title,
|
||||
description,
|
||||
|
|
@ -38,10 +37,11 @@ export const PrizeCard: React.FC<PrizeCardProps> = ({
|
|||
? 'bg-gradient-to-br from-primary-50 to-primary-100'
|
||||
: 'bg-gradient-to-br from-beige-50 to-beige-100'
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={imageAlt}
|
||||
alt=""
|
||||
width={400}
|
||||
height={400}
|
||||
className="w-full h-full object-contain object-center"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user