Website visitors now have a single registration path through the CodeGW sign-up page, while the header no longer presents a separate login action beside registration. Static export output is included so the repository matches the deployed Azure Static Web Apps artifact. Constraint: Website must use the CodeGW sign-up URL and remove the header login button. Rejected: Keeping the separate Agnet login URL | it sends registration traffic to the wrong surface. Confidence: high Scope-risk: narrow Directive: Keep public website registration links pointed at https://code.xinghanlab.com/sign-up unless product routing changes. Tested: pnpm run build; Azure Static Web Apps production deploy; live curl checks for sign-up URL and removed login link. Not-tested: Browser visual pass after deployment. Co-authored-by: OmX <omx@oh-my-codex.dev>
161 lines
5.6 KiB
TypeScript
161 lines
5.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import Image from "next/image";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Menu, X } from "lucide-react";
|
|
import type { Lang } from "@/app/page";
|
|
|
|
const copy = {
|
|
en: {
|
|
navLinks: [
|
|
{ href: "/#workflow", label: "Workflow" },
|
|
{ href: "/#products", label: "Surfaces" },
|
|
{ href: "/#features", label: "Capabilities" },
|
|
{ href: "/swarm", label: "Swarm" },
|
|
{ href: "/launch", label: "Launch" },
|
|
{ href: "/#security", label: "Security" },
|
|
],
|
|
register: "Register",
|
|
start: "Get started",
|
|
toggle: "中文",
|
|
menuOpen: "Open menu",
|
|
menuClose: "Close menu",
|
|
},
|
|
zh: {
|
|
navLinks: [
|
|
{ href: "/#workflow", label: "流程" },
|
|
{ href: "/#products", label: "产品面" },
|
|
{ href: "/#features", label: "能力" },
|
|
{ href: "/swarm", label: "蜂群" },
|
|
{ href: "/launch", label: "上线" },
|
|
{ href: "/#security", label: "安全" },
|
|
],
|
|
register: "注册",
|
|
start: "开始使用",
|
|
toggle: "EN",
|
|
menuOpen: "打开菜单",
|
|
menuClose: "关闭菜单",
|
|
},
|
|
} satisfies Record<Lang, {
|
|
navLinks: Array<{ href: string; label: string }>;
|
|
register: string;
|
|
start: string;
|
|
toggle: string;
|
|
menuOpen: string;
|
|
menuClose: string;
|
|
}>;
|
|
|
|
const MANAGER_URL = "https://code.xinghanlab.com/";
|
|
const REGISTER_URL = "https://code.xinghanlab.com/sign-up";
|
|
const DOCS_URL = "https://doc.xinghanlab.com/";
|
|
|
|
export function Header({
|
|
lang,
|
|
setLang,
|
|
}: {
|
|
lang: Lang;
|
|
setLang: (lang: Lang) => void;
|
|
}) {
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
const t = copy[lang];
|
|
const toggleLang = () => setLang(lang === "en" ? "zh" : "en");
|
|
|
|
return (
|
|
<header className="fixed top-0 left-0 right-0 z-50 border-b border-border/60 bg-background/75 backdrop-blur-xl">
|
|
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
|
|
<Link href="/" className="flex items-center gap-2">
|
|
<div className="flex h-9 w-9 items-center justify-center rounded-xl border border-border/70 bg-card/80 text-foreground shadow-sm">
|
|
<Image src="/taiji-logo.png" alt="Heicode" width={18} height={18} />
|
|
</div>
|
|
<span className="text-lg font-black tracking-tight text-foreground">
|
|
Heicode
|
|
</span>
|
|
</Link>
|
|
|
|
{/* Desktop Navigation */}
|
|
<nav className="hidden items-center gap-8 md:flex">
|
|
{t.navLinks.map((link) => (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
<div className="hidden items-center gap-4 md:flex">
|
|
<Button variant="outline" size="sm" className="rounded-full bg-card/50" onClick={toggleLang}>
|
|
{t.toggle}
|
|
</Button>
|
|
<Button variant="ghost" size="sm" className="rounded-full" asChild>
|
|
<a href={DOCS_URL} target="_blank" rel="noreferrer">
|
|
{lang === "zh" ? "文档" : "Docs"}
|
|
</a>
|
|
</Button>
|
|
<Button variant="outline" size="sm" className="rounded-full bg-card/50" asChild>
|
|
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
|
|
{t.register}
|
|
</a>
|
|
</Button>
|
|
<Button size="sm" className="rounded-full bg-foreground text-background hover:bg-foreground/90" asChild>
|
|
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
|
|
{t.start}
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Mobile Menu Button */}
|
|
<button
|
|
className="md:hidden"
|
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
aria-label={mobileMenuOpen ? t.menuClose : t.menuOpen}
|
|
>
|
|
{mobileMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Mobile Navigation */}
|
|
{mobileMenuOpen && (
|
|
<div className="border-t border-border bg-background md:hidden">
|
|
<nav className="flex flex-col px-4 py-4">
|
|
{t.navLinks.map((link) => (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
className="py-3 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
|
onClick={() => setMobileMenuOpen(false)}
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
))}
|
|
<div className="mt-4 flex flex-col gap-2">
|
|
<Button variant="outline" size="sm" className="justify-start rounded-full bg-card/50" onClick={toggleLang}>
|
|
{t.toggle}
|
|
</Button>
|
|
<Button variant="ghost" size="sm" className="justify-start rounded-full" asChild>
|
|
<a href={DOCS_URL} target="_blank" rel="noreferrer">
|
|
{lang === "zh" ? "文档" : "Docs"}
|
|
</a>
|
|
</Button>
|
|
<Button variant="outline" size="sm" className="justify-start rounded-full bg-card/50" asChild>
|
|
<a href={REGISTER_URL} target="_blank" rel="noreferrer">
|
|
{t.register}
|
|
</a>
|
|
</Button>
|
|
<Button size="sm" className="rounded-full bg-foreground text-background hover:bg-foreground/90" asChild>
|
|
<a href={MANAGER_URL} target="_blank" rel="noreferrer">
|
|
{t.start}
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
)}
|
|
</header>
|
|
);
|
|
}
|