Zum Hauptinhalt springen

Website Template Prompt für v0

Für Center-Websites – öffentliche Shopping-Center-Websites. Für Touchscreen-Kioske gibt es den Kiosk Template Prompt.


Für Redakteure: So erstellst du ein neues Template (3 Schritte)

Du brauchst keine technischen Vorkenntnisse. Kopieren, einfügen, beschreiben – fertig.

Schritt 1: Prompt kopieren

Scrolle zu „Prompt kopieren und in v0 einfügen“. Klicke auf den Kopieren-Button oder markiere den Block und kopiere ihn.

Schritt 2: In v0 einfügen

  1. Öffne v0.dev → Neuer Chat
  2. Zuerst: Screenshots deines Wunsch-Layouts einfügen (z.B. von Websites, die dir gefallen)
  3. Dann: Den kopierten Prompt einfügen
  4. Optional: In eigenen Worten ergänzen, z.B.:
    • „Oben soll ein großes Bild mit dem Center-Namen sein“
    • „Darunter drei Kacheln für Shops, Events und Angebote“
    • „Dunkles Theme, minimalistisch“

Tipp: Ein Screenshot pro Bereich (oben, Mitte, unten) funktioniert besser als eine ganze Seite.

Schritt 3: ZIP herunterladen und weitergeben

Wenn das Ergebnis passt: v0 → Download als ZIP. Die ZIP-Datei kannst du im Dashboard unter Templates hochladen oder an den technischen Ansprechpartner schicken. Die Anbindung ans Dashboard und die Custom-Reiter übernehmen wir – du lieferst nur das Design.


So sprichst du mit v0 (natürliche Sprache)

Du musst keine Fachbegriffe kennen. Beschreibe einfach, was du siehst oder willst:

Du sagst z.B. …v0 versteht …
„Oben ein großes Bild mit dem Center-Namen“Großes Bild/Banner oben (Hero)
„Darunter Kacheln für Shops und Events“Klickbare Kacheln mit Bildern
„Drei Angebote als Karten untereinander“Angebote in Karten-Form
„Shops mit Logo und Name in einem Raster“Shop-Grid mit Logos
„Wie im Screenshot, aber in unseren Farben“Layout übernehmen, Farben anpassen
„Mach die Karten größer“Größere Kacheln
„Dunkleres Design“Dunkles Farbschema
„Minimalistisch, wenig Schnickschnack“Reduziertes, klares Layout

Wichtig: Du beschreibst nur das Aussehen und die Anordnung. Der Prompt enthält die Seitenstruktur und das Design-Ziel – v0 versteht deine Absicht und macht daraus das bestmögliche Ergebnis: modern, mit Animationen, hochwertig. Du musst nicht alles im Detail vorgeben.


Technischer Hintergrund (für Entwickler)

Die Anbindung ans Dashboard, Custom-Reiter und Template-Registry übernimmt die Codebase. v0 liefert nur das visuelle Template – wir integrieren es danach.

  • Struktur: Home, Shops, Centerplan, Services, News, To-Go – jeweils mit Unterseiten
  • Globale Komponenten: Centerplan und Chatbot werden von CockpitOS injiziert – nicht im Template implementieren

Siehe docs/TEMPLATE-DEVELOPER-CHECKLIST.md für die Integration.


Prompt kopieren und in v0 einfügen

Klicke auf den Button oben – der gesamte Prompt wird kopiert. Oder markiere den Block unten mit Strg/Cmd+A und kopiere manuell.

## WICHTIG: Nutzer verstehen – bestes Ergebnis liefern

**Verstehe die Absicht des Nutzers** (Screenshots, natürliche Sprache) – und **liefere das Beste daraus**: modern, poliert, mit subtilen Animationen. Nicht wörtlich kopieren, sondern das Maximum aus dem Input machen. Wenn der Nutzer nur „dunkler" sagt → verstehe den Wunsch und liefere ein durchdachtes dunkles Design, nicht nur dunkle Farben.

**Ohne Nutzer-Input:** Von selbst das beste moderne Design liefern – Animationen, Hover-States, klare Hierarchie, zeitgemäßes Layout.

---

## TASK

Erstelle ein **vollständiges Website-Template** für ein Shopping Center. Next.js 14, TypeScript, Tailwind CSS.

**Seitenstruktur (SEO-optimiert, semantisch):**
- **Startseite:** Hero mit Center-Namen, darunter Kacheln (Shops, Events, Angebote, Kategorien)
- **Übersichten:** Shops (Grid mit Logo + Name), Aktuelles (News/Events/Angebote), Services, Gastronomie, Centerplan (Platzhalter: grauer Kasten „Centerplan“)
- **Detail-Seiten:** Einzelner Shop, News, Event, Angebot, Service, Restaurant
- **Info-Seiten:** Impressum, Datenschutz, Kontakt, Parken, Öffnungszeiten, Büros, To-Go (Mobile/QR)

**Layout:** Header (Logo + Navigation) + Footer (Impressum, Datenschutz, Kontakt) – umschließt alle Seiten.

---

