Files
tower/apps/web/app/my/memories/page.tsx
T
maaz519 3e86e0ffc0 feat: member portal Sprint 11 — Memories / Gallery
- GalleryAlbum + GalleryItem models + migration (publish gate, cover image, ordered items)
- GalleryModule: admin CRUD for albums + items
- Member endpoints: GET /my/memories (published albums w/ cover + count), GET /my/memories/:id (album + photos)
- /my/memories page: album grid with cover thumbnails
- /my/memories/[id] page: photo grid with captions
- Memories nav item; register GalleryModule

Completes Phase 2 (Intelligence) of the member portal — Sprints 9-11.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 17:10:27 +05:30

71 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { getMemberToken, getApiBaseUrl } from '../../_lib/api';
import Link from 'next/link';
interface Album {
id: string;
title: string;
description: string | null;
coverUrl: string | null;
itemCount: number;
createdAt: string;
}
async function fetchAlbums(token: string): Promise<Album[]> {
const res = await fetch(`${getApiBaseUrl()}/my/memories`, {
headers: { Authorization: `Bearer ${token}` },
cache: 'no-store',
}).catch(() => null);
if (!res || !res.ok) return [];
return (await res.json()) as Album[];
}
export default async function MemoriesPage() {
const token = await getMemberToken();
if (!token) return null;
const albums = await fetchAlbums(token);
return (
<div className="max-w-2xl mx-auto space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-xl font-semibold text-gray-900">Memories</h1>
<Link href="/my" className="text-sm text-indigo-600 hover:underline"> Dashboard</Link>
</div>
{albums.length === 0 ? (
<div className="text-center py-16 text-gray-400">
<p className="text-sm">No photo albums yet.</p>
<p className="text-xs mt-1">Your chapter admin will share event photos and memories here.</p>
</div>
) : (
<div className="grid grid-cols-2 gap-4">
{albums.map((a) => (
<Link
key={a.id}
href={`/my/memories/${a.id}`}
className="group bg-white rounded-xl border border-gray-200 overflow-hidden hover:shadow-md transition-shadow"
>
<div className="aspect-[4/3] bg-gray-100 overflow-hidden">
{a.coverUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={a.coverUrl}
alt={a.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-3xl text-gray-300">🖼</div>
)}
</div>
<div className="p-3">
<p className="text-sm font-semibold text-gray-900 truncate">{a.title}</p>
<p className="text-xs text-gray-400 mt-0.5">{a.itemCount} photo{a.itemCount !== 1 ? 's' : ''}</p>
</div>
</Link>
))}
</div>
)}
</div>
);
}