From 91abec0295477a33e60cc60c38a9002b853a3011 Mon Sep 17 00:00:00 2001 From: Tero Halla-aho Date: Mon, 24 Nov 2025 20:22:52 +0200 Subject: [PATCH] chore: make seed idempotent and fix reset suspense --- app/auth/reset/page.tsx | 12 ++- prisma/seed.js | 222 ++++++++++++++++++++-------------------- 2 files changed, 121 insertions(+), 113 deletions(-) diff --git a/app/auth/reset/page.tsx b/app/auth/reset/page.tsx index 68e72c6..ad5b81a 100644 --- a/app/auth/reset/page.tsx +++ b/app/auth/reset/page.tsx @@ -1,10 +1,10 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { Suspense, useEffect, useState } from 'react'; import { useSearchParams } from 'next/navigation'; import { useI18n } from '../../components/I18nProvider'; -export default function ResetPasswordPage() { +function ResetForm() { const { t } = useI18n(); const searchParams = useSearchParams(); const [password, setPassword] = useState(''); @@ -66,3 +66,11 @@ export default function ResetPasswordPage() { ); } + +export default function ResetPasswordPage() { + return ( +

Loading…

}> + +
+ ); +} diff --git a/prisma/seed.js b/prisma/seed.js index 86670e9..3b9d69e 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -55,12 +55,6 @@ async function main() { } const sampleHostHash = await bcrypt.hash('changeme-sample', 12); - const existing = await prisma.listingTranslation.findFirst({ where: { slug: SAMPLE_SLUG } }); - if (existing) { - console.log('Sample listing already exists, skipping seed.'); - return; - } - const owner = await prisma.user.upsert({ where: { email: SAMPLE_EMAIL }, update: { @@ -84,15 +78,12 @@ async function main() { }, }); - const listing = await prisma.listing.create({ - data: { - ownerId: owner.id, - status: ListingStatus.PUBLISHED, - approvedAt: new Date(), - approvedById: adminUser ? adminUser.id : owner.id, - country: 'Finland', - region: 'South Karelia', + const listings = [ + { + slug: SAMPLE_SLUG, city: 'Punkaharju', + region: 'South Karelia', + country: 'Finland', streetAddress: 'Saimaan rantatie 12', addressNote: 'Lakeside trail, 5 min from main road', latitude: 61.756, @@ -104,65 +95,28 @@ async function main() { hasSauna: true, hasFireplace: true, hasWifi: true, + hasAirConditioning: false, petsAllowed: false, byTheLake: true, - hasAirConditioning: false, evCharging: 'FREE', priceHintPerNightCents: 14500, - contactName: 'Sample Host', - contactEmail: SAMPLE_EMAIL, - contactPhone: '+358401234567', - externalUrl: 'https://example.com/saimaa-cabin', - published: true, - images: { - createMany: { - data: [ - { - url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80', - altText: 'Lakeside cabin with sauna', - isCover: true, - order: 1, - }, - { - url: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80', - altText: 'Wood-fired sauna by the lake', - order: 2, - }, - { - url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80', - altText: 'Living area with fireplace', - order: 3, - }, - ], - }, + cover: { + url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80', + altText: 'Lakeside cabin with sauna', }, + images: [ + { url: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80', altText: 'Wood-fired sauna by the lake' }, + { url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80', altText: 'Living area with fireplace' }, + ], + titleEn: 'Saimaa lakeside cabin with sauna', + teaserEn: 'Sauna, lake view, private dock, and cozy fireplace.', + descEn: + 'Classic timber cabin right on Lake Saimaa. Wood-fired sauna, private dock, and a short forest walk to the nearest village. Perfect for slow weekends and midsummer gatherings.', + titleFi: 'Saimaan rantamökki saunalla', + teaserFi: 'Puusauna, järvinäköala, oma laituri ja takka.', + descFi: + 'Perinteinen hirsimökki Saimaan rannalla. Puusauna, oma laituri ja lyhyt metsäreitti kylään. Sopii täydellisesti viikonloppuihin ja juhannukseen.', }, - }); - - await prisma.listingTranslation.createMany({ - data: [ - { - listingId: listing.id, - locale: DEFAULT_LOCALE, - slug: SAMPLE_SLUG, - title: 'Saimaa lakeside cabin with sauna', - description: - 'Classic timber cabin right on Lake Saimaa. Wood-fired sauna, private dock, and a short forest walk to the nearest village. Perfect for slow weekends and midsummer gatherings.', - teaser: 'Sauna, lake view, private dock, and cozy fireplace.', - }, - { - listingId: listing.id, - locale: 'fi', - slug: SAMPLE_SLUG, - title: 'Saimaan rantamökki saunalla', - description: - 'Perinteinen hirsimökki Saimaan rannalla. Puusauna, oma laituri ja lyhyt metsäreitti kylään. Sopii täydellisesti viikonloppuihin ja juhannukseen.', - teaser: 'Puusauna, järvinäköala, oma laituri ja takka.', - }, - ], - }); - - const extraListings = [ { slug: 'helsinki-design-loft', city: 'Helsinki', @@ -469,13 +423,67 @@ async function main() { }, ]; - for (const item of extraListings) { - const created = await prisma.listing.create({ + for (const item of listings) { + const existing = await prisma.listingTranslation.findFirst({ where: { slug: item.slug }, select: { listingId: true } }); + if (!existing) { + const created = await prisma.listing.create({ + data: { + ownerId: owner.id, + status: ListingStatus.PUBLISHED, + approvedAt: new Date(), + approvedById: adminUser ? adminUser.id : owner.id, + country: item.country, + region: item.region, + city: item.city, + streetAddress: item.streetAddress, + addressNote: item.addressNote, + latitude: item.latitude, + longitude: item.longitude, + maxGuests: item.maxGuests, + bedrooms: item.bedrooms, + beds: item.beds, + bathrooms: item.bathrooms, + hasSauna: item.hasSauna, + hasFireplace: item.hasFireplace, + hasWifi: item.hasWifi, + hasAirConditioning: item.hasAirConditioning, + petsAllowed: item.petsAllowed, + byTheLake: item.byTheLake, + evCharging: item.evCharging, + priceHintPerNightCents: item.priceHintPerNightCents, + contactName: 'Sample Host', + contactEmail: SAMPLE_EMAIL, + contactPhone: owner.phone, + published: true, + translations: { + createMany: { + data: [ + { locale: 'en', slug: item.slug, title: item.titleEn, teaser: item.teaserEn, description: item.descEn }, + { locale: 'fi', slug: item.slug, title: item.titleFi, teaser: item.teaserFi, description: item.descFi }, + ], + }, + }, + images: { + create: [ + { url: item.cover.url, altText: item.cover.altText, order: 1, isCover: true }, + ...item.images.map((img, idx) => ({ + url: img.url, + altText: img.altText ?? null, + order: idx + 2, + isCover: false, + })), + ], + }, + }, + }); + console.log('Seeded listing:', created.id, item.slug); + continue; + } + + const listingId = existing.listingId; + await prisma.listing.update({ + where: { id: listingId }, data: { - ownerId: owner.id, - status: ListingStatus.PUBLISHED, - approvedAt: new Date(), - approvedById: adminUser ? adminUser.id : owner.id, country: item.country, region: item.region, city: item.city, @@ -499,48 +507,40 @@ async function main() { contactEmail: SAMPLE_EMAIL, contactPhone: owner.phone, published: true, - translations: { - createMany: { - data: [ - { - locale: 'en', - slug: item.slug, - title: item.titleEn, - teaser: item.teaserEn, - description: item.descEn, - }, - { - locale: 'fi', - slug: item.slug, - title: item.titleFi, - teaser: item.teaserFi, - description: item.descFi, - }, - ], - }, - }, - images: { - create: [ - { - url: item.cover.url, - altText: item.cover.altText, - order: 1, - isCover: true, - }, - ...item.images.map((img, idx) => ({ - url: img.url, - altText: img.altText ?? null, - order: idx + 2, - isCover: false, - })), - ], - }, + status: ListingStatus.PUBLISHED, + approvedAt: new Date(), }, }); - console.log('Seeded listing:', created.id, item.slug); + + await prisma.listingTranslation.upsert({ + where: { slug_locale: { slug: item.slug, locale: 'en' } }, + create: { listingId, locale: 'en', slug: item.slug, title: item.titleEn, teaser: item.teaserEn, description: item.descEn }, + update: { title: item.titleEn, teaser: item.teaserEn, description: item.descEn }, + }); + await prisma.listingTranslation.upsert({ + where: { slug_locale: { slug: item.slug, locale: 'fi' } }, + create: { listingId, locale: 'fi', slug: item.slug, title: item.titleFi, teaser: item.teaserFi, description: item.descFi }, + update: { title: item.titleFi, teaser: item.teaserFi, description: item.descFi }, + }); + + await prisma.listingImage.deleteMany({ where: { listingId } }); + await prisma.listingImage.createMany({ + data: [ + { listingId, url: item.cover.url, altText: item.cover.altText, order: 1, isCover: true }, + ...item.images.map((img, idx) => ({ + listingId, + url: img.url, + altText: img.altText ?? null, + order: idx + 2, + isCover: false, + })), + ], + }); + + console.log('Updated listing:', item.slug); } - console.log('Seeded sample listing at slug:', SAMPLE_SLUG); + console.log('Seed completed for sample listings.'); } main()