Add terminal link to site header

This commit is contained in:
c4ch3c4d3
2026-04-27 09:15:50 -06:00
parent fd77d6ee1e
commit 8626b3d1db
3 changed files with 92 additions and 0 deletions
+3
View File
@@ -1,6 +1,8 @@
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { TerminalNavLink } from "~/components/TerminalNavLink";
export function SiteHeader() { export function SiteHeader() {
return ( return (
<header className="sticky top-0 z-20 border-b border-[#f8c27a] bg-white/95 shadow-sm backdrop-blur"> <header className="sticky top-0 z-20 border-b border-[#f8c27a] bg-white/95 shadow-sm backdrop-blur">
@@ -16,6 +18,7 @@ export function SiteHeader() {
<Link href="/labs" className="hover:text-[#F89C27]"> <Link href="/labs" className="hover:text-[#F89C27]">
Labs Labs
</Link> </Link>
<TerminalNavLink />
<Link <Link
href="https://discord.gg/Ma9UZNBxvh" href="https://discord.gg/Ma9UZNBxvh"
className="rounded-md border border-[#F89C27] px-3 py-1.5 text-[#004E78] hover:bg-[#F89C27] hover:text-white" className="rounded-md border border-[#F89C27] px-3 py-1.5 text-[#004E78] hover:bg-[#F89C27] hover:text-white"
+48
View File
@@ -0,0 +1,48 @@
import { render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { TerminalNavLink } from "~/components/TerminalNavLink";
import {
COURSEWARE_RUNTIME_CONFIG_PATH,
LAB3_DEFAULT_TERMINAL_PATH,
} from "~/lib/courseware-runtime";
describe("TerminalNavLink", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("defaults to the same-origin WeTTY path", () => {
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("not found"));
render(<TerminalNavLink />);
expect(screen.getByRole("link", { name: "Terminal" })).toHaveAttribute(
"href",
LAB3_DEFAULT_TERMINAL_PATH,
);
});
it("loads the terminal link from runtime config", async () => {
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response(
JSON.stringify({
lab3TerminalUrl: "http://127.0.0.1:7681/wetty",
}),
{ status: 200 },
),
);
render(<TerminalNavLink />);
await waitFor(() => {
expect(screen.getByRole("link", { name: "Terminal" })).toHaveAttribute(
"href",
"http://localhost:7681/wetty",
);
});
expect(fetchMock).toHaveBeenCalledWith(COURSEWARE_RUNTIME_CONFIG_PATH, {
cache: "no-store",
});
});
});
+41
View File
@@ -0,0 +1,41 @@
"use client";
import { useEffect, useState } from "react";
import {
LAB3_DEFAULT_TERMINAL_PATH,
fetchCoursewareRuntimeConfig,
} from "~/lib/courseware-runtime";
export function TerminalNavLink() {
const [terminalPath, setTerminalPath] = useState(LAB3_DEFAULT_TERMINAL_PATH);
useEffect(() => {
let isCancelled = false;
void fetchCoursewareRuntimeConfig()
.then((runtimeConfig) => {
if (isCancelled) return;
setTerminalPath(runtimeConfig.lab3TerminalUrl);
})
.catch(() => {
if (isCancelled) return;
setTerminalPath(LAB3_DEFAULT_TERMINAL_PATH);
});
return () => {
isCancelled = true;
};
}, []);
return (
<a
className="hover:text-[#F89C27]"
href={terminalPath}
rel="noreferrer"
target="_blank"
>
Terminal
</a>
);
}