## ZIELGRUPPE & KONTEXT

- Besucher von Shopping-Center-Websites (B2C, 18–55 Jahre), oft mobil unterwegs
- Schnelle Orientierung: Shops finden, Events, Öffnungszeiten, Parken
- **Mobile-first** (60 %+ Traffic), klare Struktur, einfache Navigation

---

## DESIGN-FREIRAUM

Layout, Farben, Stil, Typografie, Karten-Design, Spacing, **Animationen** – alles deine Wahl. **Ziel:** Das bestmögliche, moderne Ergebnis. Nutzer-Input (Screenshots, „dunkler", „größere Kacheln") verstehen und daraus das Maximum machen – nicht nur nachbauen, sondern verbessern. Ohne Input: von selbst ein zeitgemäßes, animiertes, hochwertiges Design liefern.

**Technik:** Nur React, next/link, next/image, Tailwind. Keine externen Pakete. Inhalte als Platzhalter/Mock-Daten – die echten Daten kommen später bei der Integration.

Viel Erfolg beim Gestalten!

Technische Details (für Entwickler – nicht in v0 kopieren)

centerConfig, Props, Integration – für CockpitOS-Entwickler

Die centerConfig-Struktur wird an alle Komponenten übergeben:

// Öffnungszeiten-Typisierung (kein any!)
interface DayHours {
open: string; // Format: "HH:MM" (z.B. "10:00")
close: string; // Format: "HH:MM" (z.B. "20:00")
closed?: boolean; // true wenn geschlossen
}

interface OpeningHours {
// Option 1: Strukturiertes Format (bevorzugt)
regularHours?: {
monday?: DayHours | null;
tuesday?: DayHours | null;
wednesday?: DayHours | null;
thursday?: DayHours | null;
friday?: DayHours | null;
saturday?: DayHours | null;
sunday?: DayHours | null;
};

// Option 2: Vereinfachtes Format (Fallback)
weekdays?: string; // z.B. "Mo - Sa: 10:00 - 20:00 Uhr"
saturday?: string;
sunday?: string;

// Option 3: Plain String (Fallback)
// Kann auch ein einfacher String sein: "Mo-Sa 10:00-20:00"
// Template muss alle Formate unterstützen
}

interface CenterWebsiteTemplateProps {
// Center-Konfiguration (aus CockpitOS Dashboard)
centerConfig: {
centerId: string;
slug: string; // z.B. "palais-vest"
name: string; // z.B. "Palais Vest"
description?: string;

// Branding
branding: {
logo?: string;
logoUrl?: string;
coverImage?: string;
heroImage?: string;
};

// Farben & Design
colors: {
primary: string; // HEX-Farbe (z.B. "#0066CC")
secondary?: string;
accent?: string;
background?: string;
};

// Kontakt & Standort
contact?: {
address?: {
street?: string;
city?: string;
zip?: string;
country?: string;
};
phone?: string;
email?: string;
website?: string;
};

// Öffnungszeiten (TYPISIERT, kein any!)
openingHours?: OpeningHours;

// Template-spezifischer Content (Dashboard unter "Template-Inhalte")
// Key = templateId (z.B. cev-ai-theme). Texte, CTAs, Section-Overrides pro Center.
templateContent?: Record<string, Record<string, unknown>>;

// SEO
seo?: {
title?: string;
description?: string;
};
};

// Dynamische Daten aus CockpitOS
tiles: Array<{
id: string;
title: string;
subtitle?: string;
description?: string;
href?: string;
image?: string;
video?: string;
gradient?: string;
color?: string;
size?: 'small' | 'medium' | 'large' | 'wide' | 'tall';
type?: 'category' | 'shop' | 'event' | 'news' | 'service' | 'custom';
}>;

shops?: Array<{
id: string;
name: string;
category?: string;
floor?: string;
description?: string;
image?: string;
logo?: string;
hours?: string;
}>;

events?: Array<{
id: string;
title: string;
description?: string;
startDate?: string;
endDate?: string;
image?: string;
location?: string;
}>;

news?: Array<{
id: string;
title: string;
excerpt?: string;
image?: string;
publishedAt?: string;
href?: string;
}>;

services?: Array<{
id: string;
name: string;
description?: string;
icon?: string;
location?: string;
}>;
}

2. SEITENSTRUKTUR (wie CockpitOS)

Die aktuelle Center-Website-App ist das technische Maximum. Das Template MUSS die gleiche Struktur abbilden:

