diff --git a/apps/web/app/[locale]/(public)/agents/[id]/error.tsx b/apps/web/app/[locale]/(public)/agents/[id]/error.tsx new file mode 100644 index 0000000..0b1893b --- /dev/null +++ b/apps/web/app/[locale]/(public)/agents/[id]/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải hồ sơ môi giới

+

+ Đã xảy ra lỗi khi tải thông tin môi giới. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về danh sách môi giới + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/agents/error.tsx b/apps/web/app/[locale]/(public)/agents/error.tsx new file mode 100644 index 0000000..e906e43 --- /dev/null +++ b/apps/web/app/[locale]/(public)/agents/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải thông tin môi giới

+

+ Đã xảy ra lỗi khi tải danh sách môi giới. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về trang chủ + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/du-an/[slug]/error.tsx b/apps/web/app/[locale]/(public)/du-an/[slug]/error.tsx new file mode 100644 index 0000000..7e19c37 --- /dev/null +++ b/apps/web/app/[locale]/(public)/du-an/[slug]/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải thông tin dự án

+

+ Đã xảy ra lỗi khi tải chi tiết dự án. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về danh sách dự án + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/du-an/error.tsx b/apps/web/app/[locale]/(public)/du-an/error.tsx new file mode 100644 index 0000000..b0fddf3 --- /dev/null +++ b/apps/web/app/[locale]/(public)/du-an/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải danh sách dự án

+

+ Đã xảy ra lỗi khi tải dự án bất động sản. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về trang chủ + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/error.tsx b/apps/web/app/[locale]/(public)/error.tsx new file mode 100644 index 0000000..96b34a0 --- /dev/null +++ b/apps/web/app/[locale]/(public)/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải trang

+

+ Đã xảy ra lỗi khi tải nội dung. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về trang chủ + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/khu-cong-nghiep/[slug]/error.tsx b/apps/web/app/[locale]/(public)/khu-cong-nghiep/[slug]/error.tsx new file mode 100644 index 0000000..ea33115 --- /dev/null +++ b/apps/web/app/[locale]/(public)/khu-cong-nghiep/[slug]/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải chi tiết khu công nghiệp

+

+ Đã xảy ra lỗi khi tải thông tin khu công nghiệp. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về danh sách khu công nghiệp + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/khu-cong-nghiep/error.tsx b/apps/web/app/[locale]/(public)/khu-cong-nghiep/error.tsx new file mode 100644 index 0000000..27a3067 --- /dev/null +++ b/apps/web/app/[locale]/(public)/khu-cong-nghiep/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải thông tin khu công nghiệp

+

+ Đã xảy ra lỗi khi tải dữ liệu khu công nghiệp. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về trang chủ + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/listings/[id]/error.tsx b/apps/web/app/[locale]/(public)/listings/[id]/error.tsx new file mode 100644 index 0000000..f13a18f --- /dev/null +++ b/apps/web/app/[locale]/(public)/listings/[id]/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải thông tin bất động sản

+

+ Đã xảy ra lỗi khi tải chi tiết bất động sản. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về danh sách + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/listings/error.tsx b/apps/web/app/[locale]/(public)/listings/error.tsx new file mode 100644 index 0000000..e657e70 --- /dev/null +++ b/apps/web/app/[locale]/(public)/listings/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Không thể tải danh sách bất động sản

+

+ Đã xảy ra lỗi khi tải danh sách. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về trang chủ + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/(public)/payment/error.tsx b/apps/web/app/[locale]/(public)/payment/error.tsx new file mode 100644 index 0000000..5cc1e85 --- /dev/null +++ b/apps/web/app/[locale]/(public)/payment/error.tsx @@ -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 ( +
+
+
+
+ + + +
+

Lỗi thanh toán

+

+ Đã 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ợ. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về bảng điều khiển + +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/auth/callback/error.tsx b/apps/web/app/[locale]/auth/callback/error.tsx new file mode 100644 index 0000000..4d56b1d --- /dev/null +++ b/apps/web/app/[locale]/auth/callback/error.tsx @@ -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 ( +
+
+
+ + + +
+

Lỗi đăng nhập

+

+ Không thể hoàn tất quá trình đăng nhập. Vui lòng thử lại. +

+ {error.digest && ( +

Mã lỗi: {error.digest}

+ )} +
+ + + Về trang đăng nhập + +
+
+
+ ); +} diff --git a/apps/web/app/global-error.tsx b/apps/web/app/global-error.tsx new file mode 100644 index 0000000..65948fb --- /dev/null +++ b/apps/web/app/global-error.tsx @@ -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 ( + + +
+
+
+ + + +
+

+ Đã xảy ra lỗi nghiêm trọng +

+

+ Ứng dụng gặp sự cố không mong muốn. Vui lòng tải lại trang. +

+ {error.digest && ( +

+ Mã lỗi: {error.digest} +

+ )} +
+ + + Về trang chủ + +
+
+
+ + + ); +} diff --git a/apps/web/docs/error-boundary-coverage.md b/apps/web/docs/error-boundary-coverage.md new file mode 100644 index 0000000..3da60d1 --- /dev/null +++ b/apps/web/docs/error-boundary-coverage.md @@ -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`