feat: implement login intent filtering for navigation links and remove unused components

This commit is contained in:
syntaxbullet
2026-07-09 12:19:40 +02:00
parent 57f2d58a8f
commit 5a08350b6a
7 changed files with 20 additions and 99 deletions

View File

@@ -1,27 +0,0 @@
plugins = []
headers = []
redirects = []
[functions]
[functions."*"]
[build]
publish = "/Users/gsebastianp/.gemini/antigravity/scratch/bmp-website/dist"
publishOrigin = "ui"
commandOrigin = "ui"
command = "npm run build"
[build.environment]
[build.processing]
[build.processing.css]
[build.processing.html]
[build.processing.images]
[build.processing.js]
[build.services]

View File

@@ -1,4 +1,5 @@
import { Button, type ButtonProps } from '@/components/ui/button'
import { isLoginIntentHref } from '@/spa/loginIntent'
import { cn } from '@/utilities/ui'
import Link from 'next/link'
import React from 'react'
@@ -40,7 +41,7 @@ export const CMSLink: React.FC<CMSLinkType> = (props) => {
}`
: url
if (!href) return null
if (!href || isLoginIntentHref(href)) return null
const size = appearance === 'link' ? 'clear' : sizeFromProps
const newTabProps = newTab ? { rel: 'noopener noreferrer', target: '_blank' } : {}

View File

@@ -1,4 +1,5 @@
import type { Footer, Header, Media } from '@/payload-types'
import { isLoginIntentHref } from '@/spa/loginIntent'
export type SpaIconName = 'instagram' | 'linkedin' | 'facebook'
export type SpaLink = { label: string; path: string }
@@ -187,6 +188,9 @@ const withoutParticipationCriteriaLink = (items: SpaHeaderNavItem[]) =>
: item,
)
export const withoutLoginCtas = (items: SpaCtaLink[]) =>
items.filter((item) => item.variant !== 'login' && !isLoginIntentHref(item.path))
type FooterPartnerLogo = {
alt?: string
image?: unknown
@@ -208,7 +212,7 @@ export function normalizeHeaderData(header?: Partial<Header>): SpaHeaderData {
socialLinks: header?.socialLinks?.length
? (header.socialLinks as unknown as SpaSocialLink[])
: defaultHeaderData.socialLinks,
ctas: header?.ctas?.length ? (header.ctas as unknown as SpaCtaLink[]) : defaultHeaderData.ctas,
ctas: withoutLoginCtas(header?.ctas?.length ? (header.ctas as unknown as SpaCtaLink[]) : defaultHeaderData.ctas),
}
}
@@ -217,7 +221,7 @@ export function normalizeFooterData(footer?: Partial<Footer>): SpaFooterData {
...defaultFooterData,
...footer,
logo: mediaToSpa(footer?.logo, defaultFooterData.logo),
ctas: footer?.ctas?.length ? (footer.ctas as unknown as SpaCtaLink[]) : defaultFooterData.ctas,
ctas: withoutLoginCtas(footer?.ctas?.length ? (footer.ctas as unknown as SpaCtaLink[]) : defaultFooterData.ctas),
linkColumns: footer?.linkColumns?.length
? (footer.linkColumns as unknown as SpaFooterData['linkColumns'])
: defaultFooterData.linkColumns,

View File

@@ -14,6 +14,7 @@ import {
type SpaFooterData,
type SpaHeaderData,
type SpaIconName,
withoutLoginCtas,
} from '@/spa/cmsNavigation';
interface LayoutProps { children: React.ReactNode; header?: SpaHeaderData; footer?: SpaFooterData; }
@@ -70,10 +71,10 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
: 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 headerCtas = withoutLoginCtas(header.ctas);
const footerCtas = withoutLoginCtas(footer.ctas);
const fallbackHeaderCtas = withoutLoginCtas(defaultHeaderData.ctas);
const fallbackFooterCtas = withoutLoginCtas(defaultFooterData.ctas);
const applicationPhase = useApplicationPhase();
const showFloatingApplicationCta = applicationPhase?.activePhase === APPLICATION_PHASE_OPEN;
const footerMembershipCta = footerCtas[1] || fallbackFooterCtas[1];
@@ -373,34 +374,6 @@ const Layout: React.FC<LayoutProps> = ({ children, header = defaultHeaderData, f
onMouseLeave={e=>{ e.currentTarget.style.background='#EFBF04'; e.currentTarget.style.boxShadow='none'; }}
>{headerCtas[0]?.label || fallbackHeaderCtas[0].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:'transparent',
color:MUTED,
fontSize:11, fontWeight:700,
textTransform:'uppercase', letterSpacing:'.14em',
padding:'10px 18px',
textDecoration:'none',
fontFamily:FF_DISPLAY,
border:'1px solid rgba(239,191,4,0.28)',
transition:'color .2s, border-color .2s, background .2s',
}}
onMouseEnter={e=>{
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=MUTED;
e.currentTarget.style.borderColor='rgba(239,191,4,0.28)';
e.currentTarget.style.background='transparent';
}}
>
{headerCtas[2]?.label || fallbackHeaderCtas[2].label}
</Link>
</div>
</div>
@@ -542,9 +515,6 @@ 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[2]?.path || fallbackHeaderCtas[2].path} onClick={() => setMobileOpen(false)}
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: social + secondary */}

1
src/spa/loginIntent.ts Normal file
View File

@@ -0,0 +1 @@
export const isLoginIntentHref = (href: string) => href === '/admin' || href.startsWith('/admin/')

View File

@@ -210,14 +210,6 @@ const Participation: React.FC = () => {
<CheckCircle2 size={18} style={{ color: GOLD, flexShrink: 0 }} />
<span style={{ fontFamily: FF, fontSize: 18, fontWeight: 600, color: '#101828' }}>{fallbackText(eligibility.nudgeText, participationContent.eligibility.nudgeText)}</span>
</div>
<Link
to={fallbackText(eligibility.nudgeCta?.url, participationContent.eligibility.nudgeCta.url)}
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '13px 28px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, transition: 'opacity 0.2s', whiteSpace: 'nowrap' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
>
{fallbackText(eligibility.nudgeCta?.label, participationContent.eligibility.nudgeCta.label)} <ArrowRight size={13} />
</Link>
</div>
</section>
@@ -324,15 +316,6 @@ const Participation: React.FC = () => {
<>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, marginBottom: 10 }}>
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.28em', fontWeight: 700 }}>{fallbackText(applicationForm.formEyebrow, participationContent.applicationForm.formEyebrow)}</span>
<Link
to={fallbackText(applicationForm.uploadCta?.url, participationContent.applicationForm.uploadCta.url)}
style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(17,29,85,0.55)', border: '1px solid rgba(17,29,85,0.2)', padding: '7px 14px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 6, transition: 'color 0.15s, border-color 0.15s', whiteSpace: 'nowrap', flexShrink: 0 }}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.color = '#111D55'; (e.currentTarget as HTMLElement).style.borderColor = '#111D55'; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = 'rgba(17,29,85,0.55)'; (e.currentTarget as HTMLElement).style.borderColor = 'rgba(17,29,85,0.2)'; }}
>
<svg width="10" height="10" viewBox="0 0 12 12" fill="none"><path d="M6 1v7M3 4l3-3 3 3M1 10h10" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>
{fallbackText(applicationForm.uploadCta?.label, participationContent.applicationForm.uploadCta.label)}
</Link>
</div>
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.2rem, 1.8vw, 1.6rem)', fontWeight: 900, color: '#111D55', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, marginBottom: 24 }}>{fallbackText(applicationForm.formTitle, participationContent.applicationForm.formTitle)}</h3>
<BewerbungsForm theme="gold" content={form} />
@@ -354,9 +337,6 @@ const Participation: React.FC = () => {
<span style={{ fontFamily: FB, fontSize: 14, color: 'rgba(255,255,255,0.55)', lineHeight: 1.6, maxWidth: 520 }}>
{fallbackText(applicationForm.uploadPrompt, participationContent.applicationForm.uploadPrompt)}
</span>
<Link to={fallbackText(applicationForm.uploadFooterCta?.url, participationContent.applicationForm.uploadFooterCta.url)} style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'rgba(239,191,4,0.7)', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 6, borderBottom: '1px solid rgba(239,191,4,0.3)', paddingBottom: 1 }}>
{fallbackText(applicationForm.uploadFooterCta?.label, participationContent.applicationForm.uploadFooterCta.label)}
</Link>
</>
) : (
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: isMobile ? '0 6px' : '0 10px' }}>
@@ -412,7 +392,6 @@ function BewerbungswegeSection({ content }: { content?: ApplicationWaysContent }
const ways = fallbackArray(section.ways, participationContent.applicationWays.ways).filter(
(way) => !isFutureAwardApplicationWay(way),
);
const fallbackCta = { ...participationContent.applicationWays.fallbackCta, ...(section.fallbackCta || {}) };
return (
<section style={{ background: CREAM, position: 'relative', overflow: 'hidden', isolation: 'isolate' }}>
<MunichSkylineBg />
@@ -466,10 +445,6 @@ function BewerbungswegeSection({ content }: { content?: ApplicationWaysContent }
</div>
))}
</div>
{/* CTA */}
<div style={{ padding: '18px 22px 22px' }}>
<a href={fallbackText(w.cta?.url, fallbackCta.url)} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, minHeight: 52, background: GOLD, color: '#101828', fontFamily: FF, fontSize: 14, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', textDecoration: 'none', borderRadius: 10 }}>{fallbackText(w.cta?.label, fallbackCta.label)} <ArrowRight size={14} /></a>
</div>
</div>
);
})}
@@ -510,14 +485,6 @@ function BewerbungswegeSection({ content }: { content?: ApplicationWaysContent }
))}
</div>
<a
href={fallbackText(w.cta?.url, fallbackCta.url)}
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '14px 24px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, marginTop: 36, alignSelf: 'flex-start', transition: 'opacity 0.15s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
>
{fallbackText(w.cta?.label, fallbackCta.label)} <ArrowRight size={13} />
</a>
</div>
);
})}

View File

@@ -3,6 +3,7 @@
import NextLink from 'next/link'
import { usePathname, useRouter } from 'next/navigation'
import React, { forwardRef, useEffect, useMemo } from 'react'
import { isLoginIntentHref } from './loginIntent'
type To = string | { pathname?: string; search?: string; hash?: string } | null | undefined
@@ -21,7 +22,11 @@ const toHref = (to: To, pathname = '') => {
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link({ to, ...props }, ref) {
const pathname = usePathname() || '/'
return <NextLink ref={ref} href={toHref(to, pathname)} {...props} />
const href = toHref(to, pathname)
if (isLoginIntentHref(href)) return null
return <NextLink ref={ref} href={href} {...props} />
})
export function useLocation() {