Skip to main content

v0 Template Scaffold (Reference)

This structure MUST have every template. Use this as a reference when generating.


Folder Structure

components/templates/[template-name]/
├── index.ts # Exports the template
├── Template.tsx # Main template (Server Component)
├── types.ts # TypeScript interfaces
├── components/
│ ├── Header.tsx # Header component
│ ├── Hero.tsx # Hero section
│ ├── TileGrid.tsx # Tiles grid (or custom layout)
│ ├── Footer.tsx # Footer component
│ └── [other subcomponents] # Only if necessary
├── DATA_REQUIREMENTS.md # Data requirements
├── IMPLEMENTATION_NOTES.md # Implementation notes
└── README.md # Template documentation

File Contents (Templates)

index.ts

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

types.ts

// Opening hours typing
interface DayHours {
open: string; // Format: "HH:MM" (e.g. "10:00")
close: string; // Format: "HH:MM" (e.g. "20:00")
closed?: boolean; // true if closed
}

interface OpeningHours {
// Option 1: Structured format (preferred)
regularHours?: {
monday?: DayHours | null;
tuesday?: DayHours | null;
wednesday?: DayHours | null;
thursday?: DayHours | null;
friday?: DayHours | null;
saturday?: DayHours | null;
sunday?: DayHours | null;
};

// Option 2: Simplified format (fallback)
weekdays?: string; // e.g. "Mon - Sat: 10:00 - 20:00"
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)

// NO 'use client' here!
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 or 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">Current 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">Contact</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>Email: {centerConfig.contact.email}</p>
)}
</div>

{centerConfig.openingHours && (
<div>
<h4 className="font-bold mb-4">Opening Hours</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

# Data Requirements for [Template-Name]

## Mandatory (MUST be filled)
- `centerConfig.name` - Center name
- `centerConfig.colors.primary` - Primary color (HEX)
- `centerConfig.slug` - Center slug (for URLs)

## Optional (recommended)
- `centerConfig.branding.logo` - Logo URL
- `centerConfig.branding.heroImage` - Hero image
- `tiles[]` - At least 3 tiles for good display
- `centerConfig.contact.address` - Contact address
- `centerConfig.openingHours` - Opening hours

## Defaults (used if not available)
- Logo: `/placeholder-logo.png`
- Hero: Gradient with primary color
- Tiles: Fallback to shops/events highlights
- Contact: Footer shows only center name

IMPLEMENTATION_NOTES.md

# Implementation Notes

## Special Features
- [Template-specific notes, e.g. "Uses Bento-Grid layout"]

## Integration
- Template is stored in `apps/center-website/components/templates/[template-name]/`
- Added as a template option in the dashboard
- Configurable per center through website configuration

## Adjustments
- Colors can be customized per center
- Layout can be adjusted via CSS variables
- Tiles are dynamically filled from cockpitOS

README.md

# [Template-Name]

[Short description of the template]

## Features
- [List of features]

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

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

Adjustments

  • [How to customize the template]

---

## Checklist for v0

When generating, ensure:

- [ ] All files are present (index.ts, Template.tsx, types.ts, components/)
- [ ] Template.tsx is a server component (no 'use client')
- [ ] next/link for internal links
- [ ] next/image for images
- [ ] openingHours typed (no any)
- [ ] Fallbacks implemented
- [ ] DATA_REQUIREMENTS.md is present
- [ ] IMPLEMENTATION_NOTES.md is present
- [ ] README.md is present

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