136 lines
4.4 KiB
TypeScript
136 lines
4.4 KiB
TypeScript
import { useState } from "react";
|
|
import { useLogin } from "../../features/auth/api/auth";
|
|
import axios from "axios";
|
|
import Input from "@/components/forms/Input";
|
|
import Alert from "@/components/feedback/Alert";
|
|
import Card from "@/components/data_display/Card/Card";
|
|
import Button from "@/components/actions/Button";
|
|
|
|
const LoginPage = () => {
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
|
|
const { ...login } = useLogin({
|
|
onError: (error) => {
|
|
setErrors(error)
|
|
}
|
|
});
|
|
|
|
const [formData, setFormData] = useState({
|
|
username: "",
|
|
password: "",
|
|
});
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[name]: value,
|
|
}));
|
|
// Hapus error untuk field yang sedang diubah
|
|
if (errors[name]) {
|
|
setErrors((prev) => {
|
|
const newErrors = { ...prev };
|
|
delete newErrors[name];
|
|
return newErrors;
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
try {
|
|
await login.mutateAsync(formData);
|
|
|
|
} catch (error: unknown) {
|
|
if (axios.isAxiosError(error) && error.response?.status === 400) {
|
|
const apiErrors = error.response.data.status.errors;
|
|
setErrors(apiErrors || {});
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-w-screen flex items-center justify-center bg-base-200 mx-0">
|
|
<div className="flex min-h-screen items-center justify-center bg-base-200 mx-0">
|
|
<Card className="w-sm bg-base-100 shadow-xl">
|
|
<Card.Body>
|
|
<h2 className="card-title text-2xl mx-auto mb-2">Login</h2>
|
|
{errors.general && (
|
|
<Alert
|
|
status="error"
|
|
soft
|
|
icon={
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24" >
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
}
|
|
>
|
|
|
|
<span>{errors.general}</span>
|
|
</Alert>
|
|
)}
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="flex flex-col w-full component-preview gap-4 items-center justify-center font-sans">
|
|
<div className="form-control w-full">
|
|
<label className="label mb-1">
|
|
<span className="label-text font-semibold">Username</span>
|
|
</label>
|
|
<Input
|
|
placeholder="Username"
|
|
name="username"
|
|
onChange={handleChange}
|
|
className={`w-full ${errors.username ? "input-error" : ""}`}
|
|
/>
|
|
{errors.username && (
|
|
<label className="label">
|
|
<span className="label-text-alt">{errors.username}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="form-control w-full">
|
|
<label className="label mb-1">
|
|
<span className="label-text font-semibold">Password</span>
|
|
</label>
|
|
<Input
|
|
placeholder="Password"
|
|
name="password"
|
|
type="password"
|
|
onChange={handleChange}
|
|
|
|
className={`w-full ${errors.password ? "input-error" : ""}`}
|
|
/>
|
|
|
|
{errors.password && (
|
|
<label className="label">
|
|
|
|
<span className="label-text-alt">{errors.password}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="form-control mt-6">
|
|
<Button
|
|
type="submit"
|
|
fullWidth
|
|
color="primary"
|
|
disabled={login.isPending}
|
|
>
|
|
{login.isPending ? (
|
|
<span className="loading loading-spinner loading-sm"></span>
|
|
) : (
|
|
"Login"
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Card.Body>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
};
|
|
export default LoginPage;
|