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

View File

@ -10,12 +10,12 @@ import { loginSchema, LoginFormData } from '../utils/validationSchemas';
import { useLogin } from './auth/core/_hooks'; import { useLogin } from './auth/core/_hooks';
export const Login = () => { export const Login = () => {
const { isAuthenticated, restoreSession } = useAuth(); const { isAuthenticated, isLoading, restoreSession } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const { mutate: login, isPending: isLoading } = useLogin(); const { mutate: login, isPending: isLoggingIn } = useLogin();
const { const {
register, register,
@ -26,6 +26,17 @@ export const Login = () => {
mode: 'onChange', 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) { if (isAuthenticated) {
return <Navigate to="/" replace />; return <Navigate to="/" replace />;
} }
@ -113,7 +124,7 @@ export const Login = () => {
<Button <Button
type="submit" type="submit"
loading={isLoading} loading={isLoggingIn}
disabled={!isValid} disabled={!isValid}
className="w-full" className="w-full"
> >

View File

@ -25,6 +25,49 @@ export interface AdminUser {
updated_at: string; 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 { export interface Tokens {
access_token: string; access_token: string;
refresh_token: string; refresh_token: string;
@ -43,6 +86,7 @@ export interface LoginRequest {
export interface AuthState { export interface AuthState {
isAuthenticated: boolean; isAuthenticated: boolean;
isLoading: boolean;
user: AdminUser | null; user: AdminUser | null;
permissions: Permission[]; permissions: Permission[];
allPermissions: Permission[]; allPermissions: Permission[];