SeiteRouteBeschreibung
Home/Hero, Tiles, Sections
Shops Übersicht/{slug}/shopsShop-Grid, Kategorien
Shop-Kategorie/{slug}/shops/categories/[category]Shops nach Kategorie
Shop Detail/{slug}/shops/[shopSlug]Einzelner Shop
Centerplan/{slug}/wayfindingSlot für WayfindingMap – CockpitOS injiziert die Karte
Services Übersicht/{slug}/servicesServices-Grid
Service Detail/{slug}/services/[serviceSlug]Einzelner Service
News/Events/Angebote/{slug}/aktuellesÜbersicht (News, Events, Angebote, Jobs)
News Detail/{slug}/aktuelles/news/[id]Einzelne News
Event Detail/{slug}/aktuelles/events/[id]Einzelnes Event
Angebot Detail/{slug}/aktuelles/angebote/[id]Einzelnes Angebot
Impressum/{slug}/impressumImpressum
Datenschutz/{slug}/datenschutzDatenschutz
Öffnungszeiten/{slug}/opening-hoursÖffnungszeiten
Anfahrt & Parken/{slug}/parkingParken, Anfahrt
Kontakt/{slug}/kontaktKontaktformular
To-Go (2-Go)/{slug}/to-go/[qrCodeId]Mobile-optimierte Seite für QR-Code: News, Events, Angebote in vertikalem Scroll-Layout. Nur über QR-Code erreichbar.
Cookie-Notice(global)Wird vom Layout eingebunden
Gastronomie/{slug}/gastronomyThemenwelten, Restaurants
Büros/Offices/{slug}/officesBüro-Themenwelten

Wichtig: Centerplan = Placeholder mit {children} oder dediziertem Slot. Chatbot (AI Chat inkl. Consent, SearchInterface, FloatingAIIcon) und Cookie-Notice sind globale CockpitOS-Komponenten im Layout – im Template nicht implementieren.


2b. PRO-SEITE LAYOUTS (v0 generiert jede Seite)

Jede Seite kann ein eigenes v0-Layout erhalten. CockpitOS ruft getPageComponent(template, pageType) – wenn das Template eine Komponente liefert, wird sie mit den folgenden Props gerendert. v0 soll pro Seite eine Komponente erstellen – mit exakt diesen Props.

PageTypeDateiProps (empfangen)
homeindex.tsxcenterConfig, tiles, categories?, categoryThemes?, highlights?, hostname?
shopspages/shops.tsxcenterConfig, categories, categoryThemes?, pageContent, centerSlug, hostname?, shops?
shop-detailpages/shop-detail.tsxcenterConfig, shop, similarShops?, centerSlug, backUrl, hostname?, floors?
aktuellespages/aktuelles.tsxnews, events, offers, jobs, pageContent, backUrl, centerSlug, centerShortName, contentCardSettings?, hostname?
servicespages/services.tsxcenterConfig, services, centerSlug, hostname?
gastronomypages/gastronomy.tsxthemes, pageContent, centerSlug
wayfindingpages/wayfinding.tsxcenterConfig, centerplan, floors
officespages/offices.tsxoffices, themes, centerSlug, pageContent
parkingpages/parking.tsxcenterConfig (name, parking, contact, openingHours), backUrl
opening-hourspages/opening-hours.tsxshops, services, centerConfig (name, openingHours), backUrl
impressumpages/impressum.tsxcenterConfig, pageContent, centerSlug
datenschutzpages/datenschutz.tsxcenterConfig, pageContent, centerSlug
kontaktpages/kontakt.tsxcenterConfig, contacts, centerSlug
to-gopages/to-go.tsxqrCodeId, centerConfig, contentItems, ...

Ordnerstruktur für Full-Site-Template:

components/templates/[name]/
├── index.tsx # Home
├── layout.tsx # Header + Footer (umschließt alle Seiten)
├── pages/
│ ├── shops.tsx
│ ├── aktuelles.tsx
│ ├── services.tsx
│ ├── gastronomy.tsx
│ ├── wayfinding.tsx
│ └── ...

Eintrag in template-registry.ts:

'cev-ai-theme': {
shops: dynamic(() => import('@/components/templates/cev-ai-theme/pages/shops').then(m => m.default)),
'shop-detail': dynamic(() => import('@/components/templates/cev-ai-theme/pages/shop-detail').then(m => m.default)),
aktuelles: dynamic(...),
// ...
}

2c. INTEGRATION-ERKENNTNISSE (CEV – für nächste Templates)

Diese Schritte sind nach der v0-Generierung von CockpitOS-Entwicklern durchzuführen. v0 liefert die UI; CockpitOS verbindet Daten und globale Komponenten.

Shops-Seite: Shop-Grid statt nur Kategorien

  • Route app/[slug]/shops/page.tsx: loadShops(centerId, 'Aktiv') parallel zu categories/pageContent laden.
  • Props: shops an Template übergeben: { id, name, slug, logo, category, description }.
  • Template: Wenn shops.length > 0 → ShopGrid mit Shop-Karten (Logo + Label, Link zu /{centerSlug}/shops/{shop.slug}); sonst → ShopCategories.
  • PageHero: breadcrumbs (Home > Shops), heroTitle/heroSubtitle aus pageContent?.heroTitle oder pageContent?.pageTitle.

