import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Layers, ArrowLeft, Eye, EyeOff, Loader2, Mail, Lock, User } from "lucide-react";
import { SiGithub, SiGoogle } from "react-icons/si";
import { Link, useLocation } from "wouter";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useToast } from "@/hooks/use-toast";
import { usePageTitle } from "@/hooks/use-page-title";
type AuthMode = "login" | "register";
export default function Login() {
usePageTitle("Sign In");
const [mode, setMode] = useState<AuthMode>("login");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [, setLocation] = useLocation();
const { toast } = useToast();
const queryClient = useQueryClient();
const loginMutation = useMutation({
mutationFn: async (data: { email: string; password: string }) => {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || "Login failed");
}
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/auth/user"] });
setLocation("/");
},
onError: (error: Error) => {
toast({
variant: "destructive",
title: "Login failed",
description: error.message,
});
},
});
const registerMutation = useMutation({
mutationFn: async (data: { email: string; password: string; firstName: string; lastName: string }) => {
const res = await fetch("/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || "Registration failed");
}
return res.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/auth/user"] });
setLocation("/");
},
onError: (error: Error) => {
toast({
variant: "destructive",
title: "Registration failed",
description: error.message,
});
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (mode === "login") {
loginMutation.mutate({ email, password });
} else {
registerMutation.mutate({ email, password, firstName, lastName });
}
};
const handleGoogleLogin = () => {
window.location.href = "/api/login";
};
const handleGithubLogin = () => {
window.location.href = "/api/login";
};
const isSubmitting = loginMutation.isPending || registerMutation.isPending;
return (
<div className="min-h-screen bg-gradient-to-br from-background via-background to-primary/5">
<nav className="fixed top-0 left-0 right-0 z-50 border-b bg-background/80 backdrop-blur-sm">
<div className="container mx-auto px-4 py-3 flex items-center justify-between gap-2">
<Link href="/" data-testid="link-back-home">
<div className="flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity">
<ArrowLeft className="h-4 w-4" />
<Layers className="h-6 w-6 text-primary" />
<span className="text-lg font-semibold">API Weaver</span>
</div>
</Link>
</div>
</nav>
<main className="pt-20 min-h-screen flex items-center justify-center px-4">
<div className="w-full max-w-md">
<Card>
<CardHeader className="text-center space-y-4">
<div className="mx-auto w-16 h-16 bg-gradient-to-br from-primary/20 to-primary/10 rounded-2xl flex items-center justify-center">
<Layers className="h-8 w-8 text-primary" />
</div>
<div>
<CardTitle className="text-2xl font-bold">
{mode === "login" ? "Welcome Back" : "Create Account"}
</CardTitle>
<CardDescription className="mt-2">
{mode === "login"
? "Sign in to access your API dashboard"
: "Register to start managing your APIs"}
</CardDescription>
</div>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-2 gap-3">
<Button
variant="outline"
className="w-full gap-2"
onClick={handleGoogleLogin}
data-testid="button-google-login"
>
<SiGoogle className="h-4 w-4" />
Google
</Button>
<Button
variant="outline"
className="w-full gap-2"
onClick={handleGithubLogin}
data-testid="button-github-login"
>
<SiGithub className="h-4 w-4" />
GitHub
</Button>
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-card px-2 text-muted-foreground">or continue with email</span>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{mode === "register" && (
<div className="grid grid-cols-2 gap-3">
<div className="space-y-2">
<Label htmlFor="firstName">First Name</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="firstName"
placeholder="John"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
className="pl-9"
required
data-testid="input-first-name"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">Last Name</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="lastName"
placeholder="Doe"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
className="pl-9"
required
data-testid="input-last-name"
/>
</div>
</div>
</div>
)}
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="email"
type="email"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="pl-9"
required
data-testid="input-email"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder={mode === "register" ? "Min 8 characters" : "Your password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="pl-9 pr-10"
required
minLength={mode === "register" ? 8 : 1}
data-testid="input-password"
/>
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-0 top-0 h-full px-3"
onClick={() => setShowPassword(!showPassword)}
tabIndex={-1}
data-testid="button-toggle-password"
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-muted-foreground" />
) : (
<Eye className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
</div>
<Button
type="submit"
className="w-full"
disabled={isSubmitting}
data-testid="button-submit-auth"
>
{isSubmitting ? (
<Loader2 className="h-4 w-4 animate-spin mr-2" />
) : null}
{mode === "login" ? "Sign In" : "Create Account"}
</Button>
</form>
<div className="text-center text-sm">
{mode === "login" ? (
<p className="text-muted-foreground">
Don't have an account?{" "}
<button
type="button"
onClick={() => setMode("register")}
className="text-primary font-medium hover:underline"
data-testid="button-switch-register"
>
Sign Up
</button>
</p>
) : (
<p className="text-muted-foreground">
Already have an account?{" "}
<button
type="button"
onClick={() => setMode("login")}
className="text-primary font-medium hover:underline"
data-testid="button-switch-login"
>
Sign In
</button>
</p>
)}
</div>
</CardContent>
</Card>
<p className="text-center text-sm text-muted-foreground mt-6">
By continuing, you agree to our{" "}
<Link href="/terms" className="text-primary hover:underline" data-testid="link-terms">
Terms of Service
</Link>{" "}
and{" "}
<Link href="/license" className="text-primary hover:underline" data-testid="link-privacy">
Privacy Policy
</Link>
</p>
</div>
</main>
</div>
);
}