126 lines
3.8 KiB
TypeScript
126 lines
3.8 KiB
TypeScript
'use client'
|
|
|
|
import { useCallback, useEffect, useState } from 'react'
|
|
import { ApiClientError, cardsApi } from '@/lib/api-client'
|
|
import type { Card } from '@/domain/card'
|
|
|
|
const EMPTY_THEME = ''
|
|
|
|
export function PlayView() {
|
|
const [themes, setThemes] = useState<string[]>([])
|
|
const [theme, setTheme] = useState<string>(EMPTY_THEME)
|
|
const [card, setCard] = useState<Card | null>(null)
|
|
const [revealed, setRevealed] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [drawing, setDrawing] = useState(false)
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
cardsApi
|
|
.themes()
|
|
.then((data) => {
|
|
if (!cancelled) setThemes(data)
|
|
})
|
|
.catch(() => {
|
|
/* thèmes optionnels : le jeu reste utilisable en tirage global */
|
|
})
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [])
|
|
|
|
const draw = useCallback(async () => {
|
|
setDrawing(true)
|
|
setRevealed(false)
|
|
setError(null)
|
|
try {
|
|
const picked = await cardsApi.random(theme === EMPTY_THEME ? undefined : theme)
|
|
setCard(picked)
|
|
} catch (e) {
|
|
if (e instanceof ApiClientError) {
|
|
setError(e.message)
|
|
setCard(null)
|
|
} else {
|
|
setError('Impossible de tirer une carte')
|
|
setCard(null)
|
|
}
|
|
} finally {
|
|
setDrawing(false)
|
|
}
|
|
}, [theme])
|
|
|
|
return (
|
|
<section aria-label="Jeu de révision" className="mx-auto max-w-2xl px-4 py-8">
|
|
<div className="mb-6 flex flex-wrap items-end gap-4">
|
|
<div>
|
|
<label htmlFor="play-theme" className="block text-sm font-medium text-slate-700">
|
|
Thème
|
|
</label>
|
|
<select
|
|
id="play-theme"
|
|
value={theme}
|
|
onChange={(e) => setTheme(e.target.value)}
|
|
className="mt-1 rounded-lg border border-slate-300 px-3 py-2 text-sm"
|
|
>
|
|
<option value={EMPTY_THEME}>Tous les thèmes</option>
|
|
{themes.map((t) => (
|
|
<option key={t} value={t}>
|
|
{t}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => void draw()}
|
|
disabled={drawing}
|
|
className="rounded-lg bg-slate-900 px-5 py-2 text-sm font-semibold text-white hover:bg-slate-700 disabled:opacity-50"
|
|
>
|
|
{drawing ? 'Tirage…' : 'Tirer une carte'}
|
|
</button>
|
|
</div>
|
|
|
|
{error && (
|
|
<p role="alert" className="text-red-600">
|
|
{error}
|
|
</p>
|
|
)}
|
|
|
|
{card && (
|
|
<article className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm">
|
|
<span className="rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600">
|
|
{card.theme}
|
|
</span>
|
|
<h2 className="mt-3 text-xl font-bold text-slate-900">{card.question}</h2>
|
|
{revealed ? (
|
|
<div className="mt-4 rounded-xl bg-slate-50 p-4">
|
|
<p className="text-slate-700">{card.response}</p>
|
|
<button
|
|
type="button"
|
|
onClick={() => setRevealed(false)}
|
|
className="mt-3 rounded-lg border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-100"
|
|
>
|
|
Masquer la réponse
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="mt-4">
|
|
<button
|
|
type="button"
|
|
onClick={() => setRevealed(true)}
|
|
className="rounded-lg bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-700"
|
|
>
|
|
Voir la réponse
|
|
</button>
|
|
</div>
|
|
)}
|
|
</article>
|
|
)}
|
|
|
|
{card === null && !error && (
|
|
<p className="text-slate-500">Cliquez sur "Tirer une carte" pour commencer.</p>
|
|
)}
|
|
</section>
|
|
)
|
|
}
|