feat: customisable 404 + gallery-not-found pages via CMS (#324)
The 404 catch-all and the "gallery not found" branches in GalleryPage
were hard-coded English strings on a default-themed background — the
one place where a white-labelled deployment leaked the PicPeak default
look. Pluggable now via the existing CMS Pages mechanism.
Backend:
- Seed two new default CMS pages: `not-found` and `gallery-not-found`,
with sensible English/German copy admins can edit in /admin/cms.
- Add `cms_pages.logo_url` (nullable) for per-page logo override; online
migration on existing deployments. Null falls back to the global
branding logo.
- New per-page logo upload (POST /api/admin/cms/pages/:slug/logo) +
clear endpoint (DELETE …/logo). Reuses the existing /uploads/logos
storage location with a `cms-<slug>-` filename prefix.
- adminCMS PUT now accepts logo_url; publicCMS GET returns it.
Frontend:
- New <CMSContentBlock slug fallback> component renders the CMS page in
the standard branded shell (logo precedence: page → branding → bundled
default), with DOMPurified content and footer/legal links.
- App.tsx: `path="*"` catch-all routes through CMSContentBlock("not-found").
- GalleryPage: collapses the two "gallery not found" branches (invalid
identifier + infoError archived/missing) into a single
CMSContentBlock("gallery-not-found"), so admins can edit one source
of truth.
- Admin CMS Page editor gains an "Upload Logo / Use site default"
control per page; falls back to the page's own English title in the
page list when no `legal.<slug>` translation is registered.
This commit is contained in:
@@ -463,8 +463,15 @@ async function ensureGlobalCategories() {
|
||||
table.text('title_de');
|
||||
table.text('content_en');
|
||||
table.text('content_de');
|
||||
table.string('logo_url').nullable();
|
||||
table.timestamp('updated_at').defaultTo(db.fn.now());
|
||||
});
|
||||
} else if (!(await db.schema.hasColumn('cms_pages', 'logo_url'))) {
|
||||
// Online migration for existing deployments — see issue #324, per-page
|
||||
// logo override for admin-customisable error pages.
|
||||
await db.schema.alterTable('cms_pages', (table) => {
|
||||
table.string('logo_url').nullable();
|
||||
});
|
||||
}
|
||||
|
||||
const categoryCountRow = await db('photo_categories').count({ count: 'id' }).first();
|
||||
@@ -501,6 +508,24 @@ async function ensureGlobalCategories() {
|
||||
content_de: '<h2>Datenschutzerklärung</h2><p>Bitte bearbeiten Sie diesen Inhalt im Admin-Panel.</p>',
|
||||
updated_at: new Date(),
|
||||
},
|
||||
// Customisable error pages — issue #324. Generic copy by default;
|
||||
// admins can edit text + logo per page in the CMS Pages tab.
|
||||
{
|
||||
slug: 'not-found',
|
||||
title_en: 'Page Not Found',
|
||||
title_de: 'Seite nicht gefunden',
|
||||
content_en: '<h2>Page Not Found</h2><p>The page you are looking for does not exist or has been moved.</p>',
|
||||
content_de: '<h2>Seite nicht gefunden</h2><p>Die gesuchte Seite existiert nicht oder wurde verschoben.</p>',
|
||||
updated_at: new Date(),
|
||||
},
|
||||
{
|
||||
slug: 'gallery-not-found',
|
||||
title_en: 'Gallery Not Found',
|
||||
title_de: 'Galerie nicht gefunden',
|
||||
content_en: '<h2>Gallery Not Found</h2><p>This gallery could not be found. The link may be incorrect, or the gallery may have expired or been archived. Please contact the organiser if you believe this is a mistake.</p>',
|
||||
content_de: '<h2>Galerie nicht gefunden</h2><p>Diese Galerie konnte nicht gefunden werden. Der Link ist möglicherweise nicht korrekt, oder die Galerie ist abgelaufen oder wurde archiviert. Bitte kontaktieren Sie den Veranstalter, falls Sie glauben, dass dies ein Fehler ist.</p>',
|
||||
updated_at: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
for (const page of defaultPages) {
|
||||
|
||||
Reference in New Issue
Block a user