Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/__tests__/NotFoundUI.copy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from 'fs'
import path from 'path'
import { describe, expect, it } from 'vitest'

const notFound = fs.readFileSync(path.join(process.cwd(), 'src/components/NotFoundUI.tsx'), 'utf8')
const errorFallback = fs.readFileSync(path.join(process.cwd(), 'src/components/ErrorFallbackUI.tsx'), 'utf8')

describe('Hive Commons 404 and error copy', () => {
it('uses Hive Commons themed 404 copy and Hive-specific docs links', () => {
expect(notFound).toContain('This page may have moved while we reorganized the Hive docs')
expect(notFound).toContain('/docs/hive/readme')
expect(notFound).toContain('/docs/hive/getting-started')
expect(notFound).toContain('/docs/hive/documentation-map')
expect(notFound).toContain('/docs/community/meetings')
expect(notFound).toContain('Search the docs')
expect(notFound).not.toMatch(/KubeStellar|Console theme|Kubernetes dashboard|multi-cluster management/i)
})

it('uses Hive Commons themed runtime error copy', () => {
expect(errorFallback).toContain('Hive Commons Docs')
expect(errorFallback).toContain('The docs hit a temporary runtime error')
expect(errorFallback).toContain('/docs/hive/readme')
expect(errorFallback).not.toMatch(/KubeStellar|Console theme|Kubernetes dashboard|multi-cluster management/i)
})
})
7 changes: 7 additions & 0 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use client";

import ErrorFallbackUI from "@/components/ErrorFallbackUI";

export default function Error({ reset }: { error: Error & { digest?: string }; reset: () => void }) {
return <ErrorFallbackUI reset={reset} />;
}
118 changes: 6 additions & 112 deletions src/app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,10 @@
"use client";

import type { CSSProperties } from "react";

// Root-level error boundary (catches errors thrown by root layouts too).
// Like the 404 pages, this uses inline styles ONLY and English-only strings:
// when this boundary renders, the app (and possibly its CSS bundle and i18n
// message loading) has already failed, so it must not depend on anything
// external. Please keep it free of Tailwind classes and external assets.

// Brand palette (mirrors src/app/globals.css / tailwind.config.ts)
const SPACE_DARK = "#0a0a0a";
const ACCENT_BLUE = "#3b82f6";
const GRADIENT =
"linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)";
const GRADIENT_FALLBACK = "#764ba2"; // if background-clip: text is unsupported
const TEXT_PRIMARY = "#f3f4f6";
const TEXT_MUTED = "#9ca3af";
const SUBTLE_BG = "rgba(255, 255, 255, 0.05)";
const SUBTLE_BORDER = "1px solid rgba(59, 130, 246, 0.35)"; // accent-tinted, not a stark white edge

const styles: Record<string, CSSProperties> = {
body: {
margin: 0,
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: SPACE_DARK,
color: TEXT_PRIMARY,
textAlign: "center",
padding: "3rem 1.5rem",
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
},
wrap: { maxWidth: "44rem" },
code: {
fontSize: "clamp(3.5rem, 12vw, 6rem)",
fontWeight: 800,
lineHeight: 1,
backgroundImage: GRADIENT,
WebkitBackgroundClip: "text",
backgroundClip: "text",
WebkitTextFillColor: "transparent",
color: GRADIENT_FALLBACK,
marginBottom: "1.25rem",
},
heading: {
fontSize: "1.75rem",
fontWeight: 600,
margin: "0 0 0.9rem",
},
message: {
fontSize: "1.05rem",
color: TEXT_MUTED,
lineHeight: 1.6,
margin: "0 0 2.25rem",
},
actions: {
display: "flex",
flexWrap: "wrap",
gap: "0.75rem",
justifyContent: "center",
},
buttonPrimary: {
display: "inline-block",
padding: "0.75rem 2rem",
borderRadius: "0.5rem",
fontSize: "1rem",
fontWeight: 500,
textDecoration: "none",
color: "#ffffff",
backgroundColor: ACCENT_BLUE,
border: `1px solid ${ACCENT_BLUE}`,
cursor: "pointer",
fontFamily: "inherit",
},
buttonSecondary: {
display: "inline-block",
padding: "0.75rem 2rem",
borderRadius: "0.5rem",
fontSize: "1rem",
fontWeight: 500,
textDecoration: "none",
color: TEXT_PRIMARY,
backgroundColor: SUBTLE_BG,
border: SUBTLE_BORDER,
},
};
import ErrorFallbackUI from "@/components/ErrorFallbackUI";

