@@ -26,6 +26,17 @@ interface SiteSettings {
2626 hasOpenaiApiKey : boolean
2727 openaiModel : string
2828 maintenanceMode : boolean
29+ socialLinks : string [ ] | null
30+ contactAddress : {
31+ venueName ?: string
32+ street ?: string
33+ postalCode ?: string
34+ locality ?: string
35+ region ?: string
36+ country ?: string
37+ lat ?: number
38+ lng ?: number
39+ } | null
2940}
3041
3142interface MenuItem {
@@ -74,6 +85,10 @@ export default function SetupPage() {
7485 const [ openaiModel , setOpenaiModel ] = useState ( 'auto' )
7586 const [ showApiKey , setShowApiKey ] = useState ( false )
7687 const [ maintenanceMode , setMaintenanceMode ] = useState ( false )
88+ const [ socialLinksText , setSocialLinksText ] = useState ( '' )
89+ const [ address , setAddress ] = useState ( {
90+ venueName : '' , street : '' , postalCode : '' , locality : '' , region : '' , country : '' , lat : '' , lng : '' ,
91+ } )
7792
7893 const fetchData = useCallback ( async ( ) => {
7994 try {
@@ -96,6 +111,13 @@ export default function SetupPage() {
96111 setHasOpenaiApiKey ( settingsData . hasOpenaiApiKey ?? false )
97112 setOpenaiModel ( settingsData . openaiModel ?? 'auto' )
98113 setMaintenanceMode ( settingsData . maintenanceMode ?? false )
114+ setSocialLinksText ( Array . isArray ( settingsData . socialLinks ) ? settingsData . socialLinks . join ( '\n' ) : '' )
115+ const ca = settingsData . contactAddress ?? { }
116+ setAddress ( {
117+ venueName : ca . venueName ?? '' , street : ca . street ?? '' , postalCode : ca . postalCode ?? '' ,
118+ locality : ca . locality ?? '' , region : ca . region ?? '' , country : ca . country ?? '' ,
119+ lat : ca . lat != null ? String ( ca . lat ) : '' , lng : ca . lng != null ? String ( ca . lng ) : '' ,
120+ } )
99121
100122 // Separate top-level items (with children already included from API)
101123 const topLevel = menuData . filter ( ( item ) => ! item . parentId )
@@ -116,11 +138,23 @@ export default function SetupPage() {
116138 setSaved ( false )
117139 setError ( '' )
118140 try {
141+ const socialLinks = socialLinksText . split ( '\n' ) . map ( ( s ) => s . trim ( ) ) . filter ( Boolean )
142+ const contactAddress = {
143+ venueName : address . venueName . trim ( ) || undefined ,
144+ street : address . street . trim ( ) || undefined ,
145+ postalCode : address . postalCode . trim ( ) || undefined ,
146+ locality : address . locality . trim ( ) || undefined ,
147+ region : address . region . trim ( ) || undefined ,
148+ country : address . country . trim ( ) || undefined ,
149+ lat : address . lat . trim ( ) ? Number ( address . lat ) : undefined ,
150+ lng : address . lng . trim ( ) ? Number ( address . lng ) : undefined ,
151+ }
119152 const res = await fetch ( '/api/admin/settings' , {
120153 method : 'PUT' ,
121154 headers : { 'Content-Type' : 'application/json' } ,
122155 body : JSON . stringify ( {
123156 siteName, logoUrl, backgroundImage, darkMode, themeSlug, logoMode, openaiModel, maintenanceMode,
157+ socialLinks, contactAddress,
124158 ...( openaiApiKey ? { openaiApiKey } : { } ) ,
125159 } ) ,
126160 } )
@@ -485,6 +519,69 @@ export default function SetupPage() {
485519 </ div >
486520 </ div >
487521
522+ { /* Contact & Social Media (structured data / JSON-LD) */ }
523+ < div className = "glass rounded-2xl p-6" >
524+ < h2 className = "text-lg font-display font-bold text-brand-text mb-1" >
525+ Kontakt & Social Media
526+ </ h2 >
527+ < p className = "text-sm text-brand-text-muted mb-4" >
528+ Fließt in die strukturierten Daten (JSON-LD) für Google & Co. — Veranstaltungsort und Social-Profile des Festivals.
529+ </ p >
530+
531+ < div className = "space-y-4" >
532+ < div >
533+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" >
534+ Social-Media-Profile
535+ </ label >
536+ < textarea
537+ value = { socialLinksText }
538+ onChange = { ( e ) => setSocialLinksText ( e . target . value ) }
539+ rows = { 3 }
540+ className = "input-glass w-full text-sm font-mono"
541+ placeholder = { 'https://www.facebook.com/...\nhttps://www.instagram.com/e.ventschau/' }
542+ />
543+ < p className = "text-[11px] text-brand-text-muted mt-1.5" >
544+ Eine URL pro Zeile (Facebook, Instagram, YouTube …). Erscheint als „sameAs“ in den strukturierten Daten.
545+ </ p >
546+ </ div >
547+
548+ < div className = "grid grid-cols-1 sm:grid-cols-2 gap-3" >
549+ < div className = "sm:col-span-2" >
550+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > Veranstaltungsort</ label >
551+ < input type = "text" value = { address . venueName } onChange = { ( e ) => setAddress ( { ...address , venueName : e . target . value } ) } className = "input-glass w-full text-sm" placeholder = "Hof Thiele" />
552+ </ div >
553+ < div className = "sm:col-span-2" >
554+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > Straße & Nr.</ label >
555+ < input type = "text" value = { address . street } onChange = { ( e ) => setAddress ( { ...address , street : e . target . value } ) } className = "input-glass w-full text-sm" placeholder = "Am Bruch 1-2" />
556+ </ div >
557+ < div >
558+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > PLZ</ label >
559+ < input type = "text" value = { address . postalCode } onChange = { ( e ) => setAddress ( { ...address , postalCode : e . target . value } ) } className = "input-glass w-full text-sm" placeholder = "21371" />
560+ </ div >
561+ < div >
562+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > Ort</ label >
563+ < input type = "text" value = { address . locality } onChange = { ( e ) => setAddress ( { ...address , locality : e . target . value } ) } className = "input-glass w-full text-sm" placeholder = "Tosterglope-Ventschau" />
564+ </ div >
565+ < div >
566+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > Bundesland</ label >
567+ < input type = "text" value = { address . region } onChange = { ( e ) => setAddress ( { ...address , region : e . target . value } ) } className = "input-glass w-full text-sm" placeholder = "Niedersachsen" />
568+ </ div >
569+ < div >
570+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > Land (ISO)</ label >
571+ < input type = "text" value = { address . country } onChange = { ( e ) => setAddress ( { ...address , country : e . target . value } ) } className = "input-glass w-full text-sm" placeholder = "DE" />
572+ </ div >
573+ < div >
574+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > Breitengrad (lat)</ label >
575+ < input type = "text" inputMode = "decimal" value = { address . lat } onChange = { ( e ) => setAddress ( { ...address , lat : e . target . value } ) } className = "input-glass w-full text-sm font-mono" placeholder = "53.207676" />
576+ </ div >
577+ < div >
578+ < label className = "block text-xs font-medium text-brand-text-muted mb-1" > Längengrad (lng)</ label >
579+ < input type = "text" inputMode = "decimal" value = { address . lng } onChange = { ( e ) => setAddress ( { ...address , lng : e . target . value } ) } className = "input-glass w-full text-sm font-mono" placeholder = "10.859330" />
580+ </ div >
581+ </ div >
582+ </ div >
583+ </ div >
584+
488585 { /* Background Image */ }
489586 < div className = "glass rounded-2xl p-6" >
490587 < h2 className = "text-lg font-display font-bold text-brand-text mb-1" >
0 commit comments