diff --git a/client/portal/src/components/ErrorPage.tsx b/client/portal/src/components/ErrorPage.tsx index 2094a40d..7a072863 100644 --- a/client/portal/src/components/ErrorPage.tsx +++ b/client/portal/src/components/ErrorPage.tsx @@ -3,93 +3,187 @@ import { isRouteErrorResponse, useNavigate, useRouteError } from "react-router"; import { Button } from "@/components/ui/button"; +/** + * Static rather than the super admin's runtime `contact_email`: this page is + * the router's error boundary, so it has to render when the API, the session, + * or the route that would have loaded that setting is exactly what failed. + */ +const SUPPORT_EMAIL = "hello@hackutd.co"; + type ErrorCopy = { status: string; + /** In-world HUD label. The plain-language title carries the real meaning. */ + label: string; title: string; message: string; + /** Raw error text, surfaced only as a trace the hacker can quote to us. */ + detail?: string; +}; + +const NOT_FOUND: ErrorCopy = { + status: "404", + label: "Signal lost", + title: "Page not found.", + message: "The page you're looking for doesn't exist or may have moved.", }; function describeError(error: unknown): ErrorCopy { + // The catch-all `*` route renders this page as an ordinary element rather + // than as an error boundary, so a mistyped URL arrives with no error at all. + // That is a 404, not the unexpected-failure case below. + if (error == null) return NOT_FOUND; + if (isRouteErrorResponse(error)) { if (error.status === 404) { - return { - status: "404", - title: "Page not found", - message: "The page you're looking for doesn't exist or may have moved.", - }; + return NOT_FOUND; } if (error.status === 401) { return { status: "401", - title: "Not signed in", + label: "Identity unverified", + title: "Not signed in.", message: "You need to sign in to view this page.", }; } if (error.status === 403) { return { status: "403", - title: "Access denied", + label: "Access denied", + title: "Clearance required.", message: "You don't have permission to access this page.", }; } return { status: String(error.status), - title: error.statusText || "Something went wrong", + label: "System fault", + title: "Something broke.", message: "An unexpected error occurred while loading this page. Please try again.", + detail: error.statusText || undefined, }; } return { status: "500", - title: "uh oh...", + label: "System fault", + title: "Something broke.", message: - error instanceof Error && error.message - ? error.message - : "An unexpected error occurred. Please try again, or head back home.", + // Non-breaking space keeps the em dash off the start of a wrapped line. + "An unexpected error occurred. Try again, or head back home — this one is on us, not you.", + detail: error instanceof Error && error.message ? error.message : undefined, }; } export function ErrorPage() { const error = useRouteError(); const navigate = useNavigate(); - const { status, title, message } = describeError(error); + const { status, label, title, message, detail } = describeError(error); return ( -
-
-
-
-

- Error {status} -

-

- {title} -

-

- {message} -

-
+
+
+
+ + {/* HUD rail — atmosphere only, never the carrier of meaning. */} +
+ HackUTD // Secure portal + MMXXVI +
+
+ HACKUTD // ZERO DAY +
+ +
+
+
+
+

+ {label} // {status} +

+
+ + + +
+
+ +
+ {/* Chromatic split — one deliberate glitch artifact, held still. */} +

+ {status} +

+

+ {title} +

+

+ {message} +

+
+ + {detail && ( +
+

+ Trace +

+

+ {detail} +

+
+ )} + +
+ + +
-
- - +
+

+ Still stuck? +

+

+ If you think this is a real issue, reach out at{" "} + + {SUPPORT_EMAIL} + {" "} + and we'll take a look. +

+
-
-
+
+ ); }