From cd80bd07cbdba0ec95e81af7ee430924212223b0 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Sun, 13 Jul 2025 04:14:12 +0700 Subject: [PATCH] feat: change expired library, update registration and login feature --- __tests__/auth-flow.test.ts | 167 + __tests__/auth/login-form.test.tsx | 264 + __tests__/setup.test.ts | 11 + .../supervisor/setting/social-media/page.tsx | 45 +- app/[locale]/auth/page.tsx | 166 +- .../auth/registration/page.backup.tsx | 1069 ++ app/[locale]/auth/registration/page.tsx | 214 + components/auth/auth-layout.tsx | 53 + components/auth/email-setup-form.tsx | 128 + components/auth/facebook-login-button.tsx | 85 + components/auth/form-field.tsx | 93 + components/auth/identity-form.tsx | 226 + components/auth/index.ts | 16 + components/auth/login-form.tsx | 213 + components/auth/otp-form.tsx | 169 + components/auth/profile-form.tsx | 497 + components/auth/registration-layout.tsx | 122 + components/auth/registration-otp-form.tsx | 193 + components/maps/Maps.tsx | 38 +- components/partials/auth/login-form.tsx | 24 +- components/ui/combobox.tsx | 181 + docs/AUTH_REFACTOR.md | 405 + docs/IMPROVEMENTS_SUMMARY.md | 330 + docs/REGISTRATION_REFACTOR.md | 290 + hooks/use-auth.ts | 320 + hooks/use-facebook-login.ts | 124 + hooks/use-registration.ts | 570 + jest.config.js | 34 + jest.setup.js | 154 + lib/auth-utils.ts | 241 + lib/registration-utils.ts | 373 + package-lock.json | 3486 ++++- package.json | 12 +- pnpm-lock.yaml | 12232 ---------------- service/auth.ts | 22 +- types/auth.ts | 172 + types/facebook-login.ts | 48 + types/react-facebook-login.d.ts | 35 - types/registration.ts | 246 + 39 files changed, 10339 insertions(+), 12729 deletions(-) create mode 100644 __tests__/auth-flow.test.ts create mode 100644 __tests__/auth/login-form.test.tsx create mode 100644 __tests__/setup.test.ts create mode 100644 app/[locale]/auth/registration/page.backup.tsx create mode 100644 app/[locale]/auth/registration/page.tsx create mode 100644 components/auth/auth-layout.tsx create mode 100644 components/auth/email-setup-form.tsx create mode 100644 components/auth/facebook-login-button.tsx create mode 100644 components/auth/form-field.tsx create mode 100644 components/auth/identity-form.tsx create mode 100644 components/auth/index.ts create mode 100644 components/auth/login-form.tsx create mode 100644 components/auth/otp-form.tsx create mode 100644 components/auth/profile-form.tsx create mode 100644 components/auth/registration-layout.tsx create mode 100644 components/auth/registration-otp-form.tsx create mode 100644 components/ui/combobox.tsx create mode 100644 docs/AUTH_REFACTOR.md create mode 100644 docs/IMPROVEMENTS_SUMMARY.md create mode 100644 docs/REGISTRATION_REFACTOR.md create mode 100644 hooks/use-auth.ts create mode 100644 hooks/use-facebook-login.ts create mode 100644 hooks/use-registration.ts create mode 100644 jest.config.js create mode 100644 jest.setup.js create mode 100644 lib/auth-utils.ts create mode 100644 lib/registration-utils.ts delete mode 100644 pnpm-lock.yaml create mode 100644 types/auth.ts create mode 100644 types/facebook-login.ts delete mode 100644 types/react-facebook-login.d.ts create mode 100644 types/registration.ts diff --git a/__tests__/auth-flow.test.ts b/__tests__/auth-flow.test.ts new file mode 100644 index 00000000..8dbc56c9 --- /dev/null +++ b/__tests__/auth-flow.test.ts @@ -0,0 +1,167 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { AuthPage } from '../app/[locale]/auth/page'; +import { useEmailValidation } from '../hooks/use-auth'; + +// Mock the hooks +jest.mock('../hooks/use-auth', () => ({ + useAuth: () => ({ + login: jest.fn(), + logout: jest.fn(), + refreshToken: jest.fn(), + isAuthenticated: false, + user: null, + loading: false, + error: null, + }), + useEmailValidation: jest.fn(), + useEmailSetup: () => ({ + setupEmail: jest.fn(), + loading: false, + error: null, + }), + useOTPVerification: () => ({ + verifyOTP: jest.fn(), + loading: false, + error: null, + }), +})); + +// Mock the components +jest.mock('../components/auth/auth-layout', () => ({ + AuthLayout: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +jest.mock('../components/auth/login-form', () => ({ + LoginForm: ({ onSuccess, onError }: any) => ( +
+ + +
+ ), +})); + +jest.mock('../components/auth/email-setup-form', () => ({ + EmailSetupForm: ({ onSuccess, onError, onBack }: any) => ( +
+ + + +
+ ), +})); + +jest.mock('../components/auth/otp-form', () => ({ + OTPForm: ({ onSuccess, onError, onResend }: any) => ( +
+ + + +
+ ), +})); + +// Mock toast +jest.mock('sonner', () => ({ + toast: { + error: jest.fn(), + success: jest.fn(), + info: jest.fn(), + }, +})); + +describe('Auth Flow', () => { + const mockValidateEmail = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + (useEmailValidation as jest.Mock).mockReturnValue({ + validateEmail: mockValidateEmail, + loading: false, + error: null, + }); + }); + + it('should start with login form', () => { + render(); + expect(screen.getByTestId('login-form')).toBeInTheDocument(); + }); + + it('should transition to email setup when validation returns "setup"', async () => { + mockValidateEmail.mockResolvedValue('setup'); + + render(); + + // Click login success to trigger email validation + fireEvent.click(screen.getByText('Login Success')); + + await waitFor(() => { + expect(screen.getByTestId('email-setup-form')).toBeInTheDocument(); + }); + }); + + it('should transition to OTP when validation returns "otp"', async () => { + mockValidateEmail.mockResolvedValue('otp'); + + render(); + + // Click login success to trigger email validation + fireEvent.click(screen.getByText('Login Success')); + + await waitFor(() => { + expect(screen.getByTestId('otp-form')).toBeInTheDocument(); + }); + }); + + it('should stay on login when validation returns "success"', async () => { + mockValidateEmail.mockResolvedValue('success'); + + render(); + + // Click login success to trigger email validation + fireEvent.click(screen.getByText('Login Success')); + + await waitFor(() => { + expect(screen.getByTestId('login-form')).toBeInTheDocument(); + }); + }); + + it('should transition from email setup to OTP', async () => { + mockValidateEmail.mockResolvedValue('setup'); + + render(); + + // First, go to email setup + fireEvent.click(screen.getByText('Login Success')); + await waitFor(() => { + expect(screen.getByTestId('email-setup-form')).toBeInTheDocument(); + }); + + // Then go to OTP + fireEvent.click(screen.getByText('Email Setup Success')); + await waitFor(() => { + expect(screen.getByTestId('otp-form')).toBeInTheDocument(); + }); + }); + + it('should go back from email setup to login', async () => { + mockValidateEmail.mockResolvedValue('setup'); + + render(); + + // First, go to email setup + fireEvent.click(screen.getByText('Login Success')); + await waitFor(() => { + expect(screen.getByTestId('email-setup-form')).toBeInTheDocument(); + }); + + // Then go back to login + fireEvent.click(screen.getByText('Back')); + await waitFor(() => { + expect(screen.getByTestId('login-form')).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/__tests__/auth/login-form.test.tsx b/__tests__/auth/login-form.test.tsx new file mode 100644 index 00000000..4affa67f --- /dev/null +++ b/__tests__/auth/login-form.test.tsx @@ -0,0 +1,264 @@ +import React from "react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { LoginForm } from "@/components/auth/login-form"; +import { useAuth, useEmailValidation } from "@/hooks/use-auth"; + +// Mock the hooks +jest.mock("@/hooks/use-auth"); +jest.mock("@/service/landing/landing", () => ({ + listRole: jest.fn().mockResolvedValue({ + data: { + data: [ + { id: 1, name: "Admin" }, + { id: 2, name: "User" }, + ], + }, + }), +})); + +// Mock next-intl +jest.mock("next-intl", () => ({ + useTranslations: () => (key: string, options?: any) => { + const defaults = { + logInPlease: "Log In Please", + acc: "Acc", + register: "Register", + password: "Password", + rememberMe: "Remember Me", + forgotPass: "Forgot Pass", + next: "Next", + categoryReg: "Category Reg", + selectOne: "Select One", + signIn: "Sign In", + }; + return options?.defaultValue || defaults[key] || key; + }, +})); + +const mockUseAuth = useAuth as jest.MockedFunction; +const mockUseEmailValidation = useEmailValidation as jest.MockedFunction; + +describe("LoginForm", () => { + const mockLogin = jest.fn(); + const mockValidateEmail = jest.fn(); + const mockOnSuccess = jest.fn(); + const mockOnError = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + + mockUseAuth.mockReturnValue({ + login: mockLogin, + logout: jest.fn(), + refreshToken: jest.fn(), + isAuthenticated: false, + user: null, + loading: false, + error: null, + }); + + mockUseEmailValidation.mockReturnValue({ + validateEmail: mockValidateEmail, + loading: false, + error: null, + }); + }); + + it("renders login form with all required fields", () => { + render( + + ); + + expect(screen.getByText("Log In Please")).toBeInTheDocument(); + expect(screen.getByLabelText(/username/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/password/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /selanjutnya/i })).toBeInTheDocument(); + }); + + it("shows validation errors for invalid input", async () => { + render( + + ); + + const submitButton = screen.getByRole("button", { name: /selanjutnya/i }); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.getByText("Username is required")).toBeInTheDocument(); + expect(screen.getByText("Password is required")).toBeInTheDocument(); + }); + }); + + it("handles successful form submission", async () => { + mockValidateEmail.mockResolvedValue("success"); + + render( + + ); + + const usernameInput = screen.getByLabelText(/username/i); + const passwordInput = screen.getByLabelText(/password/i); + const submitButton = screen.getByRole("button", { name: /selanjutnya/i }); + + fireEvent.change(usernameInput, { target: { value: "testuser" } }); + fireEvent.change(passwordInput, { target: { value: "password123" } }); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(mockValidateEmail).toHaveBeenCalledWith({ + username: "testuser", + password: "password123", + }); + }); + }); + + it("handles email validation step", async () => { + mockValidateEmail.mockResolvedValue("setup"); + + render( + + ); + + const usernameInput = screen.getByLabelText(/username/i); + const passwordInput = screen.getByLabelText(/password/i); + const submitButton = screen.getByRole("button", { name: /selanjutnya/i }); + + fireEvent.change(usernameInput, { target: { value: "testuser" } }); + fireEvent.change(passwordInput, { target: { value: "password123" } }); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(mockOnError).toHaveBeenCalledWith("Email setup required"); + }); + }); + + it("handles OTP step", async () => { + mockValidateEmail.mockResolvedValue("otp"); + + render( + + ); + + const usernameInput = screen.getByLabelText(/username/i); + const passwordInput = screen.getByLabelText(/password/i); + const submitButton = screen.getByRole("button", { name: /selanjutnya/i }); + + fireEvent.change(usernameInput, { target: { value: "testuser" } }); + fireEvent.change(passwordInput, { target: { value: "password123" } }); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(mockOnError).toHaveBeenCalledWith("OTP verification required"); + }); + }); + + it("toggles password visibility", () => { + render( + + ); + + const passwordInput = screen.getByLabelText(/password/i); + const toggleButton = screen.getByLabelText(/show password/i); + + expect(passwordInput).toHaveAttribute("type", "password"); + + fireEvent.click(toggleButton); + expect(passwordInput).toHaveAttribute("type", "text"); + expect(screen.getByLabelText(/hide password/i)).toBeInTheDocument(); + + fireEvent.click(toggleButton); + expect(passwordInput).toHaveAttribute("type", "password"); + }); + + it("handles remember me checkbox", () => { + render( + + ); + + const rememberMeCheckbox = screen.getByRole("checkbox", { name: /remember me/i }); + + expect(rememberMeCheckbox).toBeChecked(); + + fireEvent.click(rememberMeCheckbox); + expect(rememberMeCheckbox).not.toBeChecked(); + }); + + it("opens registration dialog", () => { + render( + + ); + + const registerLink = screen.getByText("Register"); + fireEvent.click(registerLink); + + expect(screen.getByText("Category Reg")).toBeInTheDocument(); + expect(screen.getByText("Select One")).toBeInTheDocument(); + expect(screen.getByLabelText("Admin")).toBeInTheDocument(); + expect(screen.getByLabelText("User")).toBeInTheDocument(); + }); + + it("handles loading state", async () => { + mockUseEmailValidation.mockReturnValue({ + validateEmail: mockValidateEmail, + loading: true, + error: null, + }); + + render( + + ); + + const submitButton = screen.getByRole("button", { name: /processing/i }); + expect(submitButton).toBeDisabled(); + }); + + it("handles error state", async () => { + mockValidateEmail.mockRejectedValue(new Error("Validation failed")); + + render( + + ); + + const usernameInput = screen.getByLabelText(/username/i); + const passwordInput = screen.getByLabelText(/password/i); + const submitButton = screen.getByRole("button", { name: /selanjutnya/i }); + + fireEvent.change(usernameInput, { target: { value: "testuser" } }); + fireEvent.change(passwordInput, { target: { value: "password123" } }); + fireEvent.click(submitButton); + + await waitFor(() => { + expect(mockOnError).toHaveBeenCalledWith("Validation failed"); + }); + }); +}); \ No newline at end of file diff --git a/__tests__/setup.test.ts b/__tests__/setup.test.ts new file mode 100644 index 00000000..62182d90 --- /dev/null +++ b/__tests__/setup.test.ts @@ -0,0 +1,11 @@ +describe('Testing Setup', () => { + it('should work correctly', () => { + expect(true).toBe(true); + }); + + it('should have testing library matchers', () => { + const element = document.createElement('div'); + element.textContent = 'Hello World'; + expect(element).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/app/[locale]/(protected)/supervisor/setting/social-media/page.tsx b/app/[locale]/(protected)/supervisor/setting/social-media/page.tsx index 222e5491..56aae0ac 100644 --- a/app/[locale]/(protected)/supervisor/setting/social-media/page.tsx +++ b/app/[locale]/(protected)/supervisor/setting/social-media/page.tsx @@ -12,10 +12,8 @@ import { } from "@/service/social-media/social-media"; import { Icon } from "@iconify/react/dist/iconify.js"; import { useEffect, useState } from "react"; -import FacebookLogin, { - ReactFacebookLoginInfo, - ReactFacebookFailureResponse -} from "react-facebook-login"; +import { FacebookLoginButton } from "@/components/auth/facebook-login-button"; +import { FacebookLoginResponse } from "@/types/facebook-login"; const SocialMediaPage = () => { const router = useRouter(); @@ -24,10 +22,8 @@ const SocialMediaPage = () => { const [isGoogleLogin, setIsGoogleLogin] = useState(false); const [, setData] = useState({}); -const responseFacebook = ( - response: ReactFacebookLoginInfo | ReactFacebookFailureResponse -) => { - if ('accessToken' in response && response.accessToken) { +const responseFacebook = (response: FacebookLoginResponse) => { + if (response.accessToken) { sendFbToken(response.accessToken); setIsFacebookLogin(true); } else { @@ -112,16 +108,31 @@ const responseFacebook = ( ) : ( <> - + onSuccess={responseFacebook} + onError={(error) => { + console.error('Facebook login error:', error); + setIsFacebookLogin(false); + }} + permissions={[ + 'public_profile', + 'user_friends', + 'pages_manage_posts', + 'pages_manage_metadata', + 'pages_event', + 'pages_read_engagement', + 'pages_manage_engagement', + 'pages_read_user_content', + 'instagram_basic', + 'instagram_content_publish', + 'instagram_manage_messages', + 'instagram_manage_comments' + ]} + className="text-white rounded-md py-2" + > + Facebook +

