Add weekday/weekend pricing and new amenities
This commit is contained in:
parent
8bd0224597
commit
bee691ebd8
9 changed files with 211 additions and 46 deletions
|
|
@ -70,3 +70,4 @@
|
||||||
- Site navbar now shows the new logo above the lomavuokraus.fi brand text on every page.
|
- Site navbar now shows the new logo above the lomavuokraus.fi brand text on every page.
|
||||||
- Language selector in the navbar aligned with other buttons and given higher-contrast styling.
|
- Language selector in the navbar aligned with other buttons and given higher-contrast styling.
|
||||||
- Security hardening: npm audit now passes cleanly after upgrading Prisma patch release and pinning `glob@10.5.0` via overrides to eliminate the glob CLI injection advisory in eslint tooling.
|
- Security hardening: npm audit now passes cleanly after upgrading Prisma patch release and pinning `glob@10.5.0` via overrides to eliminate the glob CLI injection advisory in eslint tooling.
|
||||||
|
- Listings now capture separate weekday/weekend prices and new amenities (microwave, free parking) across schema, API, UI, and seeds.
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,13 @@ function normalizeCalendarUrls(input: unknown): string[] {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parsePrice(value: unknown): number | null {
|
||||||
|
if (value === undefined || value === null || value === '') return null;
|
||||||
|
const num = Number(value);
|
||||||
|
if (Number.isNaN(num)) return null;
|
||||||
|
return Math.round(num);
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET(req: Request) {
|
export async function GET(req: Request) {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const searchParams = url.searchParams;
|
const searchParams = url.searchParams;
|
||||||
|
|
@ -78,6 +85,8 @@ export async function GET(req: Request) {
|
||||||
if (amenityFilters.includes('dishwasher')) amenityWhere.hasDishwasher = true;
|
if (amenityFilters.includes('dishwasher')) amenityWhere.hasDishwasher = true;
|
||||||
if (amenityFilters.includes('washer')) amenityWhere.hasWashingMachine = true;
|
if (amenityFilters.includes('washer')) amenityWhere.hasWashingMachine = true;
|
||||||
if (amenityFilters.includes('barbecue')) amenityWhere.hasBarbecue = true;
|
if (amenityFilters.includes('barbecue')) amenityWhere.hasBarbecue = true;
|
||||||
|
if (amenityFilters.includes('microwave')) amenityWhere.hasMicrowave = true;
|
||||||
|
if (amenityFilters.includes('parking')) amenityWhere.hasFreeParking = true;
|
||||||
|
|
||||||
const where: Prisma.ListingWhereInput = {
|
const where: Prisma.ListingWhereInput = {
|
||||||
status: ListingStatus.PUBLISHED,
|
status: ListingStatus.PUBLISHED,
|
||||||
|
|
@ -158,12 +167,15 @@ export async function GET(req: Request) {
|
||||||
hasDishwasher: listing.hasDishwasher,
|
hasDishwasher: listing.hasDishwasher,
|
||||||
hasWashingMachine: listing.hasWashingMachine,
|
hasWashingMachine: listing.hasWashingMachine,
|
||||||
hasBarbecue: listing.hasBarbecue,
|
hasBarbecue: listing.hasBarbecue,
|
||||||
|
hasMicrowave: listing.hasMicrowave,
|
||||||
|
hasFreeParking: listing.hasFreeParking,
|
||||||
evCharging: listing.evCharging,
|
evCharging: listing.evCharging,
|
||||||
maxGuests: listing.maxGuests,
|
maxGuests: listing.maxGuests,
|
||||||
bedrooms: listing.bedrooms,
|
bedrooms: listing.bedrooms,
|
||||||
beds: listing.beds,
|
beds: listing.beds,
|
||||||
bathrooms: listing.bathrooms,
|
bathrooms: listing.bathrooms,
|
||||||
priceHintPerNightEuros: listing.priceHintPerNightEuros,
|
priceWeekdayEuros: listing.priceWeekdayEuros,
|
||||||
|
priceWeekendEuros: listing.priceWeekendEuros,
|
||||||
coverImage: resolveImageUrl(listing.images.find((img) => img.isCover) ?? listing.images[0] ?? { id: '', url: null, size: null }),
|
coverImage: resolveImageUrl(listing.images.find((img) => img.isCover) ?? listing.images[0] ?? { id: '', url: null, size: null }),
|
||||||
isSample,
|
isSample,
|
||||||
hasCalendar: Boolean(listing.calendarUrls?.length),
|
hasCalendar: Boolean(listing.calendarUrls?.length),
|
||||||
|
|
@ -199,7 +211,8 @@ export async function POST(req: Request) {
|
||||||
const bedrooms = Number(body.bedrooms ?? 1);
|
const bedrooms = Number(body.bedrooms ?? 1);
|
||||||
const beds = Number(body.beds ?? 1);
|
const beds = Number(body.beds ?? 1);
|
||||||
const bathrooms = Number(body.bathrooms ?? 1);
|
const bathrooms = Number(body.bathrooms ?? 1);
|
||||||
const priceHintPerNightEuros = body.priceHintPerNightEuros !== undefined && body.priceHintPerNightEuros !== null && body.priceHintPerNightEuros !== '' ? Math.round(Number(body.priceHintPerNightEuros)) : null;
|
const priceWeekdayEuros = parsePrice(body.priceWeekdayEuros);
|
||||||
|
const priceWeekendEuros = parsePrice(body.priceWeekendEuros);
|
||||||
const calendarUrls = normalizeCalendarUrls(body.calendarUrls);
|
const calendarUrls = normalizeCalendarUrls(body.calendarUrls);
|
||||||
const translationsInputRaw = Array.isArray(body.translations) ? body.translations : [];
|
const translationsInputRaw = Array.isArray(body.translations) ? body.translations : [];
|
||||||
type TranslationInput = { locale: string; title: string; description: string; teaser: string | null; slug: string };
|
type TranslationInput = { locale: string; title: string; description: string; teaser: string | null; slug: string };
|
||||||
|
|
@ -323,8 +336,11 @@ export async function POST(req: Request) {
|
||||||
hasDishwasher: Boolean(body.hasDishwasher),
|
hasDishwasher: Boolean(body.hasDishwasher),
|
||||||
hasWashingMachine: Boolean(body.hasWashingMachine),
|
hasWashingMachine: Boolean(body.hasWashingMachine),
|
||||||
hasBarbecue: Boolean(body.hasBarbecue),
|
hasBarbecue: Boolean(body.hasBarbecue),
|
||||||
|
hasMicrowave: Boolean(body.hasMicrowave),
|
||||||
|
hasFreeParking: Boolean(body.hasFreeParking),
|
||||||
evCharging: normalizeEvCharging(body.evCharging),
|
evCharging: normalizeEvCharging(body.evCharging),
|
||||||
priceHintPerNightEuros,
|
priceWeekdayEuros,
|
||||||
|
priceWeekendEuros,
|
||||||
calendarUrls,
|
calendarUrls,
|
||||||
contactName,
|
contactName,
|
||||||
contactEmail,
|
contactEmail,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ const amenityIcons: Record<string, string> = {
|
||||||
dishwasher: '🧼',
|
dishwasher: '🧼',
|
||||||
washer: '🧺',
|
washer: '🧺',
|
||||||
barbecue: '🍖',
|
barbecue: '🍖',
|
||||||
|
microwave: '🍲',
|
||||||
|
parking: '🅿️',
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function generateMetadata({ params }: ListingPageProps): Promise<Metadata> {
|
export async function generateMetadata({ params }: ListingPageProps): Promise<Metadata> {
|
||||||
|
|
@ -70,11 +72,19 @@ export default async function ListingPage({ params }: ListingPageProps) {
|
||||||
listing.hasDishwasher ? { icon: amenityIcons.dishwasher, label: t('amenityDishwasher') } : null,
|
listing.hasDishwasher ? { icon: amenityIcons.dishwasher, label: t('amenityDishwasher') } : null,
|
||||||
listing.hasWashingMachine ? { icon: amenityIcons.washer, label: t('amenityWashingMachine') } : null,
|
listing.hasWashingMachine ? { icon: amenityIcons.washer, label: t('amenityWashingMachine') } : null,
|
||||||
listing.hasBarbecue ? { icon: amenityIcons.barbecue, label: t('amenityBarbecue') } : 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 }[];
|
].filter(Boolean) as { icon: string; label: string }[];
|
||||||
const addressLine = `${listing.streetAddress ? `${listing.streetAddress}, ` : ''}${listing.city}, ${listing.region}, ${listing.country}`;
|
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 })}`;
|
const capacityLine = `${t('capacityGuests', { count: listing.maxGuests })} · ${t('capacityBedrooms', { count: listing.bedrooms })} · ${t('capacityBeds', { count: listing.beds })} · ${t('capacityBathrooms', { count: listing.bathrooms })}`;
|
||||||
const contactLine = `${listing.contactName} · ${listing.contactEmail}${listing.contactPhone ? ` · ${listing.contactPhone}` : ''}`;
|
const contactLine = `${listing.contactName} · ${listing.contactEmail}${listing.contactPhone ? ` · ${listing.contactPhone}` : ''}`;
|
||||||
const coverImage = listing.images.find((img) => img.isCover) ?? listing.images[0] ?? null;
|
const coverImage = listing.images.find((img) => img.isCover) ?? listing.images[0] ?? null;
|
||||||
|
const priceLine =
|
||||||
|
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');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="listing-shell">
|
<main className="listing-shell">
|
||||||
|
|
@ -172,6 +182,13 @@ export default async function ListingPage({ params }: ListingPageProps) {
|
||||||
<div>{capacityLine}</div>
|
<div>{capacityLine}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="fact-row">
|
||||||
|
<span aria-hidden className="amenity-icon">💶</span>
|
||||||
|
<div>
|
||||||
|
<div style={{ color: '#cbd5e1', fontSize: 12 }}>{t('listingPrices')}</div>
|
||||||
|
<div>{priceLine}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="fact-row">
|
<div className="fact-row">
|
||||||
<span aria-hidden className="amenity-icon">✉️</span>
|
<span aria-hidden className="amenity-icon">✉️</span>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ export default function NewListingPage() {
|
||||||
const [bedrooms, setBedrooms] = useState(2);
|
const [bedrooms, setBedrooms] = useState(2);
|
||||||
const [beds, setBeds] = useState(3);
|
const [beds, setBeds] = useState(3);
|
||||||
const [bathrooms, setBathrooms] = useState(1);
|
const [bathrooms, setBathrooms] = useState(1);
|
||||||
const [price, setPrice] = useState<number | ''>('');
|
const [priceWeekday, setPriceWeekday] = useState<number | ''>('');
|
||||||
|
const [priceWeekend, setPriceWeekend] = useState<number | ''>('');
|
||||||
const [hasSauna, setHasSauna] = useState(true);
|
const [hasSauna, setHasSauna] = useState(true);
|
||||||
const [hasFireplace, setHasFireplace] = useState(true);
|
const [hasFireplace, setHasFireplace] = useState(true);
|
||||||
const [hasWifi, setHasWifi] = useState(true);
|
const [hasWifi, setHasWifi] = useState(true);
|
||||||
|
|
@ -50,6 +51,8 @@ export default function NewListingPage() {
|
||||||
const [hasDishwasher, setHasDishwasher] = useState(false);
|
const [hasDishwasher, setHasDishwasher] = useState(false);
|
||||||
const [hasWashingMachine, setHasWashingMachine] = useState(false);
|
const [hasWashingMachine, setHasWashingMachine] = useState(false);
|
||||||
const [hasBarbecue, setHasBarbecue] = useState(false);
|
const [hasBarbecue, setHasBarbecue] = useState(false);
|
||||||
|
const [hasMicrowave, setHasMicrowave] = useState(false);
|
||||||
|
const [hasFreeParking, setHasFreeParking] = useState(false);
|
||||||
const [evCharging, setEvCharging] = useState<'NONE' | 'FREE' | 'PAID'>('NONE');
|
const [evCharging, setEvCharging] = useState<'NONE' | 'FREE' | 'PAID'>('NONE');
|
||||||
const [calendarUrls, setCalendarUrls] = useState('');
|
const [calendarUrls, setCalendarUrls] = useState('');
|
||||||
const [selectedImages, setSelectedImages] = useState<SelectedImage[]>([]);
|
const [selectedImages, setSelectedImages] = useState<SelectedImage[]>([]);
|
||||||
|
|
@ -129,6 +132,8 @@ export default function NewListingPage() {
|
||||||
{ key: 'dishwasher', label: t('amenityDishwasher'), icon: '🧼', checked: hasDishwasher, toggle: setHasDishwasher },
|
{ key: 'dishwasher', label: t('amenityDishwasher'), icon: '🧼', checked: hasDishwasher, toggle: setHasDishwasher },
|
||||||
{ key: 'washer', label: t('amenityWashingMachine'), icon: '🧺', checked: hasWashingMachine, toggle: setHasWashingMachine },
|
{ key: 'washer', label: t('amenityWashingMachine'), icon: '🧺', checked: hasWashingMachine, toggle: setHasWashingMachine },
|
||||||
{ key: 'barbecue', label: t('amenityBarbecue'), icon: '🍖', checked: hasBarbecue, toggle: setHasBarbecue },
|
{ key: 'barbecue', label: t('amenityBarbecue'), icon: '🍖', checked: hasBarbecue, toggle: setHasBarbecue },
|
||||||
|
{ key: 'microwave', label: t('amenityMicrowave'), icon: '🍲', checked: hasMicrowave, toggle: setHasMicrowave },
|
||||||
|
{ key: 'parking', label: t('amenityFreeParking'), icon: '🅿️', checked: hasFreeParking, toggle: setHasFreeParking },
|
||||||
];
|
];
|
||||||
|
|
||||||
function updateTranslation(locale: Locale, field: keyof LocaleFields, value: string) {
|
function updateTranslation(locale: Locale, field: keyof LocaleFields, value: string) {
|
||||||
|
|
@ -342,7 +347,8 @@ export default function NewListingPage() {
|
||||||
bedrooms,
|
bedrooms,
|
||||||
beds,
|
beds,
|
||||||
bathrooms,
|
bathrooms,
|
||||||
priceHintPerNightEuros: price === '' ? null : Math.round(Number(price)),
|
priceWeekdayEuros: priceWeekday === '' ? null : Math.round(Number(priceWeekday)),
|
||||||
|
priceWeekendEuros: priceWeekend === '' ? null : Math.round(Number(priceWeekend)),
|
||||||
hasSauna,
|
hasSauna,
|
||||||
hasFireplace,
|
hasFireplace,
|
||||||
hasWifi,
|
hasWifi,
|
||||||
|
|
@ -353,6 +359,8 @@ export default function NewListingPage() {
|
||||||
hasDishwasher,
|
hasDishwasher,
|
||||||
hasWashingMachine,
|
hasWashingMachine,
|
||||||
hasBarbecue,
|
hasBarbecue,
|
||||||
|
hasMicrowave,
|
||||||
|
hasFreeParking,
|
||||||
evCharging,
|
evCharging,
|
||||||
coverImageIndex,
|
coverImageIndex,
|
||||||
images: parseImages(),
|
images: parseImages(),
|
||||||
|
|
@ -370,6 +378,24 @@ export default function NewListingPage() {
|
||||||
fi: { title: '', description: '', teaser: '' },
|
fi: { title: '', description: '', teaser: '' },
|
||||||
sv: { title: '', description: '', teaser: '' },
|
sv: { title: '', description: '', teaser: '' },
|
||||||
});
|
});
|
||||||
|
setMaxGuests(4);
|
||||||
|
setBedrooms(2);
|
||||||
|
setBeds(3);
|
||||||
|
setBathrooms(1);
|
||||||
|
setPriceWeekday('');
|
||||||
|
setPriceWeekend('');
|
||||||
|
setHasSauna(true);
|
||||||
|
setHasFireplace(true);
|
||||||
|
setHasWifi(true);
|
||||||
|
setPetsAllowed(false);
|
||||||
|
setByTheLake(false);
|
||||||
|
setHasAirConditioning(false);
|
||||||
|
setHasKitchen(true);
|
||||||
|
setHasDishwasher(false);
|
||||||
|
setHasWashingMachine(false);
|
||||||
|
setHasBarbecue(false);
|
||||||
|
setHasMicrowave(false);
|
||||||
|
setHasFreeParking(false);
|
||||||
setRegion('');
|
setRegion('');
|
||||||
setCity('');
|
setCity('');
|
||||||
setStreetAddress('');
|
setStreetAddress('');
|
||||||
|
|
@ -594,18 +620,32 @@ export default function NewListingPage() {
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))' }}>
|
||||||
<label>
|
<label>
|
||||||
{t('priceHintLabel')}
|
{t('priceWeekdayLabel')}
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={price}
|
value={priceWeekday}
|
||||||
onChange={(e) => setPrice(e.target.value === '' ? '' : Number(e.target.value))}
|
onChange={(e) => setPriceWeekday(e.target.value === '' ? '' : Number(e.target.value))}
|
||||||
min={0}
|
min={0}
|
||||||
step="10"
|
step="10"
|
||||||
placeholder="e.g. 120"
|
placeholder="120"
|
||||||
/>
|
/>
|
||||||
<div style={{ color: '#cbd5e1', fontSize: 12 }}>{t('priceHintHelp')}</div>
|
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
{t('priceWeekendLabel')}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={priceWeekend}
|
||||||
|
onChange={(e) => setPriceWeekend(e.target.value === '' ? '' : Number(e.target.value))}
|
||||||
|
min={0}
|
||||||
|
step="10"
|
||||||
|
placeholder="140"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#cbd5e1', fontSize: 12 }}>{t('priceHintHelp')}</div>
|
||||||
<label style={{ gridColumn: '1 / -1' }}>
|
<label style={{ gridColumn: '1 / -1' }}>
|
||||||
{t('calendarUrlsLabel')}
|
{t('calendarUrlsLabel')}
|
||||||
<textarea
|
<textarea
|
||||||
|
|
@ -616,7 +656,6 @@ export default function NewListingPage() {
|
||||||
/>
|
/>
|
||||||
<div style={{ color: '#cbd5e1', fontSize: 12 }}>{t('calendarUrlsHelp')}</div>
|
<div style={{ color: '#cbd5e1', fontSize: 12 }}>{t('calendarUrlsHelp')}</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
|
||||||
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))' }}>
|
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))' }}>
|
||||||
<label>
|
<label>
|
||||||
{t('latitudeLabel')}
|
{t('latitudeLabel')}
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,15 @@ type ListingResult = {
|
||||||
hasDishwasher: boolean;
|
hasDishwasher: boolean;
|
||||||
hasWashingMachine: boolean;
|
hasWashingMachine: boolean;
|
||||||
hasBarbecue: boolean;
|
hasBarbecue: boolean;
|
||||||
|
hasMicrowave: boolean;
|
||||||
|
hasFreeParking: boolean;
|
||||||
evCharging: 'NONE' | 'FREE' | 'PAID';
|
evCharging: 'NONE' | 'FREE' | 'PAID';
|
||||||
maxGuests: number;
|
maxGuests: number;
|
||||||
bedrooms: number;
|
bedrooms: number;
|
||||||
beds: number;
|
beds: number;
|
||||||
bathrooms: number;
|
bathrooms: number;
|
||||||
priceHintPerNightEuros: number | null;
|
priceWeekdayEuros: number | null;
|
||||||
|
priceWeekendEuros: number | null;
|
||||||
coverImage: string | null;
|
coverImage: string | null;
|
||||||
isSample: boolean;
|
isSample: boolean;
|
||||||
hasCalendar: boolean;
|
hasCalendar: boolean;
|
||||||
|
|
@ -84,6 +87,8 @@ const amenityIcons: Record<string, string> = {
|
||||||
dishwasher: '🧼',
|
dishwasher: '🧼',
|
||||||
washer: '🧺',
|
washer: '🧺',
|
||||||
barbecue: '🍖',
|
barbecue: '🍖',
|
||||||
|
microwave: '🍲',
|
||||||
|
parking: '🅿️',
|
||||||
};
|
};
|
||||||
|
|
||||||
function ListingsMap({
|
function ListingsMap({
|
||||||
|
|
@ -216,6 +221,8 @@ export default function ListingsIndexPage() {
|
||||||
{ key: 'dishwasher', label: t('amenityDishwasher'), icon: amenityIcons.dishwasher },
|
{ key: 'dishwasher', label: t('amenityDishwasher'), icon: amenityIcons.dishwasher },
|
||||||
{ key: 'washer', label: t('amenityWashingMachine'), icon: amenityIcons.washer },
|
{ key: 'washer', label: t('amenityWashingMachine'), icon: amenityIcons.washer },
|
||||||
{ key: 'barbecue', label: t('amenityBarbecue'), icon: amenityIcons.barbecue },
|
{ key: 'barbecue', label: t('amenityBarbecue'), icon: amenityIcons.barbecue },
|
||||||
|
{ key: 'microwave', label: t('amenityMicrowave'), icon: amenityIcons.microwave },
|
||||||
|
{ key: 'parking', label: t('amenityFreeParking'), icon: amenityIcons.parking },
|
||||||
];
|
];
|
||||||
|
|
||||||
async function fetchListings() {
|
async function fetchListings() {
|
||||||
|
|
@ -467,6 +474,8 @@ export default function ListingsIndexPage() {
|
||||||
{l.city}, {l.region}
|
{l.city}, {l.region}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', fontSize: 13 }}>
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', fontSize: 13 }}>
|
||||||
|
{l.priceWeekdayEuros ? <span className="badge">{t('priceWeekdayShort', { price: l.priceWeekdayEuros })}</span> : null}
|
||||||
|
{l.priceWeekendEuros ? <span className="badge">{t('priceWeekendShort', { price: l.priceWeekendEuros })}</span> : null}
|
||||||
<span className="badge">{t('capacityGuests', { count: l.maxGuests })}</span>
|
<span className="badge">{t('capacityGuests', { count: l.maxGuests })}</span>
|
||||||
<span className="badge">{t('capacityBedrooms', { count: l.bedrooms })}</span>
|
<span className="badge">{t('capacityBedrooms', { count: l.bedrooms })}</span>
|
||||||
{l.hasCalendar ? <span className="badge secondary">{t('calendarConnected')}</span> : null}
|
{l.hasCalendar ? <span className="badge secondary">{t('calendarConnected')}</span> : null}
|
||||||
|
|
@ -480,6 +489,8 @@ export default function ListingsIndexPage() {
|
||||||
{l.hasDishwasher ? <span className="badge">{t('amenityDishwasher')}</span> : null}
|
{l.hasDishwasher ? <span className="badge">{t('amenityDishwasher')}</span> : null}
|
||||||
{l.hasWashingMachine ? <span className="badge">{t('amenityWashingMachine')}</span> : null}
|
{l.hasWashingMachine ? <span className="badge">{t('amenityWashingMachine')}</span> : null}
|
||||||
{l.hasBarbecue ? <span className="badge">{t('amenityBarbecue')}</span> : null}
|
{l.hasBarbecue ? <span className="badge">{t('amenityBarbecue')}</span> : null}
|
||||||
|
{l.hasMicrowave ? <span className="badge">{t('amenityMicrowave')}</span> : null}
|
||||||
|
{l.hasFreeParking ? <span className="badge">{t('amenityFreeParking')}</span> : null}
|
||||||
{l.hasSauna ? <span className="badge">{t('amenitySauna')}</span> : null}
|
{l.hasSauna ? <span className="badge">{t('amenitySauna')}</span> : null}
|
||||||
{l.hasWifi ? <span className="badge">{t('amenityWifi')}</span> : null}
|
{l.hasWifi ? <span className="badge">{t('amenityWifi')}</span> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
31
lib/i18n.ts
31
lib/i18n.ts
|
|
@ -125,6 +125,7 @@ const baseMessages = {
|
||||||
listingLocation: 'Location',
|
listingLocation: 'Location',
|
||||||
listingAddress: 'Address',
|
listingAddress: 'Address',
|
||||||
listingCapacity: 'Capacity',
|
listingCapacity: 'Capacity',
|
||||||
|
listingPrices: 'Pricing',
|
||||||
listingAmenities: 'Amenities',
|
listingAmenities: 'Amenities',
|
||||||
listingNoAmenities: 'No amenities listed yet.',
|
listingNoAmenities: 'No amenities listed yet.',
|
||||||
listingContact: 'Contact',
|
listingContact: 'Contact',
|
||||||
|
|
@ -186,8 +187,12 @@ const baseMessages = {
|
||||||
bedroomsLabel: 'Bedrooms',
|
bedroomsLabel: 'Bedrooms',
|
||||||
bedsLabel: 'Beds',
|
bedsLabel: 'Beds',
|
||||||
bathroomsLabel: 'Bathrooms',
|
bathroomsLabel: 'Bathrooms',
|
||||||
priceHintLabel: 'Price ballpark (€ / night)',
|
priceWeekdayLabel: 'Weeknight price (€ / night)',
|
||||||
priceHintHelp: 'Rough nightly price in euros (not a binding offer).',
|
priceWeekendLabel: 'Weekend price (€ / night)',
|
||||||
|
priceHintHelp: 'Set separate weeknight and weekend prices in euros (optional, not a binding offer).',
|
||||||
|
priceWeekdayShort: '{price}€ weekday',
|
||||||
|
priceWeekendShort: '{price}€ weekend',
|
||||||
|
priceNotSet: 'Not provided',
|
||||||
calendarUrlsLabel: 'Availability calendars (iCal URLs, one per line)',
|
calendarUrlsLabel: 'Availability calendars (iCal URLs, one per line)',
|
||||||
calendarUrlsHelp: 'Paste iCal links from other platforms. We will merge them to show availability.',
|
calendarUrlsHelp: 'Paste iCal links from other platforms. We will merge them to show availability.',
|
||||||
imagesLabel: 'Images',
|
imagesLabel: 'Images',
|
||||||
|
|
@ -214,6 +219,8 @@ const baseMessages = {
|
||||||
amenityDishwasher: 'Dishwasher',
|
amenityDishwasher: 'Dishwasher',
|
||||||
amenityWashingMachine: 'Washing machine',
|
amenityWashingMachine: 'Washing machine',
|
||||||
amenityBarbecue: 'Barbecue grill',
|
amenityBarbecue: 'Barbecue grill',
|
||||||
|
amenityMicrowave: 'Microwave',
|
||||||
|
amenityFreeParking: 'Free parking',
|
||||||
amenityEvFree: 'EV charging (free)',
|
amenityEvFree: 'EV charging (free)',
|
||||||
amenityEvPaid: 'EV charging (paid)',
|
amenityEvPaid: 'EV charging (paid)',
|
||||||
evChargingLabel: 'EV charging',
|
evChargingLabel: 'EV charging',
|
||||||
|
|
@ -428,6 +435,7 @@ const baseMessages = {
|
||||||
listingLocation: 'Sijainti',
|
listingLocation: 'Sijainti',
|
||||||
listingAddress: 'Osoite',
|
listingAddress: 'Osoite',
|
||||||
listingCapacity: 'Tilat',
|
listingCapacity: 'Tilat',
|
||||||
|
listingPrices: 'Hinta',
|
||||||
listingAmenities: 'Varustelu',
|
listingAmenities: 'Varustelu',
|
||||||
listingNoAmenities: 'Varustelua ei ole listattu.',
|
listingNoAmenities: 'Varustelua ei ole listattu.',
|
||||||
listingContact: 'Yhteystiedot',
|
listingContact: 'Yhteystiedot',
|
||||||
|
|
@ -464,8 +472,12 @@ const baseMessages = {
|
||||||
bedroomsLabel: 'Makuuhuoneita',
|
bedroomsLabel: 'Makuuhuoneita',
|
||||||
bedsLabel: 'Vuoteita',
|
bedsLabel: 'Vuoteita',
|
||||||
bathroomsLabel: 'Kylpyhuoneita',
|
bathroomsLabel: 'Kylpyhuoneita',
|
||||||
priceHintLabel: 'Hinta-arvio (€ / yö)',
|
priceWeekdayLabel: 'Arkiyön hinta (€ / yö)',
|
||||||
priceHintHelp: 'Suuntaa-antava hinta euroina per yö (ei sitova).',
|
priceWeekendLabel: 'Viikonlopun hinta (€ / yö)',
|
||||||
|
priceHintHelp: 'Aseta erilliset hinnat arki- ja viikonloppuyöille euroissa (valinnainen, ei sitova).',
|
||||||
|
priceWeekdayShort: '{price}€ arki',
|
||||||
|
priceWeekendShort: '{price}€ viikonloppu',
|
||||||
|
priceNotSet: 'Ei ilmoitettu',
|
||||||
calendarUrlsLabel: 'Saatavuuskalenterit (iCal-osoitteet, yksi per rivi)',
|
calendarUrlsLabel: 'Saatavuuskalenterit (iCal-osoitteet, yksi per rivi)',
|
||||||
calendarUrlsHelp: 'Liitä iCal-linkit muilta alustoilta. Yhdistämme ne saatavuuden näyttämiseen.',
|
calendarUrlsHelp: 'Liitä iCal-linkit muilta alustoilta. Yhdistämme ne saatavuuden näyttämiseen.',
|
||||||
imagesLabel: 'Kuvat',
|
imagesLabel: 'Kuvat',
|
||||||
|
|
@ -492,6 +504,8 @@ const baseMessages = {
|
||||||
amenityDishwasher: 'Astianpesukone',
|
amenityDishwasher: 'Astianpesukone',
|
||||||
amenityWashingMachine: 'Pyykinpesukone',
|
amenityWashingMachine: 'Pyykinpesukone',
|
||||||
amenityBarbecue: 'Grilli',
|
amenityBarbecue: 'Grilli',
|
||||||
|
amenityMicrowave: 'Mikroaaltouuni',
|
||||||
|
amenityFreeParking: 'Maksuton pysäköinti',
|
||||||
amenityEvFree: 'Sähköauton lataus (ilmainen)',
|
amenityEvFree: 'Sähköauton lataus (ilmainen)',
|
||||||
amenityEvPaid: 'Sähköauton lataus (maksullinen)',
|
amenityEvPaid: 'Sähköauton lataus (maksullinen)',
|
||||||
evChargingLabel: 'Sähköauton lataus',
|
evChargingLabel: 'Sähköauton lataus',
|
||||||
|
|
@ -606,6 +620,15 @@ const svMessages: Record<keyof typeof baseMessages.en, string> = {
|
||||||
slugTaken: 'Sluggen används redan',
|
slugTaken: 'Sluggen används redan',
|
||||||
slugCheckError: 'Kunde inte kontrollera sluggen nu',
|
slugCheckError: 'Kunde inte kontrollera sluggen nu',
|
||||||
teaserHelp: 'Kort ingress som syns i korten',
|
teaserHelp: 'Kort ingress som syns i korten',
|
||||||
|
priceWeekdayLabel: 'Vardagspris (€ / natt)',
|
||||||
|
priceWeekendLabel: 'Helgpris (€ / natt)',
|
||||||
|
priceHintHelp: 'Ange separata priser för vardag och helg i euro per natt (frivilligt).',
|
||||||
|
priceWeekdayShort: '{price}€ vardag',
|
||||||
|
priceWeekendShort: '{price}€ helg',
|
||||||
|
priceNotSet: 'Ej angivet',
|
||||||
|
listingPrices: 'Priser',
|
||||||
|
amenityMicrowave: 'Mikrovågsugn',
|
||||||
|
amenityFreeParking: 'Gratis parkering',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const messages = { ...baseMessages, sv: svMessages } as const;
|
export const messages = { ...baseMessages, sv: svMessages } as const;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
-- Split single price hint into weekday/weekend prices and add new amenities
|
||||||
|
ALTER TABLE "Listing"
|
||||||
|
ADD COLUMN "priceWeekdayEuros" INTEGER,
|
||||||
|
ADD COLUMN "priceWeekendEuros" INTEGER,
|
||||||
|
ADD COLUMN "hasMicrowave" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN "hasFreeParking" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
UPDATE "Listing"
|
||||||
|
SET
|
||||||
|
"priceWeekdayEuros" = "priceHintPerNightEuros",
|
||||||
|
"priceWeekendEuros" = "priceHintPerNightEuros"
|
||||||
|
WHERE "priceWeekdayEuros" IS NULL
|
||||||
|
AND "priceWeekendEuros" IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE "Listing" DROP COLUMN "priceHintPerNightEuros";
|
||||||
|
|
@ -92,9 +92,12 @@ model Listing {
|
||||||
hasDishwasher Boolean @default(false)
|
hasDishwasher Boolean @default(false)
|
||||||
hasWashingMachine Boolean @default(false)
|
hasWashingMachine Boolean @default(false)
|
||||||
hasBarbecue Boolean @default(false)
|
hasBarbecue Boolean @default(false)
|
||||||
|
hasMicrowave Boolean @default(false)
|
||||||
|
hasFreeParking Boolean @default(false)
|
||||||
evCharging EvCharging @default(NONE)
|
evCharging EvCharging @default(NONE)
|
||||||
calendarUrls String[] @db.Text @default([])
|
calendarUrls String[] @db.Text @default([])
|
||||||
priceHintPerNightEuros Int?
|
priceWeekdayEuros Int?
|
||||||
|
priceWeekendEuros Int?
|
||||||
contactName String
|
contactName String
|
||||||
contactEmail String
|
contactEmail String
|
||||||
contactPhone String?
|
contactPhone String?
|
||||||
|
|
|
||||||
|
|
@ -157,10 +157,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: true,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: true,
|
byTheLake: true,
|
||||||
evCharging: 'FREE',
|
evCharging: 'FREE',
|
||||||
priceHintPerNightEuros: 145,
|
priceWeekdayEuros: 145,
|
||||||
|
priceWeekendEuros: 165,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'saimaa-lakeside-cabin-cover.jpg',
|
file: 'saimaa-lakeside-cabin-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -201,10 +204,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: false,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: false,
|
byTheLake: false,
|
||||||
evCharging: 'PAID',
|
evCharging: 'PAID',
|
||||||
priceHintPerNightEuros: 165,
|
priceWeekdayEuros: 165,
|
||||||
|
priceWeekendEuros: 185,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'helsinki-design-loft-cover.jpg',
|
file: 'helsinki-design-loft-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1505693415763-3bd1620f58c3?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1505693415763-3bd1620f58c3?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -243,10 +249,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: true,
|
||||||
petsAllowed: true,
|
petsAllowed: true,
|
||||||
byTheLake: false,
|
byTheLake: false,
|
||||||
evCharging: 'NONE',
|
evCharging: 'NONE',
|
||||||
priceHintPerNightEuros: 110,
|
priceWeekdayEuros: 110,
|
||||||
|
priceWeekendEuros: 125,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'turku-riverside-apartment-cover.jpg',
|
file: 'turku-riverside-apartment-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -284,10 +293,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: false,
|
||||||
|
hasFreeParking: true,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: true,
|
byTheLake: true,
|
||||||
evCharging: 'FREE',
|
evCharging: 'FREE',
|
||||||
priceHintPerNightEuros: 189,
|
priceWeekdayEuros: 189,
|
||||||
|
priceWeekendEuros: 215,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'rovaniemi-aurora-cabin-cover.jpg',
|
file: 'rovaniemi-aurora-cabin-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -325,10 +337,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: false,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: false,
|
byTheLake: false,
|
||||||
evCharging: 'NONE',
|
evCharging: 'NONE',
|
||||||
priceHintPerNightEuros: 95,
|
priceWeekdayEuros: 95,
|
||||||
|
priceWeekendEuros: 110,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'tampere-sauna-studio-cover.jpg',
|
file: 'tampere-sauna-studio-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -364,10 +379,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: true,
|
||||||
petsAllowed: true,
|
petsAllowed: true,
|
||||||
byTheLake: true,
|
byTheLake: true,
|
||||||
evCharging: 'PAID',
|
evCharging: 'PAID',
|
||||||
priceHintPerNightEuros: 245,
|
priceWeekdayEuros: 245,
|
||||||
|
priceWeekendEuros: 275,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'vaasa-seaside-villa-cover.jpg',
|
file: 'vaasa-seaside-villa-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -403,10 +421,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: true,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: true,
|
byTheLake: true,
|
||||||
evCharging: 'FREE',
|
evCharging: 'FREE',
|
||||||
priceHintPerNightEuros: 129,
|
priceWeekdayEuros: 129,
|
||||||
|
priceWeekendEuros: 149,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'kuopio-lakeside-apartment-cover.jpg',
|
file: 'kuopio-lakeside-apartment-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -442,10 +463,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: false,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: false,
|
byTheLake: false,
|
||||||
evCharging: 'NONE',
|
evCharging: 'NONE',
|
||||||
priceHintPerNightEuros: 99,
|
priceWeekdayEuros: 99,
|
||||||
|
priceWeekendEuros: 115,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'porvoo-river-loft-cover.jpg',
|
file: 'porvoo-river-loft-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -481,10 +505,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: true,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: false,
|
byTheLake: false,
|
||||||
evCharging: 'FREE',
|
evCharging: 'FREE',
|
||||||
priceHintPerNightEuros: 105,
|
priceWeekdayEuros: 105,
|
||||||
|
priceWeekendEuros: 120,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'oulu-tech-apartment-cover.jpg',
|
file: 'oulu-tech-apartment-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -520,10 +547,13 @@ async function main() {
|
||||||
hasDishwasher: false,
|
hasDishwasher: false,
|
||||||
hasWashingMachine: false,
|
hasWashingMachine: false,
|
||||||
hasBarbecue: false,
|
hasBarbecue: false,
|
||||||
|
hasMicrowave: true,
|
||||||
|
hasFreeParking: true,
|
||||||
petsAllowed: false,
|
petsAllowed: false,
|
||||||
byTheLake: true,
|
byTheLake: true,
|
||||||
evCharging: 'PAID',
|
evCharging: 'PAID',
|
||||||
priceHintPerNightEuros: 115,
|
priceWeekdayEuros: 115,
|
||||||
|
priceWeekendEuros: 130,
|
||||||
cover: {
|
cover: {
|
||||||
file: 'mariehamn-harbor-flat-cover.jpg',
|
file: 'mariehamn-harbor-flat-cover.jpg',
|
||||||
url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80',
|
url: 'https://images.unsplash.com/photo-1470246973918-29a93221c455?auto=format&fit=crop&w=1600&q=80',
|
||||||
|
|
@ -541,16 +571,20 @@ async function main() {
|
||||||
|
|
||||||
// Fill in any missing amenities/prices with reasonable defaults
|
// Fill in any missing amenities/prices with reasonable defaults
|
||||||
const randBool = (p = 0.5) => Math.random() < p;
|
const randBool = (p = 0.5) => Math.random() < p;
|
||||||
listings = listings.map((item) => ({
|
listings = listings.map((item) => {
|
||||||
|
const weekdayPrice = item.priceWeekdayEuros ?? Math.round(Math.random() * (220 - 90) + 90);
|
||||||
|
return {
|
||||||
...item,
|
...item,
|
||||||
priceHintPerNightEuros:
|
priceWeekdayEuros: weekdayPrice,
|
||||||
item.priceHintPerNightEuros ??
|
priceWeekendEuros: item.priceWeekendEuros ?? (weekdayPrice ? weekdayPrice + 15 : null),
|
||||||
Math.round(Math.random() * (220 - 90) + 90),
|
|
||||||
hasKitchen: item.hasKitchen ?? randBool(0.9),
|
hasKitchen: item.hasKitchen ?? randBool(0.9),
|
||||||
hasDishwasher: item.hasDishwasher ?? randBool(0.6),
|
hasDishwasher: item.hasDishwasher ?? randBool(0.6),
|
||||||
hasWashingMachine: item.hasWashingMachine ?? randBool(0.6),
|
hasWashingMachine: item.hasWashingMachine ?? randBool(0.6),
|
||||||
hasBarbecue: item.hasBarbecue ?? randBool(0.5),
|
hasBarbecue: item.hasBarbecue ?? randBool(0.5),
|
||||||
}));
|
hasMicrowave: item.hasMicrowave ?? randBool(0.7),
|
||||||
|
hasFreeParking: item.hasFreeParking ?? randBool(0.6),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
for (const item of listings) {
|
for (const item of listings) {
|
||||||
const existing = await prisma.listingTranslation.findFirst({ where: { slug: item.slug }, select: { listingId: true } });
|
const existing = await prisma.listingTranslation.findFirst({ where: { slug: item.slug }, select: { listingId: true } });
|
||||||
|
|
@ -582,10 +616,13 @@ async function main() {
|
||||||
hasDishwasher: item.hasDishwasher ?? false,
|
hasDishwasher: item.hasDishwasher ?? false,
|
||||||
hasWashingMachine: item.hasWashingMachine ?? false,
|
hasWashingMachine: item.hasWashingMachine ?? false,
|
||||||
hasBarbecue: item.hasBarbecue ?? false,
|
hasBarbecue: item.hasBarbecue ?? false,
|
||||||
|
hasMicrowave: item.hasMicrowave ?? false,
|
||||||
|
hasFreeParking: item.hasFreeParking ?? false,
|
||||||
petsAllowed: item.petsAllowed,
|
petsAllowed: item.petsAllowed,
|
||||||
byTheLake: item.byTheLake,
|
byTheLake: item.byTheLake,
|
||||||
evCharging: item.evCharging,
|
evCharging: item.evCharging,
|
||||||
priceHintPerNightEuros: item.priceHintPerNightEuros,
|
priceWeekdayEuros: item.priceWeekdayEuros,
|
||||||
|
priceWeekendEuros: item.priceWeekendEuros,
|
||||||
contactName: 'Sample Host',
|
contactName: 'Sample Host',
|
||||||
contactEmail: SAMPLE_EMAIL,
|
contactEmail: SAMPLE_EMAIL,
|
||||||
contactPhone: owner.phone,
|
contactPhone: owner.phone,
|
||||||
|
|
@ -629,10 +666,13 @@ async function main() {
|
||||||
hasDishwasher: item.hasDishwasher ?? false,
|
hasDishwasher: item.hasDishwasher ?? false,
|
||||||
hasWashingMachine: item.hasWashingMachine ?? false,
|
hasWashingMachine: item.hasWashingMachine ?? false,
|
||||||
hasBarbecue: item.hasBarbecue ?? false,
|
hasBarbecue: item.hasBarbecue ?? false,
|
||||||
|
hasMicrowave: item.hasMicrowave ?? false,
|
||||||
|
hasFreeParking: item.hasFreeParking ?? false,
|
||||||
petsAllowed: item.petsAllowed,
|
petsAllowed: item.petsAllowed,
|
||||||
byTheLake: item.byTheLake,
|
byTheLake: item.byTheLake,
|
||||||
evCharging: item.evCharging,
|
evCharging: item.evCharging,
|
||||||
priceHintPerNightEuros: item.priceHintPerNightEuros,
|
priceWeekdayEuros: item.priceWeekdayEuros,
|
||||||
|
priceWeekendEuros: item.priceWeekendEuros,
|
||||||
contactName: 'Sample Host',
|
contactName: 'Sample Host',
|
||||||
contactEmail: SAMPLE_EMAIL,
|
contactEmail: SAMPLE_EMAIL,
|
||||||
contactPhone: owner.phone,
|
contactPhone: owner.phone,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue