Files
ProxmoxVE-Local/src/app/_components/SetupModal.tsx
CanbiZ 9e1975dd1d Refactor modal and badge components for consistency
Standardizes import statements, string quoting, and className usage across modal and badge components. Improves code readability and consistency, updates formatting, and enhances maintainability without changing component logic.
2025-10-20 20:03:38 +02:00

228 lines
7.6 KiB
TypeScript

"use client";
import { useState } from "react";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Toggle } from "./ui/toggle";
import { Lock, User, Shield, AlertCircle } from "lucide-react";
import { useRegisterModal } from "./modal/ModalStackProvider";
import { useTranslation } from "@/lib/i18n/useTranslation";
interface SetupModalProps {
isOpen: boolean;
onComplete: () => void;
}
export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
const { t } = useTranslation("setupModal");
useRegisterModal(isOpen, {
id: "setup-modal",
allowEscape: true,
onClose: () => null,
});
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [enableAuth, setEnableAuth] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setError(null);
// Only validate passwords if authentication is enabled
if (enableAuth && password !== confirmPassword) {
setError(t("errors.passwordMismatch"));
setIsLoading(false);
return;
}
try {
const response = await fetch("/api/auth/setup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: enableAuth ? username : undefined,
password: enableAuth ? password : undefined,
enabled: enableAuth,
}),
});
if (response.ok) {
// If authentication is enabled, automatically log in the user
if (enableAuth) {
const loginResponse = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (loginResponse.ok) {
// Login successful, complete setup
onComplete();
} else {
// Setup succeeded but login failed, still complete setup
console.warn("Setup completed but auto-login failed");
onComplete();
}
} else {
// Authentication disabled, just complete setup
onComplete();
}
} else {
const errorData = (await response.json()) as { error: string };
setError(errorData.error ?? t("errors.setupFailed"));
}
} catch (error) {
console.error("Setup error:", error);
setError(t("errors.setupFailed"));
}
setIsLoading(false);
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm">
<div className="bg-card border-border w-full max-w-md rounded-lg border shadow-xl">
{/* Header */}
<div className="border-border flex items-center justify-center border-b p-6">
<div className="flex items-center gap-3">
<Shield className="text-success h-8 w-8" />
<h2 className="text-card-foreground text-2xl font-bold">
{t("title")}
</h2>
</div>
</div>
{/* Content */}
<div className="p-6">
<p className="text-muted-foreground mb-6 text-center">
{t("description")}
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="setup-username"
className="text-foreground mb-2 block text-sm font-medium"
>
{t("username.label")}
</label>
<div className="relative">
<User className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" />
<Input
id="setup-username"
type="text"
placeholder={t("username.placeholder")}
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
className="pl-10"
required={enableAuth}
minLength={3}
/>
</div>
</div>
<div>
<label
htmlFor="setup-password"
className="text-foreground mb-2 block text-sm font-medium"
>
{t("password.label")}
</label>
<div className="relative">
<Lock className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" />
<Input
id="setup-password"
type="password"
placeholder={t("password.placeholder")}
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
className="pl-10"
required={enableAuth}
minLength={6}
/>
</div>
</div>
<div>
<label
htmlFor="confirm-password"
className="text-foreground mb-2 block text-sm font-medium"
>
{t("confirmPassword.label")}
</label>
<div className="relative">
<Lock className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform" />
<Input
id="confirm-password"
type="password"
placeholder={t("confirmPassword.placeholder")}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={isLoading}
className="pl-10"
required={enableAuth}
minLength={6}
/>
</div>
</div>
<div className="border-border bg-muted/30 rounded-lg border p-4">
<div className="flex items-center justify-between">
<div>
<h4 className="text-foreground mb-1 font-medium">
{t("enableAuth.title")}
</h4>
<p className="text-muted-foreground text-sm">
{enableAuth
? t("enableAuth.descriptionEnabled")
: t("enableAuth.descriptionDisabled")}
</p>
</div>
<Toggle
checked={enableAuth}
onCheckedChange={setEnableAuth}
disabled={isLoading}
label={t("enableAuth.label")}
/>
</div>
</div>
{error && (
<div className="bg-error/10 text-error-foreground border-error/20 flex items-center gap-2 rounded-md border p-3">
<AlertCircle className="h-4 w-4" />
<span className="text-sm">{error}</span>
</div>
)}
<Button
type="submit"
disabled={
isLoading ||
(enableAuth &&
(!username.trim() ||
!password.trim() ||
!confirmPassword.trim()))
}
className="w-full"
>
{isLoading ? t("actions.settingUp") : t("actions.completeSetup")}
</Button>
</form>
</div>
</div>
</div>
);
}