feat(frontend): lesson badges + marketplace nav link
This commit is contained in:
@@ -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' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user