auth: update authentication system

- Update AuthContext with new login response structure
- Add isLoading state to prevent premature redirects
- Implement proper session restoration from localStorage
- Update permission checking logic (id=1 = super admin)
- Fix login form to use new API structure
- Store tokens, user info, and permissions in localStorage
This commit is contained in:
hosseintaromi 2025-07-22 08:48:12 +03:30
parent fa755515bb
commit 4d385f2031
3 changed files with 88 additions and 18 deletions

View File

@ -1,26 +1,37 @@
import { createContext, useContext, useReducer, useEffect } from 'react';
import { AuthState, AdminUser, Permission } from '../types/auth';
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import { AuthState, AdminUser, Permission, LoginRequest } from '../types/auth';
import toast from 'react-hot-toast';
interface AuthContextType extends AuthState {
interface AuthContextType {
isAuthenticated: boolean;
isLoading: boolean;
user: AdminUser | null;
permissions: Permission[];
allPermissions: Permission[];
token: string | null;
refreshToken: string | null;
logout: () => void;
restoreSession: () => void;
hasPermission: (permissionId: number) => boolean;
hasPermissionByTitle: (title: string) => boolean;
restoreSession: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
type AuthAction =
| { type: 'LOGIN_SUCCESS'; payload: { user: AdminUser; permissions: Permission[]; allPermissions: Permission[]; token: string; refreshToken: string } }
| { type: 'LOGIN'; payload: { user: AdminUser; permissions: Permission[]; allPermissions: Permission[]; token: string; refreshToken: string } }
| { type: 'LOGOUT' }
| { type: 'RESTORE_SESSION'; payload: { user: AdminUser; permissions: Permission[]; allPermissions: Permission[]; token: string; refreshToken: string } };
| { type: 'RESTORE_SESSION'; payload: { user: AdminUser; permissions: Permission[]; allPermissions: Permission[]; token: string; refreshToken: string } }
| { type: 'SET_LOADING'; payload: boolean };
const authReducer = (state: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
case 'LOGIN_SUCCESS':
case 'LOGIN':
case 'RESTORE_SESSION':
return {
...state,
isAuthenticated: true,
isLoading: false,
user: action.payload.user,
permissions: action.payload.permissions,
allPermissions: action.payload.allPermissions,
@ -29,21 +40,19 @@ const authReducer = (state: AuthState, action: AuthAction): AuthState => {
};
case 'LOGOUT':
return {
...state,
isAuthenticated: false,
isLoading: false,
user: null,
permissions: [],
allPermissions: [],
token: null,
refreshToken: null,
};
case 'RESTORE_SESSION':
case 'SET_LOADING':
return {
isAuthenticated: true,
user: action.payload.user,
permissions: action.payload.permissions,
allPermissions: action.payload.allPermissions,
token: action.payload.token,
refreshToken: action.payload.refreshToken,
...state,
isLoading: action.payload,
};
default:
return state;
@ -52,6 +61,7 @@ const authReducer = (state: AuthState, action: AuthAction): AuthState => {
const initialState: AuthState = {
isAuthenticated: false,
isLoading: true,
user: null,
permissions: [],
allPermissions: [],
@ -63,6 +73,8 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [state, dispatch] = useReducer(authReducer, initialState);
const restoreSession = () => {
dispatch({ type: 'SET_LOADING', payload: true });
const token = localStorage.getItem('admin_token');
const refreshToken = localStorage.getItem('admin_refresh_token');
const userStr = localStorage.getItem('admin_user');
@ -88,7 +100,10 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
localStorage.removeItem('admin_refresh_token');
localStorage.removeItem('admin_user');
localStorage.removeItem('admin_permissions');
dispatch({ type: 'SET_LOADING', payload: false });
}
} else {
dispatch({ type: 'SET_LOADING', payload: false });
}
};
@ -127,9 +142,9 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
<AuthContext.Provider value={{
...state,
logout,
restoreSession,
hasPermission,
hasPermissionByTitle,
restoreSession,
}}>
{children}
</AuthContext.Provider>

View File

@ -10,12 +10,12 @@ import { loginSchema, LoginFormData } from '../utils/validationSchemas';
import { useLogin } from './auth/core/_hooks';
export const Login = () => {
const { isAuthenticated, restoreSession } = useAuth();
const { isAuthenticated, isLoading, restoreSession } = useAuth();
const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState('');
const { mutate: login, isPending: isLoading } = useLogin();
const { mutate: login, isPending: isLoggingIn } = useLogin();
const {
register,
@ -26,6 +26,17 @@ export const Login = () => {
mode: 'onChange',
});
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
<p className="mt-4 text-gray-600 dark:text-gray-400">در حال بارگذاری...</p>
</div>
</div>
);
}
if (isAuthenticated) {
return <Navigate to="/" replace />;
}
@ -113,7 +124,7 @@ export const Login = () => {
<Button
type="submit"
loading={isLoading}
loading={isLoggingIn}
disabled={!isValid}
className="w-full"
>

View File

@ -25,6 +25,49 @@ export interface AdminUser {
updated_at: string;
}
// Complete AdminUserInfo from swagger (with roles and permissions)
export interface AdminUserInfo {
id: number;
username: string;
first_name: string;
last_name: string;
status: "active" | "deactive";
permissions: Permission[];
roles: Role[];
created_at: string;
updated_at: string;
}
// Admin User Management Types
export interface CreateAdminUserRequest {
first_name: string;
last_name: string;
username: string;
password: string;
status: string;
}
export interface UpdateAdminUserRequest {
id: number;
first_name: string;
last_name: string;
username: string;
password?: string;
status: string;
}
export interface AdminUsersListResponse {
users: AdminUserInfo[];
}
export interface AdminUserDetailResponse {
user: AdminUserInfo;
}
export interface DeleteAdminUserResponse {
message: string;
}
export interface Tokens {
access_token: string;
refresh_token: string;
@ -43,6 +86,7 @@ export interface LoginRequest {
export interface AuthState {
isAuthenticated: boolean;
isLoading: boolean;
user: AdminUser | null;
permissions: Permission[];
allPermissions: Permission[];