88 lines
3.5 KiB
TypeScript
88 lines
3.5 KiB
TypeScript
import React from 'react';
|
|
import { Page } from './_util';
|
|
import { useParams } from 'react-router-dom';
|
|
import { usePollGalleryDelta } from '../polling/usePollGalleryDelta';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
import { Button } from '@/components/ui/button';
|
|
import FiltersBar, { type GalleryFilter } from '../components/FiltersBar';
|
|
import { Link } from 'react-router-dom';
|
|
import { Heart } from 'lucide-react';
|
|
import { likePhoto } from '../services/photosApi';
|
|
|
|
export default function GalleryPage() {
|
|
const { slug } = useParams();
|
|
const { photos, loading, newCount, acknowledgeNew } = usePollGalleryDelta(slug!);
|
|
const [filter, setFilter] = React.useState<GalleryFilter>('latest');
|
|
|
|
const myPhotoIds = React.useMemo(() => {
|
|
try {
|
|
const raw = localStorage.getItem('my-photo-ids');
|
|
return new Set<number>(raw ? JSON.parse(raw) : []);
|
|
} catch { return new Set<number>(); }
|
|
}, []);
|
|
|
|
const list = React.useMemo(() => {
|
|
let arr = photos.slice();
|
|
if (filter === 'popular') {
|
|
arr.sort((a: any, b: any) => (b.likes_count ?? 0) - (a.likes_count ?? 0));
|
|
} else if (filter === 'mine') {
|
|
arr = arr.filter((p: any) => myPhotoIds.has(p.id));
|
|
} else {
|
|
arr.sort((a: any, b: any) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime());
|
|
}
|
|
return arr;
|
|
}, [photos, filter, myPhotoIds]);
|
|
const [liked, setLiked] = React.useState<Set<number>>(new Set());
|
|
const [counts, setCounts] = React.useState<Record<number, number>>({});
|
|
|
|
async function onLike(id: number) {
|
|
if (liked.has(id)) return;
|
|
setLiked(new Set(liked).add(id));
|
|
try {
|
|
const c = await likePhoto(id);
|
|
setCounts((m) => ({ ...m, [id]: c }));
|
|
// keep a simple record of liked items
|
|
try {
|
|
const raw = localStorage.getItem('liked-photo-ids');
|
|
const arr: number[] = raw ? JSON.parse(raw) : [];
|
|
if (!arr.includes(id)) localStorage.setItem('liked-photo-ids', JSON.stringify([...arr, id]));
|
|
} catch {}
|
|
} catch {
|
|
const s = new Set(liked); s.delete(id); setLiked(s);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Page title="Galerie">
|
|
<FiltersBar value={filter} onChange={setFilter} />
|
|
{newCount > 0 && (
|
|
<Alert className="mb-3">
|
|
<AlertDescription>
|
|
{newCount} neue Fotos verfügbar.{' '}
|
|
<Button variant="link" className="px-1" onClick={acknowledgeNew}>Aktualisieren</Button>
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
{loading && <p>Lade…</p>}
|
|
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
|
{list.map((p) => (
|
|
<Card key={p.id} className="relative overflow-hidden">
|
|
<CardContent className="p-0">
|
|
<Link to={`../photo/${p.id}`} state={{ photo: p }}>
|
|
<img src={p.thumbnail_path || p.file_path} alt="Foto" className="aspect-square w-full object-cover" />
|
|
</Link>
|
|
</CardContent>
|
|
<div className="absolute bottom-1 right-1 flex items-center gap-1 rounded-full bg-black/50 px-2 py-1 text-white">
|
|
<button onClick={() => onLike(p.id)} className={`inline-flex items-center ${liked.has(p.id) ? 'text-red-400' : ''}`} aria-label="Like">
|
|
<Heart className="h-4 w-4" />
|
|
</button>
|
|
<span className="text-xs">{counts[p.id] ?? p.likes_count ?? 0}</span>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</Page>
|
|
);
|
|
}
|