Shop-Detail: Centerplan mit Shop-Highlight

  • Route app/[slug]/shops/[shopSlug]/page.tsx:
    • getPageComponent(template, 'shop-detail') prüfen.
    • Bei Template-Match: loadCenterplan(centerId) aufrufen, floors an Template übergeben.
  • Template: Shop-Detail-Section mit optionalem Slot centerPlanEmbed?: ReactNode.
  • CenterPlanEmbed (CockpitOS-Komponente):
    • Props: floors, highlightShopId.
    • Floor = Etage mit Shop in mapLocations, sonst Default-Etage.
    • buildSvgIdToLocationMap (aus @mall-os/wayfinding) → highlightedSvgIds für loc.shop?.id === highlightShopId.
    • InteractiveFloorPlan mit svgContent, mapImage, highlightedSvgIds.
    • Fallback: statisches mapImage wenn kein SVG.

Template-Registry

  • shop-detail muss für jedes Template registriert werden, das eine eigene Shop-Detail-Seite hat.
  • PageType: 'shop-detail' (mit Bindestrich).

Dynamische Farben (Theme-fähige Templates)

  • Statt Hardcodes: CSS-Variablen wie var(--cev-primary), var(--cev-secondary), var(--cev-accent).
  • CockpitOS setzt diese via generateThemeStyles() aus centerConfig.branding.
  • v0 kann Klassen wie cev-btn-accent, cev-heading definieren, die auf diese Variablen verweisen.

Datenfluss-Übersicht

SeiteRoute lädtTemplate erhält
shopsloadShops, loadCategories, loadPageContentshops, categories, categoryThemes, pageContent
shop-detailloadShops, loadCenterplanshop, similarShops, floors

3. KOMPONENTENSTRUKTUR

Empfohlene Ordnerstruktur:

components/templates/[template-name]/
├── index.ts # Exportiert Template
├── Template.tsx # Haupt-Template (Server Component)
├── components/
│ ├── Header.tsx # Header mit nav zu /{slug}/shops, /{slug}/wayfinding, etc.
│ ├── Hero.tsx # Hero-Section
│ ├── TileGrid.tsx # Tiles-Grid (oder Custom Layout)
│ ├── SectionWrapper.tsx # Wrapper für Sections
│ ├── Footer.tsx # Footer-Komponente
│ └── [weitere Subcomponents] # Nur wenn nötig
├── types.ts # TypeScript Interfaces
└── README.md # Template-Dokumentation

index.ts Beispiel:

export { CenterWebsiteTemplate } from './Template';
export type { CenterWebsiteTemplateProps } from './types';

4. TEMPLATE-IMPLEMENTIERUNG

Server Component Default

// Template.tsx (Server Component, kein 'use client')
import Link from 'next/link';
import Image from 'next/image';
import { Header } from './components/Header';
import { Hero } from './components/Hero';
import { TileGrid } from './components/TileGrid';
import { Footer } from './components/Footer';
import type { CenterWebsiteTemplateProps } from './types';

export function CenterWebsiteTemplate({
centerConfig,
tiles = [],
shops = [],
events = [],
news = [],
services = []
}: CenterWebsiteTemplateProps) {
return (
<div className="min-h-screen">
<Header centerConfig={centerConfig} />
<Hero centerConfig={centerConfig} />
<TileGrid tiles={tiles} centerConfig={centerConfig} />
{/* Weitere Sections */}
<Footer centerConfig={centerConfig} />
</div>
);
}

Client Components nur wenn nötig

// components/InteractiveSlider.tsx (Client Component)
'use client';

import { useState } from 'react';
// ... interaktive Logik

// RICHTIG: Interne Links mit next/link
import Link from 'next/link';

<Link href={tile.href || '#'}>
{tile.title}
</Link>

// RICHTIG: Externe Links mit <a>
<a
href={centerConfig.contact?.website}
target="_blank"
rel="noopener noreferrer"
>
Website
</a>

Bilder

// RICHTIG: next/image für alle Bilder
import Image from 'next/image';

<Image
src={centerConfig.branding.logo || '/placeholder-logo.png'}
alt={centerConfig.name}
width={200}
height={60}
className="h-12 w-auto"
/>

// FALLBACK: Nur wenn next/image nicht möglich (z.B. externe URLs ohne Domain-Kontrolle)
{centerConfig.branding.logoUrl && !isInternalUrl(centerConfig.branding.logoUrl) ? (
<img
src={centerConfig.branding.logoUrl}
alt={centerConfig.name}
className="h-12"
/>
) : (
<Image src={centerConfig.branding.logo || '/placeholder-logo.png'} ... />
)}

6. DESIGN-GUIDELINES

Farben & Schatten

// Primary-Farbe verwenden
style={{ backgroundColor: centerConfig.colors.primary }}

// Dynamisches Schatten-System
style={{ boxShadow: 'var(--shadow-card, 0 10px 25px -5px rgba(0,0,0,0.1))' }}
className="hover:[box-shadow:var(--shadow-card-hover)]"

Visuelle Fallbacks (keine Lorem-Ipsum!)

// Option A: Gradient (kein externer Request)
{!centerConfig.branding.heroImage && (
<div
className="absolute inset-0 bg-gradient-to-br from-primary/20 to-accent/20"
style={{ backgroundColor: centerConfig.colors.primary }}
/>
)}

