Files
EpicNext-Cms/src/app/error.tsx
T
Simo 6f15e0c8a3 Production hardening: error pages, rate limiting, metadata
- Custom not-found (404) + error / global-error boundaries, styled with
  the public design system; raw errors logged, never shown to users.
- In-process rate limiter (src/lib/rate-limit.ts) wired into the abuse-
  prone flows: login (10/5min/IP), register (5/10min/IP), password-reset
  request (3/15min/IP), keyed by the proxy-forwarded client IP.
- SEO/metadata: root generateMetadata sets a `%s · {hotel}` title
  template from the live hotel_name; dynamic generateMetadata on
  news/[slug] (article title + excerpt) and u/[username] (name + motto);
  static titles on 12 primary public pages.
- env.ts: added the vars introduced since (PASSWORD_HASH, OPENAI_API_KEY,
  DISCORD_WEBHOOK_URL, ALERT_EMAIL, PAYPAL_*) so env stays authoritative.

Verified on the prod server: /missing → 404 card, news title renders
"News · Habbo". tsc 0, vitest 49/49, next build 0.
2026-06-28 20:12:12 +02:00

55 lines
1.6 KiB
TypeScript

"use client";
import { useEffect } from "react";
/**
* Route-segment error boundary. Renders inside the root layout (so the shell
* stays), shows a friendly card, and offers a reset. The raw error is logged to
* the console, never shown to the user.
*/
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<main style={{ display: "grid", gap: "1.5rem" }}>
<div className="content-card">
<div className="content-card-head">
<span className="content-card-icon" aria-hidden>
⚠️
</span>
<div className="content-card-head-text">
<p className="content-card-title">Something went wrong</p>
<p className="content-card-subtitle">An unexpected error occurred</p>
</div>
</div>
<div className="content-card-body">
<p className="muted" style={{ marginTop: 0 }}>
Try again — if the problem persists, please contact a member of staff.
</p>
<div style={{ display: "flex", gap: "0.6rem", flexWrap: "wrap" }}>
<button type="button" className="btn btn-primary" onClick={() => reset()}>
Try again
</button>
<a className="btn btn-outline" href="/">
Back home
</a>
</div>
{error.digest ? (
<p className="muted" style={{ marginTop: "0.75rem", fontSize: "0.75rem" }}>
Reference: {error.digest}
</p>
) : null}
</div>
</div>
</main>
);
}