feat: add login page and auth logic
This commit is contained in:
		@@ -2,12 +2,13 @@ import React from 'react';
 | 
			
		||||
import { Route, Routes } from 'react-router-dom';
 | 
			
		||||
import MainLayout from './pages/MainLayout';
 | 
			
		||||
import ProtectedRoute from './pages/ProtectedRoute';
 | 
			
		||||
import LoginPage from './pages/LoginPage';
 | 
			
		||||
 | 
			
		||||
function App() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="App">
 | 
			
		||||
      <Routes>
 | 
			
		||||
        <Route path="/login" element={<div>login</div>} />
 | 
			
		||||
        <Route path="/login" element={<LoginPage />} />
 | 
			
		||||
        <Route element={<ProtectedRoute />}>
 | 
			
		||||
          <Route path="*" element={<MainLayout />}></Route>
 | 
			
		||||
        </Route>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import { Access, Auth } from '../types/auth';
 | 
			
		||||
import { User } from '../types/user';
 | 
			
		||||
import { AuthService } from '../services/auth';
 | 
			
		||||
import axiosRetry from 'axios-retry';
 | 
			
		||||
 | 
			
		||||
const baseURL = `${process.env.REACT_APP_HTTP_PROTOCOL}://${process.env.REACT_APP_API_URL}/api/v1`;
 | 
			
		||||
 | 
			
		||||
