From 35f2551c6bb1ab1cd60a963f853a802786495c90 Mon Sep 17 00:00:00 2001 From: Tero Halla-aho Date: Wed, 26 Nov 2025 23:19:09 +0200 Subject: [PATCH] Add kitchen/laundry/barbecue amenities --- app/api/listings/route.ts | 8 ++++ app/listings/[slug]/page.tsx | 8 ++++ app/listings/new/page.tsx | 12 +++++ app/listings/page.tsx | 8 ++++ lib/i18n.ts | 8 ++++ .../20250210_more_amenities/migration.sql | 5 ++ .../20251122192713_init_schema/migration.sql | 5 ++ prisma/schema.prisma | 4 ++ prisma/seed.js | 48 +++++++++++++++++++ 9 files changed, 106 insertions(+) create mode 100644 prisma/migrations/20250210_more_amenities/migration.sql diff --git a/app/api/listings/route.ts b/app/api/listings/route.ts index 03c444c..6cd9494 100644 --- a/app/api/listings/route.ts +++ b/app/api/listings/route.ts @@ -100,6 +100,10 @@ export async function GET(req: Request) { petsAllowed: listing.petsAllowed, byTheLake: listing.byTheLake, hasAirConditioning: listing.hasAirConditioning, + hasKitchen: listing.hasKitchen, + hasDishwasher: listing.hasDishwasher, + hasWashingMachine: listing.hasWashingMachine, + hasBarbecue: listing.hasBarbecue, evCharging: listing.evCharging, maxGuests: listing.maxGuests, bedrooms: listing.bedrooms, @@ -230,6 +234,10 @@ export async function POST(req: Request) { petsAllowed: Boolean(body.petsAllowed), byTheLake: Boolean(body.byTheLake), hasAirConditioning: Boolean(body.hasAirConditioning), + hasKitchen: Boolean(body.hasKitchen), + hasDishwasher: Boolean(body.hasDishwasher), + hasWashingMachine: Boolean(body.hasWashingMachine), + hasBarbecue: Boolean(body.hasBarbecue), evCharging: normalizeEvCharging(body.evCharging), priceHintPerNightEuros, contactName, diff --git a/app/listings/[slug]/page.tsx b/app/listings/[slug]/page.tsx index 31cc1af..c587fd6 100644 --- a/app/listings/[slug]/page.tsx +++ b/app/listings/[slug]/page.tsx @@ -18,6 +18,10 @@ const amenityIcons: Record = { lake: '🌊', ac: '❄️', ev: '⚡', + kitchen: '🍽️', + dishwasher: '🧼', + washer: '🧺', + barbecue: '🍖', }; export async function generateMetadata({ params }: ListingPageProps): Promise { @@ -52,6 +56,10 @@ export default async function ListingPage({ params }: ListingPageProps) { listing.hasAirConditioning ? { icon: amenityIcons.ac, label: t('amenityAirConditioning') } : null, listing.evCharging === 'FREE' ? { icon: amenityIcons.ev, label: t('amenityEvFree') } : null, listing.evCharging === 'PAID' ? { icon: amenityIcons.ev, label: t('amenityEvPaid') } : 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, ].filter(Boolean) as { icon: string; label: string }[]; const addressLine = `${listing.streetAddress ? `${listing.streetAddress}, ` : ''}${listing.city}, ${listing.region}, ${listing.country}`; const capacityLine = `${t('capacityGuests', { count: listing.maxGuests })} · ${t('capacityBedrooms', { count: listing.bedrooms })} · ${t('capacityBeds', { count: listing.beds })} · ${t('capacityBathrooms', { count: listing.bathrooms })}`; diff --git a/app/listings/new/page.tsx b/app/listings/new/page.tsx index 82e3ddb..7ab675f 100644 --- a/app/listings/new/page.tsx +++ b/app/listings/new/page.tsx @@ -42,6 +42,10 @@ export default function NewListingPage() { const [petsAllowed, setPetsAllowed] = useState(false); const [byTheLake, setByTheLake] = useState(false); const [hasAirConditioning, setHasAirConditioning] = useState(false); + const [hasKitchen, setHasKitchen] = useState(true); + const [hasDishwasher, setHasDishwasher] = useState(false); + const [hasWashingMachine, setHasWashingMachine] = useState(false); + const [hasBarbecue, setHasBarbecue] = useState(false); const [evCharging, setEvCharging] = useState<'NONE' | 'FREE' | 'PAID'>('NONE'); const [selectedImages, setSelectedImages] = useState([]); const [coverImageIndex, setCoverImageIndex] = useState(1); @@ -111,6 +115,10 @@ export default function NewListingPage() { { key: 'pets', label: t('amenityPets'), icon: '🐾', checked: petsAllowed, toggle: setPetsAllowed }, { key: 'lake', label: t('amenityLake'), icon: '🌊', checked: byTheLake, toggle: setByTheLake }, { key: 'ac', label: t('amenityAirConditioning'), icon: '❄️', checked: hasAirConditioning, toggle: setHasAirConditioning }, + { key: 'kitchen', label: t('amenityKitchen'), icon: '🍽️', checked: hasKitchen, toggle: setHasKitchen }, + { key: 'dishwasher', label: t('amenityDishwasher'), icon: '🧼', checked: hasDishwasher, toggle: setHasDishwasher }, + { key: 'washer', label: t('amenityWashingMachine'), icon: '🧺', checked: hasWashingMachine, toggle: setHasWashingMachine }, + { key: 'barbecue', label: t('amenityBarbecue'), icon: '🍖', checked: hasBarbecue, toggle: setHasBarbecue }, ]; function parseImages(): ImageInput[] { @@ -162,6 +170,10 @@ export default function NewListingPage() { petsAllowed, byTheLake, hasAirConditioning, + hasKitchen, + hasDishwasher, + hasWashingMachine, + hasBarbecue, evCharging, coverImageIndex, images: parseImages(), diff --git a/app/listings/page.tsx b/app/listings/page.tsx index 3b4423e..6dcd44e 100644 --- a/app/listings/page.tsx +++ b/app/listings/page.tsx @@ -24,6 +24,10 @@ type ListingResult = { petsAllowed: boolean; byTheLake: boolean; hasAirConditioning: boolean; + hasKitchen: boolean; + hasDishwasher: boolean; + hasWashingMachine: boolean; + hasBarbecue: boolean; evCharging: 'NONE' | 'FREE' | 'PAID'; maxGuests: number; bedrooms: number; @@ -394,6 +398,10 @@ export default function ListingsIndexPage() { {l.evCharging === 'FREE' ? {t('amenityEvFree')} : null} {l.evCharging === 'PAID' ? {t('amenityEvPaid')} : null} {l.hasAirConditioning ? {t('amenityAirConditioning')} : null} + {l.hasKitchen ? {t('amenityKitchen')} : null} + {l.hasDishwasher ? {t('amenityDishwasher')} : null} + {l.hasWashingMachine ? {t('amenityWashingMachine')} : null} + {l.hasBarbecue ? {t('amenityBarbecue')} : null} {l.hasSauna ? {t('amenitySauna')} : null} {l.hasWifi ? {t('amenityWifi')} : null} diff --git a/lib/i18n.ts b/lib/i18n.ts index 9b2d16c..7064c5b 100644 --- a/lib/i18n.ts +++ b/lib/i18n.ts @@ -157,6 +157,10 @@ const allMessages = { amenityPets: 'Pets allowed', amenityLake: 'By the lake', amenityAirConditioning: 'Air conditioning', + amenityKitchen: 'Kitchen', + amenityDishwasher: 'Dishwasher', + amenityWashingMachine: 'Washing machine', + amenityBarbecue: 'Barbecue grill', amenityEvFree: 'EV charging (free)', amenityEvPaid: 'EV charging (paid)', evChargingLabel: 'EV charging', @@ -370,6 +374,10 @@ const allMessages = { amenityPets: 'Lemmikit sallittu', amenityLake: 'Järven rannalla', amenityAirConditioning: 'Ilmastointi', + amenityKitchen: 'Keittiö', + amenityDishwasher: 'Astianpesukone', + amenityWashingMachine: 'Pyykinpesukone', + amenityBarbecue: 'Grilli', amenityEvFree: 'Sähköauton lataus (ilmainen)', amenityEvPaid: 'Sähköauton lataus (maksullinen)', evChargingLabel: 'Sähköauton lataus', diff --git a/prisma/migrations/20250210_more_amenities/migration.sql b/prisma/migrations/20250210_more_amenities/migration.sql new file mode 100644 index 0000000..3f1c6ee --- /dev/null +++ b/prisma/migrations/20250210_more_amenities/migration.sql @@ -0,0 +1,5 @@ +ALTER TABLE "Listing" + ADD COLUMN "hasKitchen" BOOLEAN NOT NULL DEFAULT false, + ADD COLUMN "hasDishwasher" BOOLEAN NOT NULL DEFAULT false, + ADD COLUMN "hasWashingMachine" BOOLEAN NOT NULL DEFAULT false, + ADD COLUMN "hasBarbecue" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20251122192713_init_schema/migration.sql b/prisma/migrations/20251122192713_init_schema/migration.sql index d34201f..5f059ab 100644 --- a/prisma/migrations/20251122192713_init_schema/migration.sql +++ b/prisma/migrations/20251122192713_init_schema/migration.sql @@ -30,6 +30,11 @@ CREATE TABLE "Listing" ( "hasSauna" BOOLEAN NOT NULL DEFAULT false, "hasFireplace" BOOLEAN NOT NULL DEFAULT false, "hasWifi" BOOLEAN NOT NULL DEFAULT false, + "hasAirConditioning" BOOLEAN NOT NULL DEFAULT false, + "hasKitchen" BOOLEAN NOT NULL DEFAULT false, + "hasDishwasher" BOOLEAN NOT NULL DEFAULT false, + "hasWashingMachine" BOOLEAN NOT NULL DEFAULT false, + "hasBarbecue" BOOLEAN NOT NULL DEFAULT false, "petsAllowed" BOOLEAN NOT NULL DEFAULT false, "byTheLake" BOOLEAN NOT NULL DEFAULT false, "priceHintPerNightEuros" INTEGER, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 293e2e2..e5fc373 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -88,6 +88,10 @@ model Listing { petsAllowed Boolean @default(false) byTheLake Boolean @default(false) hasAirConditioning Boolean @default(false) + hasKitchen Boolean @default(false) + hasDishwasher Boolean @default(false) + hasWashingMachine Boolean @default(false) + hasBarbecue Boolean @default(false) evCharging EvCharging @default(NONE) priceHintPerNightEuros Int? contactName String diff --git a/prisma/seed.js b/prisma/seed.js index 8754822..a4bf11d 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -153,6 +153,10 @@ async function main() { hasFireplace: true, hasWifi: true, hasAirConditioning: false, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: true, evCharging: 'FREE', @@ -193,6 +197,10 @@ async function main() { hasFireplace: false, hasWifi: true, hasAirConditioning: true, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: false, evCharging: 'PAID', @@ -231,6 +239,10 @@ async function main() { hasFireplace: false, hasWifi: true, hasAirConditioning: false, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: true, byTheLake: false, evCharging: 'NONE', @@ -268,6 +280,10 @@ async function main() { hasFireplace: true, hasWifi: true, hasAirConditioning: false, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: true, evCharging: 'FREE', @@ -305,6 +321,10 @@ async function main() { hasFireplace: false, hasWifi: true, hasAirConditioning: true, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: false, evCharging: 'NONE', @@ -340,6 +360,10 @@ async function main() { hasFireplace: true, hasWifi: true, hasAirConditioning: false, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: true, byTheLake: true, evCharging: 'PAID', @@ -375,6 +399,10 @@ async function main() { hasFireplace: false, hasWifi: true, hasAirConditioning: false, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: true, evCharging: 'FREE', @@ -410,6 +438,10 @@ async function main() { hasFireplace: true, hasWifi: true, hasAirConditioning: false, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: false, evCharging: 'NONE', @@ -445,6 +477,10 @@ async function main() { hasFireplace: false, hasWifi: true, hasAirConditioning: true, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: false, evCharging: 'FREE', @@ -480,6 +516,10 @@ async function main() { hasFireplace: false, hasWifi: true, hasAirConditioning: false, + hasKitchen: true, + hasDishwasher: false, + hasWashingMachine: false, + hasBarbecue: false, petsAllowed: false, byTheLake: true, evCharging: 'PAID', @@ -525,6 +565,10 @@ async function main() { hasFireplace: item.hasFireplace, hasWifi: item.hasWifi, hasAirConditioning: item.hasAirConditioning, + hasKitchen: item.hasKitchen ?? false, + hasDishwasher: item.hasDishwasher ?? false, + hasWashingMachine: item.hasWashingMachine ?? false, + hasBarbecue: item.hasBarbecue ?? false, petsAllowed: item.petsAllowed, byTheLake: item.byTheLake, evCharging: item.evCharging, @@ -568,6 +612,10 @@ async function main() { hasFireplace: item.hasFireplace, hasWifi: item.hasWifi, hasAirConditioning: item.hasAirConditioning, + hasKitchen: item.hasKitchen ?? false, + hasDishwasher: item.hasDishwasher ?? false, + hasWashingMachine: item.hasWashingMachine ?? false, + hasBarbecue: item.hasBarbecue ?? false, petsAllowed: item.petsAllowed, byTheLake: item.byTheLake, evCharging: item.evCharging,