2025-11-23 13:29:31 +00:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
|
import { useRouter } from "next/navigation";
|
2025-11-23 22:28:02 +00:00
|
|
|
import { useCallback, useEffect } from "react";
|
2025-11-23 13:29:31 +00:00
|
|
|
import {
|
|
|
|
|
loginUser,
|
|
|
|
|
registerUser,
|
|
|
|
|
verifyOtp,
|
|
|
|
|
resendOtp,
|
|
|
|
|
forgotPassword,
|
|
|
|
|
verifyPasswordResetOtp,
|
|
|
|
|
resetPassword,
|
|
|
|
|
refreshToken,
|
|
|
|
|
getStoredTokens,
|
|
|
|
|
getStoredUser,
|
2025-11-25 21:25:53 +00:00
|
|
|
getStoredUserSync,
|
2025-11-23 13:29:31 +00:00
|
|
|
storeTokens,
|
|
|
|
|
storeUser,
|
|
|
|
|
clearAuthData,
|
2025-11-23 22:28:02 +00:00
|
|
|
isTokenExpired,
|
|
|
|
|
hasValidAuth,
|
2025-11-23 13:29:31 +00:00
|
|
|
} from "@/lib/actions/auth";
|
|
|
|
|
import type {
|
|
|
|
|
LoginInput,
|
|
|
|
|
RegisterInput,
|
|
|
|
|
VerifyOtpInput,
|
|
|
|
|
ResendOtpInput,
|
|
|
|
|
ForgotPasswordInput,
|
|
|
|
|
VerifyPasswordResetOtpInput,
|
|
|
|
|
ResetPasswordInput,
|
|
|
|
|
} from "@/lib/schema/auth";
|
|
|
|
|
import type { User } from "@/lib/models/auth";
|
2025-11-23 22:28:02 +00:00
|
|
|
import { toast } from "sonner";
|
2025-11-23 13:29:31 +00:00
|
|
|
|
|
|
|
|
export function useAuth() {
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
|
|
|
|
|
// Get current user from storage
|
|
|
|
|
const { data: user } = useQuery<User | null>({
|
|
|
|
|
queryKey: ["auth", "user"],
|
|
|
|
|
queryFn: () => getStoredUser(),
|
|
|
|
|
staleTime: Infinity,
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-23 22:28:02 +00:00
|
|
|
// Check if user is authenticated with valid token
|
|
|
|
|
const isAuthenticated = !!user && hasValidAuth();
|
2025-11-23 13:29:31 +00:00
|
|
|
|
2025-11-23 21:13:18 +00:00
|
|
|
// Check if user is admin (check multiple possible field names)
|
|
|
|
|
const isAdmin =
|
|
|
|
|
user?.is_admin === true ||
|
|
|
|
|
(user as any)?.isAdmin === true ||
|
|
|
|
|
(user as any)?.is_staff === true ||
|
|
|
|
|
(user as any)?.isStaff === true ||
|
|
|
|
|
(user as any)?.is_superuser === true ||
|
|
|
|
|
(user as any)?.isSuperuser === true;
|
2025-11-23 13:29:31 +00:00
|
|
|
|
|
|
|
|
// Login mutation
|
|
|
|
|
const loginMutation = useMutation({
|
|
|
|
|
mutationFn: (input: LoginInput) => loginUser(input),
|
|
|
|
|
onSuccess: (data) => {
|
|
|
|
|
if (data.tokens && data.user) {
|
|
|
|
|
storeTokens(data.tokens);
|
|
|
|
|
storeUser(data.user);
|
|
|
|
|
queryClient.setQueryData(["auth", "user"], data.user);
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ["auth"] });
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Register mutation
|
|
|
|
|
const registerMutation = useMutation({
|
|
|
|
|
mutationFn: (input: RegisterInput) => registerUser(input),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Verify OTP mutation
|
|
|
|
|
const verifyOtpMutation = useMutation({
|
|
|
|
|
mutationFn: (input: VerifyOtpInput) => verifyOtp(input),
|
|
|
|
|
onSuccess: (data) => {
|
|
|
|
|
if (data.tokens && data.user) {
|
|
|
|
|
storeTokens(data.tokens);
|
|
|
|
|
storeUser(data.user);
|
|
|
|
|
queryClient.setQueryData(["auth", "user"], data.user);
|
|
|
|
|
queryClient.invalidateQueries({ queryKey: ["auth"] });
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Resend OTP mutation
|
|
|
|
|
const resendOtpMutation = useMutation({
|
|
|
|
|
mutationFn: (input: ResendOtpInput) => resendOtp(input),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Forgot password mutation
|
|
|
|
|
const forgotPasswordMutation = useMutation({
|
|
|
|
|
mutationFn: (input: ForgotPasswordInput) => forgotPassword(input),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Verify password reset OTP mutation
|
|
|
|
|
const verifyPasswordResetOtpMutation = useMutation({
|
|
|
|
|
mutationFn: (input: VerifyPasswordResetOtpInput) => verifyPasswordResetOtp(input),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Reset password mutation
|
|
|
|
|
const resetPasswordMutation = useMutation({
|
|
|
|
|
mutationFn: (input: ResetPasswordInput) => resetPassword(input),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Refresh token mutation
|
|
|
|
|
const refreshTokenMutation = useMutation({
|
|
|
|
|
mutationFn: (refresh: string) => refreshToken({ refresh }),
|
|
|
|
|
onSuccess: (tokens) => {
|
|
|
|
|
storeTokens(tokens);
|
2025-11-23 22:28:02 +00:00
|
|
|
queryClient.invalidateQueries({ queryKey: ["auth"] });
|
|
|
|
|
},
|
|
|
|
|
onError: () => {
|
|
|
|
|
// If refresh fails, logout
|
|
|
|
|
clearAuthData();
|
|
|
|
|
queryClient.clear();
|
2025-11-23 13:29:31 +00:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Logout function
|
|
|
|
|
const logout = useCallback(() => {
|
|
|
|
|
clearAuthData();
|
|
|
|
|
queryClient.clear();
|
2025-11-23 21:13:18 +00:00
|
|
|
// Don't redirect here - let components handle redirect as needed
|
|
|
|
|
}, [queryClient]);
|
2025-11-23 13:29:31 +00:00
|
|
|
|
2025-11-23 22:28:02 +00:00
|
|
|
// Auto-logout if token is expired or missing
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const checkAuth = () => {
|
|
|
|
|
const tokens = getStoredTokens();
|
|
|
|
|
const storedUser = getStoredUser();
|
|
|
|
|
|
|
|
|
|
// If user exists but no token or token is expired, logout
|
|
|
|
|
if (storedUser && (!tokens.access || isTokenExpired(tokens.access))) {
|
|
|
|
|
// Try to refresh token first if refresh token exists
|
|
|
|
|
if (tokens.refresh && !isTokenExpired(tokens.refresh)) {
|
|
|
|
|
refreshTokenMutation.mutate(tokens.refresh, {
|
|
|
|
|
onError: () => {
|
|
|
|
|
// If refresh fails, logout
|
|
|
|
|
clearAuthData();
|
|
|
|
|
queryClient.clear();
|
|
|
|
|
toast.error("Your session has expired. Please log in again.");
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// No valid refresh token, logout immediately
|
|
|
|
|
clearAuthData();
|
|
|
|
|
queryClient.clear();
|
|
|
|
|
toast.error("Your session has expired. Please log in again.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Check immediately
|
|
|
|
|
checkAuth();
|
|
|
|
|
|
|
|
|
|
// Check every 30 seconds
|
|
|
|
|
const interval = setInterval(checkAuth, 30000);
|
|
|
|
|
|
|
|
|
|
return () => clearInterval(interval);
|
|
|
|
|
}, [queryClient, refreshTokenMutation]);
|
|
|
|
|
|
2025-11-23 13:29:31 +00:00
|
|
|
// Login function
|
|
|
|
|
const login = useCallback(
|
|
|
|
|
async (input: LoginInput) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await loginMutation.mutateAsync(input);
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[loginMutation]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Register function
|
|
|
|
|
const register = useCallback(
|
|
|
|
|
async (input: RegisterInput) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await registerMutation.mutateAsync(input);
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[registerMutation]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Verify OTP function
|
|
|
|
|
const verifyOtpCode = useCallback(
|
|
|
|
|
async (input: VerifyOtpInput) => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await verifyOtpMutation.mutateAsync(input);
|
|
|
|
|
return result;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[verifyOtpMutation]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// State
|
|
|
|
|
user,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
isAdmin,
|
|
|
|
|
isLoading: loginMutation.isPending || registerMutation.isPending,
|
|
|
|
|
|
|
|
|
|
// Actions
|
|
|
|
|
login,
|
|
|
|
|
register,
|
|
|
|
|
logout,
|
|
|
|
|
verifyOtp: verifyOtpCode,
|
|
|
|
|
|
|
|
|
|
// Mutations (for direct access if needed)
|
|
|
|
|
loginMutation,
|
|
|
|
|
registerMutation,
|
|
|
|
|
verifyOtpMutation,
|
|
|
|
|
resendOtpMutation,
|
|
|
|
|
forgotPasswordMutation,
|
|
|
|
|
verifyPasswordResetOtpMutation,
|
|
|
|
|
resetPasswordMutation,
|
|
|
|
|
refreshTokenMutation,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|