inogen/app/contexts/auth-context.tsx

240 lines
6.5 KiB
TypeScript

import React, { createContext, useContext, useState, useEffect } from "react";
import toast from "react-hot-toast";
import apiService from "~/lib/api";
interface User {
id: number;
name: string;
family: string;
email: string;
username: string;
mobile?: string;
nationalCode?: string;
status: boolean;
customTheme?: string;
}
interface Token {
id: number;
accessToken: string;
expAccessTokenStamp: string;
expAccessToken: string;
refreshToken: string;
expRefreshToken: string;
expRefreshTokenStamp: string;
}
interface AuthContextType {
user: User | null;
token: Token | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (username: string, password: string) => Promise<boolean>;
logout: () => void;
validateToken: () => Promise<boolean>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
interface AuthProviderProps {
children: React.ReactNode;
}
export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<Token | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Token validation function
const validateToken = async (tokenToValidate?: Token): Promise<boolean> => {
const currentToken = tokenToValidate || token;
if (!currentToken || !currentToken.accessToken) {
return false;
}
try {
// Check if token is expired using the expAccessTokenStamp
const expirationDate = new Date(currentToken.expAccessTokenStamp);
const currentDate = new Date();
if (expirationDate <= currentDate) {
// Token is expired
clearAuthData();
return false;
}
return true;
} catch (error) {
console.error("Token validation error:", error);
return false;
}
};
const clearAuthData = () => {
setUser(null);
setToken(null);
localStorage.removeItem("auth_user");
localStorage.removeItem("auth_token");
};
useEffect(() => {
const initAuth = async () => {
try {
// Check for existing user and token in localStorage on mount
const savedUser = localStorage.getItem("auth_user");
const savedToken = localStorage.getItem("auth_token");
if (savedUser && savedToken) {
try {
const userData = JSON.parse(savedUser);
const tokenData = JSON.parse(savedToken);
// Validate the saved token
const isValidToken = await validateToken(tokenData);
if (isValidToken) {
setUser(userData);
setToken(tokenData);
} else {
// Token is invalid, clear auth data
clearAuthData();
}
} catch (error) {
console.error("Error parsing saved user data:", error);
clearAuthData();
}
}
} catch (error) {
console.error("Auth initialization error:", error);
clearAuthData();
} finally {
setIsLoading(false);
}
};
initAuth();
}, []);
// Auto-validate token every 5 minutes
useEffect(() => {
if (!token || !user) return;
const interval = setInterval(
async () => {
const isValid = await validateToken();
if (!isValid) {
clearAuthData();
}
},
5 * 60 * 1000,
); // 5 minutes
return () => clearInterval(interval);
}, [token, user]);
const login = async (
username: string,
password: string,
): Promise<boolean> => {
if (!username || !password) {
toast.error("لطفاً تمام فیلدها را پر کنید");
return false;
}
setIsLoading(true);
try {
const result = await apiService.login(username, password);
if (result.success && result.data) {
const tokenData: Token = {
id: result.data.Token.ID,
accessToken: result.data.Token.AccessToken,
expAccessTokenStamp: result.data.Token.ExpAccessTokenStamp,
expAccessToken: result.data.Token.ExpAccessToken,
refreshToken: result.data.Token.RefreshToken,
expRefreshToken: result.data.Token.ExpRefreshToken,
expRefreshTokenStamp: result.data.Token.ExpRefreshTokenStamp,
};
const userData: User = {
id: result.data.Person.ID,
name: result.data.Person.Name,
family: result.data.Person.Family,
email: result.data.Person.Email,
username: result.data.Person.Username,
mobile: result.data.Person.Mobile,
nationalCode: result.data.Person.NationalCode,
status: result.data.Person.Status,
customTheme: result.data.Person.CustomeTheme,
};
// Validate the received token
const isValidToken = await validateToken(tokenData);
if (!isValidToken) {
toast.error("توکن دریافتی نامعتبر است");
return false;
}
setUser(userData);
setToken(tokenData);
// Save to localStorage
localStorage.setItem("auth_user", JSON.stringify(userData));
localStorage.setItem("auth_token", JSON.stringify(tokenData));
toast.success(`خوش آمدید ${userData.name} ${userData.family}!`);
return true;
} else {
toast.error(result.message || "نام کاربری یا رمز عبور اشتباه است");
return false;
}
} catch (error) {
console.error("Login error:", error);
const errorMessage =
error instanceof Error ? error.message : "خطای غیرمنتظره رخ داد";
toast.error(errorMessage);
return false;
} finally {
setIsLoading(false);
}
};
const logout = async () => {
try {
await apiService.logout();
} catch (error) {
console.error("Logout error:", error);
} finally {
// mark logout event to suppress next auth-required toast from guard
try {
sessionStorage.setItem("justLoggedOut", "1");
} catch {}
clearAuthData();
toast.success("با موفقیت خارج شدید", { id: "logout-success" });
}
};
const value: AuthContextType = {
user,
isAuthenticated: !!user && !!token,
isLoading,
login,
logout,
token,
validateToken,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}