Skip to main content

v0 Template Scaffold (Referenz)

Diese Struktur MUSS jedes Template haben. Nutze dies als Referenz beim Generieren.


Ordnerstruktur

components/templates/[template-name]/
├── index.ts # Exportiert Template
├── Template.tsx # Haupt-Template (Server Component)
├── types.ts # TypeScript Interfaces
├── components/
│ ├── Header.tsx # Header-Komponente
│ ├── Hero.tsx # Hero-Section
│ ├── TileGrid.tsx # Tiles-Grid (oder Custom Layout)
│ ├── Footer.tsx # Footer-Komponente
│ └── [weitere Subcomponents] # Nur wenn nötig
├── DATA_REQUIREMENTS.md # Daten-Anforderungen
├── IMPLEMENTATION_NOTES.md # Implementierungs-Hinweise
└── README.md # Template-Dokumentation

Datei-Inhalte (Templates)

index.ts

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

types.ts

// Öffnungszeiten-Typisierung
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;
}

export interface CenterWebsiteTemplateProps {
centerConfig: {
centerId: string;
slug: string;
name: string;
description?: string;

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

colors: {
primary: string;
secondary?: string;
accent?: string;
background?: string;
};

contact?: {
address?: {
street?: string;
city?: string;
zip?: string;
country?: string;
};
phone?: string;
email?: string;
website?: string;
};

openingHours?: OpeningHours;

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

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;
}>;
}

Template.tsx (Server Component)

// KEIN 'use client' hier!
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 && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{shops.slice(0, 6).map((shop) => (
<div key={shop.id} className="p-4 rounded-lg" style={{ boxShadow: 'var(--shadow-card)' }}>
<h3 className="font-bold">{shop.name}</h3>
</div>
))}
</div>
)}
{events.length > 0 && (
<div className="mt-8">
<h3 className="text-xl font-bold mb-4">Aktuelle Events</h3>
{events.slice(0, 3).map((event) => (
<div key={event.id} className="mb-4">
<h4 className="font-semibold">{event.title}</h4>
</div>
))}
</div>
)}
</section>
)}

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

components/Header.tsx

import Link from 'next/link';
import Image from 'next/image';
import type { CenterWebsiteTemplateProps } from '../types';

interface HeaderProps {
centerConfig: CenterWebsiteTemplateProps['centerConfig'];
}

export function Header({ centerConfig }: HeaderProps) {
return (
<header className="sticky top-0 z-50 bg-background/95 backdrop-blur-xl border-b">
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
{centerConfig.branding.logo && (
<Image
src={centerConfig.branding.logo}
alt={centerConfig.name}
width={200}
height={60}
className="h-12 w-auto"
/>
)}
<h1 className="text-2xl font-bold">{centerConfig.name}</h1>
</div>
</header>
);
}

components/Hero.tsx

import Image from 'next/image';
import type { CenterWebsiteTemplateProps } from '../types';

interface HeroProps {
centerConfig: CenterWebsiteTemplateProps['centerConfig'];
}

