fix(planner): nearby-POI lookup sends the real session (was 401)
fetchNearbyPois defaulted sessionId to the placeholder "nearby", which /api/pois's requireSession rejects (401 Unauthorized) — so the nearby-POI lookup for a selected waypoint always failed on real deployments. Thread the live planner sessionId through useNearbyPois → fetchNearbyPois → queryPois (same session the main POI markers already use), and make sessionId a required arg so the placeholder can't silently return. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0f371d868f
commit
267c2fb8b7
6 changed files with 13 additions and 11 deletions
|
|
@ -185,7 +185,7 @@ export function PlannerMap({ yjs, sessionId, onRouteRequest, highlightPosition,
|
||||||
const { draggingOver, handleDragEnter, handleDragLeave, handleDragOver, handleDrop } = useGpxDrop(yjs, onImportError);
|
const { draggingOver, handleDragEnter, handleDragLeave, handleDragOver, handleDrop } = useGpxDrop(yjs, onImportError);
|
||||||
|
|
||||||
const selectedWp = selectedWaypointIndex != null ? waypoints[selectedWaypointIndex] : undefined;
|
const selectedWp = selectedWaypointIndex != null ? waypoints[selectedWaypointIndex] : undefined;
|
||||||
const nearbyPoisState = useNearbyPois(selectedWp?.lat, selectedWp?.lon);
|
const nearbyPoisState = useNearbyPois(selectedWp?.lat, selectedWp?.lon, sessionId);
|
||||||
|
|
||||||
const handleSnapToPoi = useCallback((poi: Poi) => {
|
const handleSnapToPoi = useCallback((poi: Poi) => {
|
||||||
if (selectedWaypointIndex == null) return;
|
if (selectedWaypointIndex == null) return;
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ function useAwarenessToasts(yjs: YjsState | null, t: TFunction, addToast: (messa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function SidebarTabs({ yjs, routeStats, days, onWaypointHover, onWaypointSelect }: { yjs: YjsState; routeStats: ReturnType<typeof useRouting>["routeStats"]; days: ReturnType<typeof useDays>; onWaypointHover: (index: number | null) => void; onWaypointSelect: (index: number | null) => void }) {
|
function SidebarTabs({ yjs, sessionId, routeStats, days, onWaypointHover, onWaypointSelect }: { yjs: YjsState; sessionId: string; routeStats: ReturnType<typeof useRouting>["routeStats"]; days: ReturnType<typeof useDays>; onWaypointHover: (index: number | null) => void; onWaypointSelect: (index: number | null) => void }) {
|
||||||
const { t } = useTranslation("planner");
|
const { t } = useTranslation("planner");
|
||||||
const [tab, setTab] = useState<"waypoints" | "notes">("waypoints");
|
const [tab, setTab] = useState<"waypoints" | "notes">("waypoints");
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ function SidebarTabs({ yjs, routeStats, days, onWaypointHover, onWaypointSelect
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
{tab === "waypoints" ? (
|
{tab === "waypoints" ? (
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<WaypointSidebar yjs={yjs} routeStats={routeStats} days={days} onWaypointHover={onWaypointHover} onWaypointSelect={onWaypointSelect} />
|
<WaypointSidebar yjs={yjs} sessionId={sessionId} routeStats={routeStats} days={days} onWaypointHover={onWaypointHover} onWaypointSelect={onWaypointSelect} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
<NotesPanel yjs={yjs} />
|
<NotesPanel yjs={yjs} />
|
||||||
|
|
@ -292,7 +292,7 @@ export function SessionView({ sessionId, hasJournalCallback, returnUrl, initialW
|
||||||
</div>
|
</div>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</main>
|
</main>
|
||||||
<SidebarTabs yjs={yjs} routeStats={routeStats} days={days} onWaypointHover={setHighlightedWaypoint} onWaypointSelect={setSelectedWaypointIndex} />
|
<SidebarTabs yjs={yjs} sessionId={sessionId} routeStats={routeStats} days={days} onWaypointHover={setHighlightedWaypoint} onWaypointSelect={setSelectedWaypointIndex} />
|
||||||
</div>
|
</div>
|
||||||
<YjsDebugPanel yjs={yjs} sessionId={sessionId} />
|
<YjsDebugPanel yjs={yjs} sessionId={sessionId} />
|
||||||
{toasts.length > 0 && (
|
{toasts.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const NOTE_MAX = 500;
|
||||||
|
|
||||||
interface WaypointSidebarProps {
|
interface WaypointSidebarProps {
|
||||||
yjs: YjsState;
|
yjs: YjsState;
|
||||||
|
sessionId: string;
|
||||||
routeStats?: {
|
routeStats?: {
|
||||||
distance?: number;
|
distance?: number;
|
||||||
elevationGain?: number;
|
elevationGain?: number;
|
||||||
|
|
@ -29,7 +30,7 @@ interface WaypointSidebarProps {
|
||||||
onWaypointSelect?: (index: number | null) => void;
|
onWaypointSelect?: (index: number | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WaypointSidebar({ yjs, routeStats, days, onWaypointHover, onWaypointSelect }: WaypointSidebarProps) {
|
export function WaypointSidebar({ yjs, sessionId, routeStats, days, onWaypointHover, onWaypointSelect }: WaypointSidebarProps) {
|
||||||
const { t } = useTranslation("planner");
|
const { t } = useTranslation("planner");
|
||||||
const [waypoints, setWaypoints] = useState<WaypointData[]>([]);
|
const [waypoints, setWaypoints] = useState<WaypointData[]>([]);
|
||||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||||
|
|
@ -133,7 +134,7 @@ export function WaypointSidebar({ yjs, routeStats, days, onWaypointHover, onWayp
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedWp = selectedIndex !== null ? waypoints[selectedIndex] : undefined;
|
const selectedWp = selectedIndex !== null ? waypoints[selectedIndex] : undefined;
|
||||||
const nearbyPoisState = useNearbyPois(selectedWp?.lat, selectedWp?.lon);
|
const nearbyPoisState = useNearbyPois(selectedWp?.lat, selectedWp?.lon, sessionId);
|
||||||
const [showAllNearby, setShowAllNearby] = useState(false);
|
const [showAllNearby, setShowAllNearby] = useState(false);
|
||||||
|
|
||||||
const hasMultipleDays = days.length > 1;
|
const hasMultipleDays = days.length > 1;
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ describe("fetchNearbyPois", () => {
|
||||||
afterEach(() => vi.unstubAllGlobals());
|
afterEach(() => vi.unstubAllGlobals());
|
||||||
|
|
||||||
it("builds a bbox around the point and calls /api/pois", async () => {
|
it("builds a bbox around the point and calls /api/pois", async () => {
|
||||||
await fetchNearbyPois(50.0, 10.0, 500, drinkingWater);
|
await fetchNearbyPois(50.0, 10.0, 500, drinkingWater, "sess");
|
||||||
expect(fetch).toHaveBeenCalledOnce();
|
expect(fetch).toHaveBeenCalledOnce();
|
||||||
const [url] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string];
|
const [url] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string];
|
||||||
expect(url).toMatch(/\/api\/pois\?bbox=\d+\.\d+,\d+\.\d+,\d+\.\d+,\d+\.\d+/);
|
expect(url).toMatch(/\/api\/pois\?bbox=\d+\.\d+,\d+\.\d+,\d+\.\d+,\d+\.\d+/);
|
||||||
|
|
@ -153,7 +153,7 @@ describe("fetchNearbyPois", () => {
|
||||||
|
|
||||||
it("forwards the AbortSignal to fetch", async () => {
|
it("forwards the AbortSignal to fetch", async () => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
await fetchNearbyPois(50.0, 10.0, 500, drinkingWater, controller.signal);
|
await fetchNearbyPois(50.0, 10.0, 500, drinkingWater, "sess", controller.signal);
|
||||||
const [, opts] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string, RequestInit];
|
const [, opts] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0] as [string, RequestInit];
|
||||||
expect(opts.signal).toBe(controller.signal);
|
expect(opts.signal).toBe(controller.signal);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,8 @@ export async function fetchNearbyPois(
|
||||||
lon: number,
|
lon: number,
|
||||||
radiusMeters: number,
|
radiusMeters: number,
|
||||||
categories: PoiCategory[],
|
categories: PoiCategory[],
|
||||||
|
sessionId: string,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
sessionId = "nearby",
|
|
||||||
): Promise<Poi[]> {
|
): Promise<Poi[]> {
|
||||||
const dLat = radiusMeters * DEG_PER_METER_LAT;
|
const dLat = radiusMeters * DEG_PER_METER_LAT;
|
||||||
const dLon = dLat / Math.cos((lat * Math.PI) / 180);
|
const dLon = dLat / Math.cos((lat * Math.PI) / 180);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const rateLimitedUntilRef = { current: 0 };
|
||||||
export function useNearbyPois(
|
export function useNearbyPois(
|
||||||
lat: number | undefined,
|
lat: number | undefined,
|
||||||
lon: number | undefined,
|
lon: number | undefined,
|
||||||
|
sessionId: string,
|
||||||
): NearbyPoisState {
|
): NearbyPoisState {
|
||||||
const [state, setState] = useState<NearbyPoisState>({ pois: [], status: "idle" });
|
const [state, setState] = useState<NearbyPoisState>({ pois: [], status: "idle" });
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
|
|
@ -47,7 +48,7 @@ export function useNearbyPois(
|
||||||
setState((prev) => ({ ...prev, status: "loading" }));
|
setState((prev) => ({ ...prev, status: "loading" }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pois = await fetchNearbyPois(lat, lon, NEARBY_RADIUS_METERS, poiCategories, controller.signal);
|
const pois = await fetchNearbyPois(lat, lon, NEARBY_RADIUS_METERS, poiCategories, sessionId, controller.signal);
|
||||||
if (!controller.signal.aborted) {
|
if (!controller.signal.aborted) {
|
||||||
setState({ pois, status: "done" });
|
setState({ pois, status: "done" });
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +69,7 @@ export function useNearbyPois(
|
||||||
if (timerRef.current) clearTimeout(timerRef.current);
|
if (timerRef.current) clearTimeout(timerRef.current);
|
||||||
abortRef.current?.abort();
|
abortRef.current?.abort();
|
||||||
};
|
};
|
||||||
}, [lat, lon]);
|
}, [lat, lon, sessionId]);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue