feat: wire Rules page to live fetchRules data
- Replace static rules import with async fetchRules() function - Update RuleIcon prop from ruleId to iconKey - Add error state handling for data loading failures - Add .error CSS class for error message styling Closes Task 5's deferred compilation breakage: Rules.tsx now imports fetchRules (async) instead of the deleted static rules export, and uses the renamed iconKey prop on RuleIcon. Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
This commit is contained in:
@@ -29,3 +29,7 @@
|
||||
text-align: center;
|
||||
margin-top: var(--space-xl);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #a33;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,40 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
|
||||
const orderMock = vi.fn()
|
||||
const selectMock = vi.fn(() => ({ order: orderMock }))
|
||||
const fromMock = vi.fn<(table: string) => { select: typeof selectMock }>(() => ({ select: selectMock }))
|
||||
|
||||
vi.mock('../lib/supabase', () => ({
|
||||
supabase: { from: (table: string) => fromMock(table) },
|
||||
}))
|
||||
|
||||
const RULE_ROWS = [
|
||||
{ id: 'r1', title: 'Acompte', body: 'Un acompte est requis.', icon_key: 'deposit', sort_order: 0 },
|
||||
{ id: 'r2', title: 'Retard', body: 'Les retards ont des frais.', icon_key: 'lateness', sort_order: 1 },
|
||||
]
|
||||
|
||||
import Rules from './Rules'
|
||||
import { rules } from '../data/rules'
|
||||
|
||||
describe('Rules page', () => {
|
||||
it('renders every rule section title and body', () => {
|
||||
beforeEach(() => {
|
||||
orderMock.mockReset().mockResolvedValue({ data: RULE_ROWS, error: null })
|
||||
selectMock.mockClear()
|
||||
fromMock.mockClear()
|
||||
})
|
||||
|
||||
it('renders every rule section title and body', async () => {
|
||||
render(<MemoryRouter><Rules /></MemoryRouter>)
|
||||
for (const rule of rules) {
|
||||
expect(screen.getByText(rule.title)).toBeInTheDocument()
|
||||
for (const rule of RULE_ROWS) {
|
||||
expect(await screen.findByText(rule.title)).toBeInTheDocument()
|
||||
expect(screen.getByText(rule.body)).toBeInTheDocument()
|
||||
}
|
||||
})
|
||||
|
||||
it('links back to booking', () => {
|
||||
it('links back to booking', async () => {
|
||||
render(<MemoryRouter><Rules /></MemoryRouter>)
|
||||
await screen.findByText('Acompte')
|
||||
expect(screen.getByRole('link', { name: /réserver/i })).toHaveAttribute('href', '/book')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import ScrollReveal from '../components/ScrollReveal'
|
||||
import RuleIcon from '../components/RuleIcon'
|
||||
import { rules } from '../data/rules'
|
||||
import { fetchRules, type RuleSection } from '../data/rules'
|
||||
import styles from './Rules.module.css'
|
||||
|
||||
function Rules() {
|
||||
const [rules, setRules] = useState<RuleSection[]>([])
|
||||
const [error, setError] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
try {
|
||||
setRules(await fetchRules())
|
||||
} catch (err) {
|
||||
setError(err && typeof err === 'object' && 'message' in err ? String((err as { message: unknown }).message) : String(err))
|
||||
}
|
||||
}
|
||||
void load()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<h1>Règlements et politiques du salon</h1>
|
||||
<p>Merci de les lire avant de réserver votre rendez-vous.</p>
|
||||
{error && <p className={styles.error}>{error}</p>}
|
||||
{rules.map((rule, index) => (
|
||||
<ScrollReveal key={rule.id} delay={index * 0.08}>
|
||||
<div className={styles.section}>
|
||||
<RuleIcon ruleId={rule.id} />
|
||||
<RuleIcon iconKey={rule.iconKey} />
|
||||
<div>
|
||||
<h2>{rule.title}</h2>
|
||||
<p>{rule.body}</p>
|
||||
|
||||
Reference in New Issue
Block a user