1. 로그인
🎯 관리자 사이트는 무조건 로그인 필수
일반 페이지와 로그인 페이지를 완전히 분리하기
routes.tsx
생성
Copy const routes = [
{ path : '/login' , element : < LoginPage /> } ,
{
element : < Layout /> ,
children : [
{ path : '/' , element : < HomePage /> } ,
{ path : '/users' , element : < UserListPage /> } ,
{ path : '/categories' , element : < CategoryListPage /> } ,
{ path : '/categories/new' , element : < CategoryNewPage /> } ,
{ path : '/categories/:id/edit' , element : < CategoryEditPage /> } ,
{ path : '/products' , element : < ProductListPage /> } ,
{ path : '/products/new' , element : < ProductNewPage /> } ,
{ path : '/products/:id' , element : < ProductDetailPage /> } ,
{ path : '/products/:id/edit' , element : < ProductEditPage /> } ,
{ path : '/orders' , element : < OrderListPage /> } ,
{ path : '/orders/:id' , element : < OrderDetailPage /> } ,
{ path : '/orders/:id/edit' , element : < OrderEditPage /> } ,
] ,
} ,
];
LoginPage
구현
레이아웃이 적용되지 않아 스스로 꾸며줘야 한다는 점을 제외하면 기존과 동일
Layout
구현
로그인이 올바르게 된 경우에만 렌더링하도록 확실히 제약하기
Copy export default function Layout () {
const ready = useCheckAccessToken ();
if ( ! ready) {
return null ;
}
return (
< Container >
< Header />
< Outlet />
</ Container >
);
}
useCheckAccessToken hook 생성
로그인 페이지로 리다이렉션 하거나 ready 라고 안내하기
Copy export default function useCheckAccessToken () : boolean {
// Access Token 검증이 끝나면 ready가 된다.
const [ ready , setReady ] = useState ( false );
const { accessToken , setAccessToken } = useAccessToken ();
const navigate = useNavigate ();
useEffect (() => {
const fetchCurrentUser = async () => {
try {
await apiService .fetchCurrentUser ();
setReady ( true ); // 👈 확인됨!
} catch (e) {
setAccessToken ( '' );
}
};
fetchCurrentUser ();
} , [accessToken , setAccessToken]);
// 기존과 다른 부분
useEffect (() => {
if ( ! accessToken) {
navigate ( '/login' );
}
} , [accessToken , navigate]);
return ready;
}
헤더 구현하기
관리자로 로그인 했거나 하지 않았거나 둘 중 하나일 수 밖에 없기 때문에 단순하게 구성할 수 있음
Copy export default function Header () {
const navigate = useNavigate ();
const { setAccessToken } = useAccessToken ();
const handleClickLogout = async () => {
await apiService .logout ();
setAccessToken ( '' );
navigate ( '/' );
};
return (
< Container >
< h1 >Shop Administrator</ h1 >
< nav >
< ul >
< li >
< Link to = "/" >Home</ Link >
</ li >
{ /*...*/ }
< li >
< Button onClick = {handleClickLogout}>Logout</ Button >
</ li >
</ ul >
</ nav >
</ Container >
);
}
2. 사용자 관리
사용자 관리는 사실 대단히 복잡할 수 있다.
사용자와 관련된 정보가 많기 때문에 이를 모두 다루는 것 자체도 문제고,
고객 지원 업무에서도 사용자는 핵심 축이 되기 때문에 제대로 된 사용자 관리는 정말 복잡한 일이다.
하지만 여기서는 단순한 CRUD , 그것도 사용자 목록 정도만 확인하는 작업에 집중한다.
SWR 를 써서 단순 작업을 얼마나 간단히 처리할 수 있는지 확인해 보자.
사용자 목록
🎯 간단한 CRUD로 사용자 목록을 표시하가
Copy export default function UserListPage () {
const { users , loading , error } = useFetchUsers ();
// loading, error 처리
return (
< Container >
< h2 >Users</ h2 >
< table >
< thead >
< tr >
< th >이름</ th >
< th >이메일</ th >
< th >역할</ th >
</ tr >
</ thead >
< tbody >
{ users .map ((user) => (
< tr key = { user .id}>
< td >{ user .name}</ td >
< td >{ user .email}</ td >
< td >{ user .role}</ td >
</ tr >
))}
</ tbody >
</ table >
</ Container >
);
}
useFetchUsers hook 생성
SWR을 바로 쓰지 않고 개별 자료형에 대응하기 위해 한번 감싸주는 형태로 구성
Copy export default function useFetchUsers () {
const { data , error , loading } = useFetch <{
users : User [];
}>( '/users' );
return {
users : data ?.users ?? [] ,
error ,
loading ,
};
}
useFetch hook 생성
Copy import useSWR from 'swr' ;
import { apiService } from '../services/ApiService' ;
const API_BASE_URL = process . env . API_BASE_URL || 'http://localhost:3000/admin' ;
export default function useFetch < Data >(path : string ) {
const url = ` ${ API_BASE_URL }${ path } ` ;
const {
data , error , isLoading , mutate ,
} = useSWR < Data >(url , apiService .fetcher ());
return {
data ,
error ,
loading : isLoading ,
mutate ,
};
}
ApiService에서 fetcher 제공
SWR에서 Axios 사용하기
Copy fetcher () {
return async (url : string ) => {
const { data } = await this . instance .get (url);
return data;
};
}
데이터를 변경하지만 않으면 이런 단순한 형태로 운영할 수 있음