Add migration scripts and preload participation awards script
- Created migration index file to manage database migrations. - Added script to preload participation awards and related blog posts into the CMS. - Introduced new types and default data for application phases. - Added award articles data structure for the upcoming awards.
This commit is contained in:
@@ -4,7 +4,7 @@ import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Link, useLocation } from '@/spa/router';
|
||||
import { ArrowRight, Upload } from 'lucide-react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import LoadingScreen from '@/spa/components/ui/LoadingScreen';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
import {
|
||||
@@ -36,6 +36,10 @@ const SEP = 'rgba(239,191,4,0.2)';
|
||||
const LINE = 'rgba(239,191,4,0.35)';
|
||||
const FF_DISPLAY = '"IBM Plex Sans", sans-serif';
|
||||
const FF_BODY = '"Inter", sans-serif';
|
||||
const HIDE_MEMBERSHIP_FOOTER_ENTRY = true;
|
||||
|
||||
const isMembershipFooterLink = (link?: { label?: string; path?: string }) =>
|
||||
link?.path === '/mitglied-werden' || link?.label?.trim().toLowerCase() === 'mitglied werden';
|
||||
|
||||
/* ── SocialLink helper ─────────────────────────────────── */
|
||||
const SocialLink: React.FC<{ href:string; icon:SpaIconName; label:string }> = ({ href, icon, label }) => {
|
||||
@@ -54,24 +58,29 @@ const SocialLink: React.FC<{ href:string; icon:SpaIconName; label:string }> = ({
|
||||
const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, footer = defaultFooterData }) => {
|
||||
const NAV = header.navItems;
|
||||
const SECONDARY = header.secondaryLinks;
|
||||
const SOCIAL = header.socialLinks;
|
||||
const linkedInHeaderSocial = header.socialLinks.filter((item) => item.icon === 'linkedin');
|
||||
const linkedInFooterSocial = footer.socialLinks.filter((item) => item.icon === 'linkedin');
|
||||
const HEADER_SOCIAL = linkedInHeaderSocial.length
|
||||
? linkedInHeaderSocial
|
||||
: defaultHeaderData.socialLinks.filter((item) => item.icon === 'linkedin');
|
||||
const FOOTER_SOCIAL = linkedInFooterSocial.length
|
||||
? linkedInFooterSocial
|
||||
: defaultFooterData.socialLinks.filter((item) => item.icon === 'linkedin');
|
||||
const logo = header.logo?.url || defaultHeaderData.logo?.url || '';
|
||||
const footerLogo = footer.logo?.url || defaultFooterData.logo?.url || '';
|
||||
const headerCtas = header.ctas;
|
||||
const footerCtas = footer.ctas;
|
||||
const fallbackHeaderCtas = defaultHeaderData.ctas;
|
||||
const fallbackFooterCtas = defaultFooterData.ctas;
|
||||
const footerMembershipCta = footerCtas[1] || fallbackFooterCtas[1];
|
||||
const showFooterMembershipCta =
|
||||
!HIDE_MEMBERSHIP_FOOTER_ENTRY || !isMembershipFooterLink(footerMembershipCta);
|
||||
const [open, setOpen] = useState(false); // desktop overlay
|
||||
const [activeKey, setActive] = useState<string | null>(null); // desktop hover subnav
|
||||
const [mobileOpen, setMobileOpen] = useState(false); // mobile sidebar overlay
|
||||
const [expandKey, setExpand] = useState<string | null>(null); // mobile accordion
|
||||
const [lang, setLang] = useState(header.languages[0] || defaultHeaderData.languages[0]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [showLoader, setShowLoader] = useState(() => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
sessionStorage.removeItem('bmp-intro-seen');
|
||||
return true;
|
||||
});
|
||||
const [showLoader, setShowLoader] = useState(true);
|
||||
const [formVisible, setFormVisible] = useState(false);
|
||||
const location = useLocation();
|
||||
const firstLinkRef = useRef<HTMLAnchorElement>(null);
|
||||
@@ -220,24 +229,10 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
|
||||
<span style={{ fontSize:8, color:'rgba(5,26,135,0.5)', letterSpacing:2, userSelect:'none' }}>◆</span>
|
||||
</div>
|
||||
|
||||
{/* RIGHT – social + lang */}
|
||||
<div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'flex-end', gap:20, padding:'0 28px', height:'100%' }}>
|
||||
{/* RIGHT – social */}
|
||||
<div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'flex-end', padding:'0 28px', height:'100%' }}>
|
||||
<div style={{ display:'flex', gap:14, alignItems:'center' }}>
|
||||
{SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
</div>
|
||||
<div style={{ width:1, height:20, background:'rgba(239,191,4,0.3)' }} />
|
||||
<div style={{ display:'flex', alignItems:'center', gap:8 }}>
|
||||
{header.languages.map((l, i) => (
|
||||
<React.Fragment key={l}>
|
||||
{i===1 && <span style={{ color:'rgba(239,191,4,.35)', fontSize: 14 }}>|</span>}
|
||||
<button
|
||||
onClick={() => setLang(l)}
|
||||
style={{ fontSize:10, fontWeight:600, letterSpacing:'.1em', textTransform:'uppercase', background:'none', border:'none', cursor:'pointer', padding:'1px 0', lineHeight:1, color:lang===l ? GOLD : MUTED, transition:'color .2s', fontFamily:FF_DISPLAY }}
|
||||
onMouseEnter={e => { if (lang !== l) e.currentTarget.style.color = '#fff'; }}
|
||||
onMouseLeave={e => { if (lang !== l) e.currentTarget.style.color = MUTED; }}
|
||||
>{l}</button>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{HEADER_SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -326,7 +321,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
|
||||
transition:'opacity .2s ease, transform .2s ease',
|
||||
pointerEvents: activeKey===item.key ? 'all' : 'none',
|
||||
}}>
|
||||
<div style={{ fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.22em', color:'#009EE0', marginBottom:16, fontFamily:FF_DISPLAY }}>
|
||||
<div style={{ fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.22em', color:'rgba(255,255,255,.4)', marginBottom:16, fontFamily:FF_DISPLAY }}>
|
||||
{header.overviewLabel}
|
||||
</div>
|
||||
<div style={{ fontSize: 16, fontWeight:700, textTransform:'uppercase', letterSpacing:'.1em', color:'#fff', marginBottom:20, fontFamily:FF_DISPLAY }}>
|
||||
@@ -352,20 +347,8 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
|
||||
<div style={{ padding:'40px 80px 40px 48px', display:'flex', flexDirection:'column', gap:24 }}>
|
||||
|
||||
<div style={{ display:'flex', alignItems:'center', gap:20, flexWrap:'wrap' }}>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:8 }}>
|
||||
{header.languages.map((l,i) => (
|
||||
<React.Fragment key={l}>
|
||||
{i===1 && <span style={{ color:'rgba(239,191,4,.35)', fontSize: 14 }}>|</span>}
|
||||
<button onClick={() => setLang(l)}
|
||||
style={{ fontSize:12, fontWeight:600, letterSpacing:'.1em', textTransform:'uppercase', background:'none', border:'none', cursor:'pointer', padding:0, color:lang===l ? GOLD : MUTED, transition:'color .2s' }}
|
||||
onMouseEnter={e=>{ if(lang!==l) e.currentTarget.style.color='#fff'; }}
|
||||
onMouseLeave={e=>{ if(lang!==l) e.currentTarget.style.color=MUTED; }}
|
||||
>{l}</button>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display:'flex', gap:14 }}>
|
||||
{SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
{HEADER_SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -385,39 +368,34 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
|
||||
onMouseEnter={e=>{ e.currentTarget.style.background='#FFD130'; e.currentTarget.style.boxShadow='0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }}
|
||||
onMouseLeave={e=>{ e.currentTarget.style.background='#EFBF04'; e.currentTarget.style.boxShadow='none'; }}
|
||||
>{headerCtas[0]?.label || fallbackHeaderCtas[0].label}</Link>
|
||||
<Link to={headerCtas[1]?.path || fallbackHeaderCtas[1].path} onClick={() => setOpen(false)}
|
||||
style={{ display:'inline-block', background:'transparent', color:MUTED, fontSize: 15, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', padding:'10px 24px', textDecoration:'none', transition:'border-color .2s, color .2s', borderRadius:0, fontFamily:FF_DISPLAY, border:'1.5px solid rgba(239,191,4,0.35)' }}
|
||||
onMouseEnter={e=>{ e.currentTarget.style.borderColor='#EFBF04'; e.currentTarget.style.color='#EFBF04'; }}
|
||||
onMouseLeave={e=>{ e.currentTarget.style.borderColor='rgba(239,191,4,0.35)'; e.currentTarget.style.color=MUTED; }}
|
||||
>{headerCtas[1]?.label || fallbackHeaderCtas[1].label}</Link>
|
||||
|
||||
<div style={{ height:'1px', background:'rgba(239,191,4,0.12)', margin:'4px 0' }} />
|
||||
|
||||
<Link to={headerCtas[2]?.path || fallbackHeaderCtas[2].path} onClick={() => setOpen(false)}
|
||||
style={{
|
||||
display:'flex', alignItems:'center', gap:10,
|
||||
background:'rgba(255,255,255,0.04)',
|
||||
color:'rgba(255,255,255,0.45)',
|
||||
background:'transparent',
|
||||
color:MUTED,
|
||||
fontSize:11, fontWeight:700,
|
||||
textTransform:'uppercase', letterSpacing:'.14em',
|
||||
padding:'10px 18px',
|
||||
textDecoration:'none',
|
||||
fontFamily:FF_DISPLAY,
|
||||
border:'1px dashed rgba(255,255,255,0.15)',
|
||||
border:'1px solid rgba(239,191,4,0.28)',
|
||||
transition:'color .2s, border-color .2s, background .2s',
|
||||
}}
|
||||
onMouseEnter={e=>{
|
||||
e.currentTarget.style.color='#fff';
|
||||
e.currentTarget.style.borderColor='rgba(255,255,255,0.4)';
|
||||
e.currentTarget.style.background='rgba(255,255,255,0.07)';
|
||||
e.currentTarget.style.color=GOLD;
|
||||
e.currentTarget.style.borderColor='rgba(239,191,4,0.7)';
|
||||
e.currentTarget.style.background='rgba(239,191,4,0.05)';
|
||||
}}
|
||||
onMouseLeave={e=>{
|
||||
e.currentTarget.style.color='rgba(255,255,255,0.45)';
|
||||
e.currentTarget.style.borderColor='rgba(255,255,255,0.15)';
|
||||
e.currentTarget.style.background='rgba(255,255,255,0.04)';
|
||||
e.currentTarget.style.color=MUTED;
|
||||
e.currentTarget.style.borderColor='rgba(239,191,4,0.28)';
|
||||
e.currentTarget.style.background='transparent';
|
||||
}}
|
||||
>
|
||||
<Upload size={12} strokeWidth={2} /> {headerCtas[2]?.label || fallbackHeaderCtas[2].label}
|
||||
{headerCtas[2]?.label || fallbackHeaderCtas[2].label}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -560,31 +538,16 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
|
||||
<Link to={headerCtas[0]?.path || fallbackHeaderCtas[0].path} onClick={() => setMobileOpen(false)}
|
||||
style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:52, background:GOLD, color:'#101828', fontSize:14, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', textDecoration:'none', fontFamily:FF_DISPLAY }}
|
||||
>{headerCtas[0]?.label || fallbackHeaderCtas[0].label}</Link>
|
||||
<Link to={headerCtas[1]?.path || fallbackHeaderCtas[1].path} onClick={() => setMobileOpen(false)}
|
||||
style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:50, background:'transparent', color:'#fff', fontSize:14, fontWeight:700, textTransform:'uppercase', letterSpacing:'.12em', textDecoration:'none', fontFamily:FF_DISPLAY, border:`1.5px solid ${LINE}` }}
|
||||
>{headerCtas[1]?.label || fallbackHeaderCtas[1].label}</Link>
|
||||
<Link to={headerCtas[2]?.path || fallbackHeaderCtas[2].path} onClick={() => setMobileOpen(false)}
|
||||
style={{ display:'flex', alignItems:'center', justifyContent:'center', gap:8, minHeight:46, background:'none', color:'rgba(255,255,255,0.5)', fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.14em', textDecoration:'none', fontFamily:FF_DISPLAY, border:'1px dashed rgba(255,255,255,0.2)' }}
|
||||
><Upload size={13} strokeWidth={2} /> {headerCtas[2]?.label || fallbackHeaderCtas[2].label}</Link>
|
||||
style={{ display:'flex', alignItems:'center', justifyContent:'center', minHeight:46, background:'none', color:MUTED, fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.14em', textDecoration:'none', fontFamily:FF_DISPLAY, border:'1px solid rgba(239,191,4,0.28)' }}
|
||||
>{headerCtas[2]?.label || fallbackHeaderCtas[2].label}</Link>
|
||||
</div>
|
||||
|
||||
{/* Footer zone: lang + social + secondary */}
|
||||
{/* Footer zone: social + secondary */}
|
||||
<div style={{ marginTop:8, padding:'20px 24px 28px', borderTop:`1px solid ${SEP}`, display:'flex', flexDirection:'column', gap:18 }}>
|
||||
{/* Lang + social row */}
|
||||
<div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:10 }}>
|
||||
{header.languages.map((l, i) => (
|
||||
<React.Fragment key={l}>
|
||||
{i === 1 && <span style={{ color:LINE, fontSize:13 }}>|</span>}
|
||||
<button onClick={() => setLang(l)}
|
||||
style={{ fontSize:12, fontWeight:600, letterSpacing:'.1em', textTransform:'uppercase', background:'none', border:'none', cursor:'pointer', color:lang===l ? GOLD : MUTED, padding:'4px 0', lineHeight:1, fontFamily:FF_DISPLAY }}
|
||||
>{l}</button>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display:'flex', gap:18 }}>
|
||||
{SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
</div>
|
||||
{/* Social row */}
|
||||
<div style={{ display:'flex', gap:18 }}>
|
||||
{HEADER_SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
</div>
|
||||
{/* Secondary links */}
|
||||
<div style={{ display:'flex', flexWrap:'wrap', gap:'0 20px' }}>
|
||||
@@ -770,31 +733,33 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
|
||||
}}
|
||||
>{footerCtas[0]?.label || fallbackFooterCtas[0].label}</Link>
|
||||
|
||||
<Link
|
||||
to={footerCtas[1]?.path || fallbackFooterCtas[1].path}
|
||||
style={{
|
||||
fontFamily: FF_DISPLAY,
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.12em',
|
||||
color: MUTED,
|
||||
border: '1.5px solid rgba(239,191,4,0.35)',
|
||||
padding: '8px 22px',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-block',
|
||||
borderRadius: 2,
|
||||
transition: 'border-color 0.15s, color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.borderColor = GOLD;
|
||||
e.currentTarget.style.color = GOLD;
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.borderColor = 'rgba(239,191,4,0.35)';
|
||||
e.currentTarget.style.color = MUTED;
|
||||
}}
|
||||
>{footerCtas[1]?.label || fallbackFooterCtas[1].label}</Link>
|
||||
{showFooterMembershipCta && (
|
||||
<Link
|
||||
to={footerCtas[1]?.path || fallbackFooterCtas[1].path}
|
||||
style={{
|
||||
fontFamily: FF_DISPLAY,
|
||||
fontSize: 16,
|
||||
fontWeight: 700,
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.12em',
|
||||
color: MUTED,
|
||||
border: '1.5px solid rgba(239,191,4,0.35)',
|
||||
padding: '8px 22px',
|
||||
textDecoration: 'none',
|
||||
display: 'inline-block',
|
||||
borderRadius: 2,
|
||||
transition: 'border-color 0.15s, color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.borderColor = GOLD;
|
||||
e.currentTarget.style.color = GOLD;
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.borderColor = 'rgba(239,191,4,0.35)';
|
||||
e.currentTarget.style.color = MUTED;
|
||||
}}
|
||||
>{footerCtas[1]?.label || fallbackFooterCtas[1].label}</Link>
|
||||
)}
|
||||
|
||||
<Link
|
||||
to={footerCtas[2]?.path || fallbackFooterCtas[2].path}
|
||||
@@ -937,7 +902,7 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
|
||||
{footer.copyright}
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 20, alignItems: 'center' }}>
|
||||
{SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
{FOOTER_SOCIAL.map(s => <SocialLink key={s.label} {...s} />)}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user