always-uptime / app /components /auth-dropdown.tsx
sachnun's picture
feat: add public uptime sharing and enhance UI with glow effects
7c78153
raw
history blame
6.44 kB
import { useState, useEffect, useRef } from "react";
import { Form, useActionData, useNavigation } from "react-router";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
import { LogIn, UserPlus, ChevronDown } from "lucide-react";
export function AuthDropdown() {
const [isOpen, setIsOpen] = useState(false);
const [activeTab, setActiveTab] = useState<"login" | "register">("login");
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
}
function handleEscape(event: KeyboardEvent) {
if (event.key === "Escape") {
setIsOpen(false);
}
}
if (isOpen) {
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleEscape);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleEscape);
};
}, [isOpen]);
return (
<div className="relative" ref={dropdownRef}>
<Button
variant="outline"
size="sm"
onClick={() => setIsOpen(!isOpen)}
className="gap-1 shadow-glow-hover"
data-auth-dropdown
>
<LogIn className="h-4 w-4" />
<span>Account</span>
<ChevronDown className={`h-3 w-3 transition-transform ${isOpen ? "rotate-180" : ""}`} />
</Button>
{isOpen && (
<Card className="absolute right-0 mt-2 w-80 shadow-lg glow-card animate-scaleIn z-50">
<CardHeader className="pb-3">
{/* Tab Switcher */}
<div className="flex gap-2 mb-3">
<button
onClick={() => setActiveTab("login")}
className={`flex-1 flex items-center justify-center gap-2 py-1.5 px-3 rounded-md text-sm transition-all ${
activeTab === "login"
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground hover:bg-muted/80"
}`}
>
<LogIn className="h-3.5 w-3.5" />
<span className="font-medium">Login</span>
</button>
<button
onClick={() => setActiveTab("register")}
className={`flex-1 flex items-center justify-center gap-2 py-1.5 px-3 rounded-md text-sm transition-all ${
activeTab === "register"
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground hover:bg-muted/80"
}`}
>
<UserPlus className="h-3.5 w-3.5" />
<span className="font-medium">Register</span>
</button>
</div>
<CardTitle className="text-lg">
{activeTab === "login" ? "Welcome back" : "Create account"}
</CardTitle>
<CardDescription className="text-xs">
{activeTab === "login"
? "Enter your credentials"
: "Fill in your details"}
</CardDescription>
</CardHeader>
<CardContent className="pb-4">
{activeTab === "login" ? (
<LoginForm onSuccess={() => setIsOpen(false)} />
) : (
<RegisterForm onSuccess={() => setIsOpen(false)} />
)}
</CardContent>
</Card>
)}
</div>
);
}
function LoginForm({ onSuccess }: { onSuccess: () => void }) {
const actionData = useActionData();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post" action="/login" className="space-y-3">
<div className="space-y-1.5">
<Label htmlFor="login-username" className="text-xs">Username</Label>
<Input
id="login-username"
name="username"
type="text"
placeholder="username"
required
minLength={3}
disabled={isSubmitting}
className="h-9 text-sm"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="login-password" className="text-xs">Password</Label>
<Input
id="login-password"
name="password"
type="password"
required
minLength={6}
disabled={isSubmitting}
className="h-9 text-sm"
/>
</div>
{actionData?.error && (
<p className="text-xs text-red-500 dark:text-red-400">{actionData.error}</p>
)}
<Button
type="submit"
className="w-full h-9 text-sm shadow-glow-hover"
disabled={isSubmitting}
>
{isSubmitting ? "Signing in..." : "Sign in"}
</Button>
</Form>
);
}
function RegisterForm({ onSuccess }: { onSuccess: () => void }) {
const actionData = useActionData();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post" action="/register" className="space-y-3">
<div className="space-y-1.5">
<Label htmlFor="register-username" className="text-xs">Username</Label>
<Input
id="register-username"
name="username"
type="text"
placeholder="username"
required
minLength={3}
disabled={isSubmitting}
className="h-9 text-sm"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="register-password" className="text-xs">Password</Label>
<Input
id="register-password"
name="password"
type="password"
required
minLength={6}
disabled={isSubmitting}
className="h-9 text-sm"
/>
</div>
{actionData?.error && (
<p className="text-xs text-red-500 dark:text-red-400">{actionData.error}</p>
)}
<Button
type="submit"
className="w-full h-9 text-sm shadow-glow-hover"
disabled={isSubmitting}
>
{isSubmitting ? "Creating account..." : "Create account"}
</Button>
</Form>
);
}