import type { ComponentProps, FocusEvent } from 'react' import type { Banner as BannerType } from '@/models/app' import { useAtomValue } from 'jotai' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' import { Carousel, useCarousel } from '@/app/components/base/carousel' import { userProfileAtom } from '@/context/account-state' import { useLocale } from '@/context/i18n' import { BannerItem } from './banner-item' import { IndicatorButton } from './indicator-button' const AUTOPLAY_DELAY = 5000 const CAROUSEL_OPTIONS = { loop: true, watchDrag: (_api, event) => !(event.target instanceof Element && event.target.closest('[data-carousel-control]')), } satisfies NonNullable['opts']> type BannerCarouselContentProps = { banners: BannerType[] accountId?: string language: string } function BannerCarouselContent({ banners, accountId, language }: BannerCarouselContentProps) { const { t } = useTranslation() const { api, selectedIndex } = useCarousel() const [isPlaying, setIsPlaying] = useState(false) const trackedBannerKeysRef = useRef(new Set()) const shouldResumeAfterFocusRef = useRef(false) const nextIndex = (selectedIndex + 1) % banners.length const activeBanner = banners[selectedIndex] const trackingKey = accountId && activeBanner ? `${accountId}:${activeBanner.id}` : null const pauseRotationForFocus = () => { const autoplay = api?.plugins().autoplay if (!autoplay?.isPlaying()) return shouldResumeAfterFocusRef.current = true autoplay.stop() } const resumeRotationAfterFocus = (event: FocusEvent) => { if (event.currentTarget.contains(event.relatedTarget)) return if (!shouldResumeAfterFocusRef.current) return shouldResumeAfterFocusRef.current = false api?.plugins().autoplay?.play() } const selectBanner = (index: number) => { if (!api || index === selectedIndex) return api.scrollTo(index) } useEffect(() => { if (!accountId || !activeBanner || !trackingKey) return if (trackedBannerKeysRef.current.has(trackingKey)) return trackEvent('explore_banner_impression', { banner_id: activeBanner.id, title: activeBanner.content.title, sort: selectedIndex + 1, link: activeBanner.link, page: 'explore', language, account_id: accountId, event_time: Date.now(), }) trackedBannerKeysRef.current.add(trackingKey) }, [accountId, activeBanner, language, selectedIndex, trackingKey]) useEffect(() => { if (!api) return const handleAutoplayPlay = () => setIsPlaying(true) const handleAutoplayStop = () => setIsPlaying(false) // oxlint-disable-next-line eslint-react/set-state-in-effect -- Embla owns this external playback state. setIsPlaying(api.plugins().autoplay?.isPlaying() ?? false) api.on('autoplay:play', handleAutoplayPlay) api.on('autoplay:stop', handleAutoplayStop) return () => { api.off('autoplay:play', handleAutoplayPlay) api.off('autoplay:stop', handleAutoplayStop) } }, [api]) const controls = banners.length > 1 ? (
$['pagination.pageNumber'], { ns: 'common' })} className="pointer-events-auto flex h-7 min-w-0 shrink-0 items-center gap-2 @min-[996px]/banner:max-w-150 @min-[996px]/banner:min-w-60 @min-[996px]/banner:flex-[1_0_0] @min-[996px]/banner:pr-10" onFocusCapture={pauseRotationForFocus} onBlurCapture={resumeRotationAfterFocus} >
{banners.map((banner, index) => ( selectBanner(index)} /> ))}
) : null const hasFooter = Boolean(activeBanner?.link || controls) return ( <> {banners.map((banner, index) => { const isActive = index === selectedIndex return ( ) })} {hasFooter ? (
{activeBanner?.link ? (
{t(($) => $['banner.viewMore'], { ns: 'explore' })}
) : null} {controls}
) : null} ) } type BannerProps = { banners: BannerType[] } export function Banner({ banners }: BannerProps) { const { t } = useTranslation() const locale = useLocale() const userProfile = useAtomValue(userProfileAtom) const enabledBanners = banners.filter((banner) => banner.status === 'enabled') const carouselLabel = enabledBanners[0]?.content.category || enabledBanners[0]?.content.title const [carouselPlugins] = useState(() => [ Carousel.Plugin.Fade(), Carousel.Plugin.Autoplay({ delay: AUTOPLAY_DELAY, stopOnFocusIn: true, stopOnInteraction: false, stopOnMouseEnter: true, breakpoints: { '(prefers-reduced-motion: reduce)': { active: false }, }, }), ]) return (

{t(($) => $['banner.greeting'], { name: userProfile.name, ns: 'explore' })}

{t(($) => $['banner.tagline'], { ns: 'explore' })}

{enabledBanners.length > 0 ? ( ) : null}
) }