lomavuokraus/app/page.tsx
2025-11-27 11:21:46 +02:00

133 lines
4.9 KiB
TypeScript

'use client';
import Link from 'next/link';
import { SAMPLE_LISTING_SLUG } from '../lib/sampleListing';
import { useI18n } from './components/I18nProvider';
import { useEffect, useMemo, useState } from 'react';
type LatestListing = {
id: string;
title: string;
slug: string;
teaser: string | null;
coverImage: string | null;
city: string;
region: string;
isSample: boolean;
};
export const dynamic = 'force-dynamic';
export default function HomePage() {
const { t } = useI18n();
const [latest, setLatest] = useState<LatestListing[]>([]);
const [activeIndex, setActiveIndex] = useState(0);
const [loadingLatest, setLoadingLatest] = useState(false);
const [paused, setPaused] = useState(false);
useEffect(() => {
setLoadingLatest(true);
fetch('/api/listings?limit=8', { cache: 'no-store' })
.then((res) => res.json())
.then((data) => setLatest((data.listings ?? []).slice(0, 5)))
.catch(() => setLatest([]))
.finally(() => setLoadingLatest(false));
}, []);
useEffect(() => {
if (!latest.length || paused) return;
const id = setInterval(() => {
setActiveIndex((prev) => (prev + 1) % latest.length);
}, 4200);
return () => clearInterval(id);
}, [latest, paused]);
useEffect(() => {
if (activeIndex >= latest.length) {
setActiveIndex(0);
}
}, [activeIndex, latest.length]);
const activeListing = useMemo(() => {
if (!latest.length) return null;
return latest[Math.min(activeIndex, latest.length - 1)];
}, [latest, activeIndex]);
return (
<main>
<section className="hero">
<span className="eyebrow">{t('heroEyebrow')}</span>
<h1>{t('heroTitle')}</h1>
<p>{t('heroBody')}</p>
<div className="cta-row">
<Link className="button" href={`/listings/${SAMPLE_LISTING_SLUG}`}>
{t('ctaViewSample')}
</Link>
<Link className="button secondary" href="/listings">
{t('ctaBrowse')}
</Link>
<a className="button secondary" href="/api/health" target="_blank" rel="noreferrer">
{t('ctaHealth')}
</a>
</div>
</section>
<section className="panel latest-panel">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10 }}>
<div>
<h2 style={{ margin: 0 }}>{t('latestListingsTitle')}</h2>
<p style={{ marginTop: 4 }}>{t('latestListingsLead')}</p>
</div>
<Link className="button secondary" href="/listings">
{t('ctaBrowse')}
</Link>
</div>
{loadingLatest ? (
<p style={{ color: '#cbd5e1', marginTop: 10 }}>{t('loading')}</p>
) : !activeListing ? (
<p style={{ color: '#cbd5e1', marginTop: 10 }}>{t('mapNoResults')}</p>
) : (
<div className="latest-carousel" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
<div className="carousel-window">
<div className="carousel-track" style={{ transform: `translateX(-${activeIndex * 100}%)` }}>
{latest.map((item) => (
<article key={item.id} className="carousel-slide">
{item.coverImage ? (
<a href={`/listings/${item.slug}`} className="latest-cover-link" aria-label={item.title}>
<img src={item.coverImage} alt={item.title} className="latest-cover" />
</a>
) : (
<a href={`/listings/${item.slug}`} className="latest-cover-link" aria-label={item.title}>
<div className="latest-cover placeholder" />
</a>
)}
<div className="latest-meta">
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<span className="badge">
{item.city}, {item.region}
</span>
{item.isSample ? <span className="badge warning">{t('sampleBadge')}</span> : null}
</div>
<h3 style={{ margin: '6px 0 4px' }}>{item.title}</h3>
<p style={{ margin: 0 }}>{item.teaser}</p>
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
<Link className="button secondary" href={`/listings/${item.slug}`}>
{t('openListing')}
</Link>
</div>
</div>
</article>
))}
</div>
</div>
<div className="dot-row">
{latest.map((_, idx) => (
<span key={idx} className={`dot ${idx === activeIndex ? 'active' : ''}`} onClick={() => setActiveIndex(idx)} />
))}
</div>
</div>
)}
</section>
</main>
);
}