feat(web): add missing error boundaries across all route groups

- Add global-error.tsx at app root (inline styles, wraps html/body)
- Add group-level error.tsx for (public) — catches all unguarded public routes
- Add per-route error.tsx for high-traffic public segments:
  listings, listings/[id], du-an, du-an/[slug],
  khu-cong-nghiep, khu-cong-nghiep/[slug], agents, agents/[id], payment
- Add auth/callback/error.tsx for OAuth callback failures
- Commit coverage table to apps/web/docs/error-boundary-coverage.md

Pre-existing API test failures unrelated to this change (broker-cert,
update-listing-status, mcp.module) were already failing on master.

Closes GOO-115

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-24 10:49:15 +07:00
parent 8a15df0bdb
commit 0fc23b7ebd
13 changed files with 877 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function AgentProfileError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Agent profile error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải hồ môi giới</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải thông tin môi giới. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/agents"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về danh sách môi giới
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function AgentsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Agents page error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải thông tin môi giới</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải danh sách môi giới. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về trang chủ
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function ProjectDetailError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Project detail error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải thông tin dự án</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải chi tiết dự án. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/du-an"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về danh sách dự án
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function ProjectsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Projects (du-an) error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải danh sách dự án</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải dự án bất đng sản. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về trang chủ
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function PublicError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Public page error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải trang</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải nội dung. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về trang chủ
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function IndustrialParkDetailError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Industrial park detail error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải chi tiết khu công nghiệp</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải thông tin khu công nghiệp. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/khu-cong-nghiep"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về danh sách khu công nghiệp
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function IndustrialParksError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Industrial parks error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải thông tin khu công nghiệp</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải dữ liệu khu công nghiệp. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về trang chủ
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function ListingDetailError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Listing detail error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải thông tin bất đng sản</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải chi tiết bất đng sản. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/listings"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về danh sách
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function ListingsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Listings error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Không thể tải danh sách bất đng sản</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi khi tải danh sách. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về trang chủ
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
'use client';
import { useEffect } from 'react';
export default function PaymentError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Payment page error:', error);
}, [error]);
return (
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="flex min-h-[400px] flex-col items-center justify-center">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Lỗi thanh toán</h2>
<p className="mt-2 text-sm text-muted-foreground">
Đã xảy ra lỗi trong quá trình thanh toán. Vui lòng thử lại hoặc liên hệ hỗ trợ.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/dashboard"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về bảng điều khiển
</a>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,58 @@
'use client';
import { useEffect } from 'react';
export default function AuthCallbackError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Auth callback error:', error);
}, [error]);
return (
<div className="flex min-h-screen flex-col items-center justify-center px-4">
<div className="mx-auto max-w-md text-center">
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-7 w-7 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<h2 className="mt-4 text-xl font-semibold">Lỗi đăng nhập</h2>
<p className="mt-2 text-sm text-muted-foreground">
Không thể hoàn tất quá trình đăng nhập. Vui lòng thử lại.
</p>
{error.digest && (
<p className="mt-1 text-xs text-muted-foreground"> lỗi: {error.digest}</p>
)}
<div className="mt-6 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-9 items-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
>
Thử lại
</button>
<a
href="/login"
className="inline-flex h-9 items-center rounded-md border border-input bg-background px-4 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
>
Về trang đăng nhập
</a>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,126 @@
'use client';
import { useEffect } from 'react';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error('Global error:', error);
}, [error]);
return (
<html lang="vi">
<body>
<div
style={{
display: 'flex',
minHeight: '100vh',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '1rem',
fontFamily: 'system-ui, -apple-system, sans-serif',
backgroundColor: '#f9fafb',
}}
>
<div style={{ maxWidth: '28rem', textAlign: 'center' }}>
<div
style={{
margin: '0 auto',
display: 'flex',
height: '3.5rem',
width: '3.5rem',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '9999px',
backgroundColor: '#fee2e2',
}}
>
<svg
style={{ height: '1.75rem', width: '1.75rem', color: '#ef4444' }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<h1
style={{
marginTop: '1rem',
fontSize: '1.25rem',
fontWeight: 600,
color: '#111827',
}}
>
Đã xảy ra lỗi nghiêm trọng
</h1>
<p style={{ marginTop: '0.5rem', fontSize: '0.875rem', color: '#6b7280' }}>
ng dụng gặp sự cố không mong muốn. Vui lòng tải lại trang.
</p>
{error.digest && (
<p style={{ marginTop: '0.25rem', fontSize: '0.75rem', color: '#9ca3af' }}>
lỗi: {error.digest}
</p>
)}
<div
style={{
marginTop: '1.5rem',
display: 'flex',
justifyContent: 'center',
gap: '0.75rem',
}}
>
<button
onClick={reset}
style={{
display: 'inline-flex',
height: '2.25rem',
alignItems: 'center',
borderRadius: '0.375rem',
backgroundColor: '#2563eb',
padding: '0 1rem',
fontSize: '0.875rem',
fontWeight: 500,
color: '#ffffff',
border: 'none',
cursor: 'pointer',
}}
>
Thử lại
</button>
<a
href="/"
style={{
display: 'inline-flex',
height: '2.25rem',
alignItems: 'center',
borderRadius: '0.375rem',
border: '1px solid #d1d5db',
backgroundColor: '#ffffff',
padding: '0 1rem',
fontSize: '0.875rem',
fontWeight: 500,
color: '#374151',
textDecoration: 'none',
}}
>
Về trang chủ
</a>
</div>
</div>
</div>
</body>
</html>
);
}

