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:
@@ -7,6 +7,15 @@ export interface CMSPage {
|
||||
title_de: string;
|
||||
content_en: string;
|
||||
content_de: string;
|
||||
logo_url: string | null;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface PublicCMSPage {
|
||||
title: string;
|
||||
content: string;
|
||||
slug: string;
|
||||
logo_url: string | null;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
@@ -30,10 +39,27 @@ export const cmsService = {
|
||||
},
|
||||
|
||||
// Get public CMS page (no auth required)
|
||||
async getPublicPage(slug: string, lang: string = 'en'): Promise<{ title: string; content: string }> {
|
||||
const response = await api.get<{ title: string; content: string }>(`/public/pages/${slug}`, {
|
||||
async getPublicPage(slug: string, lang: string = 'en'): Promise<PublicCMSPage> {
|
||||
const response = await api.get<PublicCMSPage>(`/public/pages/${slug}`, {
|
||||
params: { lang }
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Upload a per-page logo (#324)
|
||||
async uploadPageLogo(slug: string, file: File): Promise<{ logo_url: string }> {
|
||||
const formData = new FormData();
|
||||
formData.append('logo', file);
|
||||
const response = await api.post<{ logo_url: string }>(
|
||||
`/admin/cms/pages/${slug}/logo`,
|
||||
formData,
|
||||
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Clear a per-page logo override (revert to global branding logo).
|
||||
async clearPageLogo(slug: string): Promise<void> {
|
||||
await api.delete(`/admin/cms/pages/${slug}/logo`);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user