Seed: developer-tools template (15 files)
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription } from "@/components/ui/dialog";
|
||||||
|
import { Menu, Terminal, Star, Github } from "lucide-react";
|
||||||
|
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||||
|
import { useAuth } from "@/hooks/use-tygarun";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ label: "Docs", to: "/docs" },
|
||||||
|
{ label: "API", to: "/docs" },
|
||||||
|
{ label: "Status", to: "/status" },
|
||||||
|
{ label: "Changelog", to: "/changelog" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function SignupDialog({ trigger }: { trigger: React.ReactNode }) {
|
||||||
|
const { signup, loading, isSignedIn } = useAuth();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
|
const handleSignup = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const res = await signup({ email, password });
|
||||||
|
const key = res?.apiKey || res?.token;
|
||||||
|
toast.success(key ? "Account created. Your API key is ready." : "Account created.");
|
||||||
|
setOpen(false);
|
||||||
|
} catch {
|
||||||
|
toast.error("Could not create account. Please try again.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||||
|
<DialogContent className="bg-slate-900 border-slate-800 text-white">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{isSignedIn ? "You are signed in" : "Create your account"}</DialogTitle>
|
||||||
|
<DialogDescription className="text-slate-400">Sign up to get your API key and start building.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<form onSubmit={handleSignup} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="signup-email" className="text-slate-300">Email</Label>
|
||||||
|
<Input id="signup-email" type="email" required value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="signup-password" className="text-slate-300">Password</Label>
|
||||||
|
<Input id="signup-password" type="password" required value={password} onChange={(e) => setPassword(e.target.value)} placeholder="••••••••" />
|
||||||
|
</div>
|
||||||
|
<Button type="submit" disabled={loading} className="w-full rounded-full bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold">
|
||||||
|
{loading ? "Creating…" : "Get API Key"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setScrolled(window.scrollY > 20);
|
||||||
|
window.addEventListener("scroll", onScroll);
|
||||||
|
return () => window.removeEventListener("scroll", onScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className={`fixed top-0 inset-x-0 z-50 transition-all duration-300 \${
|
||||||
|
scrolled
|
||||||
|
? "bg-slate-950/95 backdrop-blur-lg border-b border-slate-800 shadow-lg shadow-black/20"
|
||||||
|
: "bg-transparent"
|
||||||
|
}`}>
|
||||||
|
<div className="mx-auto max-w-7xl flex items-center justify-between px-6 py-4">
|
||||||
|
{/* Logo */}
|
||||||
|
<Link to="/" className="flex items-center gap-2.5">
|
||||||
|
<div className="h-8 w-8 rounded-lg bg-emerald-500 flex items-center justify-center">
|
||||||
|
<Terminal className="h-4 w-4 text-slate-950" />
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-bold tracking-tight text-white">
|
||||||
|
DevStack
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Desktop Nav */}
|
||||||
|
<nav className="hidden md:flex items-center gap-8">
|
||||||
|
{navLinks.map((l) => (
|
||||||
|
<Link
|
||||||
|
key={l.label}
|
||||||
|
to={l.to}
|
||||||
|
className="text-sm font-medium text-slate-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{l.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Right side */}
|
||||||
|
<div className="hidden md:flex items-center gap-3">
|
||||||
|
<a
|
||||||
|
href="https://github.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-1.5 text-sm text-slate-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<Github className="h-4 w-4" />
|
||||||
|
<Badge variant="secondary" className="bg-slate-800 text-slate-300 border-slate-700 text-xs">
|
||||||
|
<Star className="h-3 w-3 mr-1 fill-amber-400 text-amber-400" /> 4.2k
|
||||||
|
</Badge>
|
||||||
|
</a>
|
||||||
|
<ThemeToggle />
|
||||||
|
<SignupDialog
|
||||||
|
trigger={
|
||||||
|
<Button className="rounded-full bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold active:scale-[0.98]">
|
||||||
|
Get API Key
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu */}
|
||||||
|
<div className="md:hidden flex items-center gap-2">
|
||||||
|
<ThemeToggle />
|
||||||
|
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="text-white hover:bg-slate-800">
|
||||||
|
<Menu className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent side="right" className="bg-slate-950 border-slate-800 w-72">
|
||||||
|
<div className="flex flex-col gap-6 mt-8">
|
||||||
|
<Link to="/" className="flex items-center gap-2.5" onClick={() => setMobileOpen(false)}>
|
||||||
|
<div className="h-8 w-8 rounded-lg bg-emerald-500 flex items-center justify-center">
|
||||||
|
<Terminal className="h-4 w-4 text-slate-950" />
|
||||||
|
</div>
|
||||||
|
<span className="text-lg font-bold text-white">DevStack</span>
|
||||||
|
</Link>
|
||||||
|
<nav className="flex flex-col gap-4">
|
||||||
|
{navLinks.map((l) => (
|
||||||
|
<Link
|
||||||
|
key={l.label}
|
||||||
|
to={l.to}
|
||||||
|
onClick={() => setMobileOpen(false)}
|
||||||
|
className="text-slate-400 hover:text-white transition-colors font-medium"
|
||||||
|
>
|
||||||
|
{l.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
<SignupDialog
|
||||||
|
trigger={
|
||||||
|
<Button className="rounded-full bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold mt-4">
|
||||||
|
Get API Key
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user