feat(frontend): lesson badges + marketplace nav link

This commit is contained in:
2026-05-21 00:34:31 +02:00
parent 01f7df3735
commit 7eabd667ce
2 changed files with 55 additions and 50 deletions

View File

@@ -6,6 +6,7 @@ import { UserMenu } from './UserMenu.js';
const navItems = [ const navItems = [
{ to: '/', label: 'Dashboard', end: true }, { to: '/', label: 'Dashboard', end: true },
{ to: '/admin', label: 'Lessen' }, { to: '/admin', label: 'Lessen' },
{ to: '/marketplace', label: 'Marketplace 🛍️' },
{ to: '/stats', label: 'Stats' }, { to: '/stats', label: 'Stats' },
]; ];

View File

@@ -3,9 +3,11 @@ import { Link } from 'react-router-dom';
import type { LessonTreeNode } from '@flashcard/shared'; import type { LessonTreeNode } from '@flashcard/shared';
import { lessonsApi } from '../api/lessons.js'; import { lessonsApi } from '../api/lessons.js';
import { useLessons } from '../stores/lessonsStore.js'; import { useLessons } from '../stores/lessonsStore.js';
import { useAuth } from '../stores/authStore.js';
export function LessonTree({ nodes, depth = 0 }: { nodes: LessonTreeNode[]; depth?: number }) { export function LessonTree({ nodes, depth = 0 }: { nodes: LessonTreeNode[]; depth?: number }) {
const refresh = useLessons((s) => s.refresh); const refresh = useLessons((s) => s.refresh);
const currentUserId = useAuth((s) => s.user?.id);
const [addingTo, setAddingTo] = useState<number | null>(null); const [addingTo, setAddingTo] = useState<number | null>(null);
const [name, setName] = useState(''); const [name, setName] = useState('');
@@ -32,7 +34,13 @@ export function LessonTree({ nodes, depth = 0 }: { nodes: LessonTreeNode[]; dept
return ( return (
<ul className="space-y-1"> <ul className="space-y-1">
{nodes.map((n) => ( {nodes.map((n) => {
const isOwner = n.ownerId === currentUserId;
const visibilityBadge =
n.isCurated ? '⭐ Curated'
: n.visibility === 'shared' ? '🌍 Gedeeld'
: '🔒 Privé';
return (
<li key={n.id} style={{ paddingLeft: depth * 20 }}> <li key={n.id} style={{ paddingLeft: depth * 20 }}>
<div className="group flex items-center gap-2 rounded-2xl px-3 py-2 transition hover:bg-brand-50/70 dark:hover:bg-slate-800/60"> <div className="group flex items-center gap-2 rounded-2xl px-3 py-2 transition hover:bg-brand-50/70 dark:hover:bg-slate-800/60">
<span className={`h-2 w-2 rounded-full ${depth === 0 ? 'bg-brand-500' : 'bg-brand-300'}`} /> <span className={`h-2 w-2 rounded-full ${depth === 0 ? 'bg-brand-500' : 'bg-brand-300'}`} />
@@ -41,27 +49,22 @@ export function LessonTree({ nodes, depth = 0 }: { nodes: LessonTreeNode[]; dept
<span className="ml-2 rounded-full bg-brand-100 px-2 py-0.5 text-xs font-semibold text-brand-700 dark:bg-brand-900/30 dark:text-brand-200"> <span className="ml-2 rounded-full bg-brand-100 px-2 py-0.5 text-xs font-semibold text-brand-700 dark:bg-brand-900/30 dark:text-brand-200">
{n.cardCount} {n.cardCount}
</span> </span>
<span className="ml-2 rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold text-slate-600 dark:bg-slate-800 dark:text-slate-300">
{visibilityBadge}
</span>
{!isOwner && (
<span className="ml-1 rounded-full bg-amber-50 px-2 py-0.5 text-[10px] font-semibold text-amber-700 dark:bg-amber-900/30 dark:text-amber-200">
📥 Geabonneerd
</span>
)}
</Link> </Link>
{isOwner && (
<div className="flex items-center gap-1 opacity-0 transition group-hover:opacity-100"> <div className="flex items-center gap-1 opacity-0 transition group-hover:opacity-100">
<button <button className="rounded-lg px-2 py-1 text-xs font-medium text-brand-700 hover:bg-brand-100 dark:text-brand-200 dark:hover:bg-brand-900/40" onClick={() => setAddingTo(n.id)}>+ subles</button>
className="rounded-lg px-2 py-1 text-xs font-medium text-brand-700 hover:bg-brand-100 dark:text-brand-200 dark:hover:bg-brand-900/40" <button className="rounded-lg px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800" onClick={() => rename(n.id, n.name)}>rename</button>
onClick={() => setAddingTo(n.id)} <button className="rounded-lg px-2 py-1 text-xs font-medium text-danger-600 hover:bg-danger-50 dark:hover:bg-danger-400/10" onClick={() => remove(n.id)}>delete</button>
>
+ subles
</button>
<button
className="rounded-lg px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
onClick={() => rename(n.id, n.name)}
>
rename
</button>
<button
className="rounded-lg px-2 py-1 text-xs font-medium text-danger-600 hover:bg-danger-50 dark:hover:bg-danger-400/10"
onClick={() => remove(n.id)}
>
delete
</button>
</div> </div>
)}
</div> </div>
{addingTo === n.id && ( {addingTo === n.id && (
<div className="ml-6 mt-1 flex gap-2"> <div className="ml-6 mt-1 flex gap-2">
@@ -82,7 +85,8 @@ export function LessonTree({ nodes, depth = 0 }: { nodes: LessonTreeNode[]; dept
)} )}
{n.children.length > 0 && <LessonTree nodes={n.children} depth={depth + 1} />} {n.children.length > 0 && <LessonTree nodes={n.children} depth={depth + 1} />}
</li> </li>
))} );
})}
</ul> </ul>
); );
} }