feat(frontend): apply UI/UX design system - purple/green palette, gradient buttons, 3D flip, polished pages
Applied ui-ux-pro-max design system recommendations: - Tailwind theme: study purple primary + correct green accent - Inter + Plus Jakarta Sans typography - Glassmorphic surfaces with soft shadows and mesh background - Real 3D card flip with spring physics + answer feedback flash - Gradient stat cards, progress bar, animated done screen with score ring - Polished Layout, Dashboard, Admin, AdminLesson, CardTable, ImportDialog, PracticeSetup, Practice, PracticeDone - E2E smoke updated for new accessible names
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useSettings } from '../stores/settingsStore.js';
|
||||
import { useSession } from '../stores/sessionStore.js';
|
||||
|
||||
const sizeOptions: (number | 'all')[] = [10, 20, 30, 50, 'all'];
|
||||
const directions: { value: 'forward' | 'backward' | 'both'; label: string; hint: string }[] = [
|
||||
{ value: 'forward', label: 'Vraag → antwoord', hint: 'Standaard' },
|
||||
{ value: 'backward', label: 'Antwoord → vraag', hint: 'Voor bidirectionele lessen' },
|
||||
{ value: 'both', label: 'Beide richtingen', hint: 'Dubbele oefening' },
|
||||
];
|
||||
|
||||
export function PracticeSetupPage() {
|
||||
const { lessonId } = useParams();
|
||||
const id = Number(lessonId);
|
||||
@@ -22,25 +30,110 @@ export function PracticeSetupPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md p-6">
|
||||
<h1 className="mb-4 text-2xl font-semibold">Sessie starten</h1>
|
||||
<label className="mb-2 block text-sm">Max. aantal kaarten
|
||||
<select className="ml-2 rounded border px-2 py-1 dark:bg-slate-900" value={String(maxCards)} onChange={(e) => setMaxCards(e.target.value === 'all' ? 'all' : Number(e.target.value))}>
|
||||
{[10, 20, 30, 50].map((n) => <option key={n} value={n}>{n}</option>)}
|
||||
<option value="all">Alle</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="mb-2 flex items-center gap-2 text-sm"><input type="checkbox" checked={shuffle} onChange={(e) => setShuffle(e.target.checked)} /> Shuffle</label>
|
||||
<label className="mb-4 block text-sm">Richting
|
||||
<select className="ml-2 rounded border px-2 py-1 dark:bg-slate-900" value={direction} onChange={(e) => setDirection(e.target.value as typeof direction)}>
|
||||
<option value="forward">Vraag → antwoord</option>
|
||||
<option value="backward">Antwoord → vraag</option>
|
||||
<option value="both">Beide</option>
|
||||
</select>
|
||||
</label>
|
||||
<button className="w-full rounded bg-green-600 py-3 font-medium text-white" onClick={begin} disabled={busy}>
|
||||
Start
|
||||
</button>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="mx-auto max-w-lg"
|
||||
>
|
||||
<div className="surface space-y-6 p-8">
|
||||
<header>
|
||||
<h1 className="font-display text-3xl font-bold">Klaar om te oefenen?</h1>
|
||||
<p className="mt-1 text-sm text-slate-600 dark:text-slate-400">
|
||||
Configureer je sessie en begin.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<Section title="Aantal kaarten">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{sizeOptions.map((n) => (
|
||||
<Pill
|
||||
key={String(n)}
|
||||
active={maxCards === n}
|
||||
onClick={() => setMaxCards(n)}
|
||||
>
|
||||
{n === 'all' ? 'Alle' : n}
|
||||
</Pill>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Richting">
|
||||
<div className="space-y-2">
|
||||
{directions.map((d) => (
|
||||
<button
|
||||
key={d.value}
|
||||
onClick={() => setDirection(d.value)}
|
||||
className={`flex w-full items-center justify-between rounded-2xl border px-4 py-3 text-left transition ${
|
||||
direction === d.value
|
||||
? 'border-brand-400 bg-brand-50 ring-2 ring-brand-200 dark:bg-brand-900/30 dark:ring-brand-700/40'
|
||||
: 'border-brand-100 bg-white/60 hover:border-brand-300 dark:border-slate-800 dark:bg-slate-900/40'
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<div className="text-sm font-semibold">{d.label}</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400">{d.hint}</div>
|
||||
</div>
|
||||
<span
|
||||
className={`h-4 w-4 rounded-full border-2 transition ${
|
||||
direction === d.value
|
||||
? 'border-brand-600 bg-brand-600'
|
||||
: 'border-slate-300 dark:border-slate-600'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<label className="flex cursor-pointer items-center justify-between rounded-2xl border border-brand-100 bg-white/60 px-4 py-3 dark:border-slate-800 dark:bg-slate-900/40">
|
||||
<span className="text-sm">
|
||||
<span className="font-semibold">Shuffle</span>
|
||||
<span className="ml-2 text-xs text-slate-500">
|
||||
Willekeurige volgorde binnen Leitner-dozen
|
||||
</span>
|
||||
</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-5 w-5 rounded accent-brand-600"
|
||||
checked={shuffle}
|
||||
onChange={(e) => setShuffle(e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
className="btn-primary w-full py-4 text-base"
|
||||
onClick={begin}
|
||||
disabled={busy}
|
||||
>
|
||||
{busy ? 'Bezig…' : 'Start sessie →'}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-2 text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||
{title}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Pill({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`rounded-full px-4 py-1.5 text-sm font-semibold transition ${
|
||||
active
|
||||
? 'bg-brand-gradient text-white shadow-glow'
|
||||
: 'bg-white/70 text-slate-700 ring-1 ring-brand-100 hover:bg-white dark:bg-slate-900/40 dark:text-slate-200 dark:ring-slate-800'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user