fix(search): support PLZ filter on home page
The home page dropped `plz` server-side, so /markets was called with radius but no center (unfiltered) and the PLZ input rendered empty after reload. +page.server.ts now reads plz, geocodes via /geocode, and echoes plz back in searchParams for form rehydration. Relaxes /geocode DTO + guard to accept PLZ without city — Nominatim already supports postal-only lookups. URL lat/lon (GPS flow) take priority over plz on tie-break; geocode failures fall through to no geo-filter so the page always renders.
This commit is contained in:
@@ -395,7 +395,7 @@ type SubmitMarketRequest struct {
|
||||
|
||||
type GeocodeRequest struct {
|
||||
Street string `json:"street"`
|
||||
City string `json:"city" validate:"required"`
|
||||
City string `json:"city"`
|
||||
Zip string `json:"zip"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ type nominatimResult struct {
|
||||
}
|
||||
|
||||
func (g *Geocoder) Geocode(ctx context.Context, street, city, zip, country string) (*float64, *float64, error) {
|
||||
if city == "" {
|
||||
return nil, nil, fmt.Errorf("city is required for geocoding")
|
||||
if city == "" && zip == "" {
|
||||
return nil, nil, fmt.Errorf("city or zip is required for geocoding")
|
||||
}
|
||||
|
||||
// Respect Nominatim rate limit: max 1 req/sec
|
||||
|
||||
@@ -2,10 +2,34 @@ import type { PageServerLoad } from './$types.js';
|
||||
import { apiFetch, buildSearchQuery } from '$lib/api/client.js';
|
||||
import type { MarketSummary, PaginationMeta } from '$lib/api/types.js';
|
||||
|
||||
export const load: PageServerLoad = async ({ url, fetch }) => {
|
||||
const params: Record<string, string> = {};
|
||||
type Coords = { lat?: string; lon?: string };
|
||||
|
||||
async function resolveCoords(
|
||||
plz: string | null,
|
||||
urlLat: string | null,
|
||||
urlLon: string | null,
|
||||
fetch: typeof globalThis.fetch
|
||||
): Promise<Coords> {
|
||||
if (urlLat && urlLon) return { lat: urlLat, lon: urlLon };
|
||||
if (!plz) return {};
|
||||
|
||||
try {
|
||||
const res = await apiFetch<{ latitude: number | null; longitude: number | null }>('/geocode', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ city: '', zip: plz, country: 'DE' }),
|
||||
fetch
|
||||
});
|
||||
const { latitude, longitude } = res.data;
|
||||
if (latitude == null || longitude == null) return {};
|
||||
return { lat: String(latitude), lon: String(longitude) };
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export const load: PageServerLoad = async ({ url, fetch }) => {
|
||||
const q = url.searchParams.get('q');
|
||||
const plz = url.searchParams.get('plz');
|
||||
const lat = url.searchParams.get('lat');
|
||||
const lon = url.searchParams.get('lon');
|
||||
const radius = url.searchParams.get('radius');
|
||||
@@ -14,9 +38,12 @@ export const load: PageServerLoad = async ({ url, fetch }) => {
|
||||
const sort = url.searchParams.get('sort');
|
||||
const page = url.searchParams.get('page');
|
||||
|
||||
const coords = await resolveCoords(plz, lat, lon, fetch);
|
||||
|
||||
const params: Record<string, string> = {};
|
||||
if (q) params.q = q;
|
||||
if (lat) params.lat = lat;
|
||||
if (lon) params.lon = lon;
|
||||
if (coords.lat) params.lat = coords.lat;
|
||||
if (coords.lon) params.lon = coords.lon;
|
||||
if (radius) params.radius = radius;
|
||||
if (from) params.from = from;
|
||||
if (to) params.to = to;
|
||||
@@ -26,34 +53,29 @@ export const load: PageServerLoad = async ({ url, fetch }) => {
|
||||
const query = buildSearchQuery(params);
|
||||
const path = `/markets${query ? `?${query}` : ''}`;
|
||||
|
||||
const searchParams = {
|
||||
q: q ?? '',
|
||||
plz: plz ?? '',
|
||||
lat: lat ? Number(lat) : undefined,
|
||||
lon: lon ? Number(lon) : undefined,
|
||||
radius: radius ? Number(radius) : 25,
|
||||
from: from ?? '',
|
||||
to: to ?? '',
|
||||
sort: sort ?? ''
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await apiFetch<MarketSummary[]>(path, { fetch });
|
||||
return {
|
||||
markets: res.data,
|
||||
meta: res.meta as PaginationMeta,
|
||||
searchParams: {
|
||||
q: q ?? '',
|
||||
lat: lat ? Number(lat) : undefined,
|
||||
lon: lon ? Number(lon) : undefined,
|
||||
radius: radius ? Number(radius) : 25,
|
||||
from: from ?? '',
|
||||
to: to ?? '',
|
||||
sort: sort ?? ''
|
||||
}
|
||||
searchParams
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
markets: [] as MarketSummary[],
|
||||
meta: { page: 1, per_page: 20, total: 0, total_pages: 0 } as PaginationMeta,
|
||||
searchParams: {
|
||||
q: q ?? '',
|
||||
lat: lat ? Number(lat) : undefined,
|
||||
lon: lon ? Number(lon) : undefined,
|
||||
radius: radius ? Number(radius) : 25,
|
||||
from: from ?? '',
|
||||
to: to ?? '',
|
||||
sort: sort ?? ''
|
||||
}
|
||||
searchParams
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
<SearchForm
|
||||
q={data.searchParams.q}
|
||||
plz={data.searchParams.plz}
|
||||
radius={data.searchParams.radius}
|
||||
from={data.searchParams.from}
|
||||
to={data.searchParams.to}
|
||||
|
||||
Reference in New Issue
Block a user