export function Hero({ centerConfig }: HeroProps) {
return (
<section
className="relative h-[60vh] flex items-center justify-center"
style={{ backgroundColor: centerConfig.colors.primary }}
>
{centerConfig.branding.heroImage ? (
<Image
src={centerConfig.branding.heroImage}
alt={centerConfig.name}
fill
className="object-cover opacity-50"
/>
) : (
<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">
<h2 className="text-5xl font-bold mb-4">{centerConfig.name}</h2>
{centerConfig.description && (
<p className="text-xl">{centerConfig.description}</p>
)}
</div>
</section>
);
}

components/TileGrid.tsx

import Link from 'next/link';
import Image from 'next/image';
import type { CenterWebsiteTemplateProps } from '../types';

interface TileGridProps {
tiles: CenterWebsiteTemplateProps['tiles'];
centerConfig: CenterWebsiteTemplateProps['centerConfig'];
}

export function TileGrid({ tiles, centerConfig }: TileGridProps) {
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 p-8 transition-all duration-300 hover:scale-105"
style={{
boxShadow: 'var(--shadow-tile, 0 12px 30px -8px rgba(0,0,0,0.15))',
backgroundColor: tile.color || centerConfig.colors.primary
}}
>
{tile.image && (
<Image
src={tile.image}
alt={tile.title}
fill
className="object-cover opacity-20 group-hover:opacity-30 transition-opacity"
/>
)}
<div className="relative z-10">
<h3 className="text-2xl 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

import type { CenterWebsiteTemplateProps } from '../types';

interface FooterProps {
centerConfig: CenterWebsiteTemplateProps['centerConfig'];
}

export function Footer({ centerConfig }: FooterProps) {
const hasContact = centerConfig.contact?.address || centerConfig.contact?.phone || centerConfig.contact?.email;

return (
<footer className="bg-gray-900 text-white py-12">
<div className="container mx-auto px-4">
{hasContact ? (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h4 className="font-bold mb-4">Kontakt</h4>
{centerConfig.contact?.address && (
<p>
{centerConfig.contact.address.street && `${centerConfig.contact.address.street}, `}
{centerConfig.contact.address.zip && `${centerConfig.contact.address.zip} `}
{centerConfig.contact.address.city}
</p>
)}
{centerConfig.contact?.phone && (
<p>Tel: {centerConfig.contact.phone}</p>
)}
{centerConfig.contact?.email && (
<p>E-Mail: {centerConfig.contact.email}</p>
)}
</div>

{centerConfig.openingHours && (
<div>
<h4 className="font-bold mb-4">Öffnungszeiten</h4>
{centerConfig.openingHours.weekdays && (
<p>{centerConfig.openingHours.weekdays}</p>
)}
{centerConfig.openingHours.saturday && (
<p>{centerConfig.openingHours.saturday}</p>
)}
{centerConfig.openingHours.sunday && (
<p>{centerConfig.openingHours.sunday}</p>
)}
</div>
)}
</div>
) : (
<div className="text-center">
<p>{centerConfig.name}</p>
</div>
)}
</div>
</footer>
);
}

DATA_REQUIREMENTS.md

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

## Zwingend erforderlich (MUSS befüllt sein)
- `centerConfig.name` - Center-Name
- `centerConfig.colors.primary` - Primary-Farbe (HEX)
- `centerConfig.slug` - Center-Slug (für URLs)

## Optional (empfohlen)
- `centerConfig.branding.logo` - Logo-URL
- `centerConfig.branding.heroImage` - Hero-Bild
- `tiles[]` - Mindestens 3 Tiles für gute Darstellung
- `centerConfig.contact.address` - Kontakt-Adresse
- `centerConfig.openingHours` - Öffnungszeiten

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

IMPLEMENTATION_NOTES.md

# Implementierungs-Hinweise

## Besonderheiten
- [Template-spezifische Hinweise, z.B. "Verwendet Bento-Grid Layout"]

## Integration
- Template wird in `apps/center-website/components/templates/[template-name]/` gespeichert
- Wird im Dashboard als Template-Option hinzugefügt
- Pro Center konfigurierbar über Website-Konfiguration

## Anpassungen
- Farben können pro Center angepasst werden
- Layout kann über CSS-Variablen angepasst werden
- Tiles werden dynamisch aus CockpitOS befüllt

README.md

# [Template-Name]

[Kurze Beschreibung des Templates]

## Features
- [Liste der Features]

## Verwendung
```typescript
import { CenterWebsiteTemplate } from './components/templates/[template-name]';

<CenterWebsiteTemplate
centerConfig={centerConfig}
tiles={tiles}
shops={shops}
events={events}
news={news}
services={services}
/>

Anpassungen

  • [Wie kann das Template angepasst werden]

---

## Checkliste für v0

Beim Generieren sicherstellen:

- [ ] Alle Dateien vorhanden (index.ts, Template.tsx, types.ts, components/)
- [ ] Template.tsx ist Server Component (kein 'use client')
- [ ] next/link für interne Links
- [ ] next/image für Bilder
- [ ] openingHours typisiert (kein any)
- [ ] Fallbacks implementiert
- [ ] DATA_REQUIREMENTS.md vorhanden
- [ ] IMPLEMENTATION_NOTES.md vorhanden
- [ ] README.md vorhanden

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