// Option B: Platzhalter-URL für v0-Preview (bessere Vorschau)
// NUR im Fallback – Props haben Vorrang. CockpitOS übergibt echte URLs.
<Image
src={centerConfig.branding.heroImage || 'https://placehold.co/1200x600?text=Hero'}
alt={centerConfig.name}
fill
/>

// VERBOTEN: Lorem-Ipsum oder Platzhalter-Texte
// <p>Lorem ipsum dolor sit amet...</p>

7. DATEN-FALLBACKS (MUSS implementiert werden)

Das Template MUSS bewusste Fallback-States haben:

// Keine Tiles → Alternative Section zeigen
{tiles.length === 0 ? (
<section className="py-12">
<h2 className="text-2xl font-bold mb-6">Highlights</h2>
{shops.length > 0 && (
<ShopGrid shops={shops.slice(0, 6)} />
)}
{events.length > 0 && (
<EventList events={events.slice(0, 3)} />
)}
</section>
) : (
<TileGrid tiles={tiles} />
)}

// Kein Hero-Image → Gradient + Pattern
{!centerConfig.branding.heroImage ? (
<div
className="relative h-[60vh] bg-gradient-to-br"
style={{
background: `linear-gradient(135deg, ${centerConfig.colors.primary} 0%, ${centerConfig.colors.accent || centerConfig.colors.primary} 100%)`
}}
>
{/* Pattern Overlay */}
</div>
) : (
<Image src={centerConfig.branding.heroImage} ... />
)}

// Kein Kontakt → Footer reduced state
{centerConfig.contact ? (
<Footer full contact={centerConfig.contact} />
) : (
<Footer minimal centerName={centerConfig.name} />
)}

8. CHECKLISTE (für CockpitOS-Kompatibilität)

  • Server Component, TypeScript typisiert
  • next/link, next/image
  • Keine hardcoded Daten, alles aus Props
  • Fallbacks für leere States
  • Struktur: Header, Hero, TileGrid, Footer
  • Responsive, accessible
  • Primary-Farbe aus centerConfig.colors.primary
  • Tailwind CSS, keine Lorem-Ipsum-Texte

9. OUTPUT-ANFORDERUNGEN

Das Template MUSS folgende Ausgabe enthalten:

FILES

components/templates/[template-name]/
├── index.ts
├── Template.tsx
├── components/
│ ├── Header.tsx
│ ├── Hero.tsx
│ ├── TileGrid.tsx
│ ├── Footer.tsx
│ └── [weitere]
├── types.ts
└── README.md

DATA_REQUIREMENTS.md

# Daten-Anforderungen für [Template-Name]

## Zwingend erforderlich (MUSS befüllt sein)
- centerConfig.name
- centerConfig.colors.primary
- centerConfig.slug

## Optional (empfohlen)
- centerConfig.templateContent?.[templateId] – Dashboard-konfigurierbare Section-Texte (hero, newsSection, etc.)
- centerConfig.branding.logo
- centerConfig.branding.heroImage
- tiles[] (mindestens 3 für gute Darstellung)
- centerConfig.contact.address

## Defaults (werden verwendet wenn nicht vorhanden)
- Logo: /placeholder-logo.png
- Hero: Gradient mit Primary-Farbe
- Tiles: Fallback zu Shops/Events Highlights

IMPLEMENTATION_NOTES.md

# Implementierungs-Hinweise

## Besonderheiten
- [Template-spezifische Hinweise]

## Integration (CockpitOS-Entwickler)
- **Template-Registry:** Alle genutzten PageTypes in template-registry.ts eintragen (z.B. shops, shop-detail, aktuelles).
- **Template-Content-Schema:** Wenn das Template konfigurierbare Texte (Hero, Sections, CTAs) haben soll – Schema in `apps/dashboard/src/lib/template-content-schemas.ts` hinzufügen. Das Dashboard generiert dann automatisch das Formular im Tab „Template-Inhalte“. Platzhalter `{{centerName}}`, `{{centerSlug}}` werden dynamisch ersetzt.
- **Shops-Seite:** Wenn Shop-Grid gewünscht – Route muss loadShops aufrufen und shops an Template übergeben.
- **Shop-Detail:** Wenn Centerplan mit Shop-Highlight – Route muss loadCenterplan aufrufen, floors an Template übergeben. Shop-Detail-Section mit centerPlanEmbed-Slot (ReactNode). CockpitOS rendert dort CenterPlanEmbed (floors, highlightShopId).
- **Dynamische Farben:** CSS-Variablen (--cev-primary etc.) aus centerConfig.branding setzen.

## Anpassungen
- [Was kann pro Center angepasst werden]

10. Bitte vermeiden

  • API-Calls (Daten kommen als Props)
  • Hardcoded Inhalte
  • Externe Dependencies außer Next.js, React, Tailwind, Framer Motion
  • Lorem-Ipsum-Texte (visuelle Fallbacks wie Gradient sind OK)

11. ZUSAMMENFASSUNG

Erstelle ein visuell fertiges, responsives Template mit:

  1. Korrektem Props-Interface (typisiert)
  2. Server Component (Client nur bei Interaktivität)
  3. next/link & next/image
  4. Struktur: Header, Hero, TileGrid, Footer
  5. Fallbacks für leere Daten
  6. DATA_REQUIREMENTS.md + IMPLEMENTATION_NOTES.md
  7. Mobile-first, accessible

