feat: add photo folder filter to gallery and migrate to docker image registry

This commit is contained in:
Jannik committed 2026-09-21 23:22:06 +02:00
1 parent b080660188
commit 39ef0c4b19
3 files changed
+75 -7

No files matched your search

+1 -3
View File
@@ -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:
+13 -1
View File
@@ -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}
/>
</main>
);
+61 -3
View File
@@ -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<MediaItem[]>(initialItems);
const [cursor, setCursor] = useState<number | null>(initialCursor);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(!!initialCursor);
const [votingOpen, setVotingOpen] = useState(initialVotingOpen);
const [selectedDir, setSelectedDir] = useState<string>('');
const handleDirChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
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 (
<>
<div className="media-wall" ref={wallRef}>
{directories.length > 0 && (
<div style={{ padding: '80px 20px 0', maxWidth: '1920px', margin: '0 auto', display: 'flex', justifyContent: 'center' }}>
<select
value={selectedDir}
onChange={handleDirChange}
style={{
padding: '10px 20px',
borderRadius: '20px',
border: '1px solid var(--color-border)',
background: 'var(--color-surface)',
fontFamily: 'var(--font-body)',
fontSize: '1rem',
outline: 'none',
cursor: 'pointer',
boxShadow: 'var(--shadow)',
color: 'var(--color-text)',
minWidth: '200px'
}}
>
<option value="">Alle Fotos</option>
{directories.map(dir => (
<option key={dir} value={dir}>{dir}</option>
))}
</select>
</div>
)}
<div className="media-wall" ref={wallRef} style={directories.length > 0 ? { paddingTop: '20px' } : {}}>
{items.map((item, index) => (
<MediaTile
key={item.id}