From 30a0aaa42b04b45b6a4cde4ea4bd0bea030d30b0 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Wed, 7 Oct 2026 11:30:55 +0700 Subject: [PATCH] feat(create-spot): make address + access type required; move them out of the optional collapsible --- AGENTS.md | 7 +++++ src/i18n/en.ts | 1 + src/i18n/es.ts | 1 + src/i18n/th/batch5.ts | 1 + src/pages/CreateSpotPage.tsx | 56 +++++++++++++++++++----------------- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 90cbc5e..3f6dc41 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1127,6 +1127,13 @@ session with **no other memory of this project**. So: stall signing (a miss falls back to the stats' read-time region derivation). ⚠️ A geohash-prefix nearby query is still impossible — relays only exact-match `#g` — so the 2000-spot cache remains the mechanism. + **Required set (2026-10-07):** pin + name + description + **address** + + **access type** + photo. Address and access type were **moved OUT of the + "More details (optional)" collapsible** into the required flow (the collapsible + now holds only Shelter + Tags) — required fields must not be hidden under + "optional". Access keeps its **Public default** (so it is always satisfied; the + `*` marks membership in the required set), and Address has real inline + validation (`createSpot.addressRequired`). - **Map pin rendering (perf, 2026-09-29):** the spot/tour/event/crew marker effect in `SpotMap.tsx` used to `clearLayers()` + rebuild **every** marker whenever the parent handed it fresh arrays, and added each marker with a diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 699918f..1265fa0 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -509,6 +509,7 @@ nav: { photoHint: 'a photo is required to publish', nameRequired: 'Please enter a name for this spot.', descriptionRequired: 'Please describe this spot.', + addressRequired: 'Please add an address or area name.', photoRequired: 'Please add at least one photo.', draftTitle: 'You have an unsaved draft', draftBody: 'Pick up where you left off, or discard it.', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index 327cf80..7dc8a12 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -509,6 +509,7 @@ nav: { photoHint: 'se requiere una foto para publicar', nameRequired: 'Ponle un nombre a este spot.', descriptionRequired: 'Describe este spot.', + addressRequired: 'Añade una dirección o nombre de zona.', photoRequired: 'Añade al menos una foto.', draftTitle: 'Tienes un borrador sin guardar', draftBody: 'Continúa donde lo dejaste o descártalo.', diff --git a/src/i18n/th/batch5.ts b/src/i18n/th/batch5.ts index 4c5f5d7..76fcd34 100644 --- a/src/i18n/th/batch5.ts +++ b/src/i18n/th/batch5.ts @@ -369,6 +369,7 @@ const thBatch5 = { photoHint: 'ต้องมีรูปจึงจะเผยแพร่ได้', nameRequired: 'กรุณาตั้งชื่อสปอตนี้', descriptionRequired: 'กรุณาอธิบายสปอตนี้', + addressRequired: 'กรุณาเพิ่มที่อยู่หรือชื่อพื้นที่', photoRequired: 'กรุณาเพิ่มรูปอย่างน้อยหนึ่งรูป', draftTitle: 'คุณมีฉบับร่างที่ยังไม่ได้บันทึก', draftBody: 'ทำต่อจากที่ค้างไว้ หรือลบทิ้ง', diff --git a/src/pages/CreateSpotPage.tsx b/src/pages/CreateSpotPage.tsx index c2c0d84..c7346a4 100644 --- a/src/pages/CreateSpotPage.tsx +++ b/src/pages/CreateSpotPage.tsx @@ -45,6 +45,7 @@ import { Link } from 'react-router-dom'; type FieldErrors = { title?: string; description?: string; + address?: string; location?: string; type?: string; photo?: string; @@ -342,6 +343,7 @@ export default function CreateSpotPage() { const nextErrors: FieldErrors = {}; if (!title.trim()) nextErrors.title = t('createSpot.nameRequired'); if (!description.trim()) nextErrors.description = t('createSpot.descriptionRequired'); + if (!location.trim()) nextErrors.address = t('createSpot.addressRequired'); if (!hasCoords) nextErrors.location = t('spotUi.locationRequiredDesc'); if (!selectedCategory) nextErrors.type = t('spotUi.spotTypeRequiredDesc'); if (images.length === 0) nextErrors.photo = t('createSpot.photoRequired'); @@ -613,6 +615,19 @@ export default function CreateSpotPage() { + {/* Address (required) — the human-readable place name. */} +
+ + { setLocation(e.target.value); clearError('address'); }} + className={cn(errors.address && 'border-red-500/60')} + /> + {errors.address &&

{errors.address}

} +
+ {/* Duplicate warning — appears as soon as the pin lands, right below the map, instead of only on submit. */} {dupWarning && ( @@ -776,34 +791,9 @@ export default function CreateSpotPage() { isUploading={isUploading} /> - {/* ── More details (optional) ───────────────────────────── */} - - - - - - {/* Address */} -
- - setLocation(e.target.value)} - /> -
- {/* Access Type */}
- +
{/* Public */} )} + {/* ── More details (optional) ───────────────────────────── */} + + + + + {/* Shelter / weather protection */}