feat: add photo folder filter to gallery and migrate to docker image registry
This commit is contained in:
1 parent
b080660188
commit
39ef0c4b19
3 files changed
+75
-7
No files matched your search
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
image: git.orfel.de/jannik/nele-till-hochzeit:latest
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: wedding-app
|
container_name: wedding-app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
+13
-1
@@ -22,8 +22,18 @@ async function getInitialMedia() {
|
|||||||
nextCursor = extra!.id;
|
nextCursor = extra!.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch voting status for the client
|
||||||
const settings = await prisma.appSettings.findUnique({ where: { id: 1 } });
|
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
|
// Serialize BigInt values for client
|
||||||
return {
|
return {
|
||||||
items: JSON.parse(JSON.stringify(items, (_key, value) =>
|
items: JSON.parse(JSON.stringify(items, (_key, value) =>
|
||||||
@@ -31,10 +41,11 @@ async function getInitialMedia() {
|
|||||||
)),
|
)),
|
||||||
nextCursor,
|
nextCursor,
|
||||||
votingOpen: settings?.votingOpen ?? false,
|
votingOpen: settings?.votingOpen ?? false,
|
||||||
|
directories,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Page] Error fetching initial media:', 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}
|
initialItems={data.items}
|
||||||
initialCursor={data.nextCursor}
|
initialCursor={data.nextCursor}
|
||||||
initialVotingOpen={data.votingOpen}
|
initialVotingOpen={data.votingOpen}
|
||||||
|
directories={data.directories}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type MediaWallProps = {
|
|||||||
initialCursor: number | null;
|
initialCursor: number | null;
|
||||||
initialViewerIndex?: number;
|
initialViewerIndex?: number;
|
||||||
initialVotingOpen?: boolean;
|
initialVotingOpen?: boolean;
|
||||||
|
directories?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MediaWall({
|
export default function MediaWall({
|
||||||
@@ -33,12 +34,42 @@ export default function MediaWall({
|
|||||||
initialCursor,
|
initialCursor,
|
||||||
initialViewerIndex = -1,
|
initialViewerIndex = -1,
|
||||||
initialVotingOpen = false,
|
initialVotingOpen = false,
|
||||||
|
directories = [],
|
||||||
}: MediaWallProps) {
|
}: MediaWallProps) {
|
||||||
const [items, setItems] = useState<MediaItem[]>(initialItems);
|
const [items, setItems] = useState<MediaItem[]>(initialItems);
|
||||||
const [cursor, setCursor] = useState<number | null>(initialCursor);
|
const [cursor, setCursor] = useState<number | null>(initialCursor);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [hasMore, setHasMore] = useState(!!initialCursor);
|
const [hasMore, setHasMore] = useState(!!initialCursor);
|
||||||
const [votingOpen, setVotingOpen] = useState(initialVotingOpen);
|
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 } =
|
const { isOpen, currentItem, currentIndex, open, close, next, prev } =
|
||||||
useViewer(items, initialViewerIndex);
|
useViewer(items, initialViewerIndex);
|
||||||
@@ -51,7 +82,8 @@ export default function MediaWall({
|
|||||||
if (loading || !hasMore || !cursor) return;
|
if (loading || !hasMore || !cursor) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
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) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setItems((prev) => {
|
setItems((prev) => {
|
||||||
@@ -72,7 +104,7 @@ export default function MediaWall({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [loading, hasMore, cursor]);
|
}, [loading, hasMore, cursor, selectedDir]);
|
||||||
|
|
||||||
// Infinite scroll observer
|
// Infinite scroll observer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -109,7 +141,33 @@ export default function MediaWall({
|
|||||||
|
|
||||||
return (
|
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) => (
|
{items.map((item, index) => (
|
||||||
<MediaTile
|
<MediaTile
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
|||||||
Reference in new issue
Block a user