@@ -11,20 +14,75 @@ const base = axios.create({
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// base.interceptors.request.use((config) => {
 | 
			
		||||
//   const token = localStorage.getItem('accessToken');
 | 
			
		||||
//   if (token) {
 | 
			
		||||
//     config.headers.Authorization = `Bearer ${token}`;
 | 
			
		||||
//   }
 | 
			
		||||
//   return config;
 | 
			
		||||
// });
 | 
			
		||||
base.interceptors.request.use((config) => {
 | 
			
		||||
  const token = localStorage.getItem('accessToken');
 | 
			
		||||
  if (token) {
 | 
			
		||||
    config.headers.Authorization = `Bearer ${token}`;
 | 
			
		||||
  }
 | 
			
		||||
  return config;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
axiosRetry(base, {
 | 
			
		||||
  retries: 3,
 | 
			
		||||
  retryDelay: (retryCount: number) => {
 | 
			
		||||
    console.log(`retry attempt: ${retryCount}`);
 | 
			
		||||
    return retryCount * 2000;
 | 
			
		||||
  },
 | 
			
		||||
  retryCondition: async (error: any) => {
 | 
			
		||||
    if (error.code === 'ERR_CANCELED') {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
base.interceptors.response.use(
 | 
			
		||||
  (response) => {
 | 
			
		||||
    return response;
 | 
			
		||||
  },
 | 
			
		||||
  async function (error) {
 | 
			
		||||
    console.log('error', error);
 | 
			
		||||
    const originalRequest = error.response.config;
 | 
			
		||||
    console.log('originalRequest._retry', originalRequest);
 | 
			
		||||
    const urlTokens = error?.request?.responseURL.split('/');
 | 
			
		||||
    const url = urlTokens[urlTokens.length - 1];
 | 
			
		||||
    console.log('url', url);
 | 
			
		||||
    if (
 | 
			
		||||
      error.response.status === 401 &&
 | 
			
		||||
      !(originalRequest?._retry != null) &&
 | 
			
		||||
      url !== 'login' &&
 | 
			
		||||
      url !== 'refresh' &&
 | 
			
		||||
      url !== 'logout'
 | 
			
		||||
    ) {
 | 
			
		||||
      originalRequest._retry = true;
 | 
			
		||||
      const res = await AuthService.refresh().catch(async () => {
 | 
			
		||||
        await AuthService.logout();
 | 
			
		||||
      });
 | 
			
		||||
      console.log('res', res);
 | 
			
		||||
      return await base(originalRequest);
 | 
			
		||||
    }
 | 
			
		||||
    return await Promise.reject(error);
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const api = {
 | 
			
		||||
  // auth
 | 
			
		||||
  async login(auth: Auth): Promise<Access> {
 | 
			
		||||
    console.log(auth);
 | 
			
		||||
    const response = await base.post<Access>('/auth', auth);
 | 
			
		||||
    return response.data;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async refreshToken(): Promise<Access> {
 | 
			
		||||
    const response = await base.post<Access>('/auth/refresh');
 | 
			
		||||
    return response.data;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // user
 | 
			
		||||
  async getProfile(): Promise<User> {
 | 
			
		||||
    const response = await base.get<User>('/profile');
 | 
			
		||||
    return response.data;
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default api;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ import RunningProcessesPage from './RunningProcessesPage';
 | 
			
		||||
import AccountsPage from './AccountsPage';
 | 
			
		||||
import EventsListPage from './EventsListPage';
 | 
			
		||||
import ConfigurationPage from './ConfigurationPage';
 | 
			
		||||
import { useSetUserSelector } from '../store/user';
 | 
			
		||||
import { UserService } from '../services/user';
 | 
			
		||||
 | 
			
		||||
export default function MainLayout() {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
@@ -19,6 +21,8 @@ export default function MainLayout() {
 | 
			
		||||
  const [width, setWidth] = useState<number | string>('15%');
 | 
			
		||||
  const [collapsedWidth, setCollapsedWidth] = useState(50);
 | 
			
		||||
 | 
			
		||||
  const setUser = useSetUserSelector();
 | 
			
		||||
 | 
			
		||||
  const calculateWidths = () => {
 | 
			
		||||
    const windowWidth = window.innerWidth;
 | 
			
		||||
    const expanded = Math.min(Math.max(windowWidth * 0.15, 180), 240);
 | 
			
		||||
@@ -54,6 +58,21 @@ export default function MainLayout() {
 | 
			
		||||
    navigate(key);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const token = localStorage.getItem('accessToken');
 | 
			
		||||
    if (!token) {
 | 
			
		||||
      navigate('/login');
 | 
			
		||||
    } else {
 | 
			
		||||
      if (localStorage.getItem('user')) {
 | 
			
		||||
        setUser(JSON.parse(localStorage.getItem('user') as string));
 | 
			
		||||
      } else {
 | 
			
		||||
        UserService.getProfile().then((user) => {
 | 
			
		||||
          setUser(user);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Layout style={{ minHeight: '100vh' }}>
 | 
			
		||||
      <Sider
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								client/src/services/auth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								client/src/services/auth.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
import api from '../api/api';
 | 
			
		||||
import { useUserStore } from '../store/user';
 | 
			
		||||
import { Auth } from '../types/auth';
 | 
			
		||||
 | 
			
		||||
export class AuthService {
 | 
			
		||||
  static async login(auth: Auth) {
 | 
			
		||||
    const token = await api.login(auth);
 | 
			
		||||
    console.log(token);
 | 
			
		||||
    localStorage.setItem('accessToken', token.accessToken);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async logout() {
 | 
			
		||||
    useUserStore.getState().removeUser();
 | 
			
		||||
    localStorage.removeItem('userInfo');
 | 
			
		||||
    localStorage.removeItem('accessToken');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async refresh() {
 | 
			
		||||
    const token = await api.refreshToken();
 | 
			
		||||
    localStorage.setItem('accessToken', token.accessToken);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								client/src/services/user.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/src/services/user.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
import api from '../api/api';
 | 
			
		||||
import { User } from '../types/user';
 | 
			
		||||
 | 
			
		||||
export class UserService {
 | 
			
		||||
  static async getProfile(): Promise<User> {
 | 
			
		||||
    const user = api.getProfile();
 | 
			
		||||
 | 
			
		||||
    return user;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,6 +11,7 @@ type UserStoreState = {
 | 
			
		||||
 | 
			
		||||
type UserStoreActions = {
 | 
			
		||||
  setUser: (user: User) => void;
 | 
			
		||||
  removeUser: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type UserStore = UserStoreState & UserStoreActions;
 | 
			
		||||
@@ -22,6 +23,7 @@ export const useUserStore = create<UserStore>()(
 | 
			
		||||
        user: userInfo != null ? JSON.parse(userInfo) : ({} as User),
 | 
			
		||||
        loading: false,
 | 
			
		||||
        setUser: (user: User) => set({ user }),
 | 
			
		||||
        removeUser: () => set({ user: {} as User }),
 | 
			
		||||
      }),
 | 
			
		||||
      { name: 'userInfo' }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user