"use client"; import { useState, useCallback, useEffect } from "react"; import { useRouter } from "@/components/navigation"; import { toast } from "sonner"; 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"; export const useAuth = (): AuthContextType => { const router = useRouter(); const [state, setState] = useState({ isAuthenticated: false, user: null, loading: false, error: null, }); // Check if user is authenticated on mount useEffect(() => { const checkAuth = async () => { try { setState((prev) => ({ ...prev, loading: true })); // Add logic to check if user is authenticated // This could check for valid tokens, etc. } 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): 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, grantType: AUTH_CONSTANTS.GRANT_TYPE, clientId: AUTH_CONSTANTS.CLIENT_ID, }); if (response?.error) { loginRateLimiter.recordAttempt(credentials.username); throw new Error("Invalid username or password"); } const { access_token, refresh_token } = response?.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: ProfileData = profileResponse?.data?.data; if (!profile) { throw new Error("Failed to fetch user profile"); } // Validate user eligibility // if (!isUserEligible(profile)) { // clearAllCookies(); // warning( // "Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri", // "/auth" // ); // return; // } // Set profile cookies setProfileCookies(profile); // Reset rate limiter on successful login loginRateLimiter.resetAttempts(credentials.username); // Navigate based on user role const navigationPath = getNavigationPath( profile.roleId, profile.userLevel?.id, profile.userLevel?.parentLevelId ); // Update state setState({ isAuthenticated: true, user: profile, loading: false, error: null, }); // Navigate to appropriate dashboard window.location.href = navigationPath; } 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?.message; 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 response = await verifyOTPByUsername(username, otp); 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, }; };