< Summary

Information
Class: problem-boundary.tsx
Assembly: app.components
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/components/problem-boundary.tsx
Tag: 58_25416222083
Line coverage
0%
Covered lines: 0
Uncovered lines: 14
Coverable lines: 14
Total lines: 132
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 6
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ClutterStock/ClutterStock/frontend/app/components/problem-boundary.tsx

#LineLine coverage
 1import { isRouteErrorResponse } from "react-router";
 2import { asApiProblem } from "~/api/problem";
 3
 4interface ProblemBoundaryProps {
 5  readonly error: unknown;
 6  readonly scope?: "page" | "section";
 7}
 8
 9interface RenderShape {
 10  title: string;
 11  detail: string;
 12  status?: number;
 13  fieldErrors?: Record<string, string[]>;
 14  requestId?: string;
 15  traceId?: string;
 16  stack?: string;
 17}
 18
 019function shapeFromError(error: unknown): RenderShape {
 20  // The typed wrapper throws Response with a ProblemDetails JSON body, which
 21  // RR7 surfaces as a route ErrorResponse. asApiProblem unwraps either.
 022  const problem = asApiProblem(error);
 023  if (problem) {
 024    return {
 25      title: problem.title,
 26      detail: problem.detail ?? problem.message,
 27      status: problem.status,
 28      fieldErrors: problem.errors,
 29      requestId: problem.requestId,
 30      traceId: problem.traceId,
 31    };
 32  }
 33
 034  if (isRouteErrorResponse(error)) {
 035    return {
 36      title: error.status === 404 ? "Not found" : `Error ${error.status}`,
 37      detail:
 38        error.status === 404
 39          ? "The requested page could not be found."
 40          : error.statusText || "An unexpected error occurred.",
 41      status: error.status,
 42    };
 43  }
 44
 045  if (error instanceof Error) {
 046    return {
 47      title: "Unexpected error",
 48      detail: import.meta.env.DEV ? error.message : "An unexpected error occurred.",
 49      stack: import.meta.env.DEV ? error.stack : undefined,
 50    };
 51  }
 52
 053  return { title: "Unexpected error", detail: "An unexpected error occurred." };
 54}
 55
 056export function ProblemBoundary({ error, scope = "page" }: ProblemBoundaryProps) {
 057  const s = shapeFromError(error);
 58
 059  return (
 60    <div
 61      role="alert"
 62      style={{
 63        margin: scope === "page" ? "2rem auto" : "1rem 0",
 64        maxWidth: scope === "page" ? "40rem" : undefined,
 65        padding: "1rem 1.25rem",
 66        background: "var(--c-bg-2)",
 67        color: "var(--c-fg)",
 68        border: "1px solid var(--c-border)",
 69        borderLeft: "3px solid var(--c-danger)",
 70      }}
 71    >
 72      <h2
 73        style={{
 74          margin: 0,
 75          marginBottom: "0.25rem",
 76          fontSize: "1.125rem",
 77          color: "var(--c-fg)",
 78        }}
 79      >
 80        {s.title}
 81      </h2>
 82      <p style={{ margin: 0, color: "var(--c-fg-2)" }}>{s.detail}</p>
 83
 84      {s.fieldErrors && (
 85        <ul
 86          style={{
 87            marginTop: "0.5rem",
 88            paddingLeft: "1.25rem",
 89            color: "var(--c-fg-2)",
 90          }}
 91        >
 092          {Object.entries(s.fieldErrors).map(([field, msgs]) => (
 093            <li key={field}>
 94              <strong style={{ color: "var(--c-fg)" }}>{field}:</strong> {msgs.join(", ")}
 95            </li>
 96          ))}
 97        </ul>
 98      )}
 99
 100      {(s.requestId || s.traceId) && (
 101        <p
 102          style={{
 103            marginTop: "0.75rem",
 104            marginBottom: 0,
 105            fontSize: "0.75rem",
 106            color: "var(--c-fg-3)",
 107            fontFamily: "monospace",
 108          }}
 109        >
 110          Reference{s.requestId && <> · req {s.requestId}</>}
 111          {s.traceId && <> · trace {s.traceId}</>}
 112        </p>
 113      )}
 114
 115      {s.stack && (
 116        <pre
 117          style={{
 118            marginTop: "0.75rem",
 119            padding: "0.5rem",
 120            background: "var(--c-bg-3)",
 121            border: "1px solid var(--c-border-2)",
 122            color: "var(--c-fg-2)",
 123            overflowX: "auto",
 124            fontSize: "0.75rem",
 125          }}
 126        >
 127          <code>{s.stack}</code>
 128        </pre>
 129      )}
 130    </div>
 131  );
 132}