feat(frontend): resume active session prompt
This commit is contained in:
@@ -1,21 +1,38 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { statsApi, type Overview } from '../api/stats.js';
|
||||
import { sessionsApi } from '../api/sessions.js';
|
||||
import type { SessionRow } from '@flashcard/shared';
|
||||
import { useLessons } from '../stores/lessonsStore.js';
|
||||
import { formatDuration, formatDate } from '../lib/format.js';
|
||||
|
||||
export function DashboardPage() {
|
||||
const { tree, refresh } = useLessons();
|
||||
const [ov, setOv] = useState<Overview | null>(null);
|
||||
const [active, setActive] = useState<SessionRow | null>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
statsApi.overview().then(setOv);
|
||||
}, [refresh]);
|
||||
|
||||
useEffect(() => {
|
||||
sessionsApi.active().then(setActive).catch(() => {});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl p-6">
|
||||
<h1 className="mb-6 text-3xl font-semibold">Dashboard</h1>
|
||||
{active && (
|
||||
<div className="mb-4 flex items-center justify-between rounded bg-amber-100 p-3 text-sm dark:bg-amber-900/30">
|
||||
<span>Je hebt een open sessie ({active.cardsShown} kaarten behandeld).</span>
|
||||
<div className="flex gap-2">
|
||||
<button className="rounded bg-amber-600 px-3 py-1 text-white" onClick={() => navigate(`/practice/${active.lessonId}`)}>Hervatten</button>
|
||||
<button className="rounded bg-slate-200 px-3 py-1 dark:bg-slate-800" onClick={async () => { await sessionsApi.abandon(active.id); setActive(null); }}>Afsluiten</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-6 grid grid-cols-3 gap-4">
|
||||
<Stat label="🔥 Streak" value={`${ov?.streakDays ?? 0} dagen`} />
|
||||
<Stat label="Sessies" value={String(ov?.totalSessions ?? 0)} />
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import type { Card } from '@flashcard/shared';
|
||||
import { cardsApi } from '../api/cards.js';
|
||||
import { sessionsApi } from '../api/sessions.js';
|
||||
import { useSession } from '../stores/sessionStore.js';
|
||||
import { Flashcard } from '../components/Flashcard.js';
|
||||
|
||||
@@ -12,7 +13,31 @@ export function PracticePage() {
|
||||
const [card, setCard] = useState<Card | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session) { navigate(`/practice/${lessonId}/setup`); return; }
|
||||
if (useSession.getState().session) return;
|
||||
(async () => {
|
||||
const active = await sessionsApi.active();
|
||||
if (!active || String(active.lessonId) !== lessonId) return;
|
||||
const nx = await sessionsApi.next(active.id);
|
||||
useSession.setState({
|
||||
session: active,
|
||||
current: nx.done ? null : nx.item,
|
||||
done: nx.done,
|
||||
showAnswer: false,
|
||||
shownAt: Date.now(),
|
||||
});
|
||||
})();
|
||||
}, [lessonId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session) return;
|
||||
const t = setTimeout(async () => {
|
||||
if (useSession.getState().session) return;
|
||||
const active = await sessionsApi.active();
|
||||
if (!active || String(active.lessonId) !== lessonId) {
|
||||
navigate(`/practice/${lessonId}/setup`);
|
||||
}
|
||||
}, 50);
|
||||
return () => clearTimeout(t);
|
||||
}, [session, lessonId, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user