lomavuokraus/app/page.tsx
2025-11-26 14:27:55 +02:00

178 lines
6.3 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';
const highlights = [
{
keyTitle: 'highlightQualityTitle',
keyBody: 'highlightQualityBody',
},
{
keyTitle: 'highlightLocalTitle',
keyBody: 'highlightLocalBody',
},
{
keyTitle: 'highlightApiTitle',
keyBody: 'highlightApiBody',
},
];
export default function HomePage() {
const { t } = useI18n();
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
const apiBase = process.env.NEXT_PUBLIC_API_BASE || 'http://localhost:3000/api';
const appEnv = process.env.APP_ENV || 'local';
const appVersion = process.env.NEXT_PUBLIC_VERSION || 'dev';
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>
<div className="cards">
{highlights.map((item) => (
<div key={item.keyTitle} className="panel">
<h3 className="card-title">{t(item.keyTitle as any)}</h3>
<p>{t(item.keyBody as any)}</p>
</div>
))}
<div className="panel env-card">
<h3 className="card-title">{t('runtimeConfigTitle')}</h3>
<div className="meta-grid">
<span>
<strong>{t('runtimeAppEnv')}</strong> <code>{appEnv}</code>
</span>
<span>
<strong>{t('runtimeSiteUrl')}</strong> <code>{siteUrl}</code>
</span>
<span>
<strong>{t('runtimeApiBase')}</strong> <code>{apiBase}</code>
</span>
<span>
<strong>Version</strong> <code>{appVersion}</code>
</span>
</div>
</div>
</div>
<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>
);
}