Seed: healthcare template (18 files)

This commit is contained in:
2026-07-23 22:11:59 +00:00
parent 32cb0c1601
commit 1a5822db46
+140
View File
@@ -0,0 +1,140 @@
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Menu, Heart, Activity, User } from "lucide-react";
import { ThemeToggle } from "@/components/ThemeToggle";
import { useAuth } from "@/hooks/use-tygarun";
import { toast } from "sonner";
function PatientPortal({ trigger }: { trigger: React.ReactNode }) {
const { user, isSignedIn, signup, signin, signout } = useAuth();
const [openAuth, setOpenAuth] = useState(false);
const [mode, setMode] = useState<"signin" | "signup">("signin");
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [busy, setBusy] = useState(false);
if (isSignedIn) {
return (
<Button variant="ghost" size="sm" className="text-slate-600 dark:text-slate-300 hover:text-sky-500 font-medium" onClick={() => { signout(); toast.success("Signed out of patient portal"); }}>
Sign Out{user?.email ? ` (\${user.email})` : ""}
</Button>
);
}
const submit = async (e: React.FormEvent) => {
e.preventDefault();
setBusy(true);
try {
if (mode === "signup") await signup({ name, email, password });
else await signin(email, password);
toast.success(mode === "signup" ? "Patient account created!" : "Welcome back!");
setOpenAuth(false);
setName(""); setEmail(""); setPassword("");
} catch {
toast.error("Authentication failed. Please try again.");
} finally {
setBusy(false);
}
};
return (
<Dialog open={openAuth} onOpenChange={setOpenAuth}>
<DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="bg-white dark:bg-slate-950 border-slate-200 dark:border-slate-800">
<DialogHeader>
<DialogTitle>{mode === "signup" ? "Create Patient Account" : "Patient Portal Login"}</DialogTitle>
<DialogDescription className="text-muted-foreground">{mode === "signup" ? "Register to book appointments and manage your care." : "Sign in to access your appointments and records."}</DialogDescription>
</DialogHeader>
<form onSubmit={submit} className="space-y-4">
{mode === "signup" && (
<div className="space-y-1.5">
<Label htmlFor="pp-name">Name</Label>
<Input id="pp-name" value={name} onChange={(e) => setName(e.target.value)} required />
</div>
)}
<div className="space-y-1.5">
<Label htmlFor="pp-email">Email</Label>
<Input id="pp-email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
</div>
<div className="space-y-1.5">
<Label htmlFor="pp-password">Password</Label>
<Input id="pp-password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
</div>
<Button type="submit" disabled={busy} className="w-full bg-sky-500 hover:bg-sky-600 text-white rounded-full">
{busy ? "Please wait…" : mode === "signup" ? "Create Account" : "Sign In"}
</Button>
</form>
<button type="button" className="text-sm text-muted-foreground hover:text-sky-500 mt-2" onClick={() => setMode(mode === "signup" ? "signin" : "signup")}>
{mode === "signup" ? "Already registered? Sign in" : "New patient? Create an account"}
</button>
</DialogContent>
</Dialog>
);
}
export default function Header() {
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const fn = () => setScrolled(window.scrollY > 20);
window.addEventListener("scroll", fn);
return () => window.removeEventListener("scroll", fn);
}, []);
const links = [
{ label: "Services", to: "/services" },
{ label: "Doctors", to: "/doctors" },
{ label: "About", to: "/about" },
{ label: "Contact", to: "/contact" },
];
return (
<header className={`fixed top-0 inset-x-0 z-50 transition-all duration-300 \${scrolled ? "bg-white/90 dark:bg-slate-950/95 backdrop-blur-lg border-b border-slate-200 dark:border-slate-800 shadow-sm" : "bg-transparent"}`}>
<div className="mx-auto max-w-7xl flex items-center justify-between px-6 py-4">
<Link to="/" className="flex items-center gap-2 group">
<div className="relative">
<Heart className={`h-6 w-6 transition-colors \${scrolled ? "text-sky-500" : "text-white"}`} />
<Activity className={`absolute -bottom-0.5 -right-1 h-3 w-3 transition-colors \${scrolled ? "text-teal-500" : "text-sky-200"}`} />
</div>
<span className={`text-lg font-bold tracking-tight transition-colors \${scrolled ? "text-slate-900 dark:text-white" : "text-white"}`}>Vitality Medical Center</span>
</Link>
<nav className="hidden md:flex items-center gap-8">
{links.map((l) => (
<Link key={l.to} to={l.to} className={`text-sm font-medium transition-colors hover:text-sky-500 \${scrolled ? "text-slate-600 dark:text-slate-300" : "text-white/90"}`}>{l.label}</Link>
))}
</nav>
<div className="hidden md:flex items-center gap-3">
<ThemeToggle />
<PatientPortal trigger={<Button variant="ghost" size="sm" className={`rounded-full gap-1.5 \${scrolled ? "text-slate-600 dark:text-slate-300" : "text-white/90 hover:text-white hover:bg-white/10"}`}><User className="h-4 w-4" /> Portal</Button>} />
<Button asChild size="sm" className="bg-sky-500 hover:bg-sky-600 text-white rounded-full px-6 shadow-lg shadow-sky-500/20 active:scale-[0.97] transition-all">
<Link to="/contact">Book Appointment</Link>
</Button>
</div>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild className="md:hidden">
<Button variant="ghost" size="icon" className={`\${scrolled ? "text-slate-900 dark:text-white" : "text-white"}`}><Menu className="h-5 w-5" /></Button>
</SheetTrigger>
<SheetContent side="right" className="w-72 bg-white dark:bg-slate-950 border-slate-200 dark:border-slate-800">
<nav className="mt-8 flex flex-col gap-4">
{links.map((l) => (
<Link key={l.to} to={l.to} className="text-lg font-medium text-slate-700 dark:text-slate-200 hover:text-sky-500 transition-colors" onClick={() => setOpen(false)}>{l.label}</Link>
))}
<Button asChild className="mt-4 w-full bg-sky-500 hover:bg-sky-600 text-white rounded-full active:scale-[0.97] transition-all">
<Link to="/contact" onClick={() => setOpen(false)}>Book Appointment</Link>
</Button>
<PatientPortal trigger={<Button variant="outline" className="w-full rounded-full gap-1.5"><User className="h-4 w-4" /> Patient Portal</Button>} />
<ThemeToggle />
</nav>
</SheetContent>
</Sheet>
</div>
</header>
);
}