Flatten authenticated sidebar IA, move admin to the profile menu, right-align TopSearchBar actions, and replace placeholder dashboard panels with data from /api/user/stats and /api/user-cards. Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
4.2 KiB
JavaScript
119 lines
4.2 KiB
JavaScript
import Link from 'next/link';
|
|
import { VOCAB } from '../lib/collection-vocabulary.js';
|
|
|
|
/**
|
|
* Recently added cards grid on /dashboard (formerly Featured Collection).
|
|
*/
|
|
export default function DashboardFeaturedCollection({ cards = [], loading = false }) {
|
|
const slots = Array.from({ length: 8 }, (_, idx) => cards[idx] || null);
|
|
|
|
return (
|
|
<section className="glass-panel rounded-2xl p-5 sm:p-6">
|
|
<header className="flex items-center justify-between mb-4">
|
|
<h2
|
|
className="text-lg font-bold flex items-center gap-2"
|
|
style={{ color: 'var(--text-primary)' }}
|
|
>
|
|
<span
|
|
className="inline-flex h-5 w-5 items-center justify-center rounded-md"
|
|
style={{
|
|
background:
|
|
'linear-gradient(135deg, rgb(255, 140, 30) 0%, rgb(216, 67, 21) 100%)',
|
|
}}
|
|
aria-hidden="true"
|
|
>
|
|
<svg className="h-3.5 w-3.5" viewBox="0 0 24 24" fill="rgb(255, 255, 255)">
|
|
<path d="M12 2c-.5 3.5-3.5 5.5-3.5 9 0 2.5 1.5 4 3.5 4s3.5-1.5 3.5-4c0-1.5-1-3-2-4 1 2 .5 4-.5 5-1 1-2-1-1-3 .5-1 1.5-3 0-7z" />
|
|
</svg>
|
|
</span>
|
|
Recently added
|
|
</h2>
|
|
<Link
|
|
href="/my-cards"
|
|
className="text-sm font-medium hover:underline focus:outline-none focus:ring-2 rounded-md px-1"
|
|
style={{
|
|
color: 'var(--accent-ember)',
|
|
'--tw-ring-color': 'var(--accent-ember)',
|
|
}}
|
|
>
|
|
View all in {VOCAB.MY_COLLECTION}
|
|
</Link>
|
|
</header>
|
|
|
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
{slots.map((card, idx) => {
|
|
if (loading) {
|
|
return (
|
|
<div
|
|
key={`loading-${idx}`}
|
|
className="aspect-[2.5/3.5] rounded-lg motion-essential animate-pulse"
|
|
style={{ backgroundColor: 'var(--bg-tertiary)' }}
|
|
aria-hidden="true"
|
|
/>
|
|
);
|
|
}
|
|
if (!card) {
|
|
return (
|
|
<Link
|
|
key={`empty-${idx}`}
|
|
href="/cards"
|
|
className="aspect-[2.5/3.5] rounded-lg flex flex-col items-center justify-center text-center p-2 transition-colors focus:outline-none focus:ring-2"
|
|
style={{
|
|
border: '2px dashed var(--border)',
|
|
color: 'var(--text-secondary)',
|
|
'--tw-ring-color': 'var(--accent-ember)',
|
|
}}
|
|
>
|
|
<svg
|
|
className="h-6 w-6 mb-1"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
|
/>
|
|
</svg>
|
|
<span className="text-xs font-medium">Add card</span>
|
|
</Link>
|
|
);
|
|
}
|
|
const cardId = card.card_id ?? card.id;
|
|
return (
|
|
<Link
|
|
key={cardId ?? idx}
|
|
href={`/card/${cardId}`}
|
|
className="card-grid-outer-glow aspect-[2.5/3.5] rounded-lg overflow-hidden block transition-transform hover:scale-105 focus:outline-none focus:ring-2"
|
|
style={{
|
|
backgroundColor: 'var(--bg-tertiary)',
|
|
'--tw-ring-color': 'var(--accent-ember)',
|
|
}}
|
|
aria-label={card.name ?? 'Card'}
|
|
>
|
|
{card.image_url ? (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img
|
|
src={card.image_url}
|
|
alt={card.name ?? ''}
|
|
className="w-full h-full object-cover"
|
|
loading="lazy"
|
|
/>
|
|
) : (
|
|
<div
|
|
className="w-full h-full flex items-center justify-center text-center p-2 text-xs"
|
|
style={{ color: 'var(--text-secondary)' }}
|
|
>
|
|
{card.name ?? 'Card'}
|
|
</div>
|
|
)}
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|