Skip to content

Commit b969b63

Browse files
fix: unhandled validation error, add Not Found Page (#28)
1 parent 29d1d14 commit b969b63

File tree

7 files changed

+76
-51
lines changed

7 files changed

+76
-51
lines changed

app/(main)/[code]/page.tsx

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import MapDashboard from '@/components/map-dashboard'
22
import { config } from '@/lib/config'
33
import { Areas, singletonArea } from '@/lib/const'
4-
import { getSpecificData } from '@/lib/data'
5-
import { areaCodeSchema, determineAreaByCode, ucWords } from '@/lib/utils'
4+
import { getSpecificData, GetSpecificDataReturn } from '@/lib/data'
5+
import { determineAreaByCode, ucWords } from '@/lib/utils'
66
import { Metadata, ResolvingMetadata } from 'next'
7-
import { ZodError } from 'zod'
8-
import { fromZodError } from 'zod-validation-error'
7+
import { notFound } from 'next/navigation'
98

109
type Props = {
1110
params: {
1211
code: string
1312
}
1413
}
1514

16-
async function getAreaData(area: Areas, areaCode: string) {
15+
async function getAreaData(
16+
area: Areas,
17+
areaCode: string,
18+
): Promise<GetSpecificDataReturn<Areas>['data']> {
1719
const res = await getSpecificData(area, areaCode.replaceAll('.', ''))
1820

1921
if (!('data' in res)) {
@@ -30,8 +32,17 @@ export async function generateMetadata(
3032
{ params: { code } }: Props,
3133
parent: ResolvingMetadata,
3234
): Promise<Metadata> {
33-
const area = determineAreaByCode(code)
34-
const areaData = await getAreaData(area, code)
35+
let area, areaData: Awaited<ReturnType<typeof getAreaData>>
36+
try {
37+
area = determineAreaByCode(code)
38+
areaData = await getAreaData(area, code)
39+
} catch (error) {
40+
return {
41+
title: 'Area Not Found',
42+
description:
43+
'The area you are looking for does not exist. Ensure the link is correct or search the data manually in the Main Page.',
44+
}
45+
}
3546

3647
const url = `${config.appUrl}/${areaData.code}`
3748
const parentNames = Object.keys(areaData.parent ?? {}).map((parent) =>
@@ -45,7 +56,7 @@ export async function generateMetadata(
4556
...parentNames,
4657
].join(', ')
4758

48-
const title = `${areaNames} | ${config.appName}`
59+
const title = areaNames
4960
const description = `See the information about ${areaNames}, Indonesia.`
5061
const ogImage = `/api/og-image/area/${areaData.code}`
5162

@@ -68,25 +79,21 @@ export async function generateMetadata(
6879
}
6980

7081
export default async function DetailAreaPage({ params }: Props) {
71-
let areaCode
72-
82+
let area, areaData
7383
try {
74-
areaCode = areaCodeSchema.parse(params.code)
84+
area = determineAreaByCode(params.code)
85+
areaData = await getAreaData(area, params.code)
7586
} catch (error) {
76-
if (error instanceof ZodError) {
77-
throw fromZodError(error)
78-
}
79-
throw error
87+
return notFound()
8088
}
8189

82-
const area = determineAreaByCode(areaCode)
83-
const { parent: parentAreas, ...areaData } = await getAreaData(area, areaCode)
90+
const { parent, ...data } = areaData
8491

8592
return (
8693
<MapDashboard
8794
defaultSelected={{
88-
[singletonArea[area]]: areaData,
89-
...parentAreas,
95+
[singletonArea[area]]: data,
96+
...parent,
9097
}}
9198
/>
9299
)

app/(main)/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import MapDashboard from '@/components/map-dashboard'
2+
3+
export default function Home() {
4+
return <MapDashboard />
5+
}

app/api/og-image/area/[code]/route.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ export async function GET(
1919
request: Request,
2020
{ params: { code } }: { params: { code: string } },
2121
) {
22-
const area = determineAreaByCode(code)
22+
let area
23+
try {
24+
area = determineAreaByCode(code)
25+
} catch (error) {
26+
return NextResponse.json({ message: 'Invalid area code' }, { status: 400 })
27+
}
28+
2329
const config = featureConfig[area as FeatureAreas]
2430
const [resBoundary] = await Promise.all([getBoundaryData(area, code)])
2531

app/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ const title = config.appName
1616
const description = config.appDescription
1717

1818
export const metadata: Metadata = {
19-
title,
19+
title: {
20+
template: `%s | ${config.appName}`,
21+
default: config.appName,
22+
},
2023
description,
2124
robots: {
2225
index: true,

app/not-found.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Navbar } from '@/components/navbar'
2+
import { Button } from '@/components/ui/button'
3+
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
4+
import { Metadata } from 'next'
5+
import Link from 'next/link'
6+
7+
export const metadata: Metadata = {
8+
title: 'Not Found',
9+
description:
10+
"Sorry, we couldn't find the page or resource you were looking for. It might have been removed, renamed, or did not exist in the first place.",
11+
}
12+
13+
export default function NotFound() {
14+
return (
15+
<>
16+
<header>
17+
<Navbar />
18+
</header>
19+
20+
<main className="flex flex-col items-center justify-center text-center h-[calc(100vh-72px)] px-4 py-16 space-y-4">
21+
<ExclamationTriangleIcon className="h-10 w-10 text-yellow-500" />
22+
<h1 className="text-2xl font-bold">404 - Not Found</h1>
23+
<p className="max-w-prose">
24+
Sorry, we couldn&apos;t find the page or resource you were looking
25+
for. It might have been removed, renamed, or did not exist in the
26+
first place.
27+
</p>
28+
<Button asChild>
29+
<Link href="/">Go to Main Page</Link>
30+
</Button>
31+
</main>
32+
</>
33+
)
34+
}

app/page.tsx

Lines changed: 0 additions & 16 deletions
This file was deleted.

lib/utils.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { type ClassValue, clsx } from 'clsx'
22
import { twMerge } from 'tailwind-merge'
33
import { Areas, parentArea } from './const'
4-
import { z } from 'zod'
54

65
/**
76
* Add dot separator for the area code.
@@ -77,19 +76,6 @@ export function getAllParents<Area extends Areas>(area: Area): Areas[] {
7776
return [parent, ...getAllParents(parent)]
7877
}
7978

80-
/**
81-
* Schema for area code, either province, regency, district, or village.
82-
* The code can be separated by dot (.) or not.
83-
*/
84-
export const areaCodeSchema = z.string().refine(
85-
(code) => {
86-
return /^\d{2}(?:\.?\d{2})?(?:\.?\d{2})?(?:\.?\d{4})?$/.test(code)
87-
},
88-
{
89-
message: 'Invalid area code',
90-
},
91-
)
92-
9379
/**
9480
* Determine the area of the given code.
9581
* The code can be separated by dot (.) or not.

0 commit comments

Comments
 (0)