import type { Metadata } from "next/dist/types"; 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 { 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 = await cookies(); const headerList = await headers(); const locale = resolveLocale({ cookieLocale: cookieStore.get("locale")?.value, acceptLanguage: headerList.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}
); }