Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export default async function MapDetail(props: {
const searchParams = await props.searchParams;
const previousParams = await getPreviousParams();

try {
if (!isOnLocationDetailPage(params)) {
if (!isOnLocationDetailPage(params)) {
try {
// validate
getParsedSubCategory(params);
redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet({
Expand All @@ -48,26 +48,46 @@ export default async function MapDetail(props: {
})}
/>
);
} else {
const location = await fetchLocationsDetailData(
params.locationSlugOrPersonalCareSubCategory,
);
return (
<LocationsMap
locationStubs={
previousParams
? await getMapContainerData(previousParams)
: [location]
}
locationDetailStub={location}
/>
);
} catch (e) {
if (e instanceof Error404Response) {
return notFound();
}

return <LocationsMap locationStubs={[]} />;
}
}

try {
const location = await fetchLocationsDetailData(
params.locationSlugOrPersonalCareSubCategory,
);
return (
<LocationsMap
locationStubs={
previousParams
? await getMapContainerData(previousParams)
: [location]
}
locationDetailStub={location}
/>
);
} catch (e) {
if (e instanceof Error404Response) {
return notFound();
} else {
throw e; // rethrow the error to force a 500 response
}

if (previousParams) {
try {
return (
<LocationsMap
locationStubs={await getMapContainerData(previousParams)}
/>
);
} catch {
return <LocationsMap locationStubs={[]} />;
}
}

return <LocationsMap locationStubs={[]} />;
}
}
33 changes: 23 additions & 10 deletions src/app/[route]/@mapContainer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,39 @@ import LocationsMap from "../../../components/map";
import { getMapContainerData } from "../../../components/map-container-component";
import { cookies } from "next/headers";
import { redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet } from "@/components/navigation";
import { Error404Response } from "@/components/streetlives-api-service";

export default async function MapContainerPage(props: {
searchParams: Promise<SearchParams>;
params: Promise<RouteParams>;
}) {
const params = await props.params;
const searchParams = await props.searchParams;

if (!RESOURCE_ROUTES.includes(params.route)) {
return notFound();
}

redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet({
searchParams,
params,
cookies: await cookies(),
});
return RESOURCE_ROUTES.includes(params.route) ? (
<LocationsMap
locationStubs={await getMapContainerData({
searchParams,
params,
})}
/>
) : (
notFound()
);

try {
return (
<LocationsMap
locationStubs={await getMapContainerData({
searchParams,
params,
})}
/>
);
} catch (e) {
if (e instanceof Error404Response) {
return notFound();
}

return <LocationsMap locationStubs={[]} />;
Comment on lines +47 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-throw redirect control-flow errors in catch block

redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet calls Next.js redirect(...), which signals redirects by throwing a control-flow error; this catch now treats any non-Error404Response as a recoverable failure and renders an empty map instead. In the sortBy=nearby + missing lat/long cookie flow, users will no longer be redirected to the non-nearby URL and will land on the error/empty state, which is a regression in navigation behavior.

Useful? React with 👍 / 👎.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from "../../../../components/common";
import LocationDetailComponent from "../../../../components/location-detail-component";
import { SidePanelComponent } from "../../../../components/side-panel-component";
import SidePanelErrorState from "../../../../components/side-panel-error-state";
import {
Error404Response,
fetchComments,
Expand All @@ -34,15 +35,16 @@ export default async function LocationDetail(props: {
const searchParams = await props.searchParams;
const params = await props.params;

try {
if (!isOnLocationDetailPage(params)) {
if (!isOnLocationDetailPage(params)) {
redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet({
searchParams,
params,
cookies: await cookies(),
});

try {
// validate
getParsedSubCategory(params);
redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet({
searchParams,
params,
cookies: await cookies(),
});
return (
<SidePanelComponent
searchParams={searchParams}
Expand All @@ -53,28 +55,36 @@ export default async function LocationDetail(props: {
})}
/>
);
} else {
const location = map_gogetta_to_yourpeer(
await fetchLocationsDetailData(
params.locationSlugOrPersonalCareSubCategory,
),
true,
);
const comments = await fetchComments(location.id);
} catch (e) {
if (e instanceof Error404Response) {
return notFound();
}

return (
<LocationDetailComponent
location={location}
slug={params.locationSlugOrPersonalCareSubCategory}
comments={comments}
/>
);
return <SidePanelErrorState />;
}
}

try {
const location = map_gogetta_to_yourpeer(
await fetchLocationsDetailData(
params.locationSlugOrPersonalCareSubCategory,
),
true,
);
const comments = await fetchComments(location.id);

return (
<LocationDetailComponent
location={location}
slug={params.locationSlugOrPersonalCareSubCategory}
comments={comments}
/>
);
} catch (e) {
if (e instanceof Error404Response) {
return notFound();
} else {
throw e; // rethrow the error to force a 500 response
}

return <SidePanelErrorState />;
}
}
38 changes: 26 additions & 12 deletions src/app/[route]/@sidePanel/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
import { notFound } from "next/navigation";
import { SidePanelComponent } from "../../../components/side-panel-component";
import { getSidePanelComponentData } from "@/components/get-side-panel-component-data";
import SidePanelErrorState from "@/components/side-panel-error-state";
import { redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet } from "@/components/navigation";
import { cookies } from "next/headers";
import { Error404Response } from "@/components/streetlives-api-service";

export { generateMetadata } from "../../../components/metadata";

Expand All @@ -24,21 +26,33 @@ export default async function SidePanelPage(props: {
}) {
const params = await props.params;
const searchParams = await props.searchParams;

if (!RESOURCE_ROUTES.includes(params.route)) {
return notFound();
}

redirectIfNearbyAndIfLatitudeAndLongitudeIsNotSet({
searchParams,
params,
cookies: await cookies(),
});
return RESOURCE_ROUTES.includes(params.route) ? (
<SidePanelComponent
searchParams={searchParams}
sidePanelComponentData={await getSidePanelComponentData({
searchParams,
params,
cookies: await cookies(),
})}
/>
) : (
notFound()
);

try {
return (
<SidePanelComponent
searchParams={searchParams}
sidePanelComponentData={await getSidePanelComponentData({
searchParams,
params,
cookies: await cookies(),
})}
/>
);
} catch (e) {
if (e instanceof Error404Response) {
return notFound();
}

return <SidePanelErrorState />;
}
}
61 changes: 61 additions & 0 deletions src/components/side-panel-error-state.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use client";

import { useRouter } from "next/navigation";
import { useTransition } from "react";
import { Button } from "./ui/button";

export default function SidePanelErrorState() {
const router = useRouter();
const [isPending, startTransition] = useTransition();

return (
<div
className="w-full h-full flex items-center justify-center p-6"
id="side_panel_error_state"
>
<div className="max-w-sm text-center space-y-3">
<h2 className="text-lg font-semibold text-black">
Unable to load locations
</h2>
<p className="text-sm text-grey-900">
We couldn&apos;t load the locations right now. Please try again.
</p>
<Button
className="rounded-full"
onClick={() => startTransition(() => router.refresh())}
type="button"
disabled={isPending}
aria-busy={isPending}
>
{isPending ? (
<span className="inline-flex items-center gap-2">
<svg
className="h-4 w-4 animate-spin"
viewBox="0 0 24 24"
aria-hidden="true"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
fill="none"
/>
<path
className="opacity-90"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
<span>Retrying...</span>
</span>
) : (
"Retry"
)}
</Button>
</div>
</div>
);
}
Loading