Skip to content

Commit f585ad7

Browse files
committed
implement better error handling
1 parent e5ac44d commit f585ad7

20 files changed

Lines changed: 270 additions & 69 deletions

frontend/app/error.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client";
2+
3+
export default function Error({
4+
error,
5+
reset,
6+
}: {
7+
error: Error & { digest?: string };
8+
reset: () => void;
9+
}) {
10+
return (
11+
<div className="flex w-full flex-col items-center space-y-4 py-24">
12+
<h2 className="text-lg font-semibold">Something went wrong</h2>
13+
<p className="text-muted-foreground text-sm">An unexpected error occurred.</p>
14+
<button
15+
onClick={reset}
16+
className="bg-brand hover:bg-brand-hover rounded-md px-4 py-2 text-sm text-white"
17+
>
18+
Try again
19+
</button>
20+
{process.env.NODE_ENV === "development" && (
21+
<details className="text-muted-foreground mt-4 max-w-lg text-xs">
22+
<summary className="cursor-pointer">Error details</summary>
23+
<pre className="mt-2 overflow-auto rounded bg-gray-100 p-3 dark:bg-gray-900">
24+
{error.message}
25+
{error.digest && `\nDigest: ${error.digest}`}
26+
</pre>
27+
</details>
28+
)}
29+
</div>
30+
);
31+
}

frontend/app/items/[id]/error.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
3+
import Link from "next/link";
4+
5+
export default function ItemError({
6+
error,
7+
reset,
8+
}: {
9+
error: Error & { digest?: string };
10+
reset: () => void;
11+
}) {
12+
return (
13+
<div className="flex w-full flex-col items-center space-y-4 py-24">
14+
<h2 className="text-lg font-semibold">Something went wrong</h2>
15+
<p className="text-muted-foreground text-sm">Failed to load this listing.</p>
16+
<div className="flex gap-3">
17+
<button
18+
onClick={reset}
19+
className="bg-brand hover:bg-brand-hover rounded-md px-4 py-2 text-sm text-white"
20+
>
21+
Try again
22+
</button>
23+
<Link href="/items" className="rounded-md border px-4 py-2 text-sm">
24+
Back to items
25+
</Link>
26+
</div>
27+
{process.env.NODE_ENV === "development" && (
28+
<details className="text-muted-foreground mt-4 max-w-lg text-xs">
29+
<summary className="cursor-pointer">Error details</summary>
30+
<pre className="mt-2 overflow-auto rounded bg-gray-100 p-3 dark:bg-gray-900">
31+
{error.message}
32+
{error.digest && `\nDigest: ${error.digest}`}
33+
</pre>
34+
</details>
35+
)}
36+
</div>
37+
);
38+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Link from "next/link";
2+
3+
export default function ItemNotFound() {
4+
return (
5+
<div className="flex w-full flex-col items-center space-y-4 py-24 text-center">
6+
<h2 className="text-lg font-semibold">Listing not found</h2>
7+
<p className="text-muted-foreground text-sm">
8+
This listing may have been removed or doesn&apos;t exist.
9+
</p>
10+
<Link
11+
href="/items"
12+
className="bg-brand hover:bg-brand-hover rounded-md px-4 py-2 text-sm text-white"
13+
>
14+
Back to items
15+
</Link>
16+
</div>
17+
);
18+
}

frontend/app/items/[id]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { ListingDetail } from "@/components/listings/detail/ListingDetail";
2-
import { getListing } from "@/lib/actions";
2+
import { getListingOrNotFound } from "@/lib/actions";
33

44
export default async function ItemPage({ params }: { params: Promise<{ id: string }> }) {
55
const { id } = await params;
6-
const item = await getListing(id);
6+
const item = await getListingOrNotFound(id);
77

88
return <ListingDetail listing={item} initialIsFavorited={item.is_favorited ?? false} />;
99
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
3+
import Link from "next/link";
4+
5+
export default function SubletError({
6+
error,
7+
reset,
8+
}: {
9+
error: Error & { digest?: string };
10+
reset: () => void;
11+
}) {
12+
return (
13+
<div className="flex w-full flex-col items-center space-y-4 py-24">
14+
<h2 className="text-lg font-semibold">Something went wrong</h2>
15+
<p className="text-muted-foreground text-sm">Failed to load this listing.</p>
16+
<div className="flex gap-3">
17+
<button
18+
onClick={reset}
19+
className="bg-brand hover:bg-brand-hover rounded-md px-4 py-2 text-sm text-white"
20+
>
21+
Try again
22+
</button>
23+
<Link href="/sublets" className="rounded-md border px-4 py-2 text-sm">
24+
Back to sublets
25+
</Link>
26+
</div>
27+
{process.env.NODE_ENV === "development" && (
28+
<details className="text-muted-foreground mt-4 max-w-lg text-xs">
29+
<summary className="cursor-pointer">Error details</summary>
30+
<pre className="mt-2 overflow-auto rounded bg-gray-100 p-3 dark:bg-gray-900">
31+
{error.message}
32+
{error.digest && `\nDigest: ${error.digest}`}
33+
</pre>
34+
</details>
35+
)}
36+
</div>
37+
);
38+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Link from "next/link";
2+
3+
export default function SubletNotFound() {
4+
return (
5+
<div className="flex w-full flex-col items-center space-y-4 py-24 text-center">
6+
<h2 className="text-lg font-semibold">Listing not found</h2>
7+
<p className="text-muted-foreground text-sm">
8+
This listing may have been removed or doesn&apos;t exist.
9+
</p>
10+
<Link
11+
href="/sublets"
12+
className="bg-brand hover:bg-brand-hover rounded-md px-4 py-2 text-sm text-white"
13+
>
14+
Back to sublets
15+
</Link>
16+
</div>
17+
);
18+
}

frontend/app/sublets/[id]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { ListingDetail } from "@/components/listings/detail/ListingDetail";
2-
import { getListing } from "@/lib/actions";
2+
import { getListingOrNotFound } from "@/lib/actions";
33

44
export default async function SubletPage({ params }: { params: Promise<{ id: string }> }) {
55
const { id } = await params;
6-
const sublet = await getListing(id);
6+
const sublet = await getListingOrNotFound(id);
77

88
return <ListingDetail listing={sublet} initialIsFavorited={sublet.is_favorited ?? false} />;
99
}

frontend/components/listings/ListingsGrid.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,21 @@ type Props =
2020
};
2121

2222
export const ListingsGrid = ({ type, listings, currentUser }: Props) => {
23-
const { data, isFetchingNextPage, hasNextPage, ref } = useListings({ type, listings });
23+
const { data, error, isFetchingNextPage, hasNextPage, ref } = useListings({ type, listings });
2424

2525
const totalResults = data?.pages.reduce((acc, page) => acc + page.results.length, 0) || 0;
2626
const isEmpty = totalResults === 0;
2727

28+
if (error && totalResults === 0) {
29+
return (
30+
<div className="flex w-full flex-col items-center space-y-4 py-12">
31+
<p className="text-muted-foreground text-sm">
32+
Failed to load listings. Please try refreshing the page.
33+
</p>
34+
</div>
35+
);
36+
}
37+
2838
return (
2939
<div className="flex w-full flex-col items-center space-y-4">
3040
<div className="w-full">

frontend/components/listings/detail/ListingDetail.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const ListingDetail = ({ listing, initialIsFavorited }: Props) => {
3030
const isFavorited = favoritesQuery.data ?? false;
3131

3232
const toggleFavoriteMutation = useMutation({
33+
meta: { suppressErrorToast: true }, // since it's noisy to show error toast on top of optimistic update
3334
mutationFn: async (shouldFavorite: boolean) => {
3435
if (shouldFavorite) {
3536
await addToUsersFavorites(listing.id);

frontend/components/listings/form/ItemForm.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ export function ItemForm() {
5656
imageUpload.clearImages();
5757
router.replace(`/items/${data.id}`);
5858
},
59-
onError: (error: Error) => {
60-
toast.error(error.message || "Failed to create listing. Please try again.");
61-
},
6259
});
6360

6461
const onSubmit = (data: CreateItemFormData) => {

0 commit comments

Comments
 (0)