Files
goodgo-platform/apps/web/components/error-boundary/page-error-boundary.tsx
Ho Ngoc Hai 199de240b1
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 4s
CI / E2E Tests (push) Has been skipped
CI / AI Services (Python) — Smoke (push) Failing after 6s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 35s
Deploy / Build API Image (push) Failing after 15s
Deploy / Build Web Image (push) Failing after 13s
Deploy / Build AI Services Image (push) Failing after 11s
E2E Tests / Playwright E2E (push) Failing after 11s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 3s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m33s
Security Scanning / Trivy Scan — Web Image (push) Failing after 54s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 45s
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Filesystem Scan (push) Failing after 46s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
feat(web): add ErrorBoundary, PageErrorBoundary, ComponentErrorBoundary
Implements GOO-63 audit requirement — React error boundaries with
Vietnamese-language fallback UI, Sentry capture, and "Thử lại" retry.

- ErrorBoundary: generic class component wrapping Sentry.captureException
- PageErrorBoundary: full-page fallback for route layouts
- ComponentErrorBoundary: inline widget fallback (compact + standard modes)
- Applied to ListingMap, CheckoutModal, SearchResults as first targets

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-23 20:27:06 +07:00

85 lines
3.0 KiB
TypeScript

'use client';
import { type ErrorInfo, type ReactNode } from 'react';
import { ErrorBoundary, type ErrorBoundaryFallbackProps } from './error-boundary';
interface PageErrorBoundaryProps {
children: ReactNode;
/** Page title shown in the full-page error UI. */
pageName?: string;
onError?: (error: Error, info: ErrorInfo) => void;
}
/**
* Full-page error boundary for route layouts.
*
* Renders a centred Vietnamese-language error screen with a retry button and a
* link back to the home page. Wraps the generic `ErrorBoundary` with a
* page-appropriate fallback size.
*/
export function PageErrorBoundary({ children, pageName, onError }: PageErrorBoundaryProps) {
return (
<ErrorBoundary
onError={onError}
fallback={({ error, reset }) => (
<PageFallback error={error} reset={reset} pageName={pageName} />
)}
>
{children}
</ErrorBoundary>
);
}
function PageFallback({
error,
reset,
pageName,
}: ErrorBoundaryFallbackProps & { pageName?: string }) {
return (
<div
className="flex min-h-[60vh] flex-col items-center justify-center bg-background px-4 text-center"
role="alert"
>
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10">
<svg
className="h-8 w-8 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<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-bold tracking-tight">
{pageName ? `Lỗi tải trang: ${pageName}` : 'Đã xảy ra lỗi'}
</h2>
<p className="mt-2 max-w-sm text-sm text-muted-foreground">
Trang này gặp sự cố. Vui lòng thử lại hoặc quay về trang chủ.
</p>
{process.env.NODE_ENV !== 'production' && error.message && (
<p className="mt-1 max-w-sm truncate text-xs text-muted-foreground">{error.message}</p>
)}
<div className="mt-8 flex justify-center gap-3">
<button
onClick={reset}
className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-6 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
Thử lại
</button>
<a
href="/"
className="inline-flex h-10 items-center justify-center rounded-md border border-input bg-background px-6 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
Trang chủ
</a>
</div>
</div>
);
}