fix: adjust register to polri member
This commit is contained in:
parent
6a00c7b67d
commit
ba03898e8b
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -16,10 +15,11 @@ import {
|
||||||
UserCategory,
|
UserCategory,
|
||||||
journalistRegistrationSchema,
|
journalistRegistrationSchema,
|
||||||
personnelRegistrationSchema,
|
personnelRegistrationSchema,
|
||||||
generalRegistrationSchema
|
generalRegistrationSchema,
|
||||||
} from "@/types/registration";
|
} from "@/types/registration";
|
||||||
import { useUserDataValidation } from "@/hooks/use-registration";
|
import { useUserDataValidation } from "@/hooks/use-registration";
|
||||||
import { ASSOCIATIONS } from "@/lib/registration-utils";
|
import { ASSOCIATIONS } from "@/lib/registration-utils";
|
||||||
|
import React, { useState, useRef } from "react";
|
||||||
|
|
||||||
export const IdentityForm: React.FC<IdentityFormProps> = ({
|
export const IdentityForm: React.FC<IdentityFormProps> = ({
|
||||||
category,
|
category,
|
||||||
|
|
@ -28,11 +28,48 @@ export const IdentityForm: React.FC<IdentityFormProps> = ({
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const t = useTranslations("LandingPage");
|
const t = useTranslations("LandingPage");
|
||||||
const { validateJournalistData, validatePersonnelData, loading: validationLoading } = useUserDataValidation();
|
const {
|
||||||
|
validateJournalistData,
|
||||||
|
validatePersonnelData,
|
||||||
|
loading: validationLoading,
|
||||||
|
} = useUserDataValidation();
|
||||||
|
|
||||||
const [memberIdentity, setMemberIdentity] = useState("");
|
const [memberIdentity, setMemberIdentity] = useState("");
|
||||||
const [memberIdentityError, setMemberIdentityError] = useState("");
|
const [memberIdentityError, setMemberIdentityError] = useState("");
|
||||||
|
|
||||||
|
const [personelName, setPersonelName] = useState<string>("");
|
||||||
|
const [loadingPersonel, setLoadingPersonel] = useState(false);
|
||||||
|
|
||||||
|
const fetchPersonelByNRP = async (nrp: string) => {
|
||||||
|
setLoadingPersonel(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://mediahub.polri.go.id/api/v2/public/users/search-personil?nrp=${nrp}&timemilis=${Date.now()}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Personel not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
// asumsi responseData ada di json.data
|
||||||
|
const name = json?.data?.nama;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
throw new Error("Nama personel tidak ditemukan");
|
||||||
|
}
|
||||||
|
|
||||||
|
setPersonelName(name);
|
||||||
|
setValue("policeNumber" as keyof PersonnelRegistrationData, nrp);
|
||||||
|
} catch (err: any) {
|
||||||
|
setPersonelName("");
|
||||||
|
setMemberIdentityError(err.message || "NRP tidak valid");
|
||||||
|
} finally {
|
||||||
|
setLoadingPersonel(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Determine which schema to use based on category
|
// Determine which schema to use based on category
|
||||||
const getSchema = () => {
|
const getSchema = () => {
|
||||||
switch (category) {
|
switch (category) {
|
||||||
|
|
@ -51,58 +88,133 @@ export const IdentityForm: React.FC<IdentityFormProps> = ({
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
setValue,
|
setValue,
|
||||||
watch,
|
watch,
|
||||||
} = useForm<JournalistRegistrationData | PersonnelRegistrationData | GeneralRegistrationData>({
|
} = useForm<
|
||||||
|
| JournalistRegistrationData
|
||||||
|
| PersonnelRegistrationData
|
||||||
|
| GeneralRegistrationData
|
||||||
|
>({
|
||||||
resolver: zodResolver(getSchema()),
|
resolver: zodResolver(getSchema()),
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
const watchedEmail = watch("email");
|
const watchedEmail = watch("email");
|
||||||
|
|
||||||
const handleMemberIdentityChange = async (value: string) => {
|
// const handleMemberIdentityChange = async (value: string) => {
|
||||||
|
// setMemberIdentity(value);
|
||||||
|
// setMemberIdentityError("");
|
||||||
|
|
||||||
|
// if (!value.trim()) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// if (category === "6") {
|
||||||
|
// await validateJournalistData(value);
|
||||||
|
// setValue(
|
||||||
|
// "journalistCertificate" as keyof JournalistRegistrationData,
|
||||||
|
// value
|
||||||
|
// );
|
||||||
|
// } else if (category === "7") {
|
||||||
|
// await validatePersonnelData(value);
|
||||||
|
// setValue("policeNumber" as keyof PersonnelRegistrationData, value);
|
||||||
|
// }
|
||||||
|
// } catch (error: any) {
|
||||||
|
// setMemberIdentityError(error.message || "Invalid identity number");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleMemberIdentityChange = (value: string) => {
|
||||||
setMemberIdentity(value);
|
setMemberIdentity(value);
|
||||||
setMemberIdentityError("");
|
setMemberIdentityError("");
|
||||||
|
|
||||||
|
// reset nama personel jika input kosong
|
||||||
if (!value.trim()) {
|
if (!value.trim()) {
|
||||||
|
setPersonelName("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// hanya debounce untuk PERSONNEL (category 7)
|
||||||
if (category === "6") {
|
if (category !== "7") return;
|
||||||
await validateJournalistData(value);
|
|
||||||
setValue("journalistCertificate" as keyof JournalistRegistrationData, value);
|
// clear timer sebelumnya
|
||||||
} else if (category === "7") {
|
if (debounceTimerRef.current) {
|
||||||
await validatePersonnelData(value);
|
clearTimeout(debounceTimerRef.current);
|
||||||
setValue("policeNumber" as keyof PersonnelRegistrationData, value);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
setMemberIdentityError(error.message || "Invalid identity number");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set timer baru (3 detik)
|
||||||
|
debounceTimerRef.current = setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
await fetchPersonelByNRP(value);
|
||||||
|
} catch (error: any) {
|
||||||
|
setMemberIdentityError(error.message || "NRP tidak valid");
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: JournalistRegistrationData | PersonnelRegistrationData | GeneralRegistrationData) => {
|
const onSubmit = async (
|
||||||
|
data:
|
||||||
|
| JournalistRegistrationData
|
||||||
|
| PersonnelRegistrationData
|
||||||
|
| GeneralRegistrationData
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
// Additional validation for member identity
|
if (category === "7") {
|
||||||
if ((category === "6" || category === "7") && !memberIdentity.trim()) {
|
if (!memberIdentity.trim()) {
|
||||||
setMemberIdentityError("Identity number is required");
|
setMemberIdentityError("NRP wajib diisi");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!personelName) {
|
||||||
|
setMemberIdentityError("Data personel belum valid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memberIdentityError) {
|
const payload = {
|
||||||
onError?.(memberIdentityError);
|
...data,
|
||||||
return;
|
nrp: memberIdentity,
|
||||||
}
|
personelName,
|
||||||
|
};
|
||||||
|
|
||||||
onSuccess?.(data);
|
// simpan sementara untuk step OTP & next page
|
||||||
|
sessionStorage.setItem("registration_identity", JSON.stringify(payload));
|
||||||
|
|
||||||
|
onSuccess?.(payload);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
onError?.(error.message || "Form submission failed");
|
onError?.(error.message || "Form submission failed");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const onSubmit = async (
|
||||||
|
// data:
|
||||||
|
// | JournalistRegistrationData
|
||||||
|
// | PersonnelRegistrationData
|
||||||
|
// | GeneralRegistrationData
|
||||||
|
// ) => {
|
||||||
|
// try {
|
||||||
|
// // Additional validation for member identity
|
||||||
|
// if ((category === "6" || category === "7") && !memberIdentity.trim()) {
|
||||||
|
// setMemberIdentityError("Identity number is required");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (memberIdentityError) {
|
||||||
|
// onError?.(memberIdentityError);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// onSuccess?.(data);
|
||||||
|
// } catch (error: any) {
|
||||||
|
// onError?.(error.message || "Form submission failed");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const renderJournalistFields = () => (
|
const renderJournalistFields = () => (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col px-0 lg:px-8 mb-4">
|
<div className="flex flex-col px-0 lg:px-8 mb-4">
|
||||||
<Label htmlFor="association" className="mb-2">
|
<Label htmlFor="association" className="mb-2">
|
||||||
{t("member", { defaultValue: "Member" })} <span className="text-red-500">*</span>
|
{t("member", { defaultValue: "Member" })}{" "}
|
||||||
|
<span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<select
|
<select
|
||||||
className={`py-2 px-3 rounded-md border text-sm border-slate-300 bg-white dark:bg-slate-600`}
|
className={`py-2 px-3 rounded-md border text-sm border-slate-300 bg-white dark:bg-slate-600`}
|
||||||
|
|
@ -125,12 +237,17 @@ export const IdentityForm: React.FC<IdentityFormProps> = ({
|
||||||
|
|
||||||
<div className="px-0 lg:px-[34px] mb-4">
|
<div className="px-0 lg:px-[34px] mb-4">
|
||||||
<Label htmlFor="journalistCertificate">
|
<Label htmlFor="journalistCertificate">
|
||||||
{t("journalistNumber", { defaultValue: "Journalist Certificate Number" })} <span className="text-red-500">*</span>
|
{t("journalistNumber", {
|
||||||
|
defaultValue: "Journalist Certificate Number",
|
||||||
|
})}{" "}
|
||||||
|
<span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
className={`mt-2 ${memberIdentityError ? "border-red-500" : ""}`}
|
className={`mt-2 ${memberIdentityError ? "border-red-500" : ""}`}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={t("inputJournalist", { defaultValue: "Enter journalist certificate number" })}
|
placeholder={t("inputJournalist", {
|
||||||
|
defaultValue: "Enter journalist certificate number",
|
||||||
|
})}
|
||||||
type="text"
|
type="text"
|
||||||
value={memberIdentity}
|
value={memberIdentity}
|
||||||
onChange={(e) => handleMemberIdentityChange(e.target.value)}
|
onChange={(e) => handleMemberIdentityChange(e.target.value)}
|
||||||
|
|
@ -142,10 +259,13 @@ export const IdentityForm: React.FC<IdentityFormProps> = ({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const renderPersonnelFields = () => (
|
const renderPersonnelFields = () => (
|
||||||
<div className="px-0 lg:px-[34px] mb-4">
|
<div className="px-0 lg:px-[34px] mb-4">
|
||||||
<Label htmlFor="policeNumber">
|
<Label htmlFor="policeNumber">
|
||||||
<b>{t("policeNumber", { defaultValue: "Police Number" })}</b> <span className="text-red-500">*</span>
|
<b>{t("policeNumber", { defaultValue: "Police Number" })}</b>{" "}
|
||||||
|
<span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
className={`mt-2 ${memberIdentityError ? "border-red-500" : ""}`}
|
className={`mt-2 ${memberIdentityError ? "border-red-500" : ""}`}
|
||||||
|
|
@ -158,6 +278,17 @@ export const IdentityForm: React.FC<IdentityFormProps> = ({
|
||||||
{memberIdentityError && (
|
{memberIdentityError && (
|
||||||
<p className="text-red-500 text-sm mt-1">{memberIdentityError}</p>
|
<p className="text-red-500 text-sm mt-1">{memberIdentityError}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{personelName && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<Label>Nama Personel</Label>
|
||||||
|
<Input
|
||||||
|
value={personelName}
|
||||||
|
disabled
|
||||||
|
className="mt-2 bg-slate-100 dark:bg-slate-700"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -177,19 +308,26 @@ export const IdentityForm: React.FC<IdentityFormProps> = ({
|
||||||
<Input
|
<Input
|
||||||
className={`w-full ${errors.email ? "border-red-500" : ""}`}
|
className={`w-full ${errors.email ? "border-red-500" : ""}`}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={t("inputEmail", { defaultValue: "Enter your email" })}
|
placeholder={t("inputEmail", {
|
||||||
|
defaultValue: "Enter your email",
|
||||||
|
})}
|
||||||
type="email"
|
type="email"
|
||||||
{...register("email")}
|
{...register("email")}
|
||||||
/>
|
/>
|
||||||
{errors.email && (
|
{errors.email && (
|
||||||
<p className="text-sm text-red-500 mt-1">{errors.email.message}</p>
|
<p className="text-sm text-red-500 mt-1">
|
||||||
|
{errors.email.message}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Terms and conditions */}
|
{/* Terms and conditions */}
|
||||||
<div className="text-center mb-2 px-[34px]">
|
<div className="text-center mb-2 px-[34px]">
|
||||||
<p className="text-sm lg:text-base">
|
<p className="text-sm lg:text-base">
|
||||||
{t("byRegis", { defaultValue: "By registering, you agree to our" })} <br />{" "}
|
{t("byRegis", {
|
||||||
|
defaultValue: "By registering, you agree to our",
|
||||||
|
})}{" "}
|
||||||
|
<br />{" "}
|
||||||
<a href="/privacy" target="_blank" className="text-red-500">
|
<a href="/privacy" target="_blank" className="text-red-500">
|
||||||
<b>{t("terms", { defaultValue: "Terms of Service" })}</b>
|
<b>{t("terms", { defaultValue: "Terms of Service" })}</b>
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ const PasswordChecklist = dynamic(() => import("react-password-checklist"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
export const ProfileForm: React.FC<ProfileFormProps> = ({
|
export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
userData,
|
userData,
|
||||||
category,
|
category,
|
||||||
|
|
@ -60,6 +65,15 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
const [passwordConf, setPasswordConf] = useState("");
|
const [passwordConf, setPasswordConf] = useState("");
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showPasswordConf, setShowPasswordConf] = useState(false);
|
const [showPasswordConf, setShowPasswordConf] = useState(false);
|
||||||
|
const [usernameTouched, setUsernameTouched] = useState(false);
|
||||||
|
|
||||||
|
const [otpIdentity, setOtpIdentity] = useState<{
|
||||||
|
email: string;
|
||||||
|
nrp?: string;
|
||||||
|
personelName?: string;
|
||||||
|
}>({
|
||||||
|
email: "",
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
|
@ -70,9 +84,38 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
} = useForm<RegistrationFormData>({
|
} = useForm<RegistrationFormData>({
|
||||||
resolver: zodResolver(registrationSchema),
|
resolver: zodResolver(registrationSchema),
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
defaultValues: { email: userData, },
|
defaultValues: { email: userData },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const stored = sessionStorage.getItem("registration_identity");
|
||||||
|
|
||||||
|
if (stored) {
|
||||||
|
const parsed = JSON.parse(stored);
|
||||||
|
|
||||||
|
setOtpIdentity({
|
||||||
|
email: parsed.email,
|
||||||
|
nrp: parsed.nrp,
|
||||||
|
personelName: parsed.personelName,
|
||||||
|
});
|
||||||
|
|
||||||
|
// default nama dari API
|
||||||
|
if (parsed.personelName) {
|
||||||
|
setValue("firstName", parsed.personelName);
|
||||||
|
|
||||||
|
const autoUsername = parsed.personelName
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.replace(/[^a-z0-9-]/g, "");
|
||||||
|
|
||||||
|
setValue("username", autoUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue("email", parsed.email);
|
||||||
|
}
|
||||||
|
}, [setValue]);
|
||||||
|
|
||||||
const watchedPassword = watch("password");
|
const watchedPassword = watch("password");
|
||||||
|
|
||||||
const handleProvinceChange = (provinceId: string) => {
|
const handleProvinceChange = (provinceId: string) => {
|
||||||
|
|
@ -123,39 +166,119 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
setShowPasswordConf(!showPasswordConf);
|
setShowPasswordConf(!showPasswordConf);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const onSubmit = async (data: RegistrationFormData) => {
|
||||||
|
// try {
|
||||||
|
// let instituteId = 1;
|
||||||
|
|
||||||
|
// // Handle custom institute for journalists
|
||||||
|
// if (category === "6" && isCustomInstitute) {
|
||||||
|
// if (!customInstituteName.trim() || !instituteAddress.trim()) {
|
||||||
|
// onError?.("Please fill in all institute details");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const instituteData: InstituteData = {
|
||||||
|
// id: "0",
|
||||||
|
// name: customInstituteName,
|
||||||
|
// address: instituteAddress,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// instituteId = await saveInstitute(instituteData);
|
||||||
|
// } else if (category === "6" && selectedInstitute) {
|
||||||
|
// instituteId = Number(selectedInstitute);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const success = await submitRegistration(
|
||||||
|
// data,
|
||||||
|
// category,
|
||||||
|
// userData,
|
||||||
|
// instituteId
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (success) {
|
||||||
|
// onSuccess?.(data);
|
||||||
|
// }
|
||||||
|
// } catch (error: any) {
|
||||||
|
// onError?.(error.message || "Registration failed");
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const onSubmit = async (data: RegistrationFormData) => {
|
const onSubmit = async (data: RegistrationFormData) => {
|
||||||
|
// 🔹 TAMPILKAN LOADING
|
||||||
|
MySwal.fire({
|
||||||
|
title: "Processing",
|
||||||
|
text: "Pleasewait",
|
||||||
|
allowOutsideClick: false,
|
||||||
|
allowEscapeKey: false,
|
||||||
|
didOpen: () => {
|
||||||
|
Swal.showLoading();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let instituteId = 1;
|
let instituteId = 1; // default (non-journalist / personel)
|
||||||
|
|
||||||
// Handle custom institute for journalists
|
// khusus journalist
|
||||||
if (category === "6" && isCustomInstitute) {
|
if (category === "6") {
|
||||||
if (!customInstituteName.trim() || !instituteAddress.trim()) {
|
if (isCustomInstitute) {
|
||||||
onError?.("Please fill in all institute details");
|
if (!customInstituteName.trim() || !instituteAddress.trim()) {
|
||||||
return;
|
Swal.close();
|
||||||
|
onError?.("Please fill in all institute details");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instituteData: InstituteData = {
|
||||||
|
id: "0",
|
||||||
|
name: customInstituteName,
|
||||||
|
address: instituteAddress,
|
||||||
|
};
|
||||||
|
|
||||||
|
instituteId = await saveInstitute(instituteData);
|
||||||
|
} else if (selectedInstitute) {
|
||||||
|
instituteId = Number(selectedInstitute);
|
||||||
}
|
}
|
||||||
|
|
||||||
const instituteData: InstituteData = {
|
|
||||||
id: "0",
|
|
||||||
name: customInstituteName,
|
|
||||||
address: instituteAddress,
|
|
||||||
};
|
|
||||||
|
|
||||||
instituteId = await saveInstitute(instituteData);
|
|
||||||
} else if (category === "6" && selectedInstitute) {
|
|
||||||
instituteId = Number(selectedInstitute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...data,
|
||||||
|
email: otpIdentity.email,
|
||||||
|
nrp: otpIdentity.nrp,
|
||||||
|
personelName: otpIdentity.personelName,
|
||||||
|
};
|
||||||
|
|
||||||
const success = await submitRegistration(
|
const success = await submitRegistration(
|
||||||
data,
|
payload,
|
||||||
category,
|
category,
|
||||||
userData,
|
otpIdentity.email,
|
||||||
instituteId
|
instituteId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🔹 TUTUP LOADING
|
||||||
|
Swal.close();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
onSuccess?.(data);
|
sessionStorage.removeItem("registration_identity");
|
||||||
|
|
||||||
|
await Swal.fire({
|
||||||
|
icon: "success",
|
||||||
|
title:"Success",
|
||||||
|
text: "Register Success !!",
|
||||||
|
confirmButtonColor: "#dc3545",
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccess?.(payload);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
// 🔹 TUTUP LOADING
|
||||||
|
Swal.close();
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Failed",
|
||||||
|
text: error?.message || "Registration failed",
|
||||||
|
confirmButtonColor: "#dc3545",
|
||||||
|
});
|
||||||
|
|
||||||
onError?.(error.message || "Registration failed");
|
onError?.(error.message || "Registration failed");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -240,7 +363,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
: "NRP"}
|
: "NRP"}
|
||||||
<span className="text-red-500">*</span>
|
<span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
{/* <Input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className="mb-3"
|
className="mb-3"
|
||||||
|
|
@ -253,12 +376,53 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
disabled
|
disabled
|
||||||
|
/> */}
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
className="mb-3"
|
||||||
|
value={category === "7" ? otpIdentity.nrp || "" : ""}
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Personal Information */}
|
{/* Personal Information */}
|
||||||
<div className="mb-4 px-0 lg:px-[34px]">
|
{category === "7" && (
|
||||||
|
<div className="px-0 lg:px-[34px] mb-4">
|
||||||
|
<Label className="mb-2">
|
||||||
|
Nama Personel <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
className={errors.firstName ? "border-red-500" : ""}
|
||||||
|
{...register("firstName")}
|
||||||
|
onChange={(e) => {
|
||||||
|
const name = e.target.value;
|
||||||
|
|
||||||
|
// set nama
|
||||||
|
setValue("firstName", name);
|
||||||
|
|
||||||
|
// auto-generate username
|
||||||
|
const username = name
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.replace(/[^a-z0-9-]/g, "");
|
||||||
|
|
||||||
|
setValue("username", username);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.firstName && (
|
||||||
|
<div className="text-red-500 text-sm mt-1">
|
||||||
|
{errors.firstName.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <div className="mb-4 px-0 lg:px-[34px]">
|
||||||
<Label className="mb-2">
|
<Label className="mb-2">
|
||||||
{t("fullName", { defaultValue: "Full Name" })}{" "}
|
{t("fullName", { defaultValue: "Full Name" })}{" "}
|
||||||
<span className="text-red-500">*</span>
|
<span className="text-red-500">*</span>
|
||||||
|
|
@ -277,13 +441,13 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
{errors.firstName.message}
|
{errors.firstName.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<div className="mb-4 px-0 lg:px-[34px]">
|
<div className="mb-4 px-0 lg:px-[34px]">
|
||||||
<Label className="mb-2">
|
<Label className="mb-2">
|
||||||
Username <span className="text-red-500">*</span>
|
Username <span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
{/* <Input
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className={errors.username ? "border-red-500" : ""}
|
className={errors.username ? "border-red-500" : ""}
|
||||||
|
|
@ -291,13 +455,34 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
placeholder={t("enterUsername", {
|
placeholder={t("enterUsername", {
|
||||||
defaultValue: "Enter username",
|
defaultValue: "Enter username",
|
||||||
})}
|
})}
|
||||||
|
// onChange={(e) => {
|
||||||
|
// const value = e.target.value
|
||||||
|
// .replace(/[^\w.-]/g, "")
|
||||||
|
// .toLowerCase();
|
||||||
|
// setValue("username", value);
|
||||||
|
// }}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
.replace(/[^\w.-]/g, "")
|
.toLowerCase()
|
||||||
.toLowerCase();
|
.trim()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.replace(/[^a-z0-9-]/g, "");
|
||||||
|
|
||||||
|
setValue("username", value);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
<Input
|
||||||
|
{...register("username")}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUsernameTouched(true);
|
||||||
|
const value = e.target.value
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.replace(/[^a-z0-9-]/g, "");
|
||||||
setValue("username", value);
|
setValue("username", value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{errors.username && (
|
{errors.username && (
|
||||||
<div className="text-red-500 text-sm mt-1">
|
<div className="text-red-500 text-sm mt-1">
|
||||||
{errors.username.message}
|
{errors.username.message}
|
||||||
|
|
@ -309,7 +494,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
<Label className="mb-2">
|
<Label className="mb-2">
|
||||||
Email <span className="text-red-500">*</span>
|
Email <span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
{/* <Input
|
||||||
type="email"
|
type="email"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className={errors.email ? "border-red-500" : ""}
|
className={errors.email ? "border-red-500" : ""}
|
||||||
|
|
@ -317,7 +502,9 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({
|
||||||
placeholder="Enter your email"
|
placeholder="Enter your email"
|
||||||
value={userData || ""}
|
value={userData || ""}
|
||||||
disabled
|
disabled
|
||||||
/>
|
/> */}
|
||||||
|
<Input {...register("email")} value={otpIdentity.email} disabled />
|
||||||
|
|
||||||
{errors.email && (
|
{errors.email && (
|
||||||
<div className="text-red-500 text-sm mt-1">
|
<div className="text-red-500 text-sm mt-1">
|
||||||
{errors.email.message}
|
{errors.email.message}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export const registrationSchema = z.object({
|
||||||
.min(1, { message: "Full name is required" })
|
.min(1, { message: "Full name is required" })
|
||||||
.min(2, { message: "Full name must be at least 2 characters" })
|
.min(2, { message: "Full name must be at least 2 characters" })
|
||||||
.max(100, { message: "Full name must be less than 100 characters" })
|
.max(100, { message: "Full name must be less than 100 characters" })
|
||||||
.regex(/^[a-zA-Z\s]+$/, { message: "Full name can only contain letters and spaces" }),
|
.regex(/^[a-zA-Z\s.,]+$/, { message: "Full name can only contain letters and spaces" }),
|
||||||
username: z
|
username: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: "Username is required" })
|
.min(1, { message: "Username is required" })
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue