"use client"; import { useState, useCallback, useRef, useEffect } from "react"; import { createStop } from "@/actions/stops/create-stop"; import { updateStop } from "@/actions/stops/update-stop"; import GlassModal from "@/components/admin/GlassModal"; type Stop = { id: string; city: string; state: string; location: string; date: string; time: string; address?: string | null; zip?: string | null; cutoff_time?: string | null; active: boolean; brand_id: string; }; type Props = { isOpen: boolean; onClose: () => void; brandId: string; stop?: Stop | null; onSuccess?: (stopId: string) => void; }; /* Pin icon for the modal header */ const PinIcon = () => ( ); export default function EditStopModal({ isOpen, onClose, brandId, stop, onSuccess }: Props) { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [city, setCity] = useState(""); const [stateField, setStateField] = useState(""); const [location, setLocation] = useState(""); const [date, setDate] = useState(""); const [time, setTime] = useState(""); const [address, setAddress] = useState(""); const [zip, setZip] = useState(""); const [cutoffTime, setCutoffTime] = useState(""); const [status, setStatus] = useState<"draft" | "active">("draft"); const cityRef = useRef(null); const isEditing = Boolean(stop); // Reset form when modal opens or stop changes — derived during render // per React docs: "Adjusting some state when a prop changes". const [prevSyncKey, setPrevSyncKey] = useState(null); const syncKey = `${isOpen ? "open" : "closed"}:${stop?.id ?? "new"}`; if (syncKey !== prevSyncKey) { setPrevSyncKey(syncKey); if (isOpen) { if (stop) { // Edit mode - populate from existing stop. setCity(stop.city); setStateField(stop.state); setLocation(stop.location); setDate(stop.date); setTime(stop.time || ""); setAddress(stop.address ?? ""); setZip(stop.zip ?? ""); setCutoffTime(stop.cutoff_time ? stop.cutoff_time.slice(0, 16) : ""); setStatus(stop.active ? "active" : "draft"); } else { // Add mode - reset form setCity(""); setStateField(""); setLocation(""); setDate(""); setTime(""); setAddress(""); setZip(""); setCutoffTime(""); setStatus("draft"); } setError(null); } } // Focus the first field once the modal has actually opened. This is // a DOM side-effect, so it must run after the commit (i.e. in an // effect) — accessing `cityRef.current` during render is illegal. // The `prevSyncKey.startsWith("open:")` guard keeps the focus call // to the transition into the open state, not every re-render. useEffect(() => { if (prevSyncKey?.startsWith("open:")) { const id = requestAnimationFrame(() => cityRef.current?.focus()); return () => cancelAnimationFrame(id); } return undefined; }, [prevSyncKey]); const handleSubmit = useCallback( async (e: React.FormEvent) => { e.preventDefault(); setError(null); if (!city.trim() || !stateField.trim() || !location.trim() || !date) { setError("City, state, location, and date are required."); return; } setLoading(true); try { let result; if (isEditing && stop) { result = await updateStop(stop.id, brandId, { city: city.trim(), state: stateField.trim(), location: location.trim(), date, time: time || "08:00", address: address || null, zip: zip || null, cutoff_time: cutoffTime || null, active: status === "active", }); } else { result = await createStop(brandId, { city: city.trim(), state: stateField.trim(), location: location.trim(), date, time: time || "08:00", address: address || undefined, zip: zip || undefined, cutoff_time: cutoffTime || undefined, active: status === "active", }); } if (result.success) { const newStopId = isEditing ? stop!.id : (result as { success: true; id: string }).id; onSuccess?.(newStopId); onClose(); } else { setError(result.error ?? `Failed to ${isEditing ? "update" : "create"} stop`); } } catch { setError("Network error. Please try again."); } finally { setLoading(false); } }, [ brandId, city, stateField, location, date, time, address, zip, cutoffTime, status, isEditing, stop, onSuccess, onClose, ] ); const title = isEditing ? "Edit Stop" : "Add Stop"; const eyebrow = isEditing ? `${stop?.city}, ${stop?.state}` : "New stop on the route"; const submitLabel = loading ? isEditing ? "Saving…" : "Creating…" : isEditing ? "Save Changes" : status === "active" ? "Create & Publish" : "Save as Draft"; if (!isOpen) return null; return (
{error && (
{error}
)} {/* Row 1 — Where: City + State */}
setCity(e.target.value)} placeholder="Denver" className="ha-field-input" required autoComplete="address-level2" />
setStateField(e.target.value.toUpperCase())} placeholder="CO" maxLength={2} className="ha-field-input ha-field-input-mono text-center" style={{ textTransform: "uppercase" }} required autoComplete="address-level1" />
{/* Row 2 — Where at: Location (full) */}
setLocation(e.target.value)} placeholder="Whole Foods Market — Highlands" className="ha-field-input" required />
{/* Row 3 — When: Date + Time */}
setDate(e.target.value)} className="ha-field-input ha-field-input-mono" required />
setTime(e.target.value)} className="ha-field-input ha-field-input-mono" />
{/* Row 4 — Address + ZIP + Cutoff */}
setAddress(e.target.value)} placeholder="123 Main St" className="ha-field-input" autoComplete="street-address" />
setZip(e.target.value)} placeholder="80202" maxLength={10} className="ha-field-input ha-field-input-mono" autoComplete="postal-code" />
setCutoffTime(e.target.value)} className="ha-field-input ha-field-input-mono" />
{/* Row 5 — Status: segmented control */}

Visibility

Draft is hidden from customers
{/* Footer */}
Esc to close
); } // Hook for easy integration export function useEditStopModal(brandId: string) { const [isOpen, setIsOpen] = useState(false); const [editingStop, setEditingStop] = useState<{ id: string; city: string; state: string; location: string; date: string; time: string; address?: string | null; zip?: string | null; cutoff_time?: string | null; active: boolean; brand_id: string; } | null>(null); const open = useCallback((stop?: typeof editingStop) => { setEditingStop(stop ?? null); setIsOpen(true); }, []); const close = useCallback(() => { setIsOpen(false); setEditingStop(null); }, []); const Modal = useCallback(() => ( ), [isOpen, close, brandId, editingStop]); return { open, close, Modal, editingStop }; }