Improve validation UX for listings and optional calendars
This commit is contained in:
parent
a50ee24730
commit
4e344b892e
3 changed files with 49 additions and 35 deletions
|
|
@ -208,10 +208,6 @@ export async function POST(req: Request) {
|
|||
return NextResponse.json({ error: 'Missing slug' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!saveDraft && (!country || !region || !city || !contactEmail || !contactName)) {
|
||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||
}
|
||||
|
||||
const maxGuests = body.maxGuests === undefined || body.maxGuests === null || body.maxGuests === '' ? null : Number(body.maxGuests);
|
||||
const bedrooms = body.bedrooms === undefined || body.bedrooms === null || body.bedrooms === '' ? null : Number(body.bedrooms);
|
||||
const beds = body.beds === undefined || body.beds === null || body.beds === '' ? null : Number(body.beds);
|
||||
|
|
@ -247,7 +243,7 @@ export async function POST(req: Request) {
|
|||
});
|
||||
}
|
||||
|
||||
if (!translationsInput.length) {
|
||||
if (!translationsInput.length && !saveDraft) {
|
||||
return NextResponse.json({ error: 'Missing translation fields (title/description)' }, { status: 400 });
|
||||
}
|
||||
|
||||
|
|
@ -311,11 +307,20 @@ export async function POST(req: Request) {
|
|||
}
|
||||
|
||||
if (!saveDraft) {
|
||||
if (!country || !region || !city || !contactEmail || !contactName || !maxGuests || !bedrooms || !beds || !bathrooms) {
|
||||
return NextResponse.json({ error: 'Missing required fields for publish' }, { status: 400 });
|
||||
}
|
||||
if (!parsedImages.length) {
|
||||
return NextResponse.json({ error: 'At least one image is required to publish' }, { status: 400 });
|
||||
const missingFields: string[] = [];
|
||||
if (!country) missingFields.push('country');
|
||||
if (!region) missingFields.push('region');
|
||||
if (!city) missingFields.push('city');
|
||||
if (!contactEmail) missingFields.push('contactEmail');
|
||||
if (!contactName) missingFields.push('contactName');
|
||||
if (!maxGuests) missingFields.push('maxGuests');
|
||||
if (!bedrooms && bedrooms !== 0) missingFields.push('bedrooms');
|
||||
if (!beds) missingFields.push('beds');
|
||||
if (!bathrooms) missingFields.push('bathrooms');
|
||||
if (!translationsInput.length) missingFields.push('translations');
|
||||
if (!parsedImages.length) missingFields.push('images');
|
||||
if (missingFields.length) {
|
||||
return NextResponse.json({ error: `Missing required fields: ${missingFields.join(', ')}` }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,15 +367,17 @@ export async function POST(req: Request) {
|
|||
externalUrl: body.externalUrl ?? null,
|
||||
published: status === ListingStatus.PUBLISHED,
|
||||
isSample,
|
||||
translations: {
|
||||
create: translationsInput.map((t: TranslationInput) => ({
|
||||
locale: t.locale,
|
||||
slug: t.slug || slug,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
teaser: t.teaser ?? null,
|
||||
})),
|
||||
},
|
||||
translations: translationsInput.length
|
||||
? {
|
||||
create: translationsInput.map((t: TranslationInput) => ({
|
||||
locale: t.locale,
|
||||
slug: t.slug || slug,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
teaser: t.teaser ?? null,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
images: parsedImages.length
|
||||
? {
|
||||
create: parsedImages,
|
||||
|
|
|
|||
|
|
@ -315,14 +315,18 @@ export default function NewListingPage() {
|
|||
teaser: translations[loc].teaser.trim(),
|
||||
})).filter((t) => t.title && t.description);
|
||||
|
||||
if (translationEntries.length === 0) {
|
||||
setError(t('translationMissing'));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!saveDraft && selectedImages.length === 0) {
|
||||
setError(t('imagesRequired'));
|
||||
const missing: string[] = [];
|
||||
if (!slug.trim()) missing.push(t('slugLabel'));
|
||||
if (!saveDraft && translationEntries.length === 0) missing.push(t('translationMissing'));
|
||||
if (!saveDraft && !country.trim()) missing.push(t('countryLabel'));
|
||||
if (!saveDraft && !region.trim()) missing.push(t('regionLabel'));
|
||||
if (!saveDraft && !city.trim()) missing.push(t('cityLabel'));
|
||||
if (!saveDraft && !streetAddress.trim()) missing.push(t('streetAddressLabel'));
|
||||
if (!saveDraft && !contactName.trim()) missing.push(t('contactNameLabel'));
|
||||
if (!saveDraft && !contactEmail.trim()) missing.push(t('contactEmailLabel'));
|
||||
if (!saveDraft && selectedImages.length === 0) missing.push(t('imagesLabel'));
|
||||
if (missing.length) {
|
||||
setError(t('missingFields', { fields: missing.join(', ') }));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -471,11 +475,11 @@ export default function NewListingPage() {
|
|||
<h3 style={{ margin: 0 }}>{t('localeSectionTitle')}</h3>
|
||||
<label>
|
||||
{t('titleLabel')}
|
||||
<input value={translations[currentLocale].title} onChange={(e) => updateTranslation(currentLocale, 'title', e.target.value)} required />
|
||||
<input value={translations[currentLocale].title} onChange={(e) => updateTranslation(currentLocale, 'title', e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
{t('descriptionLabel')}
|
||||
<textarea value={translations[currentLocale].description} onChange={(e) => updateTranslation(currentLocale, 'description', e.target.value)} required rows={6} />
|
||||
<textarea value={translations[currentLocale].description} onChange={(e) => updateTranslation(currentLocale, 'description', e.target.value)} rows={6} />
|
||||
</label>
|
||||
<label>
|
||||
{t('teaserLabel')}
|
||||
|
|
@ -563,20 +567,20 @@ export default function NewListingPage() {
|
|||
<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 />
|
||||
<input value={country} onChange={(e) => setCountry(e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
{t('regionLabel')}
|
||||
<input value={region} onChange={(e) => setRegion(e.target.value)} required />
|
||||
<input value={region} onChange={(e) => setRegion(e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
{t('cityLabel')}
|
||||
<input value={city} onChange={(e) => setCity(e.target.value)} required />
|
||||
<input value={city} onChange={(e) => setCity(e.target.value)} />
|
||||
</label>
|
||||
</div>
|
||||
<label>
|
||||
{t('streetAddressLabel')}
|
||||
<input value={streetAddress} onChange={(e) => setStreetAddress(e.target.value)} required />
|
||||
<input value={streetAddress} onChange={(e) => setStreetAddress(e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
{t('addressNoteLabel')}
|
||||
|
|
@ -584,11 +588,11 @@ export default function NewListingPage() {
|
|||
</label>
|
||||
<label>
|
||||
{t('contactNameLabel')}
|
||||
<input value={contactName} onChange={(e) => setContactName(e.target.value)} required />
|
||||
<input value={contactName} onChange={(e) => setContactName(e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
{t('contactEmailLabel')}
|
||||
<input type="email" value={contactEmail} onChange={(e) => setContactEmail(e.target.value)} required />
|
||||
<input type="email" value={contactEmail} onChange={(e) => setContactEmail(e.target.value)} />
|
||||
</label>
|
||||
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))' }}>
|
||||
<label>
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ const baseMessages = {
|
|||
aiApplySuccess: 'Translations updated from AI response.',
|
||||
translationMissing: 'Add at least one language with a title and description.',
|
||||
saveDraft: 'Save draft',
|
||||
missingFields: 'Missing: {fields}',
|
||||
loginToCreate: 'Please log in first to create a listing.',
|
||||
slugLabel: 'Slug',
|
||||
slugHelp: 'Unique link name, use lowercase letters and hyphens only (e.g. lake-cabin).',
|
||||
|
|
@ -329,6 +330,7 @@ const baseMessages = {
|
|||
aiApplySuccess: 'Käännökset päivitetty AI-vastauksesta.',
|
||||
translationMissing: 'Täytä vähintään yhden kielen otsikko ja kuvaus.',
|
||||
saveDraft: 'Tallenna luonnos',
|
||||
missingFields: 'Puuttuu: {fields}',
|
||||
ctaViewSample: 'Katso esimerkkikohde',
|
||||
ctaHealth: 'Tarkista health-päätepiste',
|
||||
ctaBrowse: 'Selaa kohteita',
|
||||
|
|
@ -618,6 +620,7 @@ const svMessages: Record<keyof typeof baseMessages.en, string> = {
|
|||
aiApplySuccess: 'Översättningar uppdaterades från AI-svaret.',
|
||||
translationMissing: 'Lägg till minst ett språk med titel och beskrivning.',
|
||||
saveDraft: 'Spara utkast',
|
||||
missingFields: 'Saknas: {fields}',
|
||||
slugChecking: 'Kontrollerar tillgänglighet…',
|
||||
slugAvailable: 'Sluggen är ledig',
|
||||
slugTaken: 'Sluggen används redan',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue