lomavuokraus/app/listings/new/page.tsx
2025-11-24 17:15:20 +02:00

277 lines
11 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useI18n } from '../../components/I18nProvider';
import type { Locale } from '../../../lib/i18n';
type ImageInput = { url: string; altText?: string };
export default function NewListingPage() {
const { t, locale: uiLocale } = useI18n();
const [slug, setSlug] = useState('');
const [locale, setLocale] = useState(uiLocale);
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [teaser, setTeaser] = useState('');
const [country, setCountry] = useState('Finland');
const [region, setRegion] = useState('');
const [city, setCity] = useState('');
const [streetAddress, setStreetAddress] = useState('');
const [addressNote, setAddressNote] = useState('');
const [latitude, setLatitude] = useState<number | ''>('');
const [longitude, setLongitude] = useState<number | ''>('');
const [contactName, setContactName] = useState('');
const [contactEmail, setContactEmail] = useState('');
const [maxGuests, setMaxGuests] = useState(4);
const [bedrooms, setBedrooms] = useState(2);
const [beds, setBeds] = useState(3);
const [bathrooms, setBathrooms] = useState(1);
const [price, setPrice] = useState<number | ''>('');
const [hasSauna, setHasSauna] = useState(true);
const [hasFireplace, setHasFireplace] = useState(true);
const [hasWifi, setHasWifi] = useState(true);
const [petsAllowed, setPetsAllowed] = useState(false);
const [byTheLake, setByTheLake] = useState(false);
const [hasAirConditioning, setHasAirConditioning] = useState(false);
const [evCharging, setEvCharging] = useState<'NONE' | 'FREE' | 'PAID'>('NONE');
const [imagesText, setImagesText] = useState('');
const [coverImageIndex, setCoverImageIndex] = useState(1);
const [message, setMessage] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [isAuthed, setIsAuthed] = useState(false);
useEffect(() => {
setLocale(uiLocale);
}, [uiLocale]);
useEffect(() => {
// simple check if session exists
fetch('/api/auth/me', { cache: 'no-store' })
.then((res) => res.json())
.then((data) => setIsAuthed(Boolean(data.user)))
.catch(() => setIsAuthed(false));
}, []);
function parseImages(): ImageInput[] {
return imagesText
.split('\n')
.map((line) => line.trim())
.filter(Boolean)
.map((line) => ({ url: line }));
}
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setMessage(null);
setError(null);
setLoading(true);
try {
const res = await fetch('/api/listings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
slug,
locale,
title,
description,
teaser,
country,
region,
city,
streetAddress,
addressNote,
latitude: latitude === '' ? null : latitude,
longitude: longitude === '' ? null : longitude,
contactName,
contactEmail,
maxGuests,
bedrooms,
beds,
bathrooms,
priceHintPerNightCents: price === '' ? null : Number(price),
hasSauna,
hasFireplace,
hasWifi,
petsAllowed,
byTheLake,
hasAirConditioning,
evCharging,
coverImageIndex,
images: parseImages(),
}),
});
const data = await res.json();
if (!res.ok) {
setError(data.error || 'Failed to create listing');
} else {
setMessage(t('createListingSuccess', { id: data.listing.id, status: data.listing.status }));
setSlug('');
setTitle('');
setDescription('');
setTeaser('');
setRegion('');
setCity('');
setStreetAddress('');
setAddressNote('');
setLatitude('');
setLongitude('');
setContactName('');
setContactEmail('');
setImagesText('');
setCoverImageIndex(1);
}
} catch (err) {
setError('Failed to create listing');
} finally {
setLoading(false);
}
}
if (!isAuthed) {
return (
<main className="panel" style={{ maxWidth: 720, margin: '40px auto' }}>
<h1>{t('createListingTitle')}</h1>
<p>{t('loginToCreate')}</p>
</main>
);
}
return (
<main className="panel" style={{ maxWidth: 720, margin: '40px auto' }}>
<h1>{t('createListingTitle')}</h1>
<form onSubmit={onSubmit} style={{ display: 'grid', gap: 10 }}>
<label>
{t('slugLabel')}
<input value={slug} onChange={(e) => setSlug(e.target.value)} required />
</label>
<label>
{t('localeInput')}
<input value={locale} onChange={(e) => setLocale(e.target.value as Locale)} required />
</label>
<label>
{t('titleLabel')}
<input value={title} onChange={(e) => setTitle(e.target.value)} required />
</label>
<label>
{t('descriptionLabel')}
<textarea value={description} onChange={(e) => setDescription(e.target.value)} required rows={4} />
</label>
<label>
{t('teaserLabel')}
<input value={teaser} onChange={(e) => setTeaser(e.target.value)} />
</label>
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))' }}>
<label>
{t('countryLabel')}
<input value={country} onChange={(e) => setCountry(e.target.value)} required />
</label>
<label>
{t('regionLabel')}
<input value={region} onChange={(e) => setRegion(e.target.value)} required />
</label>
<label>
{t('cityLabel')}
<input value={city} onChange={(e) => setCity(e.target.value)} required />
</label>
</div>
<label>
{t('streetAddressLabel')}
<input value={streetAddress} onChange={(e) => setStreetAddress(e.target.value)} required />
</label>
<label>
{t('addressNoteLabel')}
<input value={addressNote} onChange={(e) => setAddressNote(e.target.value)} placeholder={t('addressNotePlaceholder')} />
</label>
<label>
{t('contactNameLabel')}
<input value={contactName} onChange={(e) => setContactName(e.target.value)} required />
</label>
<label>
{t('contactEmailLabel')}
<input type="email" value={contactEmail} onChange={(e) => setContactEmail(e.target.value)} required />
</label>
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))' }}>
<label>
{t('maxGuestsLabel')}
<input type="number" value={maxGuests} onChange={(e) => setMaxGuests(Number(e.target.value))} min={1} />
</label>
<label>
{t('bedroomsLabel')}
<input type="number" value={bedrooms} onChange={(e) => setBedrooms(Number(e.target.value))} min={0} />
</label>
<label>
{t('bedsLabel')}
<input type="number" value={beds} onChange={(e) => setBeds(Number(e.target.value))} min={0} />
</label>
<label>
{t('bathroomsLabel')}
<input type="number" value={bathrooms} onChange={(e) => setBathrooms(Number(e.target.value))} min={0} />
</label>
<label>
{t('priceHintLabel')}
<input type="number" value={price} onChange={(e) => setPrice(e.target.value === '' ? '' : Number(e.target.value))} min={0} />
</label>
</div>
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))' }}>
<label>
{t('latitudeLabel')}
<input type="number" value={latitude} onChange={(e) => setLatitude(e.target.value === '' ? '' : Number(e.target.value))} step="0.000001" />
</label>
<label>
{t('longitudeLabel')}
<input type="number" value={longitude} onChange={(e) => setLongitude(e.target.value === '' ? '' : Number(e.target.value))} step="0.000001" />
</label>
</div>
<div style={{ display: 'grid', gap: 10, gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<input type="checkbox" checked={hasSauna} onChange={(e) => setHasSauna(e.target.checked)} /> {t('amenitySauna')}
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<input type="checkbox" checked={hasFireplace} onChange={(e) => setHasFireplace(e.target.checked)} /> {t('amenityFireplace')}
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<input type="checkbox" checked={hasWifi} onChange={(e) => setHasWifi(e.target.checked)} /> {t('amenityWifi')}
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<input type="checkbox" checked={petsAllowed} onChange={(e) => setPetsAllowed(e.target.checked)} /> {t('amenityPets')}
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<input type="checkbox" checked={byTheLake} onChange={(e) => setByTheLake(e.target.checked)} /> {t('amenityLake')}
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<input type="checkbox" checked={hasAirConditioning} onChange={(e) => setHasAirConditioning(e.target.checked)} /> {t('amenityAirConditioning')}
</label>
<label>
{t('evChargingLabel')}
<select value={evCharging} onChange={(e) => setEvCharging(e.target.value as any)}>
<option value="NONE">{t('evChargingNone')}</option>
<option value="FREE">{t('evChargingFree')}</option>
<option value="PAID">{t('evChargingPaid')}</option>
</select>
</label>
</div>
<label>
{t('imagesLabel')}
<textarea value={imagesText} onChange={(e) => setImagesText(e.target.value)} rows={4} placeholder="https://example.com/image.jpg" />
</label>
<label>
{t('coverImageLabel')}
<input
type="number"
min={1}
value={coverImageIndex}
onChange={(e) => setCoverImageIndex(Number(e.target.value) || 1)}
placeholder={t('coverImageHelp')}
/>
<small style={{ color: '#cbd5e1' }}>{t('coverImageHelp')}</small>
</label>
<button className="button" type="submit" disabled={loading}>
{loading ? t('submittingListing') : t('submitListing')}
</button>
</form>
{message ? <p style={{ marginTop: 12, color: 'green' }}>{message}</p> : null}
{error ? <p style={{ marginTop: 12, color: 'red' }}>{error}</p> : null}
</main>
);
}