fix: resolve hydration mismatch (error #418), CSP map blockages, and lightbox visibility
This commit is contained in:
@@ -31,7 +31,6 @@ export interface PhotoItem {
|
|||||||
|
|
||||||
interface PhotoGridProps {
|
interface PhotoGridProps {
|
||||||
assets: PhotoItem[];
|
assets: PhotoItem[];
|
||||||
albumId: string;
|
|
||||||
layout?: 'masonry' | 'uniform' | 'showcase' | 'filmstrip' | 'editorial-flow';
|
layout?: 'masonry' | 'uniform' | 'showcase' | 'filmstrip' | 'editorial-flow';
|
||||||
gridStyle?: React.CSSProperties;
|
gridStyle?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
@@ -50,11 +49,15 @@ function buildPhotoHash(index: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PhotoGrid({ assets, layout = 'masonry', gridStyle }: PhotoGridProps) {
|
export function PhotoGrid({ assets, layout = 'masonry', gridStyle }: PhotoGridProps) {
|
||||||
const [lightboxIndex, setLightboxIndex] = useState<number | null>(() => {
|
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
|
||||||
if (typeof window === 'undefined') return null;
|
|
||||||
|
// ── Initial hash check (Client-only to avoid hydration mismatch) ──
|
||||||
|
useEffect(() => {
|
||||||
const idx = parsePhotoHash(window.location.hash);
|
const idx = parsePhotoHash(window.location.hash);
|
||||||
return idx !== null && idx < assets.length ? idx : null;
|
if (idx !== null && idx < assets.length) {
|
||||||
});
|
setLightboxIndex(idx);
|
||||||
|
}
|
||||||
|
}, [assets.length]);
|
||||||
|
|
||||||
// ── Sync URL hash when lightbox state changes ─────────────────
|
// ── Sync URL hash when lightbox state changes ─────────────────
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -136,9 +139,8 @@ export function PhotoGrid({ assets, layout = 'masonry', gridStyle }: PhotoGridPr
|
|||||||
{assets.map((asset, index) => (
|
{assets.map((asset, index) => (
|
||||||
<FadeIn key={asset.id} delay={index < 12 ? index * 50 : 0}>
|
<FadeIn key={asset.id} delay={index < 12 ? index * 50 : 0}>
|
||||||
<div
|
<div
|
||||||
className={`photo-grid__item${
|
className={`photo-grid__item${layout === 'showcase' && index === 0 ? ' photo-grid__featured' : ''
|
||||||
layout === 'showcase' && index === 0 ? ' photo-grid__featured' : ''
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => openLightbox(index)}
|
onClick={() => openLightbox(index)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ export default async function PathPage({ params }: PathPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
<PhotoGrid
|
<PhotoGrid
|
||||||
assets={images}
|
assets={images}
|
||||||
albumId={album.id}
|
|
||||||
layout={resolveLayout(spGrid)}
|
layout={resolveLayout(spGrid)}
|
||||||
gridStyle={buildGridStyle(spGrid)}
|
gridStyle={buildGridStyle(spGrid)}
|
||||||
/>
|
/>
|
||||||
@@ -214,7 +213,6 @@ export default async function PathPage({ params }: PathPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
<PhotoGrid
|
<PhotoGrid
|
||||||
assets={images}
|
assets={images}
|
||||||
albumId={album.id}
|
|
||||||
layout={resolveLayout(result.subpage.grid)}
|
layout={resolveLayout(result.subpage.grid)}
|
||||||
gridStyle={buildGridStyle(result.subpage.grid)}
|
gridStyle={buildGridStyle(result.subpage.grid)}
|
||||||
/>
|
/>
|
||||||
@@ -292,7 +290,6 @@ export default async function PathPage({ params }: PathPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
<PhotoGrid
|
<PhotoGrid
|
||||||
assets={images}
|
assets={images}
|
||||||
albumId={album.id}
|
|
||||||
layout={resolveLayout()}
|
layout={resolveLayout()}
|
||||||
gridStyle={buildGridStyle()}
|
gridStyle={buildGridStyle()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,35 +1,30 @@
|
|||||||
# ── Gallery Configuration ──────────────────────────────────────
|
# ── Gallery Structure ──────────────────────────────────────────────
|
||||||
# Album and subpage definitions for your public gallery.
|
# This file defines WHICH albums and subpages are shown.
|
||||||
# Secrets (API key, server URL) stay in .env.local.
|
# Global settings (theme, map, legal, title) now live in content/settings.yaml.
|
||||||
|
|
||||||
|
# Hero images for the homepage carousel (Immich asset UUIDs)
|
||||||
hero:
|
hero:
|
||||||
- 03c0df0e-01d7-4cc7-8352-cf30d2289ad6
|
- 03c0df0e-01d7-4cc7-8352-cf30d2289ad6
|
||||||
- a492300b-14ca-4167-a2c9-815c820a8802
|
- a492300b-14ca-4167-a2c9-815c820a8802
|
||||||
- 7fdc0891-8e9d-48f1-9f1b-a8697c6dbea1
|
- 7fdc0891-8e9d-48f1-9f1b-a8697c6dbea1
|
||||||
|
- c023e78a-1718-4c1a-aaa1-c22368d0be6c
|
||||||
|
- 1d518119-d333-40db-a81f-e7ca80c1a66e
|
||||||
|
|
||||||
# Standalone albums shown directly on the homepage
|
# Standalone albums (shown on the home page)
|
||||||
albums:
|
#albums:
|
||||||
- a89c6085-a105-46c2-baa4-338bc69cab93: "Wüstenzauber"
|
# - "your-album-uuid-1"
|
||||||
- 9aee9367-a239-4651-980d-3368bc70b7b6: "Heimatliebe"
|
# - "your-album-uuid-2"
|
||||||
|
|
||||||
# Subpages group multiple albums under one URL path
|
# Subpages: group albums under custom URL paths
|
||||||
subpages:
|
subpages:
|
||||||
Deutschland:
|
Deutschland:
|
||||||
- 9aee9367-a239-4651-980d-3368bc70b7b6
|
- 9aee9367-a239-4651-980d-3368bc70b7b6: "Kloster Chorin"
|
||||||
|
- 371336b4-eb59-409c-b24e-a613f81ede5a: "2025 Oktober - Sanssouci im Herbst"
|
||||||
|
- b55b4a94-b1b6-4eb8-8ce0-32238a78150f: "An der Ostsee"
|
||||||
Dubai:
|
Dubai:
|
||||||
- a89c6085-a105-46c2-baa4-338bc69cab93: "Burj Khalifa & Co"
|
- a89c6085-a105-46c2-baa4-338bc69cab93: "Dubai 2024"
|
||||||
# Support for subpages with passwords or custom grids in the map format
|
# Support for subpages with passwords or custom grids in the map format
|
||||||
Polen:
|
Polen:
|
||||||
password: clientpass123
|
password: clientpass123
|
||||||
albums:
|
albums:
|
||||||
- 4ad0ed89-55de-4e69-937f-2d9988f63ad1: "Krakau & mehr"
|
- 4ad0ed89-55de-4e69-937f-2d9988f63ad1: "Krakau & mehr"
|
||||||
|
|
||||||
# Footer — optional minimal footer with social links
|
|
||||||
footer:
|
|
||||||
name: Ralf Ohlenbostel
|
|
||||||
instagram: quasiralf
|
|
||||||
# email: hello@example.com
|
|
||||||
# website: https://example.com
|
|
||||||
|
|
||||||
# Enable the /map page
|
|
||||||
map: true
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import type { NextConfig } from 'next';
|
|||||||
// Locks down the public-facing site to only trusted sources.
|
// Locks down the public-facing site to only trusted sources.
|
||||||
const cspDirectives = [
|
const cspDirectives = [
|
||||||
"default-src 'self'",
|
"default-src 'self'",
|
||||||
"script-src 'self' 'unsafe-inline'",
|
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Added unsafe-eval for some client-side libs if needed
|
||||||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://unpkg.com",
|
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://unpkg.com",
|
||||||
"font-src 'self' https://fonts.gstatic.com",
|
"font-src 'self' https://fonts.gstatic.com",
|
||||||
"img-src 'self' data: https://*.basemaps.cartocdn.com https://a.basemaps.cartocdn.com https://b.basemaps.cartocdn.com https://c.basemaps.cartocdn.com https://d.basemaps.cartocdn.com https://unpkg.com",
|
"img-src 'self' data: blob: https://*.basemaps.cartocdn.com https://*.tile.openstreetmap.org https://unpkg.com", // Added blob: and osm
|
||||||
"connect-src 'self'",
|
"connect-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com", // Added fonts for connectivity checks
|
||||||
"frame-ancestors 'none'",
|
"frame-ancestors 'none'",
|
||||||
"base-uri 'self'",
|
"base-uri 'self'",
|
||||||
"form-action 'self'",
|
"form-action 'self'",
|
||||||
|
|||||||
Reference in New Issue
Block a user