feat: Add login/register page with form validation and token persistence

- Add LoginPage component with toggle between login and registration modes
- Add form state management for username, password, email, first_name, last_name fields
- Add handleSubmit with api.auth.login/register integration and token storage (access_token, refresh_token)
- Add error handling with red alert banner display
- Add conditional rendering for registration fields (email, first_name, last_name)
- Add loading state with
This commit is contained in:
nutcas3
2026-04-25 05:27:07 +03:00
parent 5ec4db009f
commit 595a078ec8
+151
View File
@@ -0,0 +1,151 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { api, setAuthToken, type LoginRequest, type RegisterRequest } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export default function LoginPage() {
const router = useRouter();
const [isLogin, setIsLogin] = useState(true);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [formData, setFormData] = useState<LoginRequest | RegisterRequest>({
username: "",
password: "",
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");
try {
if (isLogin) {
const response = await api.auth.login(formData as LoginRequest);
setAuthToken(response.access_token);
localStorage.setItem("refresh_token", response.refresh_token);
router.push("/");
} else {
const response = await api.auth.register(formData as RegisterRequest);
setAuthToken(response.access_token);
localStorage.setItem("refresh_token", response.refresh_token);
router.push("/");
}
} catch (err) {
setError(err instanceof Error ? err.message : "Authentication failed");
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full bg-white rounded-lg shadow-md p-8">
<h1 className="text-2xl font-bold text-center mb-6">
{isLogin ? "Login" : "Register"}
</h1>
{error && (
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
Username
</label>
<Input
id="username"
type="text"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
required
className="w-full"
/>
</div>
{!isLogin && (
<>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
Email
</label>
<Input
id="email"
type="email"
value={(formData as RegisterRequest).email || ""}
onChange={(e) => setFormData({ ...formData as RegisterRequest, email: e.target.value })}
required={!isLogin}
className="w-full"
/>
</div>
<div>
<label htmlFor="firstName" className="block text-sm font-medium text-gray-700 mb-1">
First Name
</label>
<Input
id="firstName"
type="text"
value={(formData as RegisterRequest).first_name || ""}
onChange={(e) => setFormData({ ...formData as RegisterRequest, first_name: e.target.value })}
required={!isLogin}
className="w-full"
/>
</div>
<div>
<label htmlFor="lastName" className="block text-sm font-medium text-gray-700 mb-1">
Last Name
</label>
<Input
id="lastName"
type="text"
value={(formData as RegisterRequest).last_name || ""}
onChange={(e) => setFormData({ ...formData as RegisterRequest, last_name: e.target.value })}
required={!isLogin}
className="w-full"
/>
</div>
</>
)}
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
Password
</label>
<Input
id="password"
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required
className="w-full"
/>
</div>
<Button type="submit" disabled={loading} className="w-full">
{loading ? "Loading..." : isLogin ? "Login" : "Register"}
</Button>
</form>
<div className="mt-4 text-center">
<button
type="button"
onClick={() => {
setIsLogin(!isLogin);
setError("");
setFormData({ username: "", password: "" });
}}
className="text-blue-600 hover:underline"
>
{isLogin ? "Need an account? Register" : "Already have an account? Login"}
</button>
</div>
</div>
</div>
);
}