clean up 401, simplify terminal error page

This commit is contained in:
Michael Freno
2026-01-06 17:57:58 -05:00
parent b981f953d6
commit aedcacaa57
4 changed files with 162 additions and 288 deletions

View File

@@ -1,7 +1,8 @@
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { createSignal } from "solid-js"; import { createSignal, onCleanup, onMount } from "solid-js";
import { TerminalErrorPage } from "~/components/TerminalErrorPage"; import { TerminalErrorPage } from "~/components/TerminalErrorPage";
import { useDarkMode } from "~/context/darkMode"; import { useDarkMode } from "~/context/darkMode";
import { glitchText } from "~/lib/client-utils";
export interface ErrorBoundaryFallbackProps { export interface ErrorBoundaryFallbackProps {
error: Error; error: Error;
@@ -28,9 +29,15 @@ export default function ErrorBoundaryFallback(
isDark = () => true; isDark = () => true;
} }
const [glitchText, setGlitchText] = createSignal("ERROR"); const [glitchError, setGlitchError] = createSignal("ERROR");
console.error(props.error); onMount(() => {
const interval = glitchText(glitchError(), setGlitchError);
onCleanup(() => {
clearInterval(interval);
});
});
const errorContent = ( const errorContent = (
<div class="mb-8 w-full max-w-4xl font-mono"> <div class="mb-8 w-full max-w-4xl font-mono">
@@ -43,7 +50,7 @@ export default function ErrorBoundaryFallback(
<div class="mb-2 flex items-start gap-2"> <div class="mb-2 flex items-start gap-2">
<span class="text-red text-xl"></span> <span class="text-red text-xl"></span>
<div class="flex-1"> <div class="flex-1">
<div class="text-red mb-2 text-lg">{glitchText()}</div> <div class="text-red mb-2 text-lg">{glitchError()}</div>
<div class="text-text"> <div class="text-text">
Application encountered an unexpected error Application encountered an unexpected error
</div> </div>
@@ -119,11 +126,6 @@ export default function ErrorBoundaryFallback(
return ( return (
<TerminalErrorPage <TerminalErrorPage
glitchText="ERROR"
glitchChars={"!@#$%^&*()_+-=[]{}|;':\",./<>?~`"}
glitchSpeed={150}
glitchThreshold={0.8}
glitchIntensity={0.6}
navigate={navigate!} navigate={navigate!}
location={{ location={{
pathname: typeof window !== "undefined" ? window.location.pathname : "/" pathname: typeof window !== "undefined" ? window.location.pathname : "/"
@@ -136,7 +138,6 @@ export default function ErrorBoundaryFallback(
Runtime Exception Runtime Exception
</> </>
} }
onGlitchTextChange={setGlitchText}
commandContext={{ isDark }} commandContext={{ isDark }}
/> />
); );

View File

@@ -1,12 +1,4 @@
import { import { createSignal, createEffect, onMount, For, Show, JSX } from "solid-js";
createSignal,
createEffect,
onMount,
onCleanup,
For,
Show,
JSX
} from "solid-js";
import { import {
CommandHistoryItem, CommandHistoryItem,
createTerminalCommands, createTerminalCommands,
@@ -16,22 +8,16 @@ import {
import { Btop } from "~/components/Btop"; import { Btop } from "~/components/Btop";
interface TerminalErrorPageProps { interface TerminalErrorPageProps {
glitchText: string;
glitchChars: string;
glitchSpeed?: number;
glitchThreshold?: number;
glitchIntensity?: number;
navigate: (path: string) => void; navigate: (path: string) => void;
location: { pathname: string }; location: { pathname: string };
errorContent: JSX.Element; errorContent: JSX.Element;
quickActions: JSX.Element; quickActions: JSX.Element;
footer: JSX.Element; footer: JSX.Element;
onGlitchTextChange?: (text: string) => void;
commandContext?: Partial<CommandContext>; commandContext?: Partial<CommandContext>;
disableTerminal?: boolean;
} }
export function TerminalErrorPage(props: TerminalErrorPageProps) { export function TerminalErrorPage(props: TerminalErrorPageProps) {
const [glitchText, setGlitchText] = createSignal(props.glitchText);
const [command, setCommand] = createSignal(""); const [command, setCommand] = createSignal("");
const [history, setHistory] = createSignal<CommandHistoryItem[]>([]); const [history, setHistory] = createSignal<CommandHistoryItem[]>([]);
const [historyIndex, setHistoryIndex] = createSignal(-1); const [historyIndex, setHistoryIndex] = createSignal(-1);
@@ -109,38 +95,7 @@ export function TerminalErrorPage(props: TerminalErrorPageProps) {
}; };
onMount(() => { onMount(() => {
const originalText = props.glitchText;
const glitchChars = props.glitchChars;
const glitchSpeed = props.glitchSpeed || 300;
const glitchThreshold = props.glitchThreshold || 0.85;
const glitchIntensity = props.glitchIntensity || 0.7;
const glitchInterval = setInterval(() => {
if (Math.random() > glitchThreshold) {
let glitched = "";
for (let i = 0; i < originalText.length; i++) {
if (Math.random() > glitchIntensity) {
glitched +=
glitchChars[Math.floor(Math.random() * glitchChars.length)];
} else {
glitched += originalText[i];
}
}
setGlitchText(glitched);
props.onGlitchTextChange?.(glitched);
setTimeout(() => {
setGlitchText(originalText);
props.onGlitchTextChange?.(originalText);
}, 100);
}
}, glitchSpeed);
inputRef?.focus(); inputRef?.focus();
onCleanup(() => {
clearInterval(glitchInterval);
});
}); });
return ( return (
@@ -198,27 +153,27 @@ export function TerminalErrorPage(props: TerminalErrorPageProps) {
</For> </For>
</div> </div>
</Show> </Show>
<Show when={!props.disableTerminal}>
{/* Interactive input */} <div class="w-full max-w-4xl font-mono text-sm">
<div class="w-full max-w-4xl font-mono text-sm"> <div class="flex items-center gap-2">
<div class="flex items-center gap-2"> <span class="text-green">freno@terminal</span>
<span class="text-green">freno@terminal</span> <span class="text-subtext1">:</span>
<span class="text-subtext1">:</span> <span class="text-blue">~</span>
<span class="text-blue">~</span> <span class="text-subtext1">$</span>
<span class="text-subtext1">$</span> <input
<input ref={inputRef}
ref={inputRef} type="text"
type="text" value={command()}
value={command()} onInput={(e) => setCommand(e.currentTarget.value)}
onInput={(e) => setCommand(e.currentTarget.value)} onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown} class="text-text caret-text ml-1 flex-1 border-none bg-transparent outline-none"
class="text-text caret-text ml-1 flex-1 border-none bg-transparent outline-none" autocomplete="off"
autocomplete="off" autocapitalize="off"
autocapitalize="off" spellcheck={false}
spellcheck={false} />
/> </div>
</div> </div>
</div> </Show>
{/* Footer */} {/* Footer */}
<div <div

View File

@@ -1,39 +1,113 @@
import { PageHead } from "~/components/PageHead"; import { PageHead } from "~/components/PageHead";
import { HttpStatusCode } from "@solidjs/start"; import { HttpStatusCode } from "@solidjs/start";
import { useNavigate } from "@solidjs/router"; import { useLocation, useNavigate } from "@solidjs/router";
import { createEffect, createSignal, For, onCleanup } from "solid-js"; import { createSignal, onMount, onCleanup } from "solid-js";
import { ERROR_PAGE_CONFIG } from "~/config";
import { glitchText } from "~/lib/client-utils"; import { glitchText } from "~/lib/client-utils";
import { TerminalErrorPage } from "~/components/TerminalErrorPage";
import { useDarkMode } from "~/context/darkMode";
export default function Page_401() { export default function Page_401() {
const navigate = useNavigate(); const navigate = useNavigate();
const [glitchText, setGlitchText] = createSignal("401"); const [glitch401, setGlitch401] = createSignal("401");
createEffect(() => { const location = useLocation();
const interval = glitchText(
"401",
setGlitchText,
ERROR_PAGE_CONFIG.GLITCH_INTERVAL_MS,
ERROR_PAGE_CONFIG.GLITCH_DURATION_MS
);
onCleanup(() => clearInterval(interval)); onMount(() => {
const interval = glitchText(glitch401(), setGlitch401);
onCleanup(() => {
clearInterval(interval);
});
}); });
const createParticles = () => { const errorContent = (
return Array.from({ length: ERROR_PAGE_CONFIG.PARTICLE_COUNT }, (_, i) => ({ <div class="mb-8 w-full max-w-4xl font-mono">
id: i, <div class="mb-4 flex items-center gap-2">
left: `${Math.random() * 100}%`, <span class="text-red text-3xl font-bold">{glitch401()}</span>
top: `${Math.random() * 100}%`, <div class="border-overlay0 h-8 border-l" />
animationDelay: `${Math.random() * 3}s`, <div class="flex flex-col">
animationDuration: `${2 + Math.random() * 3}s` <span class="text-red font-mono text-sm">error:</span>
})); <span class="text-text font-mono">Unauthorized</span>
}; </div>
</div>
function doubleBack() { <div class="border-red bg-mantle mb-6 border-l-4 p-4 text-sm">
window.history.go(-2); <div class="mb-2 flex items-start gap-2">
<span class="text-red"></span>
<div class="flex-1">
<div class="text-text">Access Denied</div>
<div class="text-subtext0 mt-1">
Authentication required to access this resource
</div>
</div>
</div>
<div class="text-subtext1 mt-3">
<span class="text-yellow"></span> Location:{" "}
<span class="text-peach">{location.pathname}</span>
</div>
</div>
<div class="text-subtext0 space-y-2 text-sm">
<div class="flex items-start gap-2">
<span class="text-blue"></span>
<span>
Type <span class="text-green">help</span> to see available commands,
or try one of the suggestions below
</span>
</div>
</div>
</div>
);
const quickActions = (
<div class="mb-8 w-full max-w-4xl space-y-3 font-mono text-sm">
<div class="text-subtext1">Quick actions:</div>
<button
onClick={() => navigate("/login")}
class="group border-surface0 bg-mantle hover:border-yellow hover:bg-surface0 flex w-full items-center gap-2 border px-4 py-3 text-left transition-all"
>
<span class="text-green">$</span>
<span class="text-yellow group-hover:text-peach">./authenticate</span>
<span class="text-subtext1">--login</span>
<span class="text-subtext1 ml-auto opacity-0 transition-opacity group-hover:opacity-100">
[Login]
</span>
</button>
<button
onClick={() => navigate("/")}
class="group border-surface0 bg-mantle hover:border-blue hover:bg-surface0 flex w-full items-center gap-2 border px-4 py-3 text-left transition-all"
>
<span class="text-green">$</span>
<span class="text-blue group-hover:text-sky">cd</span>
<span class="text-text group-hover:text-blue">~</span>
<span class="text-subtext1 ml-auto opacity-0 transition-opacity group-hover:opacity-100">
[Return home]
</span>
</button>
<button
onClick={() => window.history.back()}
class="group border-surface0 bg-mantle hover:border-blue hover:bg-surface0 flex w-full items-center gap-2 border px-4 py-3 text-left transition-all"
>
<span class="text-green">$</span>
<span class="text-blue group-hover:text-sky">cd</span>
<span class="text-text group-hover:text-blue">..</span>
<span class="text-subtext1 ml-auto opacity-0 transition-opacity group-hover:opacity-100">
[Go back]
</span>
</button>
</div>
);
let isDark: () => boolean;
try {
const darkMode = useDarkMode();
isDark = darkMode.isDark;
} catch (e) {
isDark = () => true;
} }
return ( return (
<> <>
<PageHead <PageHead
@@ -41,183 +115,20 @@ export default function Page_401() {
description="401 - Unauthorized access. Please log in to access this page." description="401 - Unauthorized access. Please log in to access this page."
/> />
<HttpStatusCode code={401} /> <HttpStatusCode code={401} />
<div class="relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-slate-900 via-amber-950/20 to-slate-900 dark:from-black dark:via-amber-950/30 dark:to-black"> <TerminalErrorPage
{/* Animated particle background */} navigate={navigate}
<div class="absolute inset-0 overflow-hidden"> location={location}
<For each={createParticles()}> errorContent={errorContent}
{(particle) => ( quickActions={quickActions}
<div disableTerminal
class="absolute animate-pulse" footer={
style={{ <>
left: particle.left, <span class="text-red">401</span>{" "}
top: particle.top, <span class="text-subtext0">|</span> Unauthorized Access
"animation-delay": particle.animationDelay, </>
"animation-duration": particle.animationDuration }
}} commandContext={{ isDark }}
> />
<div class="h-1 w-1 rounded-full bg-amber-400 opacity-30 dark:bg-amber-300" />
</div>
)}
</For>
</div>
{/* Animated grid background */}
<div class="absolute inset-0 opacity-10">
<div
class="h-full w-full"
style={{
"background-image": `
linear-gradient(rgba(251, 191, 36, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(251, 191, 36, 0.3) 1px, transparent 1px)
`,
"background-size": "50px 50px",
animation: "grid-move 20s linear infinite"
}}
/>
</div>
{/* Logo overlay */}
<div class="absolute inset-0 flex items-center justify-center opacity-10">
<picture class="h-80 object-cover sm:h-96">
<source
srcSet="/WhiteLogo.png"
media="(prefers-color-scheme: dark)"
/>
<img
src="/BlackLogo.png"
alt="logo"
class="mx-auto brightness-50"
/>
</picture>
</div>
{/* Main content */}
<div class="relative z-10 flex min-h-screen flex-col items-center justify-center px-4 text-center">
{/* Glitchy 401 */}
<div class="mb-8">
<h1
class="bg-gradient-to-r from-amber-400 via-orange-500 to-amber-600 bg-clip-text text-8xl font-bold text-transparent select-none md:text-9xl"
style={{
"text-shadow": "0 0 30px rgba(251, 191, 36, 0.5)",
filter: "drop-shadow(0 0 10px rgba(251, 191, 36, 0.3))"
}}
>
{glitchText()}
</h1>
<div class="mx-auto mt-2 h-1 w-32 animate-pulse bg-gradient-to-r from-transparent via-amber-500 to-transparent" />
</div>
{/* Error message */}
<div class="max-w-2xl space-y-4">
<h2 class="animate-fade-in text-2xl font-light text-slate-300 md:text-3xl dark:text-slate-400">
Access Denied
</h2>
<p class="animate-fade-in-delay text-lg text-slate-400 dark:text-slate-500">
You lack authentication sufficient for that page.
<br />
Please log in or return to a safe location.
</p>
</div>
{/* Action buttons */}
<div class="mt-12 flex flex-col gap-4 sm:flex-row">
<button
onClick={() => navigate("/login")}
class="group relative overflow-hidden rounded-lg bg-gradient-to-r from-amber-600 to-orange-600 px-8 py-4 text-lg font-medium text-white shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-amber-500/25 active:scale-95"
>
<div class="absolute inset-0 bg-gradient-to-r from-amber-700 to-orange-700 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<span class="relative flex items-center gap-2">🔐 Login</span>
</button>
<button
onClick={() => navigate("/")}
class="group relative overflow-hidden rounded-lg bg-gradient-to-r from-blue-600 to-purple-600 px-8 py-4 text-lg font-medium text-white shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-blue-500/25 active:scale-95"
>
<div class="absolute inset-0 bg-gradient-to-r from-blue-700 to-purple-700 opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
<span class="relative flex items-center gap-2">
🏠 Return Home
</span>
</button>
<button
onClick={doubleBack}
class="group relative overflow-hidden rounded-lg border-2 border-slate-600 bg-transparent px-8 py-4 text-lg font-medium text-slate-300 transition-all duration-300 hover:border-amber-500 hover:bg-amber-500/10 hover:text-amber-400 active:scale-95"
>
<span class="relative flex items-center gap-2"> Go Back</span>
</button>
</div>
{/* Floating elements */}
<div class="animate-bounce-slow absolute top-20 left-10">
<div class="h-6 w-6 rotate-45 bg-gradient-to-br from-amber-400 to-orange-500 opacity-60" />
</div>
<div class="animate-bounce-slow-delay absolute top-32 right-16">
<div class="h-4 w-4 rounded-full bg-gradient-to-br from-orange-400 to-amber-500 opacity-60" />
</div>
<div class="animate-bounce-slow absolute bottom-20 left-20">
<div
class="h-5 w-5 bg-gradient-to-br from-amber-500 to-orange-400 opacity-60"
style={{ "clip-path": "polygon(50% 0%, 0% 100%, 100% 100%)" }}
/>
</div>
{/* Footer */}
<div class="absolute bottom-8 left-1/2 -translate-x-1/2">
<p class="text-sm text-slate-500 dark:text-slate-600">
Error Code: 401 Unauthorized Access
</p>
</div>
</div>
{/* Custom styles */}
<style>{`
@keyframes grid-move {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(50px, 50px);
}
}
.animate-fade-in {
animation: fadeIn 1s ease-out 0.5s both;
}
.animate-fade-in-delay {
animation: fadeIn 1s ease-out 1s both;
}
.animate-bounce-slow {
animation: bounce-slow 3s ease-in-out infinite;
}
.animate-bounce-slow-delay {
animation: bounce-slow 3s ease-in-out infinite 1.5s;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce-slow {
0%,
100% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
`}</style>
</div>
</> </>
); );
} }

View File

@@ -1,9 +1,10 @@
import { PageHead } from "~/components/PageHead"; import { PageHead } from "~/components/PageHead";
import { HttpStatusCode } from "@solidjs/start"; import { HttpStatusCode } from "@solidjs/start";
import { useNavigate, useLocation } from "@solidjs/router"; import { useNavigate, useLocation } from "@solidjs/router";
import { createSignal, Show } from "solid-js"; import { createSignal, onCleanup, onMount, Show } from "solid-js";
import { TerminalErrorPage } from "~/components/TerminalErrorPage"; import { TerminalErrorPage } from "~/components/TerminalErrorPage";
import { useDarkMode } from "~/context/darkMode"; import { useDarkMode } from "~/context/darkMode";
import { glitchText } from "~/lib/client-utils";
// Component that crashes when rendered // Component that crashes when rendered
function CrashComponent() { function CrashComponent() {
@@ -14,14 +15,26 @@ export default function NotFound() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { isDark } = useDarkMode(); const { isDark } = useDarkMode();
const [glitchText, setGlitchText] = createSignal("404"); const [glitch404, setGlitch404] = createSignal("404");
const [shouldCrash, setShouldCrash] = createSignal(false); const [shouldCrash, setShouldCrash] = createSignal(false);
onMount(() => {
const interval = glitchText(glitch404(), setGlitch404);
onCleanup(() => {
clearInterval(interval);
});
});
const errorContent = ( const errorContent = (
<div class="mb-8 w-full max-w-4xl font-mono"> <div class="mb-8 w-full max-w-4xl font-mono">
<div class="mb-4 flex items-center gap-2"> <div class="mb-4 flex items-center gap-2">
<span class="text-red">error:</span> <span class="text-red text-3xl font-bold">{glitch404()}</span>
<span class="text-text">HTTP {glitchText()} - Not Found</span> <div class="border-overlay0 h-8 border-l" />
<div class="flex flex-col">
<span class="text-red font-mono text-sm">error:</span>
<span class="text-text font-mono">Not Found</span>
</div>
</div> </div>
<div class="border-red bg-mantle mb-6 border-l-4 p-4 text-sm"> <div class="border-red bg-mantle mb-6 border-l-4 p-4 text-sm">
@@ -107,11 +120,6 @@ export default function NotFound() {
/> />
<HttpStatusCode code={404} /> <HttpStatusCode code={404} />
<TerminalErrorPage <TerminalErrorPage
glitchText="404"
glitchChars={"!@#$%^&*()_+-=[]{}|;':\",./<>?~`0123456789"}
glitchSpeed={150}
glitchThreshold={0.85}
glitchIntensity={0.7}
navigate={navigate} navigate={navigate}
location={location} location={location}
errorContent={errorContent} errorContent={errorContent}
@@ -122,7 +130,6 @@ export default function NotFound() {
<span class="text-subtext0">|</span> Page Not Found <span class="text-subtext0">|</span> Page Not Found
</> </>
} }
onGlitchTextChange={setGlitchText}
commandContext={{ commandContext={{
triggerCrash: () => setShouldCrash(true), triggerCrash: () => setShouldCrash(true),
isDark: isDark isDark: isDark