670 lines
21 KiB
TypeScript
670 lines
21 KiB
TypeScript
"use client";
|
|
import React, { useEffect, useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import Cookies from "js-cookie";
|
|
import { Icon } from "@/components/ui/icon";
|
|
import { useForm, SubmitHandler } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { z } from "zod";
|
|
import { cn, getCookiesDecrypt, setCookiesEncrypt } from "@/lib/utils";
|
|
import { Eye, EyeOff, Loader2 } from "lucide-react";
|
|
import {
|
|
getProfile,
|
|
login,
|
|
postEmailValidation,
|
|
postSetupEmail,
|
|
requestOTP,
|
|
verifyOTPByUsername,
|
|
} from "@/service/auth";
|
|
import { toast } from "sonner";
|
|
import { useRouter } from "@/components/navigation";
|
|
import { warning } from "@/lib/swal";
|
|
import { Link } from "@/i18n/routing";
|
|
import { useTranslations } from "next-intl";
|
|
import {
|
|
InputOTP,
|
|
InputOTPGroup,
|
|
InputOTPSeparator,
|
|
InputOTPSlot,
|
|
} from "@/components/ui/input-otp";
|
|
import { error, loading } from "@/config/swal";
|
|
import { data } from "jquery";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { getUserNotifications, listRole } from "@/service/landing/landing";
|
|
|
|
// Schema validasi menggunakan zod
|
|
const schema = z.object({
|
|
username: z.string().min(1, { message: "Judul diperlukan" }),
|
|
password: z
|
|
.string()
|
|
.min(4, { message: "Password must be at least 4 characters." }),
|
|
});
|
|
|
|
// Tipe untuk form values
|
|
type LoginFormValues = {
|
|
username: string;
|
|
password: string;
|
|
};
|
|
|
|
const LoginForm = () => {
|
|
const [isPending, startTransition] = React.useTransition();
|
|
const router = useRouter();
|
|
const [passwordType, setPasswordType] = React.useState("password");
|
|
const t = useTranslations("LandingPage");
|
|
|
|
const [step, setStep] = useState<number>(1);
|
|
const [otpValue, setOtpValue] = useState("");
|
|
const [userIdentity] = useState();
|
|
const [email, setEmail] = useState();
|
|
const [category, setCategory] = useState("5");
|
|
const roleId = getCookiesDecrypt("urie");
|
|
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [oldEmail, setOldEmail] = useState("");
|
|
const [oldEmailValidate, setOldEmailValidate] = useState("");
|
|
const [role, setRole] = useState<any>();
|
|
const [menuActive, setMenuActive] = useState<string>();
|
|
const [notifications, setNotifications] = useState([]);
|
|
const [notificationsUpdate, setNotificationsUpdate] = useState([]);
|
|
const [newEmail, setNewEmail] = useState("");
|
|
const [newEmailValidate, setNewEmailValidate] = useState("");
|
|
const [otpValidate, setOtpValidate] = useState("");
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
const togglePasswordType = () => {
|
|
setPasswordType((prevType) =>
|
|
prevType === "password" ? "text" : "password"
|
|
);
|
|
};
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
getValues,
|
|
formState: { errors },
|
|
} = useForm<LoginFormValues>({
|
|
resolver: zodResolver(schema),
|
|
mode: "all",
|
|
});
|
|
|
|
const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
const { key } = event;
|
|
const target = event.currentTarget;
|
|
|
|
if (key === "Enter") {
|
|
event.preventDefault();
|
|
|
|
const inputs = Array.from(target.form?.querySelectorAll("input") || []);
|
|
const currentIndex = inputs.indexOf(target);
|
|
const nextInput = inputs[currentIndex + 1] as HTMLElement | undefined;
|
|
|
|
if (nextInput) {
|
|
nextInput.focus();
|
|
}
|
|
}
|
|
};
|
|
|
|
const onSubmit: SubmitHandler<LoginFormValues> = async (data) => {
|
|
try {
|
|
const response = await login({
|
|
...data,
|
|
grantType: "password",
|
|
clientId: "mediahub-app",
|
|
});
|
|
|
|
console.log("LOGIN: ", response);
|
|
|
|
if (response?.error) {
|
|
toast.error("Username / Password Tidak Sesuai");
|
|
} else {
|
|
const { access_token } = response?.data;
|
|
const { refresh_token } = response?.data;
|
|
const dateTime = new Date();
|
|
const newTime = dateTime.getTime() + 10 * 60 * 1000;
|
|
|
|
Cookies.set("access_token", access_token, {
|
|
expires: 1,
|
|
});
|
|
Cookies.set("refresh_token", refresh_token, {
|
|
expires: 1,
|
|
});
|
|
Cookies.set("time_refresh", new Date(newTime).toISOString(), {
|
|
expires: 1,
|
|
});
|
|
|
|
Cookies.set("is_first_login", String(true), {
|
|
secure: true,
|
|
sameSite: "strict",
|
|
});
|
|
const profile = await getProfile(access_token);
|
|
console.log("PROFILE : ", profile?.data?.data);
|
|
|
|
if (
|
|
profile?.data?.data?.isInternational == true ||
|
|
profile?.data?.data?.isActive == false ||
|
|
profile?.data?.data?.isDelete == true
|
|
) {
|
|
Object.keys(Cookies.get()).forEach((cookieName) => {
|
|
Cookies.remove(cookieName);
|
|
});
|
|
warning(
|
|
"Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri",
|
|
"/auth/login"
|
|
);
|
|
} else {
|
|
Cookies.set("home_path", profile?.data?.data?.homePath, {
|
|
expires: 1,
|
|
});
|
|
Cookies.set(
|
|
"profile_picture",
|
|
profile?.data?.data?.profilePictureUrl,
|
|
{
|
|
expires: 1,
|
|
}
|
|
);
|
|
Cookies.set("state", profile?.data?.data?.userLevel?.name, {
|
|
expires: 1,
|
|
});
|
|
Cookies.set(
|
|
"state-prov",
|
|
profile.data?.data?.userLevel?.province?.provName,
|
|
{
|
|
expires: 1,
|
|
}
|
|
);
|
|
setCookiesEncrypt("uie", profile?.data?.data?.id, {
|
|
expires: 1,
|
|
});
|
|
setCookiesEncrypt("urie", profile?.data?.data?.roleId, {
|
|
expires: 1,
|
|
});
|
|
setCookiesEncrypt("urne", profile?.data?.data?.role?.name, {
|
|
expires: 1,
|
|
});
|
|
setCookiesEncrypt("ulie", profile?.data?.data?.userLevel?.id, {
|
|
expires: 1,
|
|
});
|
|
setCookiesEncrypt(
|
|
"uplie",
|
|
profile?.data?.data?.userLevel?.parentLevelId,
|
|
{
|
|
expires: 1,
|
|
}
|
|
);
|
|
setCookiesEncrypt(
|
|
"ulne",
|
|
profile?.data?.data?.userLevel?.levelNumber,
|
|
{
|
|
expires: 1,
|
|
}
|
|
);
|
|
setCookiesEncrypt("ufne", profile?.data?.data?.fullname, {
|
|
expires: 1,
|
|
});
|
|
setCookiesEncrypt("ulnae", profile?.data?.data?.userLevel?.name, {
|
|
expires: 1,
|
|
});
|
|
setCookiesEncrypt("uinse", profile?.data?.data?.instituteId, {
|
|
expires: 1,
|
|
});
|
|
console.log("ssaddd", profile?.data?.data?.roleId);
|
|
if (
|
|
Number(profile?.data?.data?.roleId) == 2 ||
|
|
Number(profile?.data?.data?.roleId) == 3 ||
|
|
Number(profile?.data?.data?.roleId) == 4 ||
|
|
Number(profile?.data?.data?.roleId) == 9 ||
|
|
Number(profile?.data?.data?.roleId) == 10 ||
|
|
Number(profile?.data?.data?.roleId) == 11 ||
|
|
Number(profile?.data?.data?.roleId) == 12 ||
|
|
Number(profile?.data?.data?.roleId) == 18 ||
|
|
Number(profile?.data?.data?.roleId) == 19
|
|
) {
|
|
if (profile?.data?.data?.roleId === 18) {
|
|
window.location.href = "/in/dashboard/executive-data";
|
|
// router.push('/admin/dashboard');
|
|
Cookies.set("status", "login", {
|
|
expires: 1,
|
|
});
|
|
} else if (profile?.data?.data?.roleId === 2) {
|
|
window.location.href = "/in/dashboard/executive";
|
|
Cookies.set("status", "login", {
|
|
expires: 1,
|
|
});
|
|
} else if (
|
|
profile?.data?.data?.userLevel?.id == 794 ||
|
|
profile?.data?.data?.userLevel?.parentLevelId == 761
|
|
) {
|
|
window.location.href = "/in/dashboard";
|
|
Cookies.set("status", "login", {
|
|
expires: 1,
|
|
});
|
|
} else {
|
|
window.location.href = "/in/dashboard";
|
|
// router.push('/admin/dashboard');
|
|
Cookies.set("status", "login", {
|
|
expires: 1,
|
|
});
|
|
}
|
|
} else {
|
|
window.location.href = "/";
|
|
Cookies.set("status", "login", {
|
|
expires: 1,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
toast.error(err.message || "An unexpected error occurred.");
|
|
}
|
|
// startTransition( () => {
|
|
|
|
// });
|
|
};
|
|
|
|
const handleEmailValidation = async () => {
|
|
const data = getValues();
|
|
|
|
// loading();
|
|
const response = await postEmailValidation(data);
|
|
|
|
if (response?.error) {
|
|
error(response?.message);
|
|
return false;
|
|
}
|
|
const msg = response?.data?.message;
|
|
|
|
if (msg == "Continue to setup email") {
|
|
setStep(2);
|
|
} else if (msg == "Email is valid and OTP has been sent") {
|
|
setStep(3);
|
|
} else if (msg == "Username & password valid") {
|
|
onSubmit(data);
|
|
} else {
|
|
error("Username / password incorrect");
|
|
}
|
|
// else {
|
|
// setStep(1);
|
|
// }
|
|
};
|
|
|
|
const handleSetupEmail = async () => {
|
|
const values = getValues();
|
|
const data = {
|
|
username: values.username,
|
|
password: values.password,
|
|
oldEmail: oldEmail,
|
|
newEmail: newEmail,
|
|
};
|
|
|
|
// loading();
|
|
const response = await postSetupEmail(data);
|
|
// close();
|
|
if (response?.error) {
|
|
error(response.message);
|
|
return false;
|
|
}
|
|
const msg = response?.data?.message;
|
|
if (msg == "Email is valid and OTP has been sent") {
|
|
setStep(3);
|
|
} else if (msg == "The old email is not same") {
|
|
error("Email is invalid");
|
|
}
|
|
};
|
|
|
|
const checkEmail = (state: any, e: any) => {
|
|
const regEmail =
|
|
/^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\dA-Za-z\-]+\.)+[A-Za-z]{2,}))$/;
|
|
|
|
if (regEmail.test(e)) {
|
|
if (state == "old") {
|
|
setOldEmailValidate("");
|
|
setOldEmail(e);
|
|
} else {
|
|
setNewEmailValidate("");
|
|
setNewEmail(e);
|
|
}
|
|
} else {
|
|
if (state == "old") {
|
|
setOldEmailValidate("Email tidak valid");
|
|
setOldEmail("");
|
|
} else {
|
|
setNewEmailValidate("Email tidak valid");
|
|
setNewEmail("");
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleLoginOTP = async () => {
|
|
// const otp = `${otp1}${otp2}${otp3}${otp4}${otp5}${otp6}`;
|
|
const values = getValues();
|
|
|
|
if (otpValue.length === 6) {
|
|
loading();
|
|
const response = await verifyOTPByUsername(values.username, otpValue);
|
|
|
|
if (response?.error) {
|
|
error(response.message);
|
|
return false;
|
|
}
|
|
|
|
close();
|
|
|
|
if (response?.message === "success") {
|
|
onSubmit(values);
|
|
} else {
|
|
setOtpValidate("Kode OTP Tidak Valid");
|
|
}
|
|
}
|
|
};
|
|
|
|
let menu = "";
|
|
|
|
useEffect(() => {
|
|
async function initState() {
|
|
setMenuActive(menu);
|
|
const res = await listRole();
|
|
setRole(res?.data?.data);
|
|
}
|
|
|
|
async function getNotif() {
|
|
if (roleId != undefined) {
|
|
const response = await getUserNotifications(0, 2);
|
|
setNotifications(response?.data?.data?.content);
|
|
console.log("respon:", response);
|
|
}
|
|
}
|
|
|
|
async function getNotifUpdate() {
|
|
if (roleId != undefined) {
|
|
const response = await getUserNotifications(0, 3);
|
|
setNotificationsUpdate(response?.data?.data?.content);
|
|
console.log("Notiffff:", response);
|
|
}
|
|
}
|
|
|
|
initState();
|
|
getNotif();
|
|
getNotifUpdate();
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
{step !== 3 && (
|
|
<form
|
|
onSubmit={handleSubmit(onSubmit)}
|
|
className="mt-5 2xl:mt-7 space-y-4"
|
|
>
|
|
{step === 1 && (
|
|
<>
|
|
<div className="text-left 2xl:mb-10 mb-4 mt-10">
|
|
<h4 className="font-semibold text-3xl text-left">
|
|
{t("logInPlease")}
|
|
</h4>
|
|
<div className="text-default-500 text-base">
|
|
{t("acc")}
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<span className="w-full lg:w-fit px-2 h-8 text-red-500 hover:cursor-pointer">
|
|
{t("register")}
|
|
</span>
|
|
</DialogTrigger>
|
|
<DialogContent size="sm" className="sm:max-w-[425px]">
|
|
<div className="flex flex-col w-full gap-1">
|
|
<p className="text-lg font-semibold text-center">
|
|
{t("categoryReg")}
|
|
</p>
|
|
<p className="text-base text-center">
|
|
{t("selectOne")}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
{role?.map((row: any) => (
|
|
<div key={row.id}>
|
|
<input
|
|
type="radio"
|
|
id={`category${row.id}`}
|
|
name="category"
|
|
className=""
|
|
value={row.id}
|
|
checked={category == `${row.id}`}
|
|
onChange={(event) =>
|
|
setCategory(event.target.value)
|
|
}
|
|
/>
|
|
<label
|
|
className="ml-2"
|
|
htmlFor={`category${row.id}`}
|
|
>
|
|
{row.name}
|
|
</label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="border-b-2 border-black"></div>
|
|
<DialogFooter>
|
|
<Link
|
|
href={`/auth/registration?category=${category}`}
|
|
className="flex justify-center bg-red-500 px-4 py-1 rounded-md border border-black text-white"
|
|
type="submit"
|
|
>
|
|
{t("next")}{" "}
|
|
</Link>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label
|
|
htmlFor="username"
|
|
className="font-medium text-default-600"
|
|
>
|
|
Username
|
|
</Label>
|
|
<Input
|
|
size="lg"
|
|
disabled={isPending}
|
|
{...register("username")}
|
|
id="username"
|
|
type="text"
|
|
className={cn("", {
|
|
"border-destructive": errors.username,
|
|
})}
|
|
/>
|
|
{errors.username?.message && (
|
|
<div className="text-destructive mt-2 text-sm">
|
|
{errors.username.message}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mt-3.5 space-y-2">
|
|
<Label
|
|
htmlFor="password"
|
|
className="font-medium text-default-600"
|
|
>
|
|
{t("password")}
|
|
</Label>
|
|
<div className="relative">
|
|
<Input
|
|
size="lg"
|
|
disabled={isPending}
|
|
{...register("password")}
|
|
id="password"
|
|
type={showPassword ? "text" : "password"}
|
|
className="peer pr-10"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-default-500 hover:text-default-700"
|
|
tabIndex={-1}
|
|
>
|
|
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
</button>
|
|
</div>
|
|
{errors.password?.message && (
|
|
<div className="text-destructive mt-2 text-sm">
|
|
{errors.password.message}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-between">
|
|
<div className="flex gap-2 items-center">
|
|
<Checkbox id="checkbox" defaultChecked />
|
|
<Label htmlFor="checkbox">{t("rememberMe")}</Label>
|
|
</div>
|
|
<Link
|
|
href="/auth/forgot-password"
|
|
className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium"
|
|
>
|
|
{t("forgotPass")}
|
|
</Link>
|
|
</div>
|
|
|
|
<Button
|
|
type="button"
|
|
fullWidth
|
|
onClick={handleEmailValidation}
|
|
disabled={isPending}
|
|
>
|
|
Selanjutnya
|
|
</Button>
|
|
</>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<>
|
|
<div className="text-left 2xl:mb-10 mb-4">
|
|
<h4 className="font-semibold text-3xl text-left">
|
|
Anda perlu memasukkan email baru untuk bisa Login.
|
|
</h4>
|
|
</div>
|
|
<div className="flex flex-col justify-center mb-6">
|
|
<div className="space-y-2">
|
|
<Label
|
|
htmlFor="username"
|
|
className="font-medium text-default-600"
|
|
>
|
|
Email Lama <span className="text-red-500">*</span>
|
|
</Label>
|
|
<Input
|
|
size="lg"
|
|
disabled={isPending}
|
|
onChange={(e) => checkEmail("old", e.target.value)}
|
|
id="oldEmail"
|
|
type="email"
|
|
className={cn("", {
|
|
"border-destructive": errors.username,
|
|
})}
|
|
/>
|
|
<p className="invalid-feedback-custom">{oldEmailValidate}</p>
|
|
{errors.username?.message && (
|
|
<div className="text-destructive mt-2 text-sm">
|
|
{errors.username.message}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label
|
|
htmlFor="username"
|
|
className="font-medium text-default-600"
|
|
>
|
|
Email Baru <span className="text-red-500">*</span>
|
|
</Label>
|
|
<Input
|
|
size="lg"
|
|
disabled={isPending}
|
|
onChange={(e) => checkEmail("new", e.target.value)}
|
|
id="newEmail"
|
|
type="email"
|
|
className={cn("", {
|
|
"border-destructive": errors.username,
|
|
})}
|
|
/>
|
|
<p className="invalid-feedback-custom">{newEmailValidate}</p>
|
|
{errors.username?.message && (
|
|
<div className="text-destructive mt-2 text-sm">
|
|
{errors.username.message}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<Button
|
|
fullWidth
|
|
className="bg-red-500"
|
|
onClick={handleSetupEmail}
|
|
type="button"
|
|
>
|
|
Simpan
|
|
</Button>
|
|
</>
|
|
)}
|
|
</form>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<div className="mt-10 space-y-6">
|
|
<div className="text-left 2xl:mb-10 mb-4 mt-10">
|
|
<h4 className="font-semibold text-3xl text-left">
|
|
{t("pleaseEnterOtp")}
|
|
</h4>
|
|
</div>
|
|
|
|
<div className="flex justify-center mb-6">
|
|
<InputOTP
|
|
maxLength={6}
|
|
onChange={(val: string) => setOtpValue(val)}
|
|
>
|
|
<InputOTPGroup>
|
|
<InputOTPSlot index={0} onKeyDown={handleTypeOTP} />
|
|
<InputOTPSlot index={1} onKeyDown={handleTypeOTP} />
|
|
</InputOTPGroup>
|
|
<InputOTPSeparator />
|
|
<InputOTPGroup>
|
|
<InputOTPSlot index={2} onKeyDown={handleTypeOTP} />
|
|
<InputOTPSlot index={3} onKeyDown={handleTypeOTP} />
|
|
</InputOTPGroup>
|
|
<InputOTPSeparator />
|
|
<InputOTPGroup>
|
|
<InputOTPSlot index={4} onKeyDown={handleTypeOTP} />
|
|
<InputOTPSlot index={5} onKeyDown={handleTypeOTP} />
|
|
</InputOTPGroup>
|
|
</InputOTP>
|
|
</div>
|
|
|
|
{otpValidate && (
|
|
<p className="invalid-feedback-custom text-center">
|
|
<b>{otpValidate}</b>
|
|
</p>
|
|
)}
|
|
|
|
<Button
|
|
fullWidth
|
|
className="bg-red-500"
|
|
type="button"
|
|
onClick={handleLoginOTP}
|
|
disabled={otpValue.length !== 6}
|
|
>
|
|
{t("signIn")}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default LoginForm;
|