From 39ef0c4b191751c7254968217f61b850d8914507 Mon Sep 17 00:00:00 2001 From: orfelorfel23 Date: Mon, 21 Sep 2026 23:22:06 +0200 Subject: [PATCH] feat: add photo folder filter to gallery and migrate to docker image registry --- docker-compose.yml | 4 +-- src/app/page.tsx | 14 +++++++- src/components/MediaWall.tsx | 64 ++++++++++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c771059..bde9080 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,6 @@ services: app: - build: - context: . - dockerfile: Dockerfile + image: git.orfel.de/jannik/nele-till-hochzeit:latest container_name: wedding-app restart: unless-stopped ports: diff --git a/src/app/page.tsx b/src/app/page.tsx index b88fbf6..2cf19cd 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -22,8 +22,18 @@ async function getInitialMedia() { nextCursor = extra!.id; } + // Fetch voting status for the client const settings = await prisma.appSettings.findUnique({ where: { id: 1 } }); + // Fetch available directories for filtering + const dirs = await prisma.mediaItem.findMany({ + where: { published: true }, + select: { directory: true }, + distinct: ['directory'], + orderBy: { directory: 'asc' } + }); + const directories = dirs.map(d => d.directory).filter(Boolean); + // Serialize BigInt values for client return { items: JSON.parse(JSON.stringify(items, (_key, value) => @@ -31,10 +41,11 @@ async function getInitialMedia() { )), nextCursor, votingOpen: settings?.votingOpen ?? false, + directories, }; } catch (error) { console.error('[Page] Error fetching initial media:', error); - return { items: [], nextCursor: null, votingOpen: false }; + return { items: [], nextCursor: null, votingOpen: false, directories: [] }; } } @@ -48,6 +59,7 @@ export default async function HomePage() { initialItems={data.items} initialCursor={data.nextCursor} initialVotingOpen={data.votingOpen} + directories={data.directories} /> ); diff --git a/src/components/MediaWall.tsx b/src/components/MediaWall.tsx index 1dff738..9eaedcd 100644 --- a/src/components/MediaWall.tsx +++ b/src/components/MediaWall.tsx @@ -26,6 +26,7 @@ type MediaWallProps = { initialCursor: number | null; initialViewerIndex?: number; initialVotingOpen?: boolean; + directories?: string[]; }; export default function MediaWall({ @@ -33,12 +34,42 @@ export default function MediaWall({ initialCursor, initialViewerIndex = -1, initialVotingOpen = false, + directories = [], }: MediaWallProps) { const [items, setItems] = useState(initialItems); const [cursor, setCursor] = useState(initialCursor); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(!!initialCursor); const [votingOpen, setVotingOpen] = useState(initialVotingOpen); + const [selectedDir, setSelectedDir] = useState(''); + + const handleDirChange = async (e: React.ChangeEvent) => { + const dir = e.target.value; + setSelectedDir(dir); + + if (dir === '') { + setItems(initialItems); + setCursor(initialCursor); + setHasMore(!!initialCursor); + return; + } + + setLoading(true); + setItems([]); + try { + const res = await fetch(`/api/media?limit=50&directory=${encodeURIComponent(dir)}`); + if (res.ok) { + const data = await res.json(); + setItems(data.items); + setCursor(data.nextCursor); + setHasMore(!!data.nextCursor); + } + } catch (err) { + console.error('Error fetching filtered media:', err); + } finally { + setLoading(false); + } + }; const { isOpen, currentItem, currentIndex, open, close, next, prev } = useViewer(items, initialViewerIndex); @@ -51,7 +82,8 @@ export default function MediaWall({ if (loading || !hasMore || !cursor) return; setLoading(true); try { - const res = await fetch(`/api/media?limit=50&cursor=${cursor}`); + const dirParam = selectedDir ? `&directory=${encodeURIComponent(selectedDir)}` : ''; + const res = await fetch(`/api/media?limit=50&cursor=${cursor}${dirParam}`); if (res.ok) { const data = await res.json(); setItems((prev) => { @@ -72,7 +104,7 @@ export default function MediaWall({ } finally { setLoading(false); } - }, [loading, hasMore, cursor]); + }, [loading, hasMore, cursor, selectedDir]); // Infinite scroll observer useEffect(() => { @@ -109,7 +141,33 @@ export default function MediaWall({ return ( <> -
+ {directories.length > 0 && ( +
+ +
+ )} +
0 ? { paddingTop: '20px' } : {}}> {items.map((item, index) => (