feat:login OTP, fix performance polda,polres,fix sidebar polda, satker

This commit is contained in:
Anang Yusman 2025-04-09 14:55:30 +08:00
parent 142a420366
commit aff407f3ce
9 changed files with 231 additions and 202 deletions

View File

@ -194,10 +194,7 @@ const options = {
const SurveyFormModal = ({ onClose }: { onClose: () => void }) => { const SurveyFormModal = ({ onClose }: { onClose: () => void }) => {
useEffect(() => { useEffect(() => {
// Lock body scroll when modal is open
document.body.style.overflow = "hidden"; document.body.style.overflow = "hidden";
// Clean up when modal closes
return () => { return () => {
document.body.style.overflow = ""; document.body.style.overflow = "";
}; };
@ -359,7 +356,7 @@ const SurveyFormModal = ({ onClose }: { onClose: () => void }) => {
); );
}; };
const ONE_HOUR = 60 * 60 * 1000; const FIVE_MINUTES = 5 * 60 * 1000;
const Hero: React.FC = () => { const Hero: React.FC = () => {
const router = useRouter(); const router = useRouter();
@ -394,7 +391,7 @@ const Hero: React.FC = () => {
const now = new Date().getTime(); const now = new Date().getTime();
if (roleId && (!lastShown || now - parseInt(lastShown) > ONE_HOUR)) { if (roleId && (!lastShown || now - parseInt(lastShown) > FIVE_MINUTES)) {
setShowSurveyModal(true); setShowSurveyModal(true);
Cookies.set("surveyLastShown", now.toString(), { expires: 1 }); Cookies.set("surveyLastShown", now.toString(), { expires: 1 });
} }

View File

@ -17,6 +17,7 @@ import {
postEmailValidation, postEmailValidation,
postSetupEmail, postSetupEmail,
requestOTP, requestOTP,
verifyOTPByUsername,
} from "@/service/auth"; } from "@/service/auth";
import { toast } from "sonner"; import { toast } from "sonner";
import { useRouter } from "@/components/navigation"; import { useRouter } from "@/components/navigation";
@ -30,6 +31,7 @@ import {
InputOTPSlot, InputOTPSlot,
} from "@/components/ui/input-otp"; } from "@/components/ui/input-otp";
import { error, loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { data } from "jquery";
// Schema validasi menggunakan zod // Schema validasi menggunakan zod
const schema = z.object({ const schema = z.object({
@ -57,22 +59,13 @@ const LoginForm = () => {
const [email, setEmail] = useState(); const [email, setEmail] = useState();
const [category, setCategory] = useState("5"); const [category, setCategory] = useState("5");
// const handleSignInClick = () => {
// handleSendOTP();
// setIsOtpStep(true);
// };
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [oldEmail, setOldEmail] = useState(""); const [oldEmail, setOldEmail] = useState("");
const [oldEmailValidate, setOldEmailValidate] = useState(""); const [oldEmailValidate, setOldEmailValidate] = useState("");
const [newEmail, setNewEmail] = useState(""); const [newEmail, setNewEmail] = useState("");
const [newEmailValidate, setNewEmailValidate] = useState(""); const [newEmailValidate, setNewEmailValidate] = useState("");
const [otpValidate, setOtpValidate] = useState("");
// const handleSignInClick = () => {
// handleSendOTP();
// setIsOtpStep(true);
// };
const [otp1, setOtp1] = useState(); const [otp1, setOtp1] = useState();
const [otp2, setOtp2] = useState(); const [otp2, setOtp2] = useState();
@ -90,6 +83,7 @@ const LoginForm = () => {
const { const {
register, register,
handleSubmit, handleSubmit,
getValues,
formState: { errors }, formState: { errors },
} = useForm<LoginFormValues>({ } = useForm<LoginFormValues>({
resolver: zodResolver(schema), resolver: zodResolver(schema),
@ -103,37 +97,8 @@ const LoginForm = () => {
event.preventDefault(); event.preventDefault();
}; };
// const handleNextStep = () => {
// setIsOtpStep(true);
// };
const handleSendOTP = async () => {
console.log(userIdentity, email);
console.log("UMUM");
if (email != "") {
const data = {
memberIdentity: null,
email: "",
category,
};
// loading();
const response = await requestOTP(data);
if (response.error) {
error(response.message);
return false;
}
close();
}
};
// Fungsi submit form
const onSubmit: SubmitHandler<LoginFormValues> = async (data) => { const onSubmit: SubmitHandler<LoginFormValues> = async (data) => {
try { try {
// const response = null;
const response = await login({ const response = await login({
...data, ...data,
grantType: "password", grantType: "password",
@ -193,6 +158,13 @@ const LoginForm = () => {
Cookies.set("state", profile?.data?.data?.userLevel?.name, { Cookies.set("state", profile?.data?.data?.userLevel?.name, {
expires: 1, expires: 1,
}); });
Cookies.set(
"state-prov",
profile.data?.data?.userLevel?.province?.provName,
{
expires: 1,
}
);
setCookiesEncrypt("uie", profile?.data?.data?.id, { setCookiesEncrypt("uie", profile?.data?.data?.id, {
expires: 1, expires: 1,
}); });
@ -278,19 +250,16 @@ const LoginForm = () => {
}; };
const handleEmailValidation = async () => { const handleEmailValidation = async () => {
const data = { const data = getValues();
username: username,
password: password,
};
loading(); // loading();
const response = await postEmailValidation(data); const response = await postEmailValidation(data);
close();
if (response.error) { if (response?.error) {
error(response.message); error(response?.message);
return false; return false;
} }
const msg = response.data?.message; const msg = response?.data?.message;
if (msg == "Continue to setup email") { if (msg == "Continue to setup email") {
setStep(2); setStep(2);
} else if (msg == "Email is valid and OTP has been sent") { } else if (msg == "Email is valid and OTP has been sent") {
@ -298,26 +267,27 @@ const LoginForm = () => {
} else if (msg == "Username & password valid") { } else if (msg == "Username & password valid") {
onSubmit(data); onSubmit(data);
} else { } else {
error("Username / password incorrect"); setStep(2);
} }
}; };
const handleSetupEmail = async () => { const handleSetupEmail = async () => {
const values = getValues();
const data = { const data = {
username: username, username: values.username,
password: password, password: values.password,
oldEmail: oldEmail, oldEmail: oldEmail,
newEmail: newEmail, newEmail: newEmail,
}; };
loading(); // loading();
const response = await postSetupEmail(data); const response = await postSetupEmail(data);
close(); // close();
if (response.error) { if (response?.error) {
error(response.message); error(response.message);
return false; return false;
} }
const msg = response.data?.message; const msg = response?.data?.message;
if (msg == "Email is valid and OTP has been sent") { if (msg == "Email is valid and OTP has been sent") {
setStep(3); setStep(3);
} else if (msg == "The old email is not same") { } else if (msg == "The old email is not same") {
@ -325,6 +295,52 @@ const LoginForm = () => {
} }
}; };
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");
}
}
};
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className="mt-5 2xl:mt-7 space-y-4"> <form onSubmit={handleSubmit(onSubmit)} className="mt-5 2xl:mt-7 space-y-4">
{step === 1 ? ( {step === 1 ? (
@ -391,7 +407,7 @@ const LoginForm = () => {
</Link> </Link>
</div> </div>
<Button <Button
type="submit" type="button"
fullWidth fullWidth
onClick={handleEmailValidation} onClick={handleEmailValidation}
disabled={isPending} disabled={isPending}
@ -417,13 +433,14 @@ const LoginForm = () => {
<Input <Input
size="lg" size="lg"
disabled={isPending} disabled={isPending}
onChange={(e) => setOldEmail(e.target.value)} onChange={(e) => checkEmail("old", e.target.value)}
id="oldEmail" id="oldEmail"
type="email" type="email"
className={cn("", { className={cn("", {
"border-destructive": errors.username, "border-destructive": errors.username,
})} })}
/> />
<p className="invalid-feedback-custom">{oldEmailValidate}</p>
{errors.username?.message && ( {errors.username?.message && (
<div className="text-destructive mt-2 text-sm"> <div className="text-destructive mt-2 text-sm">
{errors.username.message} {errors.username.message}
@ -440,13 +457,14 @@ const LoginForm = () => {
<Input <Input
size="lg" size="lg"
disabled={isPending} disabled={isPending}
onChange={(e) => setNewEmail(e.target.value)} onChange={(e) => checkEmail("new", e.target.value)}
id="newEmail" id="newEmail"
type="email" type="email"
className={cn("", { className={cn("", {
"border-destructive": errors.username, "border-destructive": errors.username,
})} })}
/> />
<p className="invalid-feedback-custom">{newEmailValidate}</p>
{errors.username?.message && ( {errors.username?.message && (
<div className="text-destructive mt-2 text-sm"> <div className="text-destructive mt-2 text-sm">
{errors.username.message} {errors.username.message}
@ -454,8 +472,13 @@ const LoginForm = () => {
)} )}
</div> </div>
</div> </div>
<Button fullWidth className="bg-red-500"> <Button
Sign in fullWidth
className="bg-red-500"
onClick={handleSetupEmail}
type="submit"
>
Simpan
</Button> </Button>
</> </>
) : ( ) : (
@ -466,49 +489,37 @@ const LoginForm = () => {
</h4> </h4>
</div> </div>
<div className="flex justify-center mb-6"> <div className="flex justify-center mb-6">
<InputOTP maxLength={6} onChange={(e) => setOtpValue(e)}> <InputOTP
maxLength={6}
onChange={(val: string) => setOtpValue(val)}
>
<InputOTPGroup> <InputOTPGroup>
<InputOTPSlot <InputOTPSlot index={0} onKeyUp={handleTypeOTP} />
index={0} <InputOTPSlot index={1} onKeyUp={handleTypeOTP} />
onChange={(e: any) => setOtp1(e.target.value)}
onKeyUp={handleTypeOTP}
/>
<InputOTPSlot
index={1}
onChange={(e: any) => setOtp2(e.target.value)}
onKeyUp={handleTypeOTP}
/>
</InputOTPGroup> </InputOTPGroup>
<InputOTPSeparator /> <InputOTPSeparator />
<InputOTPGroup> <InputOTPGroup>
<InputOTPSlot <InputOTPSlot index={2} onKeyUp={handleTypeOTP} />
index={2} <InputOTPSlot index={3} onKeyUp={handleTypeOTP} />
onChange={(e: any) => setOtp3(e.target.value)}
onKeyUp={handleTypeOTP}
/>
<InputOTPSlot
index={3}
onChange={(e: any) => setOtp4(e.target.value)}
onKeyUp={handleTypeOTP}
/>
</InputOTPGroup> </InputOTPGroup>
<InputOTPSeparator /> <InputOTPSeparator />
<InputOTPGroup> <InputOTPGroup>
<InputOTPSlot <InputOTPSlot index={4} onKeyUp={handleTypeOTP} />
index={4} <InputOTPSlot index={5} onKeyUp={handleTypeOTP} />
onChange={(e: any) => setOtp5(e.target.value)}
onKeyUp={handleTypeOTP}
/>
<InputOTPSlot
index={5}
onChange={(e: any) => setOtp6(e.target.value)}
onKeyUp={handleTypeOTP}
/>
</InputOTPGroup> </InputOTPGroup>
</InputOTP> </InputOTP>
<p className="invalid-feedback-custom text-center">
<b>{otpValidate}</b>
</p>
</div> </div>
<Button fullWidth className="bg-red-500"> <Button
Sign in fullWidth
className="bg-red-500"
type="button"
onClick={handleLoginOTP}
disabled={otpValue.length !== 6}
>
Verifikasi OTP & Masuk
</Button> </Button>
</> </>
)} )}

View File

@ -33,8 +33,8 @@ export default function PerformancePoldaViz() {
levelName == "MABES POLRI" levelName == "MABES POLRI"
? isInternational[0] ? isInternational[0]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-top10?" ? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-top10?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-ranking-polda?" : "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10?"
: `views/2023_09_db-ranking-polda_rev100/db-ranking-13?`; : `/views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${poldaState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true"; const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";

View File

@ -27,7 +27,7 @@ export default function PerformancePolresViz() {
? isInternational[0] ? isInternational[0]
? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-top10?" ? "views/2023_04_MediaHUB-Viz_INTL_Rev202/db-konten-top10?"
: "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10?" : "views/2023_04_MediaHUB-Viz-POLDA_Rev201/db-konten-top10?"
: `/views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${provState}&`; : `/views/2023_09_db-ranking-polres-by-polda_rev100/db-ranking-by-polda?polda-selected=${poldaState}&`;
const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true"; const param = ":embed=yes&:toolbar=yes&:iframeSizedToWindow=true";
@ -59,9 +59,7 @@ export default function PerformancePolresViz() {
<div className="flex flex-col gap-2 bg-white rounded-lg p-3"> <div className="flex flex-col gap-2 bg-white rounded-lg p-3">
<p className="text-lg"> <p className="text-lg">
<b> <b>
{isInternational[0] {isInternational[0] ? "POLRES PERFORMANCE" : "POLFORMANCE POLRES"}
? "POLRES PERFORMANCE"
: "POLFORMANCE POLRES"}
</b> </b>
</p> </p>
<div className="my-5"> <div className="my-5">

View File

@ -3001,20 +3001,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
], ],
}, },
// {
// groupLabel: "",
// id: "agenda-setting",
// menus: [
// {
// id: "agenda-setting",
// href: "/contributor/agenda-setting",
// label: t("agenda-setting"),
// active: pathname.includes("/agenda-setting"),
// icon: "iconoir:journal-page",
// submenus: [],
// },
// ],
// },
{ {
groupLabel: "", groupLabel: "",
id: "management-user", id: "management-user",
@ -3057,6 +3044,20 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
], ],
}, },
// {
// groupLabel: "",
// id: "agenda-setting",
// menus: [
// {
// id: "agenda-setting",
// href: "/contributor/agenda-setting",
// label: t("agenda-setting"),
// active: pathname.includes("/agenda-setting"),
// icon: "iconoir:journal-page",
// submenus: [],
// },
// ],
// },
{ {
groupLabel: "", groupLabel: "",
id: "performance-polda", id: "performance-polda",
@ -3352,20 +3353,20 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
], ],
}, },
// { {
// groupLabel: "", groupLabel: "",
// id: "agenda-setting", id: "agenda-setting",
// menus: [ menus: [
// { {
// id: "agenda-setting", id: "agenda-setting",
// href: "/contributor/agenda-setting", href: "/contributor/agenda-setting",
// label: t("agenda-setting"), label: t("agenda-setting"),
// active: pathname.includes("/agenda-setting"), active: pathname.includes("/agenda-setting"),
// icon: "iconoir:journal-page", icon: "iconoir:journal-page",
// submenus: [], submenus: [],
// }, },
// ], ],
// }, },
{ {
groupLabel: "", groupLabel: "",
id: "performance-polres", id: "performance-polres",
@ -3437,70 +3438,70 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
], ],
}, },
{ // {
groupLabel: "", // groupLabel: "",
id: "settings", // id: "settings",
menus: [ // menus: [
{ // {
id: "settings", // id: "settings",
href: "/admin/settings", // href: "/admin/settings",
label: t("settings"), // label: t("settings"),
active: pathname.includes("/settinng"), // active: pathname.includes("/settinng"),
icon: "material-symbols:settings", // icon: "material-symbols:settings",
submenus: [ // submenus: [
{ // {
href: "/admin/settings/category", // href: "/admin/settings/category",
label: t("category"), // label: t("category"),
active: pathname === "/admin/settings/category", // active: pathname === "/admin/settings/category",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
{ // {
href: "/admin/settings/tag", // href: "/admin/settings/tag",
label: "Tag", // label: "Tag",
active: pathname === "/admin/settings/tag", // active: pathname === "/admin/settings/tag",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
{ // {
href: "/admin/settings/banner", // href: "/admin/settings/banner",
label: "Banner", // label: "Banner",
active: pathname === "/admin/settings/banner", // active: pathname === "/admin/settings/banner",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
{ // {
href: "/admin/settings/feedback", // href: "/admin/settings/feedback",
label: "Feedback", // label: "Feedback",
active: pathname === "/admin/settings/feedback", // active: pathname === "/admin/settings/feedback",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
{ // {
href: "/admin/settings/faq", // href: "/admin/settings/faq",
label: "FAQ", // label: "FAQ",
active: pathname === "/admin/settings/faq", // active: pathname === "/admin/settings/faq",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
{ // {
href: "https://nat-mediahub.polri.go.id/", // href: "https://nat-mediahub.polri.go.id/",
label: "Mediahub 2022", // label: "Mediahub 2022",
active: pathname === "/admin/settings/mediahub-2022", // active: pathname === "/admin/settings/mediahub-2022",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
{ // {
href: "/admin/settings/privacy", // href: "/admin/settings/privacy",
label: t("privacy"), // label: t("privacy"),
active: pathname === "/admin/settings/privacy", // active: pathname === "/admin/settings/privacy",
icon: "heroicons:arrow-trending-up", // icon: "heroicons:arrow-trending-up",
children: [], // children: [],
}, // },
], // ],
}, // },
], // ],
}, // },
]; ];
} else { } else {
menusSelected = [ menusSelected = [
@ -3632,6 +3633,20 @@ export function getMenuList(pathname: string, t: any): Group[] {
}, },
], ],
}, },
{
groupLabel: "",
id: "experts",
menus: [
{
id: "experts",
href: "/admin/add-experts",
label: t("add-experts"),
active: pathname.includes("/add-experts"),
icon: "majesticons:user",
submenus: [],
},
],
},
{ {
groupLabel: "", groupLabel: "",
id: "settings", id: "settings",

View File

@ -328,7 +328,7 @@
"performance-satker": "Performance Satker", "performance-satker": "Performance Satker",
"analysis": "Analysis", "analysis": "Analysis",
"management-content": "Content Management ", "management-content": "Content Management ",
"add-experts": "Add Experts", "add-experts": "Experts",
"category": "Category", "category": "Category",
"add-category": "Add Category", "add-category": "Add Category",
"tags": "Tags", "tags": "Tags",

View File

@ -329,7 +329,7 @@
"performance-satker": "Performa Satker", "performance-satker": "Performa Satker",
"analysis": "Analisa", "analysis": "Analisa",
"management-content": "Manajemen Konten", "management-content": "Manajemen Konten",
"add-experts": "Tambah Tenaga Ahli", "add-experts": "Tenaga Ahli",
"category": "Kategori", "category": "Kategori",
"add-category": "Tambah Kategori", "add-category": "Tambah Kategori",
"tags": "Tag", "tags": "Tag",

View File

@ -70,12 +70,17 @@ export async function getProfile(token: any) {
export async function postEmailValidation(data: any) { export async function postEmailValidation(data: any) {
const url = "public/users/email-validation"; const url = "public/users/email-validation";
return postAPIWithJson(url, data); return httpPostInterceptor(url, data);
} }
export async function postSetupEmail(data: any) { export async function postSetupEmail(data: any) {
const url = "public/users/setup-email"; const url = "public/users/setup-email";
return httpGetInterceptorWithToken({ url, data }); return httpPostInterceptor(url, data);
}
export async function verifyOTPByUsername(username: any, otp: any) {
const url = `public/users/verify-otp?username=${username}&otp=${otp}`;
return httpPostInterceptor(url);
} }
export async function getSubjects() { export async function getSubjects() {

View File

@ -175,10 +175,13 @@ export async function httpGetInterceptorWithToken(pathUrl: any, headers?: any) {
} }
} }
export async function postAPIWithJson(url: any, data: any, headers?: any) { export async function postAPIWithJson(url: any, data: any, token: any) {
const headers = {
Authorization: `Bearer ${token}`,
};
const response = await axiosInstanceJson const response = await axiosInstanceJson
.post(url, data, { headers }) .post(url, data, { headers })
.catch((error: any) => error.response); .catch((error) => error.response);
if (response?.status > 300) { if (response?.status > 300) {
return { return {
error: true, error: true,