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
- Öffne v0.dev → Neuer Chat
- Zuerst: Screenshots deines Wunsch-Layouts einfügen (z.B. von Websites, die dir gefallen)
- Dann: Den kopierten Prompt einfügen
- 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:
| Seite | Route | Beschreibung |
|---|---|---|
| Home | / | Hero, Tiles, Sections |
| Shops Übersicht | /{slug}/shops | Shop-Grid, Kategorien |
| Shop-Kategorie | /{slug}/shops/categories/[category] | Shops nach Kategorie |
| Shop Detail | /{slug}/shops/[shopSlug] | Einzelner Shop |
| Centerplan | /{slug}/wayfinding | Slot für WayfindingMap – CockpitOS injiziert die Karte |
| Services Übersicht | /{slug}/services | Services-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}/impressum | Impressum |
| Datenschutz | /{slug}/datenschutz | Datenschutz |
| Öffnungszeiten | /{slug}/opening-hours | Öffnungszeiten |
| Anfahrt & Parken | /{slug}/parking | Parken, Anfahrt |
| Kontakt | /{slug}/kontakt | Kontaktformular |
| 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}/gastronomy | Themenwelten, Restaurants |
| Büros/Offices | /{slug}/offices | Bü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.
| PageType | Datei | Props (empfangen) |
|---|---|---|
home | index.tsx | centerConfig, tiles, categories?, categoryThemes?, highlights?, hostname? |
shops | pages/shops.tsx | centerConfig, categories, categoryThemes?, pageContent, centerSlug, hostname?, shops? |
shop-detail | pages/shop-detail.tsx | centerConfig, shop, similarShops?, centerSlug, backUrl, hostname?, floors? |
aktuelles | pages/aktuelles.tsx | news, events, offers, jobs, pageContent, backUrl, centerSlug, centerShortName, contentCardSettings?, hostname? |
services | pages/services.tsx | centerConfig, services, centerSlug, hostname? |
gastronomy | pages/gastronomy.tsx | themes, pageContent, centerSlug |
wayfinding | pages/wayfinding.tsx | centerConfig, centerplan, floors |
offices | pages/offices.tsx | offices, themes, centerSlug, pageContent |
parking | pages/parking.tsx | centerConfig (name, parking, contact, openingHours), backUrl |
opening-hours | pages/opening-hours.tsx | shops, services, centerConfig (name, openingHours), backUrl |
impressum | pages/impressum.tsx | centerConfig, pageContent, centerSlug |
datenschutz | pages/datenschutz.tsx | centerConfig, pageContent, centerSlug |
kontakt | pages/kontakt.tsx | centerConfig, contacts, centerSlug |
to-go | pages/to-go.tsx | qrCodeId, 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:
shopsan 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/heroSubtitleauspageContent?.heroTitleoderpageContent?.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,floorsan 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) →highlightedSvgIdsfürloc.shop?.id === highlightShopId.InteractiveFloorPlanmitsvgContent,mapImage,highlightedSvgIds.- Fallback: statisches
mapImagewenn kein SVG.
- Props:
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()auscenterConfig.branding. - v0 kann Klassen wie
cev-btn-accent,cev-headingdefinieren, die auf diese Variablen verweisen.
Datenfluss-Übersicht
| Seite | Route lädt | Template erhält |
|---|---|---|
| shops | loadShops, loadCategories, loadPageContent | shops, categories, categoryThemes, pageContent |
| shop-detail | loadShops, loadCenterplan | shop, 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
5. LINKS & BILDER (Next.js Best Practices)
Links
// 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:
- Korrektem Props-Interface (typisiert)
- Server Component (Client nur bei Interaktivität)
- next/link & next/image
- Struktur: Header, Hero, TileGrid, Footer
- Fallbacks für leere Daten
- DATA_REQUIREMENTS.md + IMPLEMENTATION_NOTES.md
- 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;
Fehler 3: Normale <a> Tags für interne Links
// 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
Header-Komponente (Links mit centerSlug – wichtig für v0-Preview)
// 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>
);
}
Footer mit Kontakt-Fallbacks
// 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ürcenterPlanEmbed(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