Files
caddy-proxy-manager/app/(auth)/login/LoginClient.tsx
akanealw 99819b70ff
Some checks failed
Build and Push Docker Images (Trusted) / build-and-push (., docker/caddy/Dockerfile, caddy) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/l4-port-manager/Dockerfile, l4-port-manager) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/web/Dockerfile, web) (push) Has been cancelled
Tests / test (push) Has been cancelled
added caddy-proxy-manager for testing
2026-04-21 22:49:08 +00:00

162 lines
5.5 KiB
TypeScript
Executable File

"use client";
import { useRouter } from "next/navigation";
import { FormEvent, useState } from "react";
import { authClient } from "@/src/lib/auth-client";
import { LogIn } from "lucide-react";
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 { Alert, AlertDescription } from "@/components/ui/alert";
import { Separator } from "@/components/ui/separator";
interface LoginClientProps {
enabledProviders: Array<{ id: string; name: string }>;
}
export default function LoginClient({ enabledProviders = [] }: LoginClientProps) {
const router = useRouter();
const [loginError, setLoginError] = useState<string | null>(null);
const [loginPending, setLoginPending] = useState(false);
const [oauthPending, setOauthPending] = useState<string | null>(null);
const handleSignIn = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoginError(null);
setLoginPending(true);
const formData = new FormData(event.currentTarget);
const username = String(formData.get("username") ?? "").trim();
const password = String(formData.get("password") ?? "");
if (!username || !password) {
setLoginError("Username and password are required.");
setLoginPending(false);
return;
}
const { error } = await authClient.signIn.username({
username,
password,
});
if (error) {
let message: string | null = null;
if (error.status === 429) {
message = error.message || "Too many login attempts. Try again in a few minutes.";
} else if (error.message) {
message = error.message;
}
setLoginError(message ?? "Invalid username or password.");
setLoginPending(false);
return;
}
router.replace("/");
router.refresh();
};
const handleOAuthSignIn = async (providerId: string) => {
setLoginError(null);
setOauthPending(providerId);
try {
await authClient.signIn.social({ provider: providerId, callbackURL: "/" });
} catch {
setLoginError("Failed to sign in with OAuth");
setOauthPending(null);
}
};
const disabled = loginPending || !!oauthPending;
return (
<div className="min-h-screen flex items-center justify-center bg-background px-4">
<Card className="w-full max-w-sm">
<CardHeader className="text-center space-y-1">
<CardTitle className="text-2xl font-bold">Caddy Proxy Manager</CardTitle>
<CardDescription>
{enabledProviders.length > 0
? "Sign in to your account"
: "Sign in with your credentials"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{loginError && (
<Alert variant="destructive">
<AlertDescription>{loginError}</AlertDescription>
</Alert>
)}
{enabledProviders.length > 0 && (
<>
<div className="space-y-2">
{enabledProviders.map((provider) => {
const isPending = oauthPending === provider.id;
return (
<Button
key={provider.id}
variant="outline"
className="w-full"
onClick={() => handleOAuthSignIn(provider.id)}
disabled={disabled}
>
{isPending ? (
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent mr-2" />
) : (
<LogIn className="h-4 w-4 mr-2" />
)}
{isPending ? `Signing in with ${provider.name}` : `Continue with ${provider.name}`}
</Button>
);
})}
</div>
<div className="relative">
<Separator />
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-card px-2 text-xs text-muted-foreground">
Or sign in with credentials
</span>
</div>
</>
)}
<form onSubmit={handleSignIn} className="space-y-3">
<div className="space-y-1.5">
<Label htmlFor="username">Username</Label>
<Input
id="username"
name="username"
required
autoComplete="username"
autoFocus={enabledProviders.length === 0}
disabled={disabled}
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="password">Password</Label>
<Input
id="password"
name="password"
type="password"
required
autoComplete="current-password"
disabled={disabled}
/>
</div>
<Button type="submit" className="w-full" disabled={disabled}>
{loginPending ? (
<>
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent mr-2" />
Signing in
</>
) : (
"Sign in"
)}
</Button>
</form>
</CardContent>
</Card>
</div>
);
}