feat: implement login intent filtering for navigation links and remove unused components
This commit is contained in:
@@ -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]
|
||||
@@ -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' } : {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
1
src/spa/loginIntent.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const isLoginIntentHref = (href: string) => href === '/admin' || href.startsWith('/admin/')
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user