// Root-level error boundary. Keep this dependency-light: the shared fallback
// uses inline Hive Commons theme styles so it still renders during asset-load
// or runtime failures.
export default function GlobalError({
reset,
}: {
Expand All @@ -96,31 +13,8 @@ export default function GlobalError({
}) {
return (
<html lang="en">
<body style={styles.body}>
<main style={styles.wrap}>
<div style={styles.code}>Oops</div>
<h1 style={styles.heading}>Something went wrong</h1>
<p style={styles.message}>
An unexpected error occurred while loading this page. It may be a
temporary problem &mdash; trying again usually fixes it.
</p>
<div style={styles.actions}>
<button type="button" onClick={reset} style={styles.buttonPrimary}>
Try again
</button>
{/* Plain <a> is deliberate here: when the global error boundary
renders, the app (router included) has already crashed, so a
full-page navigation is the most reliable way out. */}
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
<a href="/" style={styles.buttonSecondary}>
Hive Commons home
</a>
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
<a href="/docs" style={styles.buttonSecondary}>
Browse the docs
</a>
</div>
</main>
<body>
<ErrorFallbackUI reset={reset} />
</body>
</html>
);
Expand Down
113 changes: 113 additions & 0 deletions src/components/ErrorFallbackUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"use client";

import Link from "next/link";
import type { CSSProperties } from "react";

const styles: Record<string, CSSProperties> = {
page: {
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "2rem",
background: "var(--ef-bg)",
color: "var(--ef-ink)",
fontFamily:
'var(--font-inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif)',
},
card: {
width: "min(100%, 48rem)",
border: "1px solid var(--ef-line)",
borderRadius: "1.5rem",
background: "var(--ef-bg-2)",
boxShadow: "0 24px 80px rgba(0,0,0,.22)",
padding: "clamp(1.5rem, 5vw, 3rem)",
textAlign: "center",
},
logo: { width: 48, height: 48, margin: "0 auto 1rem", display: "block" },
eyebrow: {
color: "var(--ef-honey)",
fontWeight: 750,
letterSpacing: "0.08em",
textTransform: "uppercase",
fontSize: "0.85rem",
marginBottom: "0.75rem",
},
title: {
color: "var(--ef-ink)",
fontSize: "clamp(2rem, 5vw, 3.5rem)",
lineHeight: 1.05,
letterSpacing: "-0.04em",
margin: "0 0 1rem",
},
message: { color: "var(--ef-ink-2)", fontSize: "1.05rem", lineHeight: 1.7, margin: "0 auto 1.75rem", maxWidth: "38rem" },
actions: { display: "flex", flexWrap: "wrap", justifyContent: "center", gap: "0.75rem" },
primary: {
border: "1px solid var(--ef-honey)",
background: "var(--ef-honey)",
color: "var(--ef-honey-ink)",
borderRadius: "0.75rem",
padding: "0.8rem 1rem",
font: "inherit",
fontWeight: 750,
cursor: "pointer",
textDecoration: "none",
},
secondary: {
border: "1px solid var(--ef-line)",
background: "transparent",
color: "var(--ef-ink)",
borderRadius: "0.75rem",
padding: "0.8rem 1rem",
fontWeight: 700,
textDecoration: "none",
},
};

function ThemeVars() {
return (
<style>{`
:root, html.dark {
--ef-bg: #14110b;
--ef-bg-2: #1b170f;
--ef-ink: #efe7d7;
--ef-ink-2: #b9ad93;
--ef-honey: #e0a33a;
--ef-honey-ink: #14110b;
--ef-line: #332c1e;
}
html:not(.dark) {
--ef-bg: #f6efe0;
--ef-bg-2: #efe6d2;
--ef-ink: #1c1710;
--ef-ink-2: #5a5040;
--ef-honey: #b8791a;
--ef-honey-ink: #fff8e8;
--ef-line: #d8cbae;
}
html, body { margin: 0; padding: 0; background: var(--ef-bg); }
`}</style>
);
}

export default function ErrorFallbackUI({ reset }: { reset: () => void }) {
return (
<main style={styles.page}>
<ThemeVars />
<section style={styles.card} aria-labelledby="runtime-error-title">
<img src="/hive-commons-logo.png" width="48" height="48" alt="Hive Commons logo" style={styles.logo} />
<div style={styles.eyebrow}>Hive Commons Docs</div>
<h1 id="runtime-error-title" style={styles.title}>Something went wrong</h1>
<p style={styles.message}>
The docs hit a temporary runtime error. Try again, or jump back into the Hive docs while we recover this page.
</p>
<div style={styles.actions}>
<button type="button" onClick={reset} style={styles.primary}>Try again</button>
<Link href="/docs/hive/readme" style={styles.secondary}>Hive intro</Link>
<Link href="/docs" style={styles.secondary}>Docs home</Link>
<Link href="/docs/community/meetings" style={styles.secondary}>Community meetings</Link>
</div>
</section>
</main>
);
}
Loading
Loading