This commit is contained in:
Andras Schmelczer 2026-03-15 17:38:26 +00:00
parent 80c093b7ba
commit f72c43a9fa
101 changed files with 2168 additions and 1177 deletions

View file

@ -19,7 +19,15 @@ function normalizePostcode(s: string): string {
export type SearchResult =
| { type: 'postcode'; label: string }
| { type: 'place'; name: string; slug: string; place_type: string; lat: number; lon: number; city?: string };
| {
type: 'place';
name: string;
slug: string;
place_type: string;
lat: number;
lon: number;
city?: string;
};
export function useLocationSearch(mode?: string) {
const [query, setQuery] = useState('');
@ -29,60 +37,63 @@ export function useLocationSearch(mode?: string) {
const abortRef = useRef<AbortController | null>(null);
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
const handleInputChange = useCallback((value: string) => {
setQuery(value);
setActiveIndex(-1);
const handleInputChange = useCallback(
(value: string) => {
setQuery(value);
setActiveIndex(-1);
abortRef.current?.abort();
if (debounceRef.current) clearTimeout(debounceRef.current);
abortRef.current?.abort();
if (debounceRef.current) clearTimeout(debounceRef.current);
const trimmed = value.trim();
if (!trimmed) {
setResults([]);
setOpen(false);
return;
}
if (!mode && looksLikePostcode(trimmed)) {
setResults([{ type: 'postcode', label: normalizePostcode(trimmed) }]);
setOpen(true);
return;
}
if (trimmed.length < 2) {
setResults([]);
setOpen(false);
return;
}
debounceRef.current = setTimeout(async () => {
const controller = new AbortController();
abortRef.current = controller;
try {
const params = new URLSearchParams({ q: trimmed, limit: '7' });
if (mode) params.set('mode', mode);
const res = await fetch(
`/api/places?${params}`,
authHeaders({ signal: controller.signal }),
);
if (!res.ok) return;
const json: { places: PlaceResult[] } = await res.json();
const placeResults: SearchResult[] = json.places.map((p) => ({
type: 'place' as const,
name: p.name,
slug: p.slug,
place_type: p.place_type,
lat: p.lat,
lon: p.lon,
city: p.city,
}));
setResults(placeResults);
setOpen(placeResults.length > 0);
} catch (err) {
logNonAbortError('places search', err);
const trimmed = value.trim();
if (!trimmed) {
setResults([]);
setOpen(false);
return;
}
}, 200);
}, [mode]);
if (!mode && looksLikePostcode(trimmed)) {
setResults([{ type: 'postcode', label: normalizePostcode(trimmed) }]);
setOpen(true);
return;
}
if (trimmed.length < 2) {
setResults([]);
setOpen(false);
return;
}
debounceRef.current = setTimeout(async () => {
const controller = new AbortController();
abortRef.current = controller;
try {
const params = new URLSearchParams({ q: trimmed, limit: '7' });
if (mode) params.set('mode', mode);
const res = await fetch(
`/api/places?${params}`,
authHeaders({ signal: controller.signal })
);
if (!res.ok) return;
const json: { places: PlaceResult[] } = await res.json();
const placeResults: SearchResult[] = json.places.map((p) => ({
type: 'place' as const,
name: p.name,
slug: p.slug,
place_type: p.place_type,
lat: p.lat,
lon: p.lon,
city: p.city,
}));
setResults(placeResults);
setOpen(placeResults.length > 0);
} catch (err) {
logNonAbortError('places search', err);
}
}, 200);
},
[mode]
);
const close = useCallback(() => setOpen(false), []);
@ -112,7 +123,7 @@ export function useLocationSearch(mode?: string) {
setOpen(false);
}
},
[results, activeIndex, query],
[results, activeIndex, query]
);
// Cleanup on unmount