240 lines
6.5 KiB
TypeScript
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;
|
|
}
|