View File

@@ -0,0 +1,93 @@
# Error Boundary Coverage
Audited: 2026-04-24 | Issue: [GOO-115](/GOO/issues/GOO-115)
## Summary
| Route group | Segment | `error.tsx` | Notes |
|---|---|:---:|---|
| root | `app/` | ✅ | `app/error.tsx` |
| root global | `app/` | ✅ | `app/global-error.tsx` — added GOO-115 |
| locale root | `app/[locale]/` | ✅ | `app/[locale]/error.tsx` |
| **(admin)** group | `(admin)/` | ✅ | covers all admin sub-routes |
| **(auth)** group | `(auth)/` | ✅ | covers login / register |
| auth callback | `[locale]/auth/callback/` | ✅ | added GOO-115 |
| **(dashboard)** group | `(dashboard)/` | ✅ | covers all dashboard sub-routes |
| **(public)** group | `(public)/` | ✅ | added GOO-115 — fallback for uncovered public routes |
| public — search | `(public)/search/` | ✅ | existed pre-audit |
| public — listings | `(public)/listings/` | ✅ | added GOO-115 |
| public — listings detail | `(public)/listings/[id]/` | ✅ | added GOO-115 |
| public — du-an | `(public)/du-an/` | ✅ | added GOO-115 |
| public — du-an detail | `(public)/du-an/[slug]/` | ✅ | added GOO-115 |
| public — khu-cong-nghiep | `(public)/khu-cong-nghiep/` | ✅ | added GOO-115 |
| public — khu-cong-nghiep detail | `(public)/khu-cong-nghiep/[slug]/` | ✅ | added GOO-115 |
| public — agents | `(public)/agents/` | ✅ | added GOO-115 |
| public — agent profile | `(public)/agents/[id]/` | ✅ | added GOO-115 |
| public — payment | `(public)/payment/` | ✅ | added GOO-115 |
## Routes covered by group boundary (no per-route file needed)
These routes fall under a group-level `error.tsx` that handles them:
| Route | Covered by |
|---|---|
| `(public)/bao-cao/` | `(public)/error.tsx` |
| `(public)/bao-cao/[id]/` | `(public)/error.tsx` |
| `(public)/bao-cao/tao-moi/` | `(public)/error.tsx` |
| `(public)/chuyen-nhuong/` | `(public)/error.tsx` |
| `(public)/chuyen-nhuong/[id]/` | `(public)/error.tsx` |
| `(public)/chuyen-nhuong/dang-tin/` | `(public)/error.tsx` |
| `(public)/compare/` | `(public)/error.tsx` |
| `(public)/design-system/` | `(public)/error.tsx` |
| `(public)/khu-cong-nghiep/cho-thue/` | `(public)/khu-cong-nghiep/error.tsx` |
| `(public)/khu-cong-nghiep/so-sanh/` | `(public)/khu-cong-nghiep/error.tsx` |
| `(public)/payment/return/` | `(public)/payment/error.tsx` |
| `(public)/pricing/` | `(public)/error.tsx` |
| `(admin)/admin/accounts/developers/` | `(admin)/error.tsx` |
| `(admin)/admin/accounts/park-operators/` | `(admin)/error.tsx` |
| `(admin)/admin/audit-log/` | `(admin)/error.tsx` |
| `(admin)/admin/kyc/` | `(admin)/error.tsx` |
| `(admin)/admin/moderation/` | `(admin)/error.tsx` |
| `(admin)/admin/settings/ai/` | `(admin)/error.tsx` |
| `(admin)/admin/users/` | `(admin)/error.tsx` |
| `(auth)/login/` | `(auth)/error.tsx` |
| `(auth)/register/` | `(auth)/error.tsx` |
| `(dashboard)/dashboard/kyc/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/payments/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/profile/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/reports/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/reports/[id]/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/reports/new/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/saved-searches/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/subscription/` | `(dashboard)/error.tsx` |
| `(dashboard)/dashboard/valuation/` | `(dashboard)/error.tsx` |
| `(dashboard)/dev/tokens/` | `(dashboard)/error.tsx` |
| `(dashboard)/industrial-parks/` | `(dashboard)/error.tsx` |
| `(dashboard)/industrial-parks/[id]/edit/` | `(dashboard)/error.tsx` |
| `(dashboard)/industrial-parks/new/` | `(dashboard)/error.tsx` |
| `(dashboard)/inquiries/` | `(dashboard)/error.tsx` |
| `(dashboard)/leads/` | `(dashboard)/error.tsx` |
| `(dashboard)/my-listings/` | `(dashboard)/error.tsx` |
| `(dashboard)/my-listings/[id]/edit/` | `(dashboard)/error.tsx` |
| `(dashboard)/my-listings/new/` | `(dashboard)/error.tsx` |
| `(dashboard)/projects/` | `(dashboard)/error.tsx` |
| `(dashboard)/projects/[id]/edit/` | `(dashboard)/error.tsx` |
| `(dashboard)/projects/new/` | `(dashboard)/error.tsx` |
| `(dashboard)/analytics/` | `(dashboard)/error.tsx` |
| `[locale]/auth/callback/google/` | `auth/callback/error.tsx` |
| `[locale]/auth/callback/zalo/` | `auth/callback/error.tsx` |
## Files added in GOO-115
- `apps/web/app/global-error.tsx`
- `apps/web/app/[locale]/(public)/error.tsx`
- `apps/web/app/[locale]/(public)/listings/error.tsx`
- `apps/web/app/[locale]/(public)/listings/[id]/error.tsx`
- `apps/web/app/[locale]/(public)/du-an/error.tsx`
- `apps/web/app/[locale]/(public)/du-an/[slug]/error.tsx`
- `apps/web/app/[locale]/(public)/khu-cong-nghiep/error.tsx`
- `apps/web/app/[locale]/(public)/khu-cong-nghiep/[slug]/error.tsx`
- `apps/web/app/[locale]/(public)/agents/error.tsx`
- `apps/web/app/[locale]/(public)/agents/[id]/error.tsx`
- `apps/web/app/[locale]/(public)/payment/error.tsx`
- `apps/web/app/[locale]/auth/callback/error.tsx`