Tidak Terhubung

)} diff --git a/app/[locale]/auth/page.tsx b/app/[locale]/auth/page.tsx index 4519f933..ce69cd31 100644 --- a/app/[locale]/auth/page.tsx +++ b/app/[locale]/auth/page.tsx @@ -1,60 +1,122 @@ -// import React from 'react' -// import { redirect } from 'next/navigation' -// const page = ({ params: { locale } }: { params: { locale: string } }) => { -// redirect(`/${locale}/auth/login`) -// return null -// } +"use client"; -// export default page +import React, { useState } from "react"; +import { AuthLayout } from "@/components/auth/auth-layout"; +import { LoginForm } from "@/components/auth/login-form"; +import { EmailSetupForm } from "@/components/auth/email-setup-form"; +import { OTPForm } from "@/components/auth/otp-form"; +import { LoginFormData } from "@/types/auth"; +import { useAuth, useEmailValidation } from "@/hooks/use-auth"; +import { toast } from "sonner"; -import { Link } from "@/i18n/routing"; -import LoginForm from "@/components/partials/auth/login-form"; -import Image from "next/image"; -import Social from "@/components/partials/auth/social"; -import Copyright from "@/components/partials/auth/copyright"; -import Logo from "@/components/partials/auth/logo"; -import { useTranslations } from "next-intl"; +type AuthStep = "login" | "email-setup" | "otp"; -const Login = ({ params: { locale } }: { params: { locale: string } }) => { - const t = useTranslations("LandingPage"); +const AuthPage = ({ params: { locale } }: { params: { locale: string } }) => { + const [currentStep, setCurrentStep] = useState("login"); + const [loginCredentials, setLoginCredentials] = useState(null); + const { validateEmail } = useEmailValidation(); + const { login } = useAuth(); + + const handleLoginSuccess = async (data: LoginFormData) => { + setLoginCredentials(data); + // Check email validation to determine next step + try { + const result = await validateEmail(data); + switch (result) { + case "setup": + setCurrentStep("email-setup"); + break; + case "otp": + setCurrentStep("otp"); + break; + case "success": + // The login hook will handle navigation automatically + break; + default: + toast.error("Unexpected response from email validation"); + } + } catch (error: any) { + toast.error(error.message || "Email validation failed"); + } + }; + + const handleLoginError = (error: string) => { + toast.error(error); + }; + + const handleEmailSetupSuccess = () => { + setCurrentStep("otp"); + }; + + const handleEmailSetupError = (error: string) => { + toast.error(error); + }; + + const handleEmailSetupBack = () => { + setCurrentStep("login"); + }; + + const handleOTPSuccess = async () => { + if (loginCredentials) { + try { + await login(loginCredentials); + // Navigation handled by login + } catch (error: any) { + toast.error(error.message || "Login failed after OTP verification"); + } + } + }; + + const handleOTPError = (error: string) => { + toast.error(error); + }; + + const handleOTPResend = () => { + toast.info("OTP resent successfully"); + }; + + const renderCurrentStep = () => { + switch (currentStep) { + case "login": + return ( + + ); + case "email-setup": + return ( + + ); + case "otp": + return ( + + ); + default: + return ( + + ); + } + }; return ( - <> -
-
-
-
- - - -
-
- -
-
-
-
-
- -
-
-
-
-
- + + {renderCurrentStep()} + ); }; -export default Login; +export default AuthPage; diff --git a/app/[locale]/auth/registration/page.backup.tsx b/app/[locale]/auth/registration/page.backup.tsx new file mode 100644 index 00000000..abf8a63d --- /dev/null +++ b/app/[locale]/auth/registration/page.backup.tsx @@ -0,0 +1,1069 @@ +"use client"; + +import React 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 Image from "next/image"; +import { close, error, loading, registerConfirm } from "@/config/swal"; +import { Link, useRouter } from "@/i18n/routing"; +import { getDataByNIK, getDataByNRP, getDataJournalist, getDataPersonil, listCity, listDistricts, listInstitusi, listProvince, postRegistration, requestOTP, saveInstitutes, verifyOTP } from "@/service/auth"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useParams, useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import { useForm, SubmitHandler } from "react-hook-form"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import * as Yup from "yup"; +import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/ui/input-otp"; +import { Textarea } from "@/components/ui/textarea"; +import { Icon } from "@/components/ui/icon"; +import dynamic from "next/dynamic"; +import sanitizeHtml from "sanitize-html"; +import { useTranslations } from "next-intl"; + +type Inputs = { + example: string; + exampleRequired: string; +}; + +const PasswordChecklist = dynamic(() => import("react-password-checklist"), { + ssr: false, +}); + +const page = () => { + const params = useParams(); + const [stepOneActive, setStepOneActive] = useState(true); + const [stepTwoActive, setStepTwoActive] = useState(false); + const [stepThreeActive, setStepThreeActive] = useState(false); + const [typePass, setTypePass] = useState("password"); + const [typePassConf, setTypePassConf] = useState("password"); + const searchParams = useSearchParams(); + const [formProfile, setFormProfile] = useState(false); + const [journalistCertificate, setJournalistCertificate] = useState(); + const [personilNRP, setPersonilNRP] = useState(); + const [journalistData, setJournalistData] = useState(); + const [personilData, setPersonilData] = useState(); + const [userIdentity] = useState(); + const [userIdentityValidate, setUserIdentityValidate] = useState(""); + const [email, setEmail] = useState(); + const [emailValidate, setEmailValidate] = useState(""); + const [otpValidate, setOtpValidate] = useState(""); + const [password, setPassword] = useState(""); + const [city, setCity] = useState([]); + const [isValidPassword, setIsValidPassword] = useState(false); + const MySwal = withReactContent(Swal); + const [isCustomActive, setIsCustomActive] = useState(false); + const [institusi, setInstitusi] = useState(); + const [customInstituteName, setCustomInstituteName] = useState(""); + const [institusiAddress, setInstitusiAddress] = useState(); + const [institution, setInstitution] = useState([]); + const [province, setProvince] = useState([]); + const router = useRouter(); + const category = searchParams?.get("category"); + const [districts, setDistricts] = useState([]); + const [, setAssociation] = useState(); + const [warningPassConf] = useState(); + const [otpValue, setOtpValue] = useState(""); + const t = useTranslations("LandingPage"); + + const [otp1, setOtp1] = useState(); + const [otp2, setOtp2] = useState(); + const [otp3, setOtp3] = useState(); + const [otp4, setOtp4] = useState(); + const [otp5, setOtp5] = useState(); + const [otp6, setOtp6] = useState(); + + const nMinuteSeconds = 60; + const nSecondInMiliseconds = 1000; + const [, setRefreshTimer] = useState(false); + + const [passwordType, setPasswordType] = React.useState("password"); + const [passwordConf, setPasswordConf] = React.useState("password"); + + const togglePasswordType = () => { + setPasswordType((prevType) => (prevType === "password" ? "text" : "password")); + setPasswordConf((prevType) => (prevType === "same password" ? "text" : "same password")); + }; + + const validationSchema = Yup.object().shape({ + firstName: Yup.string().required(t("nameEmpty")), + username: Yup.string().required(t("usernameEmpty")), + phoneNumber: Yup.string().required(t("numberEmpty")), + address: Yup.string().required(t("addressEmpty")), + email: Yup.string().required(t("emailEmpty")), + provinsi: Yup.string().required(t("provinceEmpty")), + kota: Yup.string().required(t("cityEmpty")), + kecamatan: Yup.string().required(t("subdistrictEmpty")), + password: Yup.string().required(t("passwordEmpty")), + passwordConf: Yup.string().required(t("confirmEmpty")), + }); + + const formOptions = { + resolver: yupResolver(validationSchema), + }; + + const { register, handleSubmit, formState, setValue } = useForm(formOptions); + + const convertMinutesToMiliseconds = (minute: any) => minute * nMinuteSeconds * nSecondInMiliseconds; + + const convertMilisecondsToHour = (miliseconds: any) => new Date(miliseconds).toISOString().slice(17, -5); + + let [timerCount, setTimerCount] = useState(convertMinutesToMiliseconds(1)); + let interval: any; + + const setValUsername = (e: any) => { + const uname = e.replaceAll(/[^\w.-]/g, ""); + setValue("username", uname.toLowerCase()); + }; + + const setValPassword = (e: any) => { + setValue("password", e); + setPassword(e); + }; + + const setValPasswordConf = (e: any) => { + setPasswordConf(e); + }; + + const { errors }: any = formState; + + async function save(data: any) { + // loading(); + let institutionId = 1; + + if (Number(category) == 6) { + const dataInstitution = + isCustomActive == true + ? { + name: customInstituteName, + address: institusiAddress, + categoryRoleId: Number(category), + } + : { + id: institusi, + address: institusiAddress, + }; + + console.log(dataInstitution); + + const resInstitution = await saveInstitutes(dataInstitution); + + institutionId = resInstitution?.data?.data?.id; + if (resInstitution?.error) { + error(resInstitution?.message); + return false; + } + } + + const sanitizedFirstName = sanitizeHtml(data.firstName); + + if (sanitizedFirstName == "") { + error("Invalid Name"); + } else { + const datas = { + firstName: sanitizedFirstName, + lastName: sanitizedFirstName, + username: data.username, + phoneNumber: data.phoneNumber, + email, + address: data.address, + memberIdentity: userIdentity, + provinceId: Number(data.provinsi), + cityId: Number(data.kota), + districtId: Number(data.kecamatan), + password: data.password, + instituteId: Number(institutionId), + roleId: Number(category), + }; + + const response = await postRegistration(datas); + console.log(response); + if (response?.error) { + error(response?.message); + return false; + } + + registerConfirm(); + return false; + } + } + + const handleSendOTP = async () => { + console.log(userIdentity, email); + if (Number(category) == 6) { + // const journalist = await handleJournalistCertificate(); + console.log(email, journalistCertificate); + if (email != "" && journalistCertificate != undefined) { + const data = { + memberIdentity: journalistCertificate, + email, + category: 6, + }; + + loading(); + const response = await requestOTP(data); + + if (response?.error) { + error(response.message); + return false; + } + + close(); + handleJournalistCertificate(); + setStepTwoActive(true); + } else { + console.log("identity empty"); + setUserIdentityValidate("Nomor identitas tidak boleh kosong"); + } + } else if (Number(category) == 7) { + console.log(email, personilNRP); + if (email != "" && personilNRP != undefined) { + const data = { + memberIdentity: personilNRP, + email, + category: 7, + }; + + loading(); + const response = await requestOTP(data); + + if (response?.error) { + error(response?.message); + return false; + } + + close(); + handleDataNRP(); + setStepTwoActive(true); + } else { + console.log("identity empty"); + setUserIdentityValidate("Nomor identitas tidak boleh kosong"); + } + } else { + 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(); + setStepTwoActive(true); + } + } + }; + + const handleJournalistCertificate = async () => { + const response = await getDataJournalist(journalistCertificate); + const data = response?.data?.data; + console.log(data); + if (data) { + setJournalistData(data[0]); + } + + console.log("Data jurnalis:", data); + return data; + }; + + const handleTypeOTP = (event: any) => { + const { form } = event.target; + const index = [...form].indexOf(event.target); + form.elements[index + 1].focus(); + event.preventDefault(); + }; + + const handleResendOTP = async () => { + const timer = convertMilisecondsToHour(timerCount); + + if (timer == "00") { + handleSendOTP(); + restartTimer(); + } + }; + + function restartTimer() { + setTimerCount(60_000); + setRefreshTimer(true); + startInterval(); + } + + function startInterval() { + interval = setInterval(() => { + if (timerCount == 1000 && interval) { + clearInterval(interval); + console.log("Reset"); + } + + timerCount = timerCount == 0 ? 60_000 : timerCount; + setTimerCount((timerCount -= nSecondInMiliseconds)); + }, nSecondInMiliseconds); + } + + const handleDataNRP = async () => { + const response = await getDataPersonil(personilNRP); + const data = response?.data?.data; + setPersonilData(data); + console.log("Data personil:", data); + return data; + }; + + const handleInstituteOption = (e: any) => { + if (e.target.value == "0") { + setIsCustomActive(true); + } else { + setIsCustomActive(false); + setInstitusi(e.target.value); + } + }; + + // const { + // register, + // handleSubmit, + // watch, + // formState: { errors }, + // } = useForm(); + // const onSubmit: SubmitHandler = (data) => console.log(data); + // console.log(watch("example")); + + function setDataToForm() { + if (Number(category) == 6 && journalistData) { + setValue("firstName", journalistData.journalistName); + setValue("memberIdentity", journalistData.certNumber); + const selectedProvince = province.find((o: any) => o.provName == journalistData.province?.toUpperCase()); + + if (selectedProvince !== undefined) { + setValue("provinsi", selectedProvince.id); + } + } else if (Number(category) == 7 && personilData) { + setValue("firstName", personilData.lastName); + setValue("memberIdentity", personilData.nrp); + } + } + + const checkEmail = (e: any) => { + const regEmail = /^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\dA-Za-z\-]+\.)+[A-Za-z]{2,}))$/; + + if (regEmail.test(e)) { + setEmailValidate(""); + setEmail(e); + } else { + setEmailValidate(t("emailValid")); + setEmail(""); + } + }; + + async function onSubmit(data: any) { + console.log("Submit"); + + if (isValidPassword) { + MySwal.fire({ + title: "Buat akun", + text: "", + icon: "warning", + showCancelButton: true, + cancelButtonColor: "#d33", + confirmButtonColor: "#3085d6", + confirmButtonText: "Buat", + }).then((result) => { + if (result.isConfirmed) { + save(data); + } + }); + } else { + error("Kata Sandi harus sama"); + } + } + + const handleIdentity = async (e: any) => { + const id = e; + console.log(id); + await new Promise((resolve) => setTimeout(resolve, 2000)); // 2 sec + if (Number(category) == 5) { + const reqId = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dlZF9pbiI6ImxvZ2luX3RydWUiLCJpZF91c2VyX2FjY291bnQiOiJJRDExMyIsInVzZXJuYW1lIjoiaXdhbjc0M0BnbWFpbC5jb20iLCJub21lcktUUCI6bnVsbCwicm9sZSI6IjEiLCJyZWRpcmVjdF9sb2dpbl92YWxpZCI6IjM0IiwiY29udHJvbGxlciI6InN5c3RlbXMiLCJuYW1hX2xlbmdrYXAiOiJJd2FuIEphZWxhbmksIFMuS29tIiwiaWRXYXJnYSI6IjIwMTgxMjA2MTMwNTQ1Iiwia29kZV9yYWhhc2lhIjoiWGJqMHRRR2djWXdVMnYiLCJpZF9zZXNzaW9uIjoiMnRwMTZUV2VpTEhQN1o0RGpyYkt2TlVBelhHIiwicGVyc29uYWxfYXRhdV9sZW1iYWdhIjoiTEVNQkFHQSIsImV4cGlyZV9zZXNzaW9uIjoiMjAyMi0wMS0yNyAxNTowMzozNiJ9.Nzq3QqAlKaeKAdDujI9fGuj_mJcIIyWe8lvBI_Ui06o"; + const response = await getDataByNIK(reqId, id); + const data = response?.data?.data_return[0]; + + console.log(data); + if (data !== undefined) { + setValue("firstName", data.nama_lengkap); + setValue("address", data.alamat); + const selectedProvince = province.find((o: any) => o.provName == data.prov?.toUpperCase()); + + if (selectedProvince !== undefined) { + setValue("provinsi", selectedProvince.id); + } + + const selectedCity: any = city.find((o: any) => o.cityName == data.kab_kota?.toUpperCase()); + + if (selectedCity !== undefined) { + setValue("kota", selectedCity?.id); + } + + const selectedDistrict: any = districts.find((o: any) => o.disName == data.kec?.toUpperCase()); + + if (selectedDistrict !== undefined) { + setValue("kecamatan", selectedDistrict?.id); + } + } + } else if (Number(category) == 7) { + const reqId = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dlZF9pbiI6ImxvZ2luX3RydWUiLCJpZF91c2VyX2FjY291bnQiOiJJRDExMyIsInVzZXJuYW1lIjoiaXdhbjc0M0BnbWFpbC5jb20iLCJub21lcktUUCI6bnVsbCwicm9sZSI6IjEiLCJyZWRpcmVjdF9sb2dpbl92YWxpZCI6IjM0IiwiY29udHJvbGxlciI6InN5c3RlbXMiLCJuYW1hX2xlbmdrYXAiOiJJd2FuIEphZWxhbmksIFMuS29tIiwiaWRXYXJnYSI6IjIwMTgxMjA2MTMwNTQ1Iiwia29kZV9yYWhhc2lhIjoiWGJqMHRRR2djWXdVMnYiLCJpZF9zZXNzaW9uIjoiMnRwMTZUV2VpTEhQN1o0RGpyYkt2TlVBelhHIiwicGVyc29uYWxfYXRhdV9sZW1iYWdhIjoiTEVNQkFHQSIsImV4cGlyZV9zZXNzaW9uIjoiMjAyMi0wMS0yNyAxNTowMzozNiJ9.Nzq3QqAlKaeKAdDujI9fGuj_mJcIIyWe8lvBI_Ui06o"; + const response = await getDataByNRP(reqId, id); + const data = response?.data?.data_return[0]; + + console.log("Data :", data); + if (data !== undefined) { + setValue("firstName", data.nama); + setValue("address", data.lokasi); + } + } + }; + + const getCity = async (e: any) => { + const res = await listCity(e); + setCity(res?.data.data); + }; + + const getDistricts = async (e: any) => { + const res = await listDistricts(e); + setDistricts(res?.data.data); + }; + + const handleVerifyOTP = async () => { + const otp = `${otp1}${otp2}${otp3}${otp4}${otp5}${otp6}`; + const dummyOtp = "123456"; + if (email != "" && otpValue.length == 6) { + console.log("verify otp"); + loading(); + // const response = { + // message: "success", + // }; + const response = { + message: otpValue == dummyOtp ? "success" : "failed", + }; + + // const response = await verifyOTP(email, otp); + // if (response?.error) { + // error(response?.message); + // return false; + // } + close(); + // console.log(response); + if (response?.message == "success") { + console.log("success"); + setStepTwoActive(false); + setStepThreeActive(true); + setFormProfile(true); + setOtpValidate(""); + setValue("email", email); + setValue("memberIdentity", userIdentity); + handleIdentity(userIdentity); + setDataToForm(); + } else { + setOtpValidate("Kode OTP Tidak Valid"); + } + } + }; + + useEffect(() => { + async function initState() { + if (category != undefined) { + const resInstiution = await listInstitusi(category); + const res = await listProvince(); + setInstitution(resInstiution?.data?.data); + setProvince(res?.data?.data); + } + } + + initState(); + }, [category]); + + useEffect(() => { + function initState() { + for (const element of institution) { + const { id } = element; + + if (id == institusi) { + setInstitusiAddress(element.address); + } + } + } + + initState(); + }, [institusi]); + + return ( +
+
+
+ + + +
+
+ +
+
+ +
+
+
+
    +
  • +
    + 1 +
    +
  • +
    +
  • +
    + 2 +
    +
  • +
    +
  • +
    + 3 +
    +
  • +
+
+ +
+
+

{stepThreeActive ? t("userData") : t("registerFirst")}

+

+ {t("alreadyHave")}{" "} + + {t("logIn")} + +

+
+
+

{t("enterOTP")}

+

{t("checkInbox")}

+
+
+ +
+
+ + +
{errors.association?.message}
+
+ + {Number(category) == 7 ? ( +
+ + setPersonilNRP(event.target.value)} /> +

{userIdentityValidate}

+
+ ) : ( + "" + )} + {Number(category) == 6 ? ( +
+ + setJournalistCertificate(event.target.value)} /> +
+ ) : ( + "" + )} +
+ + checkEmail(event.target.value)} /> +

{emailValidate}

+
+
+ +
+
+ setOtpValue(e)}> + + setOtp1(e.target.value)} onKeyUp={handleTypeOTP} /> + setOtp2(e.target.value)} onKeyUp={handleTypeOTP} /> + + + + setOtp3(e.target.value)} onKeyUp={handleTypeOTP} /> + setOtp4(e.target.value)} onKeyUp={handleTypeOTP} /> + + + + setOtp5(e.target.value)} onKeyUp={handleTypeOTP} /> + setOtp6(e.target.value)} onKeyUp={handleTypeOTP} /> + + +
+

+ {otpValidate} +

+ +
+ +
+
+ {Number(category) == 6 || Number(category) == 7 ? ( +
+ + handleIdentity(e.target.value)} + disabled + /> +
{errors.memberIdentity?.message}
+
+ ) : ( + "" + )} +
+ + +
{errors.firstName?.message}
+
+
+ + { + setValUsername(e.target.value.trim()); + }} + onPaste={(e: any) => { + setValUsername(e.target.value.trim()); + }} + onCopy={(e: any) => { + setValUsername(e.target.value.trim()); + }} + /> +
{errors.username?.message}
+
+
+ + +
{errors.email?.message}
+
+
+
+ + +
{errors.phoneNumber?.message}
+
+
+ +