mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 16:14:12 +00:00
feat: add content blocks and global settings
Blocks for page builder: - HeroBlock: hero sections with CTA - TextBlock: rich text content - ImageTextBlock: image with text layout - CardGridBlock: grid of cards - CTABlock: call-to-action sections - QuoteBlock: testimonial quotes - VideoBlock: embedded videos - DividerBlock: visual separators - ContactFormBlock: contact forms - NewsletterBlock: newsletter signup - ProcessStepsBlock: step-by-step processes - TimelineBlock: timeline displays - TestimonialsBlock: testimonial carousels - PostsListBlock: blog post listings Globals: - Navigation: site navigation structure - SiteSettings: general site configuration - SEOSettings: default SEO settings per tenant 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
885ec93748
commit
95c9d2a4bc
18 changed files with 1820 additions and 0 deletions
67
src/blocks/CTABlock.ts
Normal file
67
src/blocks/CTABlock.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const CTABlock: Block = {
|
||||
slug: 'cta-block',
|
||||
labels: {
|
||||
singular: 'Call-to-Action',
|
||||
plural: 'Call-to-Actions',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'headline',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'buttons',
|
||||
type: 'array',
|
||||
label: 'Buttons',
|
||||
maxRows: 2,
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Link',
|
||||
},
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'primary',
|
||||
label: 'Stil',
|
||||
options: [
|
||||
{ label: 'Primär', value: 'primary' },
|
||||
{ label: 'Sekundär', value: 'secondary' },
|
||||
{ label: 'Outline', value: 'outline' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'dark',
|
||||
label: 'Hintergrundfarbe',
|
||||
options: [
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Akzent', value: 'accent' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
68
src/blocks/CardGridBlock.ts
Normal file
68
src/blocks/CardGridBlock.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const CardGridBlock: Block = {
|
||||
slug: 'card-grid-block',
|
||||
labels: {
|
||||
singular: 'Karten-Raster',
|
||||
plural: 'Karten-Raster',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'headline',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'cards',
|
||||
type: 'array',
|
||||
label: 'Karten',
|
||||
minRows: 1,
|
||||
maxRows: 6,
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'text',
|
||||
label: 'Link',
|
||||
},
|
||||
{
|
||||
name: 'linkText',
|
||||
type: 'text',
|
||||
defaultValue: 'mehr',
|
||||
label: 'Link-Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
48
src/blocks/ContactFormBlock.ts
Normal file
48
src/blocks/ContactFormBlock.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const ContactFormBlock: Block = {
|
||||
slug: 'contact-form-block',
|
||||
labels: {
|
||||
singular: 'Kontaktformular',
|
||||
plural: 'Kontaktformulare',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'headline',
|
||||
type: 'text',
|
||||
defaultValue: 'Kontakt',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'recipientEmail',
|
||||
type: 'email',
|
||||
defaultValue: 'info@porwoll.de',
|
||||
label: 'Empfänger E-Mail',
|
||||
},
|
||||
{
|
||||
name: 'showPhone',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Telefon anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showAddress',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Adresse anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showSocials',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Social Media anzeigen',
|
||||
},
|
||||
],
|
||||
}
|
||||
33
src/blocks/DividerBlock.ts
Normal file
33
src/blocks/DividerBlock.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const DividerBlock: Block = {
|
||||
slug: 'divider-block',
|
||||
labels: {
|
||||
singular: 'Trenner',
|
||||
plural: 'Trenner',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'space',
|
||||
label: 'Stil',
|
||||
options: [
|
||||
{ label: 'Linie', value: 'line' },
|
||||
{ label: 'Abstand', value: 'space' },
|
||||
{ label: 'Punkte', value: 'dots' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'spacing',
|
||||
type: 'select',
|
||||
defaultValue: 'medium',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein', value: 'small' },
|
||||
{ label: 'Mittel', value: 'medium' },
|
||||
{ label: 'Groß', value: 'large' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
76
src/blocks/HeroBlock.ts
Normal file
76
src/blocks/HeroBlock.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const HeroBlock: Block = {
|
||||
slug: 'hero-block',
|
||||
labels: {
|
||||
singular: 'Hero',
|
||||
plural: 'Heroes',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'backgroundImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Hintergrundbild',
|
||||
},
|
||||
{
|
||||
name: 'headline',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subline',
|
||||
type: 'textarea',
|
||||
label: 'Unterüberschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'alignment',
|
||||
type: 'select',
|
||||
defaultValue: 'center',
|
||||
label: 'Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Zentriert', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'overlay',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Dunkles Overlay',
|
||||
},
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Call-to-Action',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'text',
|
||||
label: 'Link',
|
||||
},
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'primary',
|
||||
label: 'Button-Stil',
|
||||
options: [
|
||||
{ label: 'Primär', value: 'primary' },
|
||||
{ label: 'Sekundär', value: 'secondary' },
|
||||
{ label: 'Outline', value: 'outline' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
58
src/blocks/ImageTextBlock.ts
Normal file
58
src/blocks/ImageTextBlock.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const ImageTextBlock: Block = {
|
||||
slug: 'image-text-block',
|
||||
labels: {
|
||||
singular: 'Bild & Text',
|
||||
plural: 'Bild & Text',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
required: true,
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'imagePosition',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Bildposition',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'headline',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
label: 'Inhalt',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Call-to-Action',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'text',
|
||||
label: 'Link',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
156
src/blocks/NewsletterBlock.ts
Normal file
156
src/blocks/NewsletterBlock.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Newsletter Block
|
||||
* Anmeldeformular für Newsletter
|
||||
*/
|
||||
export const NewsletterBlock: Block = {
|
||||
slug: 'newsletter-block',
|
||||
labels: {
|
||||
singular: 'Newsletter Anmeldung',
|
||||
plural: 'Newsletter Anmeldungen',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
defaultValue: 'Newsletter abonnieren',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
defaultValue: 'Erhalten Sie regelmäßig Updates und Neuigkeiten direkt in Ihr Postfach.',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'inline',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Inline (Eingabe + Button nebeneinander)', value: 'inline' },
|
||||
{ label: 'Gestapelt (untereinander)', value: 'stacked' },
|
||||
{ label: 'Mit Bild (50/50)', value: 'with-image' },
|
||||
{ label: 'Minimal (nur Input)', value: 'minimal' },
|
||||
{ label: 'Card (Karte)', value: 'card' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'with-image',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'imagePosition',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Bildposition',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'with-image',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'collectName',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Name abfragen',
|
||||
},
|
||||
{
|
||||
name: 'showInterests',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Interessen zur Auswahl anbieten',
|
||||
},
|
||||
{
|
||||
name: 'availableInterests',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
label: 'Verfügbare Interessen',
|
||||
options: [
|
||||
{ label: 'Allgemeine Updates', value: 'general' },
|
||||
{ label: 'Blog-Artikel', value: 'blog' },
|
||||
{ label: 'Produkt-News', value: 'products' },
|
||||
{ label: 'Angebote & Aktionen', value: 'offers' },
|
||||
{ label: 'Events', value: 'events' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showInterests,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'buttonText',
|
||||
type: 'text',
|
||||
defaultValue: 'Anmelden',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'placeholderEmail',
|
||||
type: 'text',
|
||||
defaultValue: 'Ihre E-Mail-Adresse',
|
||||
label: 'Placeholder E-Mail',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'successMessage',
|
||||
type: 'textarea',
|
||||
defaultValue:
|
||||
'Vielen Dank! Bitte bestätigen Sie Ihre E-Mail-Adresse über den Link in der Bestätigungsmail.',
|
||||
label: 'Erfolgsmeldung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'errorMessage',
|
||||
type: 'text',
|
||||
defaultValue: 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.',
|
||||
label: 'Fehlermeldung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'privacyText',
|
||||
type: 'textarea',
|
||||
defaultValue:
|
||||
'Mit der Anmeldung akzeptieren Sie unsere Datenschutzerklärung. Sie können sich jederzeit abmelden.',
|
||||
label: 'Datenschutz-Hinweis',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'privacyLink',
|
||||
type: 'text',
|
||||
defaultValue: '/datenschutz',
|
||||
label: 'Link zur Datenschutzerklärung',
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
type: 'text',
|
||||
defaultValue: 'website',
|
||||
label: 'Tracking-Quelle',
|
||||
admin: {
|
||||
description: 'Wird gespeichert um zu tracken, wo die Anmeldung erfolgte',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'accent',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Akzentfarbe', value: 'accent' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
159
src/blocks/PostsListBlock.ts
Normal file
159
src/blocks/PostsListBlock.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Posts List Block
|
||||
* Zeigt Blog-Artikel, News oder andere Post-Typen an
|
||||
*/
|
||||
export const PostsListBlock: Block = {
|
||||
slug: 'posts-list-block',
|
||||
labels: {
|
||||
singular: 'Blog/News Liste',
|
||||
plural: 'Blog/News Listen',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'postType',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'blog',
|
||||
label: 'Beitragstyp',
|
||||
options: [
|
||||
{ label: 'Blog-Artikel', value: 'blog' },
|
||||
{ label: 'News/Aktuelles', value: 'news' },
|
||||
{ label: 'Pressemitteilungen', value: 'press' },
|
||||
{ label: 'Ankündigungen', value: 'announcement' },
|
||||
{ label: 'Alle Beiträge', value: 'all' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid (Karten)', value: 'grid' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Featured + Grid', value: 'featured' },
|
||||
{ label: 'Kompakt (Sidebar)', value: 'compact' },
|
||||
{ label: 'Masonry', value: 'masonry' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.layout === 'grid' ||
|
||||
siblingData?.layout === 'featured' ||
|
||||
siblingData?.layout === 'masonry',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 6,
|
||||
min: 1,
|
||||
max: 24,
|
||||
label: 'Anzahl Beiträge',
|
||||
},
|
||||
{
|
||||
name: 'showFeaturedOnly',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Nur hervorgehobene anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'filterByCategory',
|
||||
type: 'relationship',
|
||||
relationTo: 'categories',
|
||||
hasMany: true,
|
||||
label: 'Nach Kategorien filtern',
|
||||
admin: {
|
||||
description: 'Leer = alle Kategorien',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showExcerpt',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kurzfassung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showDate',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Datum anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showAuthor',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Autor anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showCategory',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kategorie anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showPagination',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Pagination anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showReadMore',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: '"Alle anzeigen" Link',
|
||||
},
|
||||
{
|
||||
name: 'readMoreLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Alle Beiträge anzeigen',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showReadMore,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'readMoreLink',
|
||||
type: 'text',
|
||||
defaultValue: '/blog',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showReadMore,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
145
src/blocks/ProcessStepsBlock.ts
Normal file
145
src/blocks/ProcessStepsBlock.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Process Steps Block
|
||||
* Zeigt Prozess-Schritte / "So funktioniert es"
|
||||
*/
|
||||
export const ProcessStepsBlock: Block = {
|
||||
slug: 'process-steps-block',
|
||||
labels: {
|
||||
singular: 'Prozess/Schritte',
|
||||
plural: 'Prozess/Schritte',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
defaultValue: 'So funktioniert es',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'horizontal',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Horizontal (nebeneinander)', value: 'horizontal' },
|
||||
{ label: 'Vertikal (untereinander)', value: 'vertical' },
|
||||
{ label: 'Alternierend (Zickzack)', value: 'alternating' },
|
||||
{ label: 'Mit Verbindungslinien', value: 'connected' },
|
||||
{ label: 'Timeline-Stil', value: 'timeline' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showNumbers',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Schritt-Nummern anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showIcons',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Icons anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'steps',
|
||||
type: 'array',
|
||||
label: 'Schritte',
|
||||
minRows: 2,
|
||||
maxRows: 10,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Schritt-Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'text',
|
||||
label: 'Icon',
|
||||
admin: {
|
||||
description: 'Emoji oder Icon-Name (z.B. "📞", "✓", "1")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild (optional)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Call-to-Action',
|
||||
fields: [
|
||||
{
|
||||
name: 'show',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'CTA anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
defaultValue: 'Jetzt starten',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'href',
|
||||
type: 'text',
|
||||
label: 'Link',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'variant',
|
||||
type: 'select',
|
||||
defaultValue: 'default',
|
||||
label: 'Button-Stil',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Ghost', value: 'ghost' },
|
||||
{ label: 'Light', value: 'light' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
47
src/blocks/QuoteBlock.ts
Normal file
47
src/blocks/QuoteBlock.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const QuoteBlock: Block = {
|
||||
slug: 'quote-block',
|
||||
labels: {
|
||||
singular: 'Zitat',
|
||||
plural: 'Zitate',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'quote',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
label: 'Zitat',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
type: 'text',
|
||||
label: 'Autor',
|
||||
// Author bleibt nicht lokalisiert - Namen sind sprachunabhängig
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
type: 'text',
|
||||
label: 'Rolle/Position',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Autorenbild',
|
||||
},
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'simple',
|
||||
label: 'Stil',
|
||||
options: [
|
||||
{ label: 'Einfach', value: 'simple' },
|
||||
{ label: 'Hervorgehoben', value: 'highlighted' },
|
||||
{ label: 'Mit Bild', value: 'with-image' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
145
src/blocks/TestimonialsBlock.ts
Normal file
145
src/blocks/TestimonialsBlock.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Testimonials Block
|
||||
* Zeigt Kundenstimmen aus der Testimonials Collection
|
||||
*/
|
||||
export const TestimonialsBlock: Block = {
|
||||
slug: 'testimonials-block',
|
||||
labels: {
|
||||
singular: 'Testimonials',
|
||||
plural: 'Testimonials',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
defaultValue: 'Das sagen unsere Kunden',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'slider',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Slider/Karussell', value: 'slider' },
|
||||
{ label: 'Grid (Karten)', value: 'grid' },
|
||||
{ label: 'Einzeln (Featured)', value: 'single' },
|
||||
{ label: 'Masonry', value: 'masonry' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.layout === 'grid' || siblingData?.layout === 'masonry',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'displayMode',
|
||||
type: 'select',
|
||||
defaultValue: 'all',
|
||||
label: 'Auswahl',
|
||||
options: [
|
||||
{ label: 'Alle aktiven Testimonials', value: 'all' },
|
||||
{ label: 'Handverlesene Auswahl', value: 'selected' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'selectedTestimonials',
|
||||
type: 'relationship',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
relationTo: 'testimonials' as any,
|
||||
hasMany: true,
|
||||
label: 'Testimonials auswählen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.displayMode === 'selected',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 6,
|
||||
min: 1,
|
||||
max: 20,
|
||||
label: 'Maximale Anzahl',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.displayMode === 'all',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showRating',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Sterne-Bewertung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showImage',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Foto anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showCompany',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Unternehmen anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showSource',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Quelle anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'autoplay',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Automatisch wechseln',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'slider',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'autoplaySpeed',
|
||||
type: 'number',
|
||||
defaultValue: 5000,
|
||||
min: 2000,
|
||||
max: 15000,
|
||||
label: 'Wechselintervall (ms)',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.layout === 'slider' && siblingData?.autoplay,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'light',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Akzentfarbe', value: 'accent' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
29
src/blocks/TextBlock.ts
Normal file
29
src/blocks/TextBlock.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const TextBlock: Block = {
|
||||
slug: 'text-block',
|
||||
labels: {
|
||||
singular: 'Text',
|
||||
plural: 'Texte',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
label: 'Inhalt',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'select',
|
||||
defaultValue: 'medium',
|
||||
label: 'Breite',
|
||||
options: [
|
||||
{ label: 'Schmal', value: 'narrow' },
|
||||
{ label: 'Mittel', value: 'medium' },
|
||||
{ label: 'Volle Breite', value: 'full' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
128
src/blocks/TimelineBlock.ts
Normal file
128
src/blocks/TimelineBlock.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Timeline Block (erweitert)
|
||||
* Chronologische Darstellung von Ereignissen
|
||||
*/
|
||||
export const TimelineBlock: Block = {
|
||||
slug: 'timeline-block',
|
||||
labels: {
|
||||
singular: 'Timeline',
|
||||
plural: 'Timelines',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'vertical',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Vertikal (Standard)', value: 'vertical' },
|
||||
{ label: 'Alternierend (links/rechts)', value: 'alternating' },
|
||||
{ label: 'Horizontal (Zeitleiste)', value: 'horizontal' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showConnector',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Verbindungslinie anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'markerStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'dot',
|
||||
label: 'Marker-Stil',
|
||||
options: [
|
||||
{ label: 'Punkt', value: 'dot' },
|
||||
{ label: 'Nummer', value: 'number' },
|
||||
{ label: 'Icon', value: 'icon' },
|
||||
{ label: 'Jahr/Datum', value: 'date' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Einträge',
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'year',
|
||||
type: 'text',
|
||||
label: 'Jahr/Datum',
|
||||
admin: {
|
||||
description: 'z.B. "2024", "Januar 2024", "15.03.2024"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'text',
|
||||
label: 'Icon',
|
||||
admin: {
|
||||
description: 'Emoji oder Icon-Name',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild (optional)',
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'group',
|
||||
label: 'Link (optional)',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Link-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'href',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
37
src/blocks/VideoBlock.ts
Normal file
37
src/blocks/VideoBlock.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const VideoBlock: Block = {
|
||||
slug: 'video-block',
|
||||
labels: {
|
||||
singular: 'Video',
|
||||
plural: 'Videos',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'videoUrl',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Video-URL',
|
||||
admin: {
|
||||
description: 'YouTube oder Vimeo URL',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'text',
|
||||
label: 'Beschriftung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
type: 'select',
|
||||
defaultValue: '16:9',
|
||||
label: 'Seitenverhältnis',
|
||||
options: [
|
||||
{ label: '16:9', value: '16:9' },
|
||||
{ label: '4:3', value: '4:3' },
|
||||
{ label: '1:1', value: '1:1' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
16
src/blocks/index.ts
Normal file
16
src/blocks/index.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export { HeroBlock } from './HeroBlock'
|
||||
export { TextBlock } from './TextBlock'
|
||||
export { ImageTextBlock } from './ImageTextBlock'
|
||||
export { CardGridBlock } from './CardGridBlock'
|
||||
export { QuoteBlock } from './QuoteBlock'
|
||||
export { CTABlock } from './CTABlock'
|
||||
export { ContactFormBlock } from './ContactFormBlock'
|
||||
export { TimelineBlock } from './TimelineBlock'
|
||||
export { DividerBlock } from './DividerBlock'
|
||||
export { VideoBlock } from './VideoBlock'
|
||||
|
||||
// Neue universelle Blocks
|
||||
export { PostsListBlock } from './PostsListBlock'
|
||||
export { TestimonialsBlock } from './TestimonialsBlock'
|
||||
export { NewsletterBlock } from './NewsletterBlock'
|
||||
export { ProcessStepsBlock } from './ProcessStepsBlock'
|
||||
131
src/globals/Navigation.ts
Normal file
131
src/globals/Navigation.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
|
||||
export const Navigation: GlobalConfig = {
|
||||
slug: 'navigation',
|
||||
label: 'Navigation',
|
||||
access: {
|
||||
read: () => true,
|
||||
update: ({ req }) => !!req.user,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'mainMenu',
|
||||
type: 'array',
|
||||
label: 'Hauptmenü',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
defaultValue: 'page',
|
||||
options: [
|
||||
{ label: 'Seite', value: 'page' },
|
||||
{ label: 'Eigener Link', value: 'custom' },
|
||||
{ label: 'Untermenü', value: 'submenu' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
type: 'relationship',
|
||||
relationTo: 'pages',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.type === 'page',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.type === 'custom',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'openInNewTab',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'submenu',
|
||||
type: 'array',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.type === 'submenu',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'linkType',
|
||||
type: 'select',
|
||||
defaultValue: 'page',
|
||||
options: [
|
||||
{ label: 'Seite', value: 'page' },
|
||||
{ label: 'Eigener Link', value: 'custom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
type: 'relationship',
|
||||
relationTo: 'pages',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.linkType === 'page',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.linkType === 'custom',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'footerMenu',
|
||||
type: 'array',
|
||||
label: 'Footer-Menü',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'linkType',
|
||||
type: 'select',
|
||||
defaultValue: 'page',
|
||||
options: [
|
||||
{ label: 'Seite', value: 'page' },
|
||||
{ label: 'Eigener Link', value: 'custom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
type: 'relationship',
|
||||
relationTo: 'pages',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.linkType === 'page',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.linkType === 'custom',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
385
src/globals/SEOSettings.ts
Normal file
385
src/globals/SEOSettings.ts
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
|
||||
/**
|
||||
* SEO Settings Global
|
||||
*
|
||||
* Globale SEO-Einstellungen pro Tenant für:
|
||||
* - Meta-Defaults
|
||||
* - Social Media Profile
|
||||
* - Schema.org Organisation
|
||||
* - robots.txt Regeln
|
||||
*/
|
||||
export const SEOSettings: GlobalConfig = {
|
||||
slug: 'seo-settings',
|
||||
label: 'SEO Einstellungen',
|
||||
admin: {
|
||||
group: 'Einstellungen',
|
||||
description: 'Globale SEO-Konfiguration und Schema.org Daten',
|
||||
},
|
||||
fields: [
|
||||
// === META DEFAULTS ===
|
||||
{
|
||||
name: 'metaDefaults',
|
||||
type: 'group',
|
||||
label: 'Standard Meta-Tags',
|
||||
fields: [
|
||||
{
|
||||
name: 'titleSuffix',
|
||||
type: 'text',
|
||||
label: 'Titel-Suffix',
|
||||
defaultValue: '| Website',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Wird an jeden Seitentitel angehängt (z.B. "Startseite | Firmenname")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'defaultDescription',
|
||||
type: 'textarea',
|
||||
label: 'Standard Meta-Beschreibung',
|
||||
maxLength: 160,
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Wird verwendet, wenn keine spezifische Beschreibung gesetzt ist',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'defaultOgImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Standard Social Media Bild',
|
||||
admin: {
|
||||
description: 'Fallback-Bild für Social Media Shares (empfohlen: 1200x630px)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
type: 'text',
|
||||
hasMany: true,
|
||||
label: 'Standard Keywords',
|
||||
admin: {
|
||||
description: 'Globale Keywords (optional, geringe SEO-Relevanz)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === ORGANIZATION SCHEMA ===
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'group',
|
||||
label: 'Organisation (Schema.org)',
|
||||
admin: {
|
||||
description: 'Daten für das Organization Schema',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Firmenname',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'legalName',
|
||||
type: 'text',
|
||||
label: 'Rechtlicher Name',
|
||||
admin: {
|
||||
description: 'Vollständiger rechtlicher Firmenname (falls abweichend)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Unternehmensbeschreibung',
|
||||
maxLength: 300,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'logo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Logo',
|
||||
admin: {
|
||||
description: 'Firmenlogo für Schema.org (min. 112x112px, empfohlen: 512x512px)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'foundingDate',
|
||||
type: 'date',
|
||||
label: 'Gründungsdatum',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayOnly',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === CONTACT INFO ===
|
||||
{
|
||||
name: 'contact',
|
||||
type: 'group',
|
||||
label: 'Kontaktdaten',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
label: 'Telefon',
|
||||
admin: {
|
||||
description: 'Im Format +49 123 456789',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fax',
|
||||
type: 'text',
|
||||
label: 'Fax',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === ADDRESS ===
|
||||
{
|
||||
name: 'address',
|
||||
type: 'group',
|
||||
label: 'Adresse',
|
||||
fields: [
|
||||
{
|
||||
name: 'street',
|
||||
type: 'text',
|
||||
label: 'Straße & Hausnummer',
|
||||
},
|
||||
{
|
||||
name: 'postalCode',
|
||||
type: 'text',
|
||||
label: 'Postleitzahl',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
type: 'text',
|
||||
label: 'Stadt',
|
||||
},
|
||||
{
|
||||
name: 'region',
|
||||
type: 'text',
|
||||
label: 'Bundesland/Region',
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
type: 'text',
|
||||
label: 'Land',
|
||||
defaultValue: 'Deutschland',
|
||||
},
|
||||
{
|
||||
name: 'countryCode',
|
||||
type: 'text',
|
||||
label: 'Ländercode',
|
||||
defaultValue: 'DE',
|
||||
admin: {
|
||||
description: 'ISO 3166-1 Alpha-2 Code',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === GEO LOCATION ===
|
||||
{
|
||||
name: 'geo',
|
||||
type: 'group',
|
||||
label: 'Geo-Koordinaten',
|
||||
admin: {
|
||||
description: 'Für Local Business Schema',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'latitude',
|
||||
type: 'number',
|
||||
label: 'Breitengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'longitude',
|
||||
type: 'number',
|
||||
label: 'Längengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === SOCIAL PROFILES ===
|
||||
{
|
||||
name: 'socialProfiles',
|
||||
type: 'array',
|
||||
label: 'Social Media Profile',
|
||||
admin: {
|
||||
description: 'URLs zu Social Media Profilen (für sameAs Schema)',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'platform',
|
||||
type: 'select',
|
||||
label: 'Plattform',
|
||||
options: [
|
||||
{ label: 'Facebook', value: 'facebook' },
|
||||
{ label: 'Instagram', value: 'instagram' },
|
||||
{ label: 'Twitter/X', value: 'twitter' },
|
||||
{ label: 'LinkedIn', value: 'linkedin' },
|
||||
{ label: 'YouTube', value: 'youtube' },
|
||||
{ label: 'TikTok', value: 'tiktok' },
|
||||
{ label: 'Pinterest', value: 'pinterest' },
|
||||
{ label: 'XING', value: 'xing' },
|
||||
{ label: 'Andere', value: 'other' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Profil-URL',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === LOCAL BUSINESS ===
|
||||
{
|
||||
name: 'localBusiness',
|
||||
type: 'group',
|
||||
label: 'Local Business',
|
||||
admin: {
|
||||
description: 'Zusätzliche Daten für lokale Unternehmen',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'enabled',
|
||||
type: 'checkbox',
|
||||
label: 'Local Business Schema aktivieren',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
label: 'Geschäftstyp',
|
||||
options: [
|
||||
{ label: 'Lokales Unternehmen', value: 'LocalBusiness' },
|
||||
{ label: 'Arztpraxis', value: 'Physician' },
|
||||
{ label: 'Zahnarzt', value: 'Dentist' },
|
||||
{ label: 'Anwaltskanzlei', value: 'Attorney' },
|
||||
{ label: 'Restaurant', value: 'Restaurant' },
|
||||
{ label: 'Hotel', value: 'Hotel' },
|
||||
{ label: 'Einzelhandel', value: 'Store' },
|
||||
{ label: 'Fitnessstudio', value: 'HealthClub' },
|
||||
{ label: 'Friseursalon', value: 'HairSalon' },
|
||||
{ label: 'Autowerkstatt', value: 'AutoRepair' },
|
||||
{ label: 'Immobilienmakler', value: 'RealEstateAgent' },
|
||||
{ label: 'Finanzdienstleister', value: 'FinancialService' },
|
||||
{ label: 'IT-Dienstleister', value: 'ProfessionalService' },
|
||||
{ label: 'Pflegedienst', value: 'MedicalBusiness' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data) => data?.localBusiness?.enabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'priceRange',
|
||||
type: 'select',
|
||||
label: 'Preiskategorie',
|
||||
options: [
|
||||
{ label: '€ (Günstig)', value: '€' },
|
||||
{ label: '€€ (Mittel)', value: '€€' },
|
||||
{ label: '€€€ (Gehoben)', value: '€€€' },
|
||||
{ label: '€€€€ (Premium)', value: '€€€€' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data) => data?.localBusiness?.enabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'openingHours',
|
||||
type: 'array',
|
||||
label: 'Öffnungszeiten',
|
||||
admin: {
|
||||
condition: (data) => data?.localBusiness?.enabled,
|
||||
description: 'Im Format "Mo-Fr 09:00-17:00"',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'specification',
|
||||
type: 'text',
|
||||
label: 'Öffnungszeit',
|
||||
admin: {
|
||||
placeholder: 'Mo-Fr 09:00-17:00',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === ROBOTS SETTINGS ===
|
||||
{
|
||||
name: 'robots',
|
||||
type: 'group',
|
||||
label: 'Robots & Indexierung',
|
||||
fields: [
|
||||
{
|
||||
name: 'allowIndexing',
|
||||
type: 'checkbox',
|
||||
label: 'Indexierung erlauben',
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
description: 'Wenn deaktiviert, wird die gesamte Website von Suchmaschinen ausgeschlossen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'additionalDisallow',
|
||||
type: 'text',
|
||||
hasMany: true,
|
||||
label: 'Zusätzliche Pfade ausschließen',
|
||||
admin: {
|
||||
description: 'Pfade die nicht gecrawlt werden sollen (z.B. "/intern", "/preview")',
|
||||
condition: (data) => data?.robots?.allowIndexing,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// === VERIFICATION CODES ===
|
||||
{
|
||||
name: 'verification',
|
||||
type: 'group',
|
||||
label: 'Verifizierungscodes',
|
||||
admin: {
|
||||
description: 'Codes für Suchmaschinen-Verifizierung',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'google',
|
||||
type: 'text',
|
||||
label: 'Google Search Console',
|
||||
admin: {
|
||||
description: 'Meta-Tag Content (nur der Code, nicht das gesamte Tag)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bing',
|
||||
type: 'text',
|
||||
label: 'Bing Webmaster Tools',
|
||||
},
|
||||
{
|
||||
name: 'yandex',
|
||||
type: 'text',
|
||||
label: 'Yandex Webmaster',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
92
src/globals/SiteSettings.ts
Normal file
92
src/globals/SiteSettings.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
|
||||
export const SiteSettings: GlobalConfig = {
|
||||
slug: 'site-settings',
|
||||
label: 'Site Settings',
|
||||
access: {
|
||||
read: () => true,
|
||||
update: ({ req }) => !!req.user,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'siteName',
|
||||
type: 'text',
|
||||
defaultValue: 'porwoll.de',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'siteTagline',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'logo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'favicon',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
},
|
||||
{
|
||||
name: 'contact',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
type: 'textarea',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'footer',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'copyrightText',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'showSocialLinks',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'seo',
|
||||
type: 'group',
|
||||
label: 'SEO',
|
||||
fields: [
|
||||
{
|
||||
name: 'defaultMetaTitle',
|
||||
type: 'text',
|
||||
label: 'Standard Meta-Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'defaultMetaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Standard Meta-Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'defaultOgImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Standard Social Media Bild',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
Loading…
Reference in a new issue