Layout und Stil sind frei wählbar – Hauptsache, die Daten kommen aus Props und es funktioniert in CockpitOS.


Beispiel: Minimales Template-Scaffold

// Template.tsx
import Link from 'next/link';
import Image from 'next/image';
import { Header } from './components/Header';
import { Hero } from './components/Hero';
import { TileGrid } from './components/TileGrid';
import { Footer } from './components/Footer';
import type { CenterWebsiteTemplateProps } from './types';

export function CenterWebsiteTemplate({
centerConfig,
tiles = [],
shops = [],
events = [],
news = [],
services = []
}: CenterWebsiteTemplateProps) {
return (
<div className="min-h-screen bg-background">
<Header centerConfig={centerConfig} />
<Hero centerConfig={centerConfig} />

{/* Tiles oder Fallback */}
{tiles.length > 0 ? (
<TileGrid tiles={tiles} centerConfig={centerConfig} />
) : (
<section className="container mx-auto px-4 py-12">
<h2 className="text-2xl font-bold mb-6">Highlights</h2>
{shops.length > 0 && <ShopGrid shops={shops.slice(0, 6)} />}
{events.length > 0 && <EventList events={events.slice(0, 3)} />}
</section>
)}

<Footer centerConfig={centerConfig} />
</div>
);
}

Das Template wird dann nahtlos in CockpitOS integriert und kann pro Center konfiguriert werden.


12. HÄUFIGE FEHLER & WIE MAN SIE VERMEIDET

Fehler 1: Hardcoded Inhalte

// FALSCH - Hardcoded Text
<h1>Willkommen im Palais Vest</h1>

// RICHTIG - Aus Props
<h1>Willkommen im {centerConfig.name}</h1>

Fehler 2: openingHours als any

// FALSCH
openingHours?: any;

// RICHTIG
openingHours?: OpeningHours;
// FALSCH
<a href={tile.href}>{tile.title}</a>

// RICHTIG
import Link from 'next/link';
<Link href={tile.href || '#'}>{tile.title}</Link>

Fehler 4: Normale <img> Tags statt next/image

// FALSCH
<img src={centerConfig.branding.logo} alt={centerConfig.name} />

// RICHTIG
import Image from 'next/image';
<Image
src={centerConfig.branding.logo || '/placeholder-logo.png'}
alt={centerConfig.name}
width={200}
height={60}
/>

Fehler 5: Keine Fallbacks für fehlende Daten

// FALSCH - Leerer State wenn keine Tiles
<TileGrid tiles={tiles} />

// RICHTIG - Fallback implementiert
{tiles.length > 0 ? (
<TileGrid tiles={tiles} />
) : (
<section className="py-12">
<h2 className="text-2xl font-bold mb-6">Highlights</h2>
{shops.length > 0 && <ShopGrid shops={shops.slice(0, 6)} />}
</section>
)}

Fehler 6: Platzhalter-URL als primäre Quelle (statt Fallback)

// FALSCH - Platzhalter hardcoded, Props ignoriert → Aufräumen nötig
<Image src="https://picsum.photos/800/400" alt="Hero" />

// RICHTIG - Prop zuerst, Platzhalter nur wenn leer
<Image src={heroImage || 'https://placehold.co/800x400?text=Hero'} alt={centerConfig.name} />

Fehler 7: CockpitOS-Imports im Template (bricht v0-Preview)

// FALSCH - @mall-os, @/lib etc. existieren in v0 nicht → Preview-Fehler
import { InteractiveFloorPlan } from '@mall-os/wayfinding';
import { loadCenterplan } from '@/lib/server-data-loader';

// RICHTIG - Slot als Prop, Platzhalter wenn undefined
{centerPlanEmbed ?? (
<div className="min-h-[280px] rounded-xl bg-gray-200 flex items-center justify-center text-gray-500">
Centerplan
</div>
)}

13. BEST PRACTICES FÜR HÄUFIGE KOMPONENTEN

// components/Header.tsx
import Link from 'next/link';
import Image from 'next/image';

export function Header({ centerConfig }: { centerConfig: CenterWebsiteTemplateProps['centerConfig'] }) {
const slug = centerConfig?.slug ?? 'preview'; // Nie undefined – v0-Preview bricht sonst
return (
<header className="sticky top-0 z-50 bg-white/95 backdrop-blur-sm border-b border-gray-200">
<nav className="container mx-auto px-4 py-4 flex items-center justify-between">
<Link href={`/${slug}`} className="flex items-center gap-3">
{centerConfig.branding.logo && (
<Image
src={centerConfig.branding.logo}
alt={centerConfig.name}
width={120}
height={40}
className="h-10 w-auto"
/>
)}
<span className="text-xl font-bold">{centerConfig.name}</span>
</Link>
<div className="flex gap-6">
<Link href={`/${slug}/shops`} className="hover:text-primary transition-colors">
Shops
</Link>
<Link href={`/${slug}/aktuelles`} className="hover:text-primary transition-colors">
Aktuelles
</Link>
</div>
</nav>
</header>
);
}

