From 71ae322b3f64b692f9e0f76fa7314df8ad9c79ef Mon Sep 17 00:00:00 2001 From: muken Date: Sat, 27 Jun 2026 19:29:08 -0500 Subject: [PATCH] feat(landscape): multi-photo gallery like rooms with uploads and ordering --- src/app/admin/page.tsx | 29 +++-- src/app/api/places/[id]/route.ts | 8 +- src/app/api/places/route.ts | 8 +- src/app/landscape/page.tsx | 196 ++++++++++++++++++++++--------- src/lib/schema.ts | 9 +- 5 files changed, 174 insertions(+), 76 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 6f3e05b..ac59373 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -67,7 +67,8 @@ interface Place { id: number; name: string; description: string; - photo: string | null; + photos: string[]; + featured_photo: string | null; distance_km: string | null; visit_time: string | null; suggestion: string | null; @@ -140,7 +141,7 @@ export default function AdminPage() { const [editingMenu, setEditingMenu] = useState(null); const [places, setPlaces] = useState([]); - const [placeForm, setPlaceForm] = useState>({ name: '', description: '', photo: '', distance_km: '', visit_time: '', suggestion: '', active: true, sort_order: 0 }); + const [placeForm, setPlaceForm] = useState>({ name: '', description: '', photos: [], featured_photo: null, distance_km: '', visit_time: '', suggestion: '', active: true, sort_order: 0 }); const [editingPlace, setEditingPlace] = useState(null); const [reviews, setReviews] = useState([]); @@ -305,7 +306,7 @@ export default function AdminPage() { const savePlace = async (e: React.FormEvent) => { e.preventDefault(); - const payload = { ...placeForm, active: placeForm.active !== false, sort_order: placeForm.sort_order || 0 }; + const payload = { ...placeForm, photos: placeForm.photos || [], featured_photo: placeForm.featured_photo || null, active: placeForm.active !== false, sort_order: placeForm.sort_order || 0 }; if (editingPlace) { await api(`/api/places/${editingPlace}`, 'PUT', payload); setPlaces((prev) => prev.map((p) => (p.id === editingPlace ? { ...p, ...payload } as Place : p))); @@ -314,11 +315,11 @@ export default function AdminPage() { const json = await api('/api/places', 'POST', payload); setPlaces((prev) => [...prev, json.data]); } - setPlaceForm({ name: '', description: '', photo: '', distance_km: '', visit_time: '', suggestion: '', active: true, sort_order: 0 }); + setPlaceForm({ name: '', description: '', photos: [], featured_photo: null, distance_km: '', visit_time: '', suggestion: '', active: true, sort_order: 0 }); notify('Place saved.'); }; - const editPlace = (p: Place) => { setPlaceForm({ ...p }); setEditingPlace(p.id); }; + const editPlace = (p: Place) => { setPlaceForm({ ...p, photos: p.photos || [], featured_photo: p.featured_photo || null }); setEditingPlace(p.id); }; const deletePlace = async (id: number) => { await api(`/api/places/${id}`, 'DELETE'); setPlaces((prev) => prev.filter((p) => p.id !== id)); }; const approveReview = async (id: number) => { @@ -558,7 +559,7 @@ export default function AdminPage() { setRoomForm({ ...roomForm, price: v })} /> setRoomForm({ ...roomForm, description: v })} /> setRoomForm({ ...roomForm, sort_order: parseInt(v || '0', 10) })} /> - setPlaceForm({ ...placeForm, name: v })} /> setPlaceForm({ ...placeForm, distance_km: v })} /> setPlaceForm({ ...placeForm, visit_time: v })} /> - setPlaceForm({ ...placeForm, photo: v })} /> setPlaceForm({ ...placeForm, sort_order: parseInt(v || '0', 10) })} /> + setPlaceForm({ ...placeForm, photos, featured_photo: featured })} + /> setPlaceForm({ ...placeForm, description: v })} /> setPlaceForm({ ...placeForm, suggestion: v })} />
- {editingPlace && { setEditingPlace(null); setPlaceForm({ name: '', description: '', photo: '', distance_km: '', visit_time: '', suggestion: '', active: true, sort_order: 0 }); }}>Cancel} + {editingPlace && { setEditingPlace(null); setPlaceForm({ name: '', description: '', photos: ([] as string[]), featured_photo: null, distance_km: '', visit_time: '', suggestion: '', active: true, sort_order: 0 }); }}>Cancel}
- [p.name, `${p.distance_km || ''} km`, p.description.slice(0, 60), p.active ? 'On' : 'Off', `#${p.sort_order}`])} onEdit={(i) => editPlace(places[i])} onDelete={(i) => deletePlace(places[i].id)} /> + [p.name, `${p.distance_km || ''} km`, p.description.slice(0, 60), p.photos.length > 0 ? `🖼️ ${p.photos.length}` : 'no image', p.active ? 'On' : 'Off', `#${p.sort_order}`])} onEdit={(i) => editPlace(places[i])} onDelete={(i) => deletePlace(places[i].id)} />
@@ -823,7 +830,7 @@ function ListTable({ rows, onEdit, onDelete, onComments }: { rows: string[][]; o ); } -function RoomPhotoPicker({ +function PhotoPicker({ label, images, photos, featuredPhoto, onChange, }: { label: string; @@ -873,7 +880,7 @@ function RoomPhotoPicker({ {allPhotos.length > 0 && (
- Featured photo: + Featured: