"use client"; import { useState, useCallback, useEffect } from "react"; import { useRouter } from "@/components/navigation"; import { toast } from "sonner"; import Cookies from "js-cookie"; import { LoginFormData, ProfileData, AuthState, AuthContextType, EmailValidationData, OTPData, } from "@/types/auth"; import { login, getProfile, postEmailValidation, postSetupEmail, verifyOTPByUsername, doLogin, } from "@/service/auth"; import { setAuthCookies, setProfileCookies, clearAllCookies, isUserEligible, isProtectedRole, getNavigationPath, showAuthError, showAuthSuccess, loginRateLimiter, AUTH_CONSTANTS, } from "@/lib/auth-utils"; import { warning } from "@/lib/swal"; import { setCookiesEncrypt } from "@/lib/utils"; export const useAuth = (): AuthContextType => { const router = useRouter(); const [state, setState] = useState({ isAuthenticated: false, user: null, loading: false, error: null, }); useEffect(() => { const checkAuth = async () => { try { setState((prev) => ({ ...prev, loading: true })); } catch (error) { setState((prev) => ({ ...prev, isAuthenticated: false, user: null, error: "Authentication check failed", })); } finally { setState((prev) => ({ ...prev, loading: false })); } }; checkAuth(); }, []); const login = useCallback( async ( credentials: LoginFormData, options?: { skipRedirect?: boolean }, ): Promise => { try { setState((prev) => ({ ...prev, loading: true, error: null })); // Check rate limiting if (!loginRateLimiter.canAttempt(credentials.username)) { const remainingTime = loginRateLimiter.getRemainingTime( credentials.username, ); const minutes = Math.ceil(remainingTime / (60 * 1000)); throw new Error( `Too many login attempts. Please try again in ${minutes} minutes.`, ); } // Attempt login const response = await doLogin({ ...credentials, }); if (response?.error) { loginRateLimiter.recordAttempt(credentials.username); throw new Error("Invalid username or password"); } const { access_token, refresh_token } = response?.data?.data || {}; if (!access_token || !refresh_token) { throw new Error("Invalid response from server"); } // Set auth cookies setAuthCookies(access_token, refresh_token); // Get user profile const profileResponse = await getProfile(access_token); const profile = profileResponse?.data?.data; if (!profile) { throw new Error("Failed to fetch user profile"); } const dateTime: any = new Date(); const newTime: any = dateTime.getTime() + 10 * 60 * 1000; Cookies.set("access_token", response?.data?.data?.access_token, { expires: 1, }); Cookies.set("refresh_token", response?.data?.data?.refresh_token, { expires: 1, }); Cookies.set("time_refresh", newTime, { expires: 1, }); if (response?.data?.data?.approvalWorkflowInfo?.hasWorkflowSetup) { Cookies.set( "default_workflow", response?.data?.data?.approvalWorkflowInfo?.defaultWorkflowId, { expires: 1, }, ); } Cookies.set("is_first_login", "true", { secure: true, sameSite: "strict", }); // await saveActivity( // { // activityTypeId: 1, // url: "https://dev.mikulnews.com/auth", // userId: profile?.data?.data?.id, // }, // response?.data?.data?.access_token // ); Cookies.set("profile_picture", profile?.profilePictureUrl, { expires: 1, }); Cookies.set("uie", profile?.id, { expires: 1, }); Cookies.set("ufne", profile?.fullname, { expires: 1, }); Cookies.set("ulie", profile?.userLevelGroup, { expires: 1, }); Cookies.set("username", profile?.username, { expires: 1, }); setCookiesEncrypt("urie", profile?.userRoleId, { expires: 1, }); Cookies.set("roleName", profile?.roleName, { expires: 1, }); Cookies.set("masterPoldaId", profile?.masterPoldaId, { expires: 1, }); Cookies.set("ulne", profile?.userLevelId, { expires: 1, }); Cookies.set("urce", profile?.roleCode, { expires: 1, }); Cookies.set("email", profile?.email, { expires: 1, }); Cookies.set("status", "login", { expires: 1, }); // Reset rate limiter on successful login loginRateLimiter.resetAttempts(credentials.username); if (!options?.skipRedirect) { if (profile?.userRoleId === 4 || profile?.userRoleId === 5) { router.push("/"); } else { router.push("/admin/dashboard"); } } // if (profile?.userRoleId === 4 || profile?.userRoleId === 5) { // router.push("/"); // } else { // router.push("/admin/dashboard"); // } } catch (error: any) { const errorMessage = error?.message || "Login failed"; setState((prev) => ({ ...prev, loading: false, error: errorMessage, })); showAuthError(error, "Login failed"); } }, [router], ); const logout = useCallback((): void => { clearAllCookies(); setState({ isAuthenticated: false, user: null, loading: false, error: null, }); router.push("/auth"); }, [router]); const refreshToken = useCallback(async (): Promise => { try { setState((prev) => ({ ...prev, loading: true })); // Add token refresh logic here // This would typically call an API to refresh the access token } catch (error) { logout(); } finally { setState((prev) => ({ ...prev, loading: false })); } }, [logout]); return { ...state, login, logout, refreshToken, }; }; // Hook for email validation step export const useEmailValidation = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const validateEmail = useCallback( async (credentials: LoginFormData): Promise => { try { setLoading(true); setError(null); const response = await postEmailValidation(credentials); if (response?.error) { throw new Error(response?.message || "Email validation failed"); } const message = response?.data?.messages[0]; switch (message) { case "Continue to setup email": return "setup"; case "Email is valid and OTP has been sent": return "otp"; case "Username & password valid": return "success"; case "Skip to login": return "skip"; default: return "login"; } } catch (error: any) { const errorMessage = error?.message || "Email validation failed"; setError(errorMessage); showAuthError(error, "Email validation failed"); throw error; } finally { setLoading(false); } }, [], ); return { validateEmail, loading, error, }; }; // Hook for email setup step export const useEmailSetup = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const setupEmail = useCallback( async ( credentials: LoginFormData, emailData: EmailValidationData, ): Promise => { try { setLoading(true); setError(null); const data = { username: credentials.username, password: credentials.password, oldEmail: emailData.oldEmail, newEmail: emailData.newEmail, }; const response = await postSetupEmail(data); if (response?.error) { throw new Error(response.message || "Email setup failed"); } const message = response?.data?.message; switch (message) { case "Email is valid and OTP has been sent": return "otp"; case "The old email is not same": throw new Error("Email is invalid"); default: return "success"; } } catch (error: any) { const errorMessage = error?.message || "Email setup failed"; setError(errorMessage); showAuthError(error, "Email setup failed"); throw error; } finally { setLoading(false); } }, [], ); return { setupEmail, loading, error, }; }; // Hook for OTP verification export const useOTPVerification = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const verifyOTP = useCallback( async (username: string, otp: string): Promise => { try { setLoading(true); setError(null); if (otp.length !== 6) { throw new Error("OTP must be exactly 6 digits"); } const data = { username: username, otpCode: otp, }; const response = await verifyOTPByUsername(data); if (response?.error) { throw new Error(response.message || "OTP verification failed"); } return response?.message === "success"; } catch (error: any) { const errorMessage = error?.message || "OTP verification failed"; setError(errorMessage); showAuthError(error, "OTP verification failed"); throw error; } finally { setLoading(false); } }, [], ); return { verifyOTP, loading, error, }; };