Hero-Komponente mit Fallbacks

// components/Hero.tsx
import Image from 'next/image';

export function Hero({ centerConfig }: { centerConfig: CenterWebsiteTemplateProps['centerConfig'] }) {
const hasHeroImage = !!centerConfig.branding.heroImage;

return (
<section className="relative h-[60vh] min-h-[400px] flex items-center justify-center overflow-hidden">
{hasHeroImage ? (
<Image
src={centerConfig.branding.heroImage!}
alt={centerConfig.name}
fill
className="object-cover"
priority
/>
) : (
<div
className="absolute inset-0 bg-gradient-to-br"
style={{
background: `linear-gradient(135deg, ${centerConfig.colors.primary} 0%, ${centerConfig.colors.accent || centerConfig.colors.primary} 100%)`
}}
/>
)}
<div className="relative z-10 text-center text-white px-4">
<h1 className="text-4xl md:text-6xl font-bold mb-4">{centerConfig.name}</h1>
{centerConfig.description && (
<p className="text-xl md:text-2xl max-w-2xl mx-auto">{centerConfig.description}</p>
)}
</div>
</section>
);
}

TileGrid mit Responsive Design

// components/TileGrid.tsx
import Link from 'next/link';
import Image from 'next/image';

export function TileGrid({ tiles, centerConfig }: { tiles: CenterWebsiteTemplateProps['tiles'], centerConfig: CenterWebsiteTemplateProps['centerConfig'] }) {
if (tiles.length === 0) return null;

return (
<section className="container mx-auto px-4 py-12">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{tiles.map((tile) => (
<Link
key={tile.id}
href={tile.href || '#'}
className="group relative overflow-hidden rounded-2xl aspect-[4/3]"
>
{tile.image ? (
<Image
src={tile.image}
alt={tile.title}
fill
className="object-cover group-hover:scale-110 transition-transform duration-300"
/>
) : tile.gradient ? (
<div className={`absolute inset-0 bg-gradient-to-br ${tile.gradient}`} />
) : (
<div
className="absolute inset-0"
style={{ backgroundColor: tile.color || centerConfig.colors.primary }}
/>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" />
<div className="absolute bottom-0 left-0 right-0 p-6 text-white">
<h3 className="text-xl font-bold mb-2">{tile.title}</h3>
{tile.subtitle && <p className="text-sm opacity-90">{tile.subtitle}</p>}
</div>
</Link>
))}
</div>
</section>
);
}
// components/Footer.tsx
export function Footer({ centerConfig }: { centerConfig: CenterWebsiteTemplateProps['centerConfig'] }) {
const hasContact = !!centerConfig.contact;

return (
<footer className="bg-gray-900 text-white py-12">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 className="text-xl font-bold mb-4">{centerConfig.name}</h3>
{centerConfig.description && (
<p className="text-gray-400">{centerConfig.description}</p>
)}
</div>

{hasContact && centerConfig.contact?.address && (
<div>
<h4 className="text-lg font-semibold mb-4">Kontakt</h4>
<address className="text-gray-400 not-italic">
{centerConfig.contact.address.street && (
<p>{centerConfig.contact.address.street}</p>
)}
{centerConfig.contact.address.zip && centerConfig.contact.address.city && (
<p>{centerConfig.contact.address.zip} {centerConfig.contact.address.city}</p>
)}
{centerConfig.contact.phone && (
<p className="mt-2">
<a href={`tel:${centerConfig.contact.phone}`} className="hover:text-white">
{centerConfig.contact.phone}
</a>
</p>
)}
</address>
</div>
)}

{centerConfig.openingHours && (
<div>
<h4 className="text-lg font-semibold mb-4">Öffnungszeiten</h4>
<OpeningHoursDisplay openingHours={centerConfig.openingHours} />
</div>
)}
</div>
</div>
</footer>
);
}

14. PERFORMANCE-OPTIMIERUNGEN

Lazy Loading für Bilder

// Bilder unterhalb des Foldes lazy laden
<Image
src={tile.image}
alt={tile.title}
fill
loading="lazy" // Nur für Bilder unterhalb des Foldes
className="object-cover"
/>

Priority für Hero-Bilder

// Hero-Bilder sollten priority haben
<Image
src={centerConfig.branding.heroImage}
alt={centerConfig.name}
fill
priority // Wichtig für Above-the-Fold Content
className="object-cover"
/>

Code Splitting für Client Components

// Nur interaktive Komponenten als Client Components
'use client';

import { useState } from 'react';
import dynamic from 'next/dynamic';

// Schwere Komponenten dynamisch laden
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <div>Lädt...</div>
});

15. ACCESSIBILITY (WCAG 2.1 AA)

Semantisches HTML

// RICHTIG - Semantische Tags verwenden
<header>
<nav aria-label="Hauptnavigation">
{/* Navigation */}
</nav>
</header>

<main>
<section aria-labelledby="hero-heading">
<h1 id="hero-heading">{centerConfig.name}</h1>
</section>
</main>

<footer>
{/* Footer Content */}
</footer>

ARIA-Labels für interaktive Elemente

// Links mit beschreibenden Labels
<Link
href={tile.href}
aria-label={`Mehr über ${tile.title} erfahren`}
>
{tile.title}
</Link>

// Buttons mit Labels
<button
aria-label="Menü öffnen"
aria-expanded={isMenuOpen}
>
Menü
</button>

Keyboard-Navigation

// Focus-Styles für Keyboard-Navigation
<Link
href={tile.href}
className="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
>
{tile.title}
</Link>

Alt-Texte für Bilder

// Beschreibende Alt-Texte (nicht "Bild" oder leer)
<Image
src={tile.image}
alt={tile.title} // Gut: Beschreibend
// alt="Bild" // SCHLECHT: Nicht beschreibend
// alt="" // SCHLECHT: Leer
/>

16. SEO-BEST PRACTICES

Meta-Tags (werden automatisch von CockpitOS gesetzt)

// Meta-Tags werden automatisch generiert, aber du kannst strukturierte Daten hinzufügen
export function CenterWebsiteTemplate(props: CenterWebsiteTemplateProps) {
const structuredData = {
"@context": "https://schema.org",
"@type": "ShoppingCenter",
"name": props.centerConfig.name,
"address": props.centerConfig.contact?.address,
"openingHours": formatOpeningHoursForSchema(props.centerConfig.openingHours)
};

return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
{/* Rest des Templates */}
</>
);
}

Semantische Überschriften-Hierarchie

// RICHTIG - H1 nur einmal, dann H2, H3, etc.
<h1>{centerConfig.name}</h1> {/* Nur einmal pro Seite */}
<h2>Unsere Shops</h2>
<h3>Mode & Accessoires</h3>

// FALSCH - Mehrere H1 oder falsche Hierarchie
<h1>Willkommen</h1>
<h1>Shops</h1> {/* FALSCH - Nur ein H1 */}

17. RESPONSIVE DESIGN (MOBILE-FIRST)

Breakpoints verwenden

// Mobile-first Ansatz
<div className="
grid
grid-cols-1 // Mobile: 1 Spalte
md:grid-cols-2 // Tablet: 2 Spalten
lg:grid-cols-3 // Desktop: 3 Spalten
xl:grid-cols-4 // Large Desktop: 4 Spalten
gap-4 md:gap-6
">
{/* Tiles */}
</div>

Touch-optimierte Buttons

// Mindestens 44x44px für Touch-Targets
<button className="
min-h-[44px]
min-w-[44px]
px-4 py-2
md:px-6 md:py-3
">
Klick mich
</button>

Responsive Typography

// Schriftgrößen skalieren mit Viewport
<h1 className="text-3xl md:text-4xl lg:text-5xl xl:text-6xl">
{centerConfig.name}
</h1>

<p className="text-sm md:text-base lg:text-lg">
{centerConfig.description}
</p>

18. ÖFFNUNGSZEITEN KORREKT HANDHABEN

Helper-Funktion für openingHours

// Helper-Funktion für verschiedene openingHours-Formate
function formatOpeningHours(openingHours?: OpeningHours): string {
if (!openingHours) return 'Auf Anfrage';

// Option 1: Strukturiertes Format
if (openingHours.regularHours) {
const hours = openingHours.regularHours;
if (hours.monday && hours.saturday &&
hours.monday.open === hours.saturday.open &&
hours.monday.close === hours.saturday.close) {
return `Mo-Sa: ${hours.monday.open} - ${hours.monday.close} Uhr`;
}
// Weitere Formatierungen...
}

// Option 2: Vereinfachtes Format
if (openingHours.weekdays) {
return openingHours.weekdays;
}

// Option 3: Plain String (Fallback)
if (typeof openingHours === 'string') {
return openingHours;
}

return 'Auf Anfrage';
}

// Verwendung
<p>{formatOpeningHours(centerConfig.openingHours)}</p>

19. FINALE CHECKLISTE VOR DEM EXPORT

Bevor du das Template exportierst, prüfe:

  • shops.tsx akzeptiert shops? und rendert ShopGrid wenn shops.length > 0
  • shop-detail.tsx akzeptiert floors? und hat Slot für centerPlanEmbed (ReactNode)
  • Alle Texte kommen aus Props (keine Hardcodes)
  • openingHours ist typisiert (kein any)
  • next/link für interne Links verwendet
  • next/image für alle Bilder verwendet
  • Fallbacks für alle optionalen Daten implementiert
  • Responsive Design getestet (Mobile, Tablet, Desktop)
  • Accessibility geprüft (ARIA-Labels, Keyboard-Navigation)
  • Performance optimiert (Lazy Loading, Priority für Hero)
  • SEO-freundlich (semantisches HTML, strukturierte Daten)
  • DATA_REQUIREMENTS.md erstellt
  • IMPLEMENTATION_NOTES.md erstellt
  • README.md mit Template-Beschreibung erstellt

Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /templates/website-prompt