Neuer Bereich /webseite im Manager (Nav: "Mehr"-Blatt) zur Pflege der 6
Webseiten-Seiten gegen Pams WEB-0-Backend (LAN-only, keine Auth):
- Übersicht: alle Seiten mit Status (Entwurf/Veröffentlicht).
- Seiten-Editor: Titel + SEO-Beschreibung + Status speichern.
- Geordneter Block-Editor: Blöcke hinzufügen (Überschrift, Textabschnitt/
Markdown, Bild, Galerie, Kontaktangaben), bearbeiten, nach oben/unten
verschieben (PUT .../blocks/order), löschen. Die dynamische AbgabetiereList
ist nicht manuell anlegbar; nur ihr Einleitungstext ist editierbar.
Vertraege: GET /pages, GET /pages/{slug}, PUT /pages/{id},
POST /pages/{pageId}/blocks, PUT /blocks/{id}, DELETE /blocks/{id},
PUT /pages/{pageId}/blocks/order. Enums als Strings (Draft/Published, Heading...).
Deutsch via de.ts (LF beibehalten), mobile-first. e2e-Mock um die CMS-Routen
erweitert (zustandsbehaftet) + Smoke (Block anlegen/bearbeiten/verschieben,
bleibt erhalten). Gate gruen: build, eslint, vitest 69, e2e 68.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
150 lines
4.0 KiB
TypeScript
150 lines
4.0 KiB
TypeScript
/**
|
|
* WEB-0b: CMS-Inhalte der öffentlichen Webseite (Seiten + Blöcke).
|
|
*
|
|
* Backend-Vertrag (GerbilManagerWebAPI/Endpoints/CmsEndpoints.cs):
|
|
* - GET /pages -> PageSummary[] (kein Paging!)
|
|
* - GET /pages/{slug} -> Page (inkl. Blöcke)
|
|
* - PUT /pages/{id} -> 204
|
|
* - POST /pages/{pageId}/blocks -> 201 Block
|
|
* - PUT /blocks/{id} -> 204
|
|
* - DELETE /blocks/{id} -> 204
|
|
* - PUT /pages/{pageId}/blocks/order -> 204 (Body: { blockIds })
|
|
*
|
|
* Enums kommen dank globalem JsonStringEnumConverter als STRINGS
|
|
* ("Draft"/"Published", "Heading"/"RichText"/…). Block.data ist ein
|
|
* typ-spezifisches JSON-Objekt (siehe BlockData-Typen unten).
|
|
*/
|
|
import { api } from './client'
|
|
|
|
export type PageStatus = 'Draft' | 'Published'
|
|
|
|
export type BlockType =
|
|
| 'Heading'
|
|
| 'RichText'
|
|
| 'Image'
|
|
| 'Gallery'
|
|
| 'ContactInfo'
|
|
| 'AbgabetiereList'
|
|
|
|
/** Block-Typen, die die Hand-Editier-Oberfläche neu anlegen kann (in Anzeige-Reihenfolge). */
|
|
export const ADDABLE_BLOCK_TYPES: readonly BlockType[] = [
|
|
'Heading',
|
|
'RichText',
|
|
'Image',
|
|
'Gallery',
|
|
'ContactInfo',
|
|
] as const
|
|
|
|
// ── Typ-spezifische Block-Daten ────────────────────────────────────────────
|
|
export interface HeadingData {
|
|
text: string
|
|
level: 2 | 3
|
|
}
|
|
export interface RichTextData {
|
|
/** Markdown-Quelltext (wird erst beim Veröffentlichen gerendert + bereinigt). */
|
|
markdown: string
|
|
}
|
|
export interface ImageData {
|
|
url: string
|
|
alt: string
|
|
}
|
|
export interface GalleryImage {
|
|
url: string
|
|
alt: string
|
|
}
|
|
export interface GalleryData {
|
|
images: GalleryImage[]
|
|
}
|
|
export interface ContactInfoData {
|
|
name: string
|
|
email: string
|
|
phone: string
|
|
address: string
|
|
}
|
|
export interface AbgabetiereListData {
|
|
/** auto = aus den aktuellen Abgabetieren aufgelöst; manual = von Hand gepflegt. */
|
|
mode: 'auto' | 'manual'
|
|
intro: string
|
|
}
|
|
|
|
export type BlockData = Record<string, unknown>
|
|
|
|
export interface Block {
|
|
id: string
|
|
order: number
|
|
type: BlockType
|
|
data: BlockData
|
|
}
|
|
|
|
export interface PageSummary {
|
|
id: string
|
|
slug: string
|
|
title: string
|
|
seoDescription: string | null
|
|
status: PageStatus
|
|
}
|
|
|
|
export interface Page extends PageSummary {
|
|
blocks: Block[]
|
|
}
|
|
|
|
export interface PageInput {
|
|
slug: string
|
|
title: string
|
|
seoDescription: string | null
|
|
status: PageStatus
|
|
}
|
|
|
|
export interface BlockInput {
|
|
type: BlockType
|
|
data: BlockData
|
|
order?: number
|
|
}
|
|
|
|
/** Sinnvolle Startwerte für einen neu angelegten Block. */
|
|
export function defaultBlockData(type: BlockType): BlockData {
|
|
switch (type) {
|
|
case 'Heading':
|
|
return { text: '', level: 2 }
|
|
case 'RichText':
|
|
return { markdown: '' }
|
|
case 'Image':
|
|
return { url: '', alt: '' }
|
|
case 'Gallery':
|
|
return { images: [] }
|
|
case 'ContactInfo':
|
|
return { name: '', email: '', phone: '', address: '' }
|
|
case 'AbgabetiereList':
|
|
return { mode: 'auto', intro: '' }
|
|
}
|
|
}
|
|
|
|
// ── API-Aufrufe ──────────────────────────────────────────────────────────────
|
|
export function listPages(): Promise<PageSummary[]> {
|
|
return api.get<PageSummary[]>('/pages')
|
|
}
|
|
|
|
export function getPage(slug: string): Promise<Page> {
|
|
return api.get<Page>(`/pages/${encodeURIComponent(slug)}`)
|
|
}
|
|
|
|
export function updatePage(id: string, input: PageInput): Promise<void> {
|
|
return api.put<void>(`/pages/${id}`, input)
|
|
}
|
|
|
|
export function addBlock(pageId: string, input: BlockInput): Promise<Block> {
|
|
return api.post<Block>(`/pages/${pageId}/blocks`, input)
|
|
}
|
|
|
|
export function updateBlock(id: string, input: BlockInput): Promise<void> {
|
|
return api.put<void>(`/blocks/${id}`, input)
|
|
}
|
|
|
|
export function deleteBlock(id: string): Promise<void> {
|
|
return api.delete(`/blocks/${id}`)
|
|
}
|
|
|
|
export function reorderBlocks(pageId: string, blockIds: string[]): Promise<void> {
|
|
return api.put<void>(`/pages/${pageId}/blocks/order`, { blockIds })
|
|
}
|