118 lines
3.6 KiB
TypeScript
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>
|
|
)
|
|
}
|