/** * 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_BASE_URL, api } from './client' /** * WEB-3-Fix: Die CMS-Endpunkte liegen unter der /api-Gruppe * (CmsEndpoints: MapGroup("/api")) — die Aufrufe hier liefen vorher gegen * /pages und wären gegen die ECHTE API 404 gelaufen (der e2e-Mock hat den * Unterschied kaschiert, weil er das /api-Präfix normalisiert). */ const CMS = '/api' 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 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 { return api.get(`${CMS}/pages`) } export function getPage(slug: string): Promise { return api.get(`${CMS}/pages/${encodeURIComponent(slug)}`) } export function updatePage(id: string, input: PageInput): Promise { return api.put(`${CMS}/pages/${id}`, input) } export function addBlock(pageId: string, input: BlockInput): Promise { return api.post(`${CMS}/pages/${pageId}/blocks`, input) } export function updateBlock(id: string, input: BlockInput): Promise { return api.put(`${CMS}/blocks/${id}`, input) } export function deleteBlock(id: string): Promise { return api.delete(`${CMS}/blocks/${id}`) } export function reorderBlocks(pageId: string, blockIds: string[]): Promise { return api.put(`${CMS}/pages/${pageId}/blocks/order`, { blockIds }) } /** * WEB-3: Absolute URL der lokalen Vorschau (GET /api/preview/… rendert live). * "start" liegt auf index.html, alle anderen Seiten auf {slug}/index.html — * dieselbe Abbildung wie im SiteRenderer. */ export function previewUrl(slug?: string | null): string { const path = !slug || slug === 'start' ? 'index.html' : `${encodeURIComponent(slug)}/index.html` return `${API_BASE_URL}/api/preview/${path}` }