kontenhumas-fe/hooks/use-auth.ts

393 lines
10 KiB
TypeScript

"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<AuthState>({
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<void> => {
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<void> => {
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<string | null>(null);
const validateEmail = useCallback(
async (credentials: LoginFormData): Promise<string> => {
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<string | null>(null);
const setupEmail = useCallback(
async (
credentials: LoginFormData,
emailData: EmailValidationData,
): Promise<string> => {
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<string | null>(null);
const verifyOTP = useCallback(
async (username: string, otp: string): Promise<boolean> => {
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,
};
};