Seed: developer-tools template (15 files)

This commit is contained in:
2026-07-23 22:15:21 +00:00
parent b93f9e1cdb
commit 06ffc7a296
+171
View File
@@ -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>
);
}