Skip to content

Commit

Permalink
feat(fe): apply error boundary for main directory (#2241)
Browse files Browse the repository at this point in the history
* feat(fe): install suspensive, implement ErrorFallback component, apply ErrorBoundary to home page

* chore(fe): fix pnpm-lock.yaml

* feat(fe): add retry button in FetchErrorFallback

* feat(fe): apply ErrorBoundary

* chore(fe): remove console logging

* chore(fe): specify importing type ErrorBoundaryFallbackProps

* chore(fe): narrow the scope of ErrorBoundary in contest page
  • Loading branch information
B0XERCAT authored Nov 28, 2024
1 parent 68d949c commit 1f878f7
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 97 deletions.
53 changes: 31 additions & 22 deletions apps/frontend/app/(client)/(main)/contest/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import FetchErrorFallback from '@/components/FetchErrorFallback'
import { Separator } from '@/components/shadcn/separator'
import { Skeleton } from '@/components/shadcn/skeleton'
import { auth } from '@/libs/auth'
import { ErrorBoundary } from '@suspensive/react'
import { redirect } from 'next/navigation'
import { Suspense } from 'react'
import SearchBar from '../_components/SearchBar'
Expand Down Expand Up @@ -64,25 +66,30 @@ export default async function Contest({ searchParams }: ContestProps) {
return (
<>
<div className="mb-12 flex flex-col gap-12">
<Suspense fallback={<ContestCardListFallback />}>
<ContestCardList
title="Join the contest now!"
type="Ongoing"
session={session}
/>
</Suspense>
<Suspense fallback={<ContestCardListFallback />}>
<ContestCardList
title="Check out upcoming contests"
type="Upcoming"
session={session}
/>
</Suspense>
<ErrorBoundary fallback={FetchErrorFallback}>
<Suspense fallback={<ContestCardListFallback />}>
<ContestCardList
title="Join the contest now!"
type="Ongoing"
session={session}
/>
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={FetchErrorFallback}>
<Suspense fallback={<ContestCardListFallback />}>
<ContestCardList
title="Check out upcoming contests"
type="Upcoming"
session={session}
/>
</Suspense>
</ErrorBoundary>
</div>
<div className="flex-col">
<h1 className="mb-6 text-2xl font-bold text-gray-700">
List of Contests
</h1>

<Suspense fallback={<FinishedContestTableFallback />}>
{session ? (
<TableSwitchButton registered={registered} />
Expand All @@ -92,14 +99,16 @@ export default async function Contest({ searchParams }: ContestProps) {
</p>
)}
<Separator className="mb-3" />
<div className="flex justify-end py-8">
<SearchBar className="w-60" />
</div>
{session && registered ? (
<RegisteredContestTable search={search} />
) : (
<FinishedContestTable search={search} session={session} />
)}
<ErrorBoundary fallback={FetchErrorFallback}>
<div className="flex justify-end py-8">
<SearchBar className="w-60" />
</div>
{session && registered ? (
<RegisteredContestTable search={search} />
) : (
<FinishedContestTable search={search} session={session} />
)}
</ErrorBoundary>
</Suspense>
</div>
</>
Expand Down
51 changes: 29 additions & 22 deletions apps/frontend/app/(client)/(main)/notice/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import FetchErrorFallback from '@/components/FetchErrorFallback'
import { Skeleton } from '@/components/shadcn/skeleton'
import type { Notice } from '@/types/type'
import { ErrorBoundary } from '@suspensive/react'
import { Suspense } from 'react'
import SearchBar from '../_components/SearchBar'
import NoticeTable from './_components/NoticeTable'
Expand All @@ -16,28 +18,33 @@ export default function Notice({ searchParams }: NoticeProps) {
<div className="flex w-full justify-end">
<SearchBar />
</div>
<Suspense
fallback={
<>
<div className="mt-4 flex">
<span className="w-2/4 md:w-4/6">
<Skeleton className="h-6 w-20" />
</span>
<span className="w-1/4 md:w-1/6">
<Skeleton className="mx-auto h-6 w-20" />
</span>
<span className="w-1/4 md:w-1/6">
<Skeleton className="mx-auto h-6 w-20" />
</span>
</div>
{[...Array(5)].map((_, i) => (
<Skeleton key={i} className="my-2 flex h-12 w-full rounded-xl" />
))}
</>
}
>
<NoticeTable search={search} />
</Suspense>
<ErrorBoundary fallback={FetchErrorFallback}>
<Suspense
fallback={
<>
<div className="mt-4 flex">
<span className="w-2/4 md:w-4/6">
<Skeleton className="h-6 w-20" />
</span>
<span className="w-1/4 md:w-1/6">
<Skeleton className="mx-auto h-6 w-20" />
</span>
<span className="w-1/4 md:w-1/6">
<Skeleton className="mx-auto h-6 w-20" />
</span>
</div>
{[...Array(5)].map((_, i) => (
<Skeleton
key={i}
className="my-2 flex h-12 w-full rounded-xl"
/>
))}
</>
}
>
<NoticeTable search={search} />
</Suspense>
</ErrorBoundary>
</>
)
}
11 changes: 8 additions & 3 deletions apps/frontend/app/(client)/(main)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import FetchErrorFallback from '@/components/FetchErrorFallback'
import { Button } from '@/components/shadcn/button'
import { ErrorBoundary } from '@suspensive/react'
import Link from 'next/link'
import Carousel from './_components/Carousel'
import ContestCards from './_components/ContestCards'
Expand Down Expand Up @@ -67,7 +69,9 @@ export default function Home() {
</Button>
</Link>
</div>
<ContestCards />
<ErrorBoundary fallback={FetchErrorFallback}>
<ContestCards />
</ErrorBoundary>
</div>

<div className="flex w-full flex-col gap-6">
Expand All @@ -79,8 +83,9 @@ export default function Home() {
</Button>
</Link>
</div>
{/**TODO: add error boundary */}
<ProblemCards />
<ErrorBoundary fallback={FetchErrorFallback}>
<ProblemCards />
</ErrorBoundary>
</div>
</div>
)
Expand Down
37 changes: 37 additions & 0 deletions apps/frontend/components/FetchErrorFallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client'

import type { ErrorBoundaryFallbackProps } from '@suspensive/react'
import { useRouter } from 'next/navigation'
import { startTransition, useState } from 'react'
import { RiAlertFill } from 'react-icons/ri'
import { Button } from './shadcn/button'

export default function FetchErrorFallback({
reset
}: ErrorBoundaryFallbackProps) {
const router = useRouter()
const [isResetting, setIsResetting] = useState(false)

const handleRetry = () => {
setIsResetting(true)

startTransition(() => {
router.refresh()
reset()
setIsResetting(false)
})
}
return (
<div className="flex w-full flex-col items-center py-6">
<RiAlertFill className="text-gray-300" size={42} />
<p className="text-2xl font-bold">Failed to load data</p>
<Button
onClick={() => handleRetry()}
disabled={isResetting}
className="mt-2"
>
Retry
</Button>
</div>
)
}
Loading

0 comments on commit 1f878f7

Please sign in to comment.