visitapp-web/src/components/HomeHero.tsx
2026-05-09 00:22:28 +02:00

118 lines
3.6 KiB
TypeScript

'use client'
import { useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import Image from 'next/image'
import { useVisitor } from '@/context/VisitorContext'
import { t, tPlain } from '@/lib/i18n'
import type { SectionDTO } from '@/lib/api/types'
import LanguageSelector from '@/components/ui/LanguageSelector'
interface Props {
featuredEvent?: SectionDTO
mainImageUrl?: string
slug: string
configurationId?: string
}
function formatDateRange(start?: string, end?: string, locale: string = 'fr'): string {
if (!start) return ''
const d1 = new Date(start)
const d2 = end ? new Date(end) : null
const opt: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'short' }
if (!d2 || d1.toDateString() === d2.toDateString()) {
return d1.toLocaleDateString(locale, opt)
}
return `${d1.toLocaleDateString(locale, opt)}${d2.toLocaleDateString(locale, opt)}`
}
export default function HomeHero({ featuredEvent, mainImageUrl, slug, configurationId }: Props) {
const { language } = useVisitor()
const heroRef = useRef<HTMLDivElement>(null)
const [opacity, setOpacity] = useState(1)
useEffect(() => {
const onScroll = () => {
if (!heroRef.current) return
const heroHeight = heroRef.current.offsetHeight
setOpacity(Math.max(0, 1 - window.scrollY / heroHeight))
}
window.addEventListener('scroll', onScroll, { passive: true })
return () => window.removeEventListener('scroll', onScroll)
}, [])
const imageUrl = featuredEvent?.imageSource ?? mainImageUrl
const isClickable = !!featuredEvent && !!configurationId
const dateLabel = formatDateRange(
featuredEvent?.event?.startDate,
featuredEvent?.event?.endDate,
language.toLowerCase()
)
const inner = (
<div
className="relative w-full overflow-hidden rounded-b-3xl"
style={{
height: '50vh',
minHeight: 200,
boxShadow: '0 4px 16px rgba(0,0,0,0.35)',
}}
>
{imageUrl ? (
<Image
src={imageUrl}
alt={featuredEvent ? tPlain(featuredEvent.title, language) : ''}
fill
className="object-cover"
sizes="100vw"
priority
/>
) : (
<div
className="absolute inset-0"
style={{ background: 'linear-gradient(135deg, var(--color-primary), var(--color-secondary))' }}
/>
)}
<div
className="absolute inset-0"
style={{ background: 'linear-gradient(to top, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.08) 55%)' }}
/>
{featuredEvent && (
<div className="absolute bottom-4 left-4 right-12 flex flex-col gap-2">
<h2
className="text-white text-xl font-bold leading-tight [&_p]:m-0 line-clamp-2"
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
dangerouslySetInnerHTML={{ __html: t(featuredEvent.title, language) || '' }}
/>
{dateLabel && (
<span
className="self-start px-2.5 py-1 rounded-full text-xs font-bold"
style={{ background: 'var(--color-primary)', color: 'var(--color-on-primary)' }}
>
{dateLabel}
</span>
)}
</div>
)}
</div>
)
return (
<div ref={heroRef} style={{ opacity, position: 'relative' }}>
{isClickable ? (
<Link href={`/${slug}/${configurationId}/sections/${featuredEvent!.id}`} className="block">
{inner}
</Link>
) : (
inner
)}
<div className="absolute top-3 right-3 z-10">
<LanguageSelector overlay />
</div>
</div>
)
}