kontenhumas-fe/components/auth/login-form.tsx

257 lines
8.2 KiB
TypeScript

"use client";
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import {
Dialog,
DialogContent,
DialogFooter,
DialogTrigger,
} from "@/components/ui/dialog";
import { FormField } from "@/components/auth/form-field";
import { loginSchema, LoginFormData, LoginFormProps } from "@/types/auth";
import { useAuth } from "@/hooks/use-auth";
import { listRole } from "@/service/landing/landing";
import { Role } from "@/types/auth";
import Link from "next/link";
import { useTranslations } from "next-intl";
export const LoginForm: React.FC<LoginFormProps> = ({
onSuccess,
onError,
className,
}) => {
const { login } = useAuth();
const t = useTranslations("MediaUpdate");
const [showPassword, setShowPassword] = useState(false);
const [rememberMe, setRememberMe] = useState(false);
const [roles, setRoles] = useState<Role[]>([]);
const [selectedCategory, setSelectedCategory] = useState("5");
const [isDialogOpen, setIsDialogOpen] = useState(false);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
getValues,
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
mode: "onChange",
});
// Load roles on component mount
React.useEffect(() => {
const loadRoles = async () => {
try {
const response = await listRole();
setRoles(response?.data?.data || []);
} catch (error) {
console.error("Failed to load roles:", error);
}
};
loadRoles();
}, []);
const handlePasswordToggle = () => {
setShowPassword(!showPassword);
};
const handleLogin = async (data: LoginFormData) => {
try {
await login(data, { skipRedirect: true });
// await login(data);
onSuccess?.(data);
} catch (error: any) {
const message = getLoginErrorMessage(error);
onError?.(message);
}
};
// const onSubmit = async (data: LoginFormData) => {
// try {
// // onSuccess?.(data);
// await handleLogin(data);
// } catch (error: any) {
// onError?.(error.message || "Login failed");
// }
// };
const onSubmit = async (data: LoginFormData) => {
try {
onSuccess?.(data); // hanya kirim data ke AuthPage
} catch (error: any) {
onError?.(error.message || "Login failed");
}
};
const getLoginErrorMessage = (error: any): string => {
const data = error?.response?.data;
// Backend Netidhub
if (Array.isArray(data?.messages) && data.messages.length > 0) {
return data.messages[0];
}
// Fallback lain
if (typeof data?.message === "string") return data.message;
if (typeof error?.message === "string") return error.message;
return "Username atau kata sandi salah";
};
return (
<div className={className}>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
{/* Header */}
<div className="text-left space-y-2">
<div className="text-center mb-8">
<div className=" w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-8">
<img
src="/assets/logo1.png"
alt="netidhub Logo"
className="max-w-[150px] h-auto drop-shadow-lg"
/>
</div>
<h2 className="text-lg font-bold text-gray-900 mb-2 mt-5">
{t("unite")}
</h2>
</div>
{/* <div className="text-default-500 text-base">
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<span className="w-full lg:w-fit px-2 h-8 text-red-500 hover:cursor-pointer hover:underline">
Register
</span>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<div className="flex flex-col w-full gap-1">
<p className="text-lg font-semibold text-center">
Category Reg
</p>
<p className="text-base text-center">Select One</p>
</div>
<div className="space-y-2">
{roles.map((role) => (
<div key={role.id} className="flex items-center space-x-2">
<input
type="radio"
id={`category${role.id}`}
name="category"
value={role.id.toString()}
checked={selectedCategory === role.id.toString()}
onChange={(e) => setSelectedCategory(e.target.value)}
className="text-red-500 focus:ring-red-500"
/>
<Label htmlFor={`category${role.id}`} className="text-sm">
{role.name}
</Label>
</div>
))}
</div>
<div className="border-b-2 border-black"></div>
<DialogFooter>
<Link
href={`/auth/registration?category=${selectedCategory}`}
className="flex justify-center bg-red-500 px-4 py-2 rounded-md border border-black text-white hover:bg-red-600 transition-colors"
>
Next
</Link>
</DialogFooter>
</DialogContent>
</Dialog>
</div> */}
</div>
{/* Username Field */}
<FormField
label="Username"
name="username"
type="text"
placeholder={t("username")}
error={errors.username?.message}
disabled={isSubmitting}
required
inputProps={{
...register("username"),
}}
/>
{/* Password Field */}
<FormField
label={t("password")}
name="password"
type="password"
placeholder={t("password2")}
error={errors.password?.message}
disabled={isSubmitting}
required
showPasswordToggle
showPassword={showPassword}
onPasswordToggle={handlePasswordToggle}
inputProps={{
...register("password"),
}}
/>
{/* Remember Me and Forgot Password */}
<div className="flex justify-between items-center">
<div className="flex gap-2 items-center">
<input
id="rememberMe"
type="checkbox"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
disabled={isSubmitting}
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<label htmlFor="rememberMe" className="text-sm cursor-pointer">
{t("rememberMe")}
</label>
</div>
{/* <div className="flex gap-2 items-center">
<Checkbox
id="rememberMe"
checked={rememberMe}
onCheckedChange={(checked) => setRememberMe(checked as boolean)}
disabled={isSubmitting}
/>
<Label htmlFor="rememberMe" className="text-sm">
{t("rememberMe")}
</Label>
</div> */}
<Link
href="/auth/forgot-password"
className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium hover:underline"
>
{t("forgotPass")}
</Link>
</div>
{/* Submit Button */}
<Button
type="submit"
fullWidth
disabled={isSubmitting}
className="mt-6 bg-red-700 cursor-pointer"
color="primary"
>
{isSubmitting ? (
<div className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin " />
Processing...
</div>
) : (
t("enterOTP5")
)}
</Button>
</form>
</div>
);
};