import type { Metadata } from 'next'; import { ListingStatus } from '@prisma/client'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { cookies, headers } from 'next/headers'; import { getListingBySlug, DEFAULT_LOCALE, withResolvedListingImages } from '../../../lib/listings'; import { SAMPLE_LISTING_SLUGS } from '../../../lib/sampleListing'; import { resolveLocale, t as translate } from '../../../lib/i18n'; import AvailabilityCalendar from '../../components/AvailabilityCalendar'; import { verifyAccessToken } from '../../../lib/jwt'; import AvailabilityCalendar from '../../components/AvailabilityCalendar'; import { getSiteSettings } from '../../../lib/settings'; import type { UrlObject } from 'url'; type ListingPageProps = { params: { slug: string }; }; const amenityIcons: Record = { sauna: '๐Ÿง–', fireplace: '๐Ÿ”ฅ', wifi: '๐Ÿ“ถ', pets: '๐Ÿพ', lake: '๐ŸŒŠ', ac: 'โ„๏ธ', ev: 'โšก', evOnSite: '๐Ÿ”Œ', kitchen: '๐Ÿฝ๏ธ', dishwasher: '๐Ÿงผ', washer: '๐Ÿงบ', barbecue: '๐Ÿ–', microwave: '๐Ÿฒ', parking: '๐Ÿ…ฟ๏ธ', accessible: 'โ™ฟ', ski: 'โ›ท๏ธ', }; export async function generateMetadata({ params }: ListingPageProps): Promise { const translation = await getListingBySlug({ slug: params.slug, locale: DEFAULT_LOCALE }); return { title: translation ? `${translation.title} | Lomavuokraus.fi` : `${params.slug} | Lomavuokraus.fi`, description: translation?.teaser ?? translation?.description?.slice(0, 140), }; } export default async function ListingPage({ params }: ListingPageProps) { const cookieStore = cookies(); const locale = resolveLocale({ cookieLocale: cookieStore.get('locale')?.value, acceptLanguage: headers().get('accept-language') }); const t = (key: any, vars?: Record) => translate(locale, key as any, vars); const sessionToken = cookieStore.get('session_token')?.value; let viewerId: string | null = null; if (sessionToken) { try { const payload = await verifyAccessToken(sessionToken); viewerId = payload.userId; } catch { viewerId = null; } } const siteSettings = await getSiteSettings(); const translationRaw = await getListingBySlug({ slug: params.slug, locale: locale ?? DEFAULT_LOCALE, includeOwnerDraftsForUserId: viewerId ?? undefined, }); const translation = translationRaw ? withResolvedListingImages(translationRaw) : null; if (!translation) { notFound(); } const { listing, title, description, teaser, locale: translationLocale } = translation; const isSample = listing.isSample || listing.contactEmail === 'host@lomavuokraus.fi' || SAMPLE_LISTING_SLUGS.includes(params.slug); const calendarUrls = (listing.calendarUrls ?? []).filter((url) => { if (!url) return false; try { const parsed = new URL(url); return parsed.protocol === 'http:' || parsed.protocol === 'https:'; } catch { return false; } }); const hasCalendar = calendarUrls.length > 0; const amenities = [ listing.hasSauna ? { icon: amenityIcons.sauna, label: t('amenitySauna') } : null, listing.hasFireplace ? { icon: amenityIcons.fireplace, label: t('amenityFireplace') } : null, listing.hasWifi ? { icon: amenityIcons.wifi, label: t('amenityWifi') } : null, listing.petsAllowed ? { icon: amenityIcons.pets, label: t('amenityPets') } : null, listing.byTheLake ? { icon: amenityIcons.lake, label: t('amenityLake') } : null, listing.hasAirConditioning ? { icon: amenityIcons.ac, label: t('amenityAirConditioning') } : null, listing.evChargingOnSite ? { icon: amenityIcons.evOnSite, label: t('amenityEvOnSite') } : null, listing.evChargingAvailable && !listing.evChargingOnSite ? { icon: amenityIcons.ev, label: t('amenityEvNearby') } : null, listing.wheelchairAccessible ? { icon: amenityIcons.accessible, label: t('amenityWheelchairAccessible') } : null, listing.hasSkiPass ? { icon: amenityIcons.ski, label: t('amenitySkiPass') } : null, listing.hasKitchen ? { icon: amenityIcons.kitchen, label: t('amenityKitchen') } : null, listing.hasDishwasher ? { icon: amenityIcons.dishwasher, label: t('amenityDishwasher') } : null, listing.hasWashingMachine ? { icon: amenityIcons.washer, label: t('amenityWashingMachine') } : null, listing.hasBarbecue ? { icon: amenityIcons.barbecue, label: t('amenityBarbecue') } : null, listing.hasMicrowave ? { icon: amenityIcons.microwave, label: t('amenityMicrowave') } : null, listing.hasFreeParking ? { icon: amenityIcons.parking, label: t('amenityFreeParking') } : null, ].filter(Boolean) as { icon: string; label: string }[]; const addressLine = `${listing.streetAddress ? `${listing.streetAddress}, ` : ''}${listing.city}, ${listing.region}, ${listing.country}`; const capacityParts = [ listing.maxGuests ? t('capacityGuests', { count: listing.maxGuests }) : null, listing.bedrooms ? t('capacityBedrooms', { count: listing.bedrooms }) : null, listing.beds ? t('capacityBeds', { count: listing.beds }) : null, listing.bathrooms ? t('capacityBathrooms', { count: listing.bathrooms }) : null, ].filter(Boolean) as string[]; const capacityLine = capacityParts.length ? capacityParts.join(' ยท ') : t('capacityUnknown'); const contactParts = [listing.contactName, listing.contactEmail, listing.contactPhone].filter(Boolean) as string[]; const contactLine = contactParts.length ? contactParts.join(' ยท ') : 'โ€”'; const canViewContact = !siteSettings.requireLoginForContactDetails || Boolean(viewerId); const loginRedirectUrl: UrlObject = { pathname: '/auth/login', query: { redirect: `/listings/${params.slug}` } }; const coverImage = listing.images.find((img) => img.isCover) ?? listing.images[0] ?? null; const priceCandidates = [listing.priceWeekdayEuros, listing.priceWeekendEuros].filter((p): p is number => typeof p === 'number'); const startingFromEuros = priceCandidates.length ? Math.min(...priceCandidates) : null; const priceLine = listing.priceWeekdayEuros || listing.priceWeekendEuros ? `${startingFromEuros !== null ? t('priceStartingFromShort', { price: startingFromEuros }) : ''}${ listing.priceWeekdayEuros || listing.priceWeekendEuros ? ` (${[listing.priceWeekdayEuros ? t('priceWeekdayShort', { price: listing.priceWeekdayEuros }) : null, listing.priceWeekendEuros ? t('priceWeekendShort', { price: listing.priceWeekendEuros }) : null] .filter(Boolean) .join(' ยท ')})` : '' }` : t('priceNotSet'); const isDraftOrPending = listing.status !== ListingStatus.PUBLISHED; const isOwnerView = viewerId && listing.ownerId === viewerId; return (
{t('homeCrumb')} / {params.slug}
{isDraftOrPending ? (
{isOwnerView ? t('statusLabel') : 'Status'}: {listing.status}
) : null} {isSample ? (
{t('sampleBadge')}
) : null}

{title}

{teaser ?? description}

{listing.addressNote ? (
{listing.addressNote}
) : null} {listing.externalUrl ? ( ) : null} {(coverImage || hasCalendar) && (
{coverImage ? ( {coverImage.altText ) : (
)}
{!hasCalendar ? (
{t('availabilityMissing')}
) : null}
)} {listing.images.length > 0 ? (
{listing.images .filter((img) => Boolean(img.url)) .map((img) => (
{img.altText
))}
) : null}
{t('localeLabel')}: {translationLocale}
); }