diff --git a/src/App.tsx b/src/App.tsx index f450a916..0251c267 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,10 @@ -import AppRoutes from './routes/AppRoutes'; import { ThemeProvider } from 'styled-components'; import { GlobalStyle } from './style/global'; import { defaultTheme } from './style/theme'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { SearchFilteringProvider } from './context/SearchFilteringContext'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import MergeRoutes from './routes/MergeRoutes'; const queryClient = new QueryClient({ defaultOptions: { @@ -22,7 +22,7 @@ function App() { - + diff --git a/src/api/http.api.ts b/src/api/http.api.ts index 7df96e74..18e9ef22 100644 --- a/src/api/http.api.ts +++ b/src/api/http.api.ts @@ -67,7 +67,6 @@ export const createClient = (config?: AxiosRequestConfig) => { // } logout(); - useAuthStore.persist.clearStorage(); window.location.href = '/login'; return Promise.reject(error); } diff --git a/src/assets/logout.svg b/src/assets/logout.svg new file mode 100644 index 00000000..299f084a --- /dev/null +++ b/src/assets/logout.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/src/components/common/admin/sidebar/AdminSidebar.styled.ts b/src/components/common/admin/sidebar/AdminSidebar.styled.ts new file mode 100644 index 00000000..f6de17c5 --- /dev/null +++ b/src/components/common/admin/sidebar/AdminSidebar.styled.ts @@ -0,0 +1,42 @@ +import styled from 'styled-components'; + +export const SidebarContainer = styled.section` + height: 100vh; + padding: 1rem; + width: 15rem; + border-right: 1px solid ${({ theme }) => theme.color.grey}; +`; + +export const SidebarLogoWrapper = styled.div` + display: flex; + justify-content: space-around; + align-items: flex-end; + margin: 0 1.5rem 1.5rem 0; +`; + +export const SidebarLogoImg = styled.img` + width: 5rem; +`; + +export const LogoutButton = styled.button` + display: flex; + flex-direction: column; + margin-bottom: 0.3rem; +`; + +export const LogoutImg = styled.img` + width: 2rem; + filter: invert(70%); +`; + +export const LogoutSpan = styled.span` + font-size: 0.5rem; + color: ${({ theme }) => theme.color.deepGrey}; + margin-left: -7px; +`; + +export const MovedListContainerAll = styled.nav` + margin-top: 1.5rem; + display: grid; + gap: 1.5rem; +`; diff --git a/src/components/common/admin/sidebar/AdminSidebar.tsx b/src/components/common/admin/sidebar/AdminSidebar.tsx new file mode 100644 index 00000000..e91064f6 --- /dev/null +++ b/src/components/common/admin/sidebar/AdminSidebar.tsx @@ -0,0 +1,44 @@ +import { SIDEBAR_LIST } from '../../../../constants/admin/sidebar'; +import * as S from './AdminSidebar.styled'; +import logo from '../../../../assets/mainlogo.svg'; +import logoutIcon from '../../../../assets/logout.svg'; +import AdminSidebarList from './sidebarList/AdminSidebarList'; +import ContentBorder from '../../contentBorder/ContentBorder'; +import useAuthStore from '../../../../store/authStore'; +import { useNavigate } from 'react-router-dom'; +import { ROUTES } from '../../../../constants/routes'; + +export default function AdminSidebar() { + const navigate = useNavigate(); + const logout = useAuthStore((state) => state.logout); + + const handleClickLogout = () => { + logout(); + setTimeout(() => { + navigate(ROUTES.main); + }, 1000); + }; + + return ( + + + + + + Logout + + + + + + + + + + ); +} diff --git a/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.styled.ts b/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.styled.ts new file mode 100644 index 00000000..de625e0a --- /dev/null +++ b/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.styled.ts @@ -0,0 +1,35 @@ +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +export const MovedListContainer = styled.nav``; + +export const MovedListLink = styled(Link)` + display: flex; + align-items: center; + gap: 1rem; + padding: 0.8rem; + + &:hover { + color: ${({ theme }) => theme.color.deepGrey}; + } +`; + +export const MovedListIcon = styled.div` + display: flex; + align-items: center; + + svg { + width: 1.5rem; + } +`; + +export const MovedList = styled.div` + font-size: 1.2rem; +`; + +export const MovedListTitleWrapper = styled.div``; + +export const MovedListTitle = styled.h3` + padding: 0.5rem; + color: ${({ theme }) => theme.color.deepGrey}; +`; diff --git a/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx b/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx new file mode 100644 index 00000000..2544768d --- /dev/null +++ b/src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx @@ -0,0 +1,54 @@ +import * as S from './AdminSidebarList.styled'; +import { + ArrowRightStartOnRectangleIcon, + ChatBubbleBottomCenterTextIcon, + EnvelopeIcon, + ExclamationTriangleIcon, + HomeIcon, + MegaphoneIcon, + PhotoIcon, + TagIcon, + UserGroupIcon, +} from '@heroicons/react/24/outline'; + +const iconMap = { + mainPage: , + movedSite: , + notice: , + banner: , + tags: , + allUser: , + reports: , + inquiries: , + manage: , +}; + +type IconKey = keyof typeof iconMap; + +interface AdminSidebarListProps { + title: string; + list: readonly { name: IconKey; title: string; router: string }[]; +} + +export default function AdminSidebarList({ + title, + list, +}: AdminSidebarListProps) { + return ( + + + {title && {title}} + + {list.map((item) => ( + + {iconMap[item.name]} + {item.title} + + ))} + + ); +} diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index a8c6c482..3bd8513a 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -17,7 +17,7 @@ import { formatImgPath } from '../../../util/formatImgPath'; // import { useEffect } from 'react'; // import { testLiveAlarm } from '../../../api/alarm.api'; import { useMyProfileInfo } from '../../../hooks/user/useMyInfo'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; function Header() { const location = useLocation(); @@ -26,6 +26,12 @@ function Header() { const isLoggedIn = useAuthStore((state) => state.isLoggedIn); const { myData, isLoading } = useMyProfileInfo(); + const handleClickLogout = () => { + userLogout(); + useAuthStore.persist.clearStorage(); + localStorage.clear(); + }; + // const { signalData, setSignalData } = useNotification(); // useEffect(() => { @@ -100,7 +106,7 @@ function Header() { e.preventDefault()}> 로그아웃 diff --git a/src/components/common/header/Notification/Notification.tsx b/src/components/common/header/Notification/Notification.tsx index cf8daa84..8265e347 100644 --- a/src/components/common/header/Notification/Notification.tsx +++ b/src/components/common/header/Notification/Notification.tsx @@ -5,7 +5,7 @@ import LoadingSpinner from '../../loadingSpinner/LoadingSpinner'; import useAlarmList from '../../../../hooks/user/useAlarmList'; import arrow_right from '../../../../assets/ArrowRight.svg'; import { useNavigate } from 'react-router-dom'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; const Notification = () => { const navigate = useNavigate(); diff --git a/src/components/user/comment/commentComponent/commentComponent/CommentComponent.tsx b/src/components/user/comment/commentComponent/commentComponent/CommentComponent.tsx index 69cccde1..721403f8 100644 --- a/src/components/user/comment/commentComponent/commentComponent/CommentComponent.tsx +++ b/src/components/user/comment/commentComponent/commentComponent/CommentComponent.tsx @@ -4,7 +4,7 @@ import chat from '../../../../../assets/chat.svg'; import { Link } from 'react-router-dom'; import CommentInput from '../../commentInput/CommentInput'; import type { CommentType } from '../../../../../models/comment'; -import { ROUTES } from '../../../../../constants/user/routes'; +import { ROUTES } from '../../../../../constants/routes'; import Avatar from '../../../../common/avatar/Avatar'; interface CommentComponentProps { diff --git a/src/components/user/comment/replyComponent/ReplyComponent.tsx b/src/components/user/comment/replyComponent/ReplyComponent.tsx index 5cd9b6ae..fe2af8b7 100644 --- a/src/components/user/comment/replyComponent/ReplyComponent.tsx +++ b/src/components/user/comment/replyComponent/ReplyComponent.tsx @@ -8,7 +8,7 @@ import CommentInput from '../commentInput/CommentInput'; import useGetReply from '../../../../hooks/user/CommentHooks/useGetReply'; import LoadingSpinner from '../../../common/loadingSpinner/LoadingSpinner'; import { Link } from 'react-router-dom'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; import dropdownButton from '../../../../assets/dropdownButton.svg'; interface ReplyComponentProps { diff --git a/src/components/user/customerService/MoveInquiredLink.tsx b/src/components/user/customerService/MoveInquiredLink.tsx index a3ada3b7..e0a06092 100644 --- a/src/components/user/customerService/MoveInquiredLink.tsx +++ b/src/components/user/customerService/MoveInquiredLink.tsx @@ -1,5 +1,5 @@ import { useLocation } from 'react-router-dom'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import * as S from './MoveInquiredLink.styled'; export default function MovedInquiredLink() { diff --git a/src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx b/src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx index 1cae25b3..609094f9 100644 --- a/src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx +++ b/src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx @@ -1,4 +1,4 @@ -import { ROUTES } from '../../../../../../constants/user/routes'; +import { ROUTES } from '../../../../../../constants/routes'; import ContentBorder from '../../../../../common/contentBorder/ContentBorder'; import * as S from './ListButton.styled'; diff --git a/src/components/user/customerService/noticeDetail/bottom/button/OtherNoticeButton.tsx b/src/components/user/customerService/noticeDetail/bottom/button/OtherNoticeButton.tsx index 7c98295d..1cd3478d 100644 --- a/src/components/user/customerService/noticeDetail/bottom/button/OtherNoticeButton.tsx +++ b/src/components/user/customerService/noticeDetail/bottom/button/OtherNoticeButton.tsx @@ -1,4 +1,4 @@ -import { ROUTES } from '../../../../../../constants/user/routes'; +import { ROUTES } from '../../../../../../constants/routes'; import type { OtherNotice } from '../../../../../../models/customerService'; import { formatDate } from '../../../../../../util/format'; import * as S from './OtherNoticeButton.styled'; diff --git a/src/components/user/home/projectCardLists/ProjectCardLists.tsx b/src/components/user/home/projectCardLists/ProjectCardLists.tsx index 3c62ab01..45783e36 100644 --- a/src/components/user/home/projectCardLists/ProjectCardLists.tsx +++ b/src/components/user/home/projectCardLists/ProjectCardLists.tsx @@ -3,7 +3,7 @@ import { useProjectCardListData } from '../../../../hooks/user/useProjectCardLis import CardList from './cardList/CardList'; import * as S from './ProjectCardLists.styled'; import { Link } from 'react-router-dom'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; import EmptyLoading from '../../../common/emptyLoading/EmptyLoading'; import NoResult from '../../../common/noResult/NoResult'; import { useSaveSearchFiltering } from '../../../../hooks/user/useSaveSearchFiltering'; diff --git a/src/components/user/manageProjects/Card.tsx b/src/components/user/manageProjects/Card.tsx index ab5cfcab..10da8a64 100644 --- a/src/components/user/manageProjects/Card.tsx +++ b/src/components/user/manageProjects/Card.tsx @@ -2,7 +2,7 @@ import * as S from './Card.styled'; import type { ManagedProject } from '../../../models/manageMyProject'; import AvatarList from '../../common/avatar/AvatarList'; import { formatDate } from '../../../util/formatDate'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; interface CardProps { project: ManagedProject; } diff --git a/src/components/user/manageProjects/CardList.tsx b/src/components/user/manageProjects/CardList.tsx index 544775b4..ca0c3b58 100644 --- a/src/components/user/manageProjects/CardList.tsx +++ b/src/components/user/manageProjects/CardList.tsx @@ -2,7 +2,7 @@ import * as S from './CardList.styled'; import type { ManagedProject } from '../../../models/manageMyProject'; import Card from './Card'; import CreateButton from '../../../assets/createProjectButton.svg'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; interface CardListProps { projects: ManagedProject[]; diff --git a/src/components/user/mypage/ContentTab.tsx b/src/components/user/mypage/ContentTab.tsx index 35b32858..51d99bf3 100644 --- a/src/components/user/mypage/ContentTab.tsx +++ b/src/components/user/mypage/ContentTab.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import * as S from './ContentTab.styled'; import { Link, Outlet, useLocation } from 'react-router-dom'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import ScrollWrapper from './ScrollWrapper'; import MovedInquiredLink from '../customerService/MoveInquiredLink'; diff --git a/src/components/user/mypage/joinedProject/MyJoinProjects.tsx b/src/components/user/mypage/joinedProject/MyJoinProjects.tsx index 4774c168..0dc4fc7b 100644 --- a/src/components/user/mypage/joinedProject/MyJoinProjects.tsx +++ b/src/components/user/mypage/joinedProject/MyJoinProjects.tsx @@ -4,7 +4,7 @@ import Project from './Project'; import Spinner from '../Spinner'; import ScrollWrapper from '../ScrollWrapper'; import { useMyJoinedProjectList } from '../../../../hooks/user/useMyInfo'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; import NoContent from '../../../common/noContent/NoContent'; const MyJoinProjects = () => { diff --git a/src/components/user/mypage/joinedProject/Project.tsx b/src/components/user/mypage/joinedProject/Project.tsx index 42d2f66d..5dac04cc 100644 --- a/src/components/user/mypage/joinedProject/Project.tsx +++ b/src/components/user/mypage/joinedProject/Project.tsx @@ -2,7 +2,7 @@ import * as S from './Project.styled'; import { EllipsisHorizontalIcon } from '@heroicons/react/24/outline'; import type { JoinedProject } from '../../../../models/userProject'; import beginner from '../../../../assets/beginner.svg'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; interface ProjectProps { project: JoinedProject; diff --git a/src/components/user/mypage/myProfile/MyProfile.tsx b/src/components/user/mypage/myProfile/MyProfile.tsx index 39a98518..ab665932 100644 --- a/src/components/user/mypage/myProfile/MyProfile.tsx +++ b/src/components/user/mypage/myProfile/MyProfile.tsx @@ -7,7 +7,7 @@ import { useRef } from 'react'; import ScrollWrapper from '../ScrollWrapper'; import { useModal } from '../../../../hooks/useModal'; import { useMyProfileInfo } from '../../../../hooks/user/useMyInfo'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; import Modal from '../../../common/modal/Modal'; const MyProfile = () => { diff --git a/src/components/user/mypage/myProfile/editProfile/EditProfile.tsx b/src/components/user/mypage/myProfile/editProfile/EditProfile.tsx index 6ebceb14..d89ead01 100644 --- a/src/components/user/mypage/myProfile/editProfile/EditProfile.tsx +++ b/src/components/user/mypage/myProfile/editProfile/EditProfile.tsx @@ -13,7 +13,7 @@ import type { UserInfo } from '../../../../../models/userInfo'; import { useSearchFilteringSkillTag } from '../../../../../hooks/user/useSearchFilteringSkillTag'; import { useEditMyProfileInfo } from '../../../../../hooks/user/useMyInfo'; import useNickNameVerification from '../../../../../hooks/user/useNicknameVerification'; -import { ROUTES } from '../../../../../constants/user/routes'; +import { ROUTES } from '../../../../../constants/routes'; import Button from '../../../../common/Button/Button'; import { ERROR_MESSAGES, diff --git a/src/components/user/mypage/myProfile/profile/Profile.tsx b/src/components/user/mypage/myProfile/profile/Profile.tsx index 5e73c62e..dbe1c1f2 100644 --- a/src/components/user/mypage/myProfile/profile/Profile.tsx +++ b/src/components/user/mypage/myProfile/profile/Profile.tsx @@ -6,7 +6,7 @@ import { useEffect } from 'react'; import MyProfileWrapper from '../MyProfileWrapper'; import type { UserInfo } from '../../../../../models/userInfo'; import { PROFILE_DEFAULT_MESSAGE } from '../../../../../constants/user/myPageProfile'; -import { ROUTES } from '../../../../../constants/user/routes'; +import { ROUTES } from '../../../../../constants/routes'; import 'chart.js/auto'; import { chartOptions } from '../../../../../constants/evaluationChartData'; diff --git a/src/components/user/mypage/notifications/appliedProjects/AppliedProjects.tsx b/src/components/user/mypage/notifications/appliedProjects/AppliedProjects.tsx index f84c37eb..4454ca87 100644 --- a/src/components/user/mypage/notifications/appliedProjects/AppliedProjects.tsx +++ b/src/components/user/mypage/notifications/appliedProjects/AppliedProjects.tsx @@ -4,7 +4,7 @@ import Spinner from '../../Spinner'; import AppliedProjectsStatus from './appliedProjectsStatus/AppliedProjectsStatus'; import NoContent from '../../../../common/noContent/NoContent'; import { useMyAppliedStatusList } from '../../../../../hooks/user/useMyInfo'; -import { ROUTES } from '../../../../../constants/user/routes'; +import { ROUTES } from '../../../../../constants/routes'; export default function AppliedProjects() { const { myAppliedStatusListData, isLoading } = useMyAppliedStatusList(); diff --git a/src/components/user/userPage/userProjectList/UserProjectList.tsx b/src/components/user/userPage/userProjectList/UserProjectList.tsx index 9742d612..829b60b6 100644 --- a/src/components/user/userPage/userProjectList/UserProjectList.tsx +++ b/src/components/user/userPage/userProjectList/UserProjectList.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; import * as S from '../../mypage/joinedProject/MyJoinProjects.styled'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; import NoContent from '../../../common/noContent/NoContent'; import ScrollWrapper from '../../mypage/ScrollWrapper'; import Spinner from '../../mypage/Spinner'; diff --git a/src/constants/admin/sidebar.ts b/src/constants/admin/sidebar.ts new file mode 100644 index 00000000..55535b2a --- /dev/null +++ b/src/constants/admin/sidebar.ts @@ -0,0 +1,55 @@ +import { ADMIN_ROUTE } from '../routes'; + +export const SIDEBAR_LIST = { + main: [ + { + name: 'mainPage', + title: '메인페이지', + router: ADMIN_ROUTE.admin, + }, + { + name: 'movedSite', + title: '사이트 이동', + router: ADMIN_ROUTE.devPals, + }, + ], + service: [ + { + name: 'notice', + title: '공지사항', + router: ADMIN_ROUTE.notice, + }, + { + name: 'banner', + title: '배너관리', + router: ADMIN_ROUTE.banner, + }, + { + name: 'tags', + title: '태그관리', + router: ADMIN_ROUTE.tags, + }, + ], + user: [ + { + name: 'allUser', + title: '전체 회원 조회', + router: ADMIN_ROUTE.allUser, + }, + { + name: 'reports', + title: '신고검토', + router: ADMIN_ROUTE.reports, + }, + { + name: 'inquiries', + title: '문의확인', + router: ADMIN_ROUTE.inquiries, + }, + { + name: 'manage', + title: '공고/댓글 관리', + router: ADMIN_ROUTE.manage, + }, + ], +} as const; diff --git a/src/constants/user/routes.ts b/src/constants/routes.ts similarity index 81% rename from src/constants/user/routes.ts rename to src/constants/routes.ts index 020f9f6e..fe64fdfd 100644 --- a/src/constants/user/routes.ts +++ b/src/constants/routes.ts @@ -30,3 +30,15 @@ export const ROUTES = { evaluation: '/evaluation', loginSuccess: '/oauth-redirect', } as const; + +export const ADMIN_ROUTE = { + admin: '/admin', + devPals: '/main', + notice: 'notice', + banner: 'banner', + tags: 'tags', + allUser: 'all-user', + reports: 'reports', + inquiries: 'inquiries', + manage: 'manage', +}; diff --git a/src/constants/sidebarItems.tsx b/src/constants/sidebarItems.tsx index ee3a9571..dd76a964 100644 --- a/src/constants/sidebarItems.tsx +++ b/src/constants/sidebarItems.tsx @@ -1,4 +1,4 @@ -import { ROUTES } from './user/routes'; +import { ROUTES } from './routes'; import { UserGroupIcon, PencilSquareIcon, diff --git a/src/constants/user/myPageFilter.ts b/src/constants/user/myPageFilter.ts index 67c6edbe..eeab4fb6 100644 --- a/src/constants/user/myPageFilter.ts +++ b/src/constants/user/myPageFilter.ts @@ -1,4 +1,4 @@ -import { ROUTES } from './routes'; +import { ROUTES } from '../routes'; export const NOTIFICATION_FILTER = [ { title: '전체', url: ``, id: 0 }, diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts index 259e4883..b8741ce0 100644 --- a/src/hooks/useAuth.ts +++ b/src/hooks/useAuth.ts @@ -1,3 +1,4 @@ +import { ADMIN_ROUTE } from './../constants/routes'; import { useNavigate } from 'react-router-dom'; import { postLogin, postResetPassword, postSignUp } from '../api/auth.api'; import { loginFormValues } from '../pages/login/Login'; @@ -7,7 +8,7 @@ import type { LoginResponse } from '../models/auth'; import { AxiosError } from 'axios'; import { myInfoKey } from './queries/user/keys'; import { MODAL_MESSAGE } from '../constants/user/modalMessage'; -import { ROUTES } from '../constants/user/routes'; +import { ROUTES } from '../constants/routes'; import { registerFormValues } from '../pages/user/register/Register'; import { changePasswordFormValues } from '../pages/user/changePassword/ChangePassword'; @@ -67,10 +68,16 @@ export const useAuth = (handleModalOpen: (message: string) => void) => { }, onSuccess: async (data) => { const { accessToken, userData } = data; + const isAdmin = userData.admin; + handleModalOpen(MODAL_MESSAGE.loginSuccess); setTimeout(() => { login(accessToken, userData); - navigate(ROUTES.main); + if (isAdmin) { + return navigate(ADMIN_ROUTE.admin); + } else { + return navigate(ROUTES.main); + } }, 1000); }, onError: () => { @@ -95,10 +102,9 @@ export const useAuth = (handleModalOpen: (message: string) => void) => { }; const userLogout = () => { - logout(); queryClient.removeQueries({ queryKey: myInfoKey.myProfile }); - useAuthStore.persist.clearStorage(); handleModalOpen(MODAL_MESSAGE.logout); + logout(); setTimeout(() => { navigate(ROUTES.main); }, 1000); diff --git a/src/hooks/user/ProjectHooks/useApplyProject.ts b/src/hooks/user/ProjectHooks/useApplyProject.ts index 7b7daf9c..c925ac94 100644 --- a/src/hooks/user/ProjectHooks/useApplyProject.ts +++ b/src/hooks/user/ProjectHooks/useApplyProject.ts @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { postApplicantProject } from '../../../api/joinProject.api'; import { joinProject } from '../../../models/joinProject'; import { MODAL_MESSAGE } from '../../../constants/user/modalMessage'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; interface UseApplyProjectProps { id: number; diff --git a/src/hooks/user/ProjectHooks/useCreateProject.ts b/src/hooks/user/ProjectHooks/useCreateProject.ts index b90a4829..403f6a7a 100644 --- a/src/hooks/user/ProjectHooks/useCreateProject.ts +++ b/src/hooks/user/ProjectHooks/useCreateProject.ts @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { postProject } from '../../../api/joinProject.api'; import { MODAL_MESSAGE } from '../../../constants/user/modalMessage'; import { managedProjectKey } from '../../queries/user/keys'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import type { FormData } from '../../../models/createProject'; interface UseCreateProjectProps { diff --git a/src/hooks/user/ProjectHooks/useUpdateProject.ts b/src/hooks/user/ProjectHooks/useUpdateProject.ts index 60dc103b..a1ea85a6 100644 --- a/src/hooks/user/ProjectHooks/useUpdateProject.ts +++ b/src/hooks/user/ProjectHooks/useUpdateProject.ts @@ -4,7 +4,7 @@ import { putProject } from '../../../api/joinProject.api'; import { managedProjectKey } from '../../queries/user/keys'; import type { FormData } from '../../../models/createProject'; import { MODAL_MESSAGE } from '../../../constants/user/modalMessage'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; interface UseUpdateProjectProps { id: number; diff --git a/src/hooks/user/useMyInfo.ts b/src/hooks/user/useMyInfo.ts index 08ce923f..c0ebecbc 100644 --- a/src/hooks/user/useMyInfo.ts +++ b/src/hooks/user/useMyInfo.ts @@ -12,7 +12,7 @@ import { putMyInfo, } from '../../api/mypage.api'; import { MODAL_MESSAGE } from '../../constants/user/modalMessage'; -import { ROUTES } from '../../constants/user/routes'; +import { ROUTES } from '../../constants/routes'; import type { ApiAppliedProject, ApiJoinedProject, diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index 632497bb..328dc22d 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; import * as S from './Login.styled'; -import Mainlogo from '../../assets/mainlogo.svg'; +import MainLogo from '../../assets/mainlogo.svg'; import { EnvelopeIcon, KeyIcon } from '@heroicons/react/24/outline'; import Title from '../../components/common/title/Title'; import { z } from 'zod'; @@ -12,7 +12,7 @@ import { OAUTH_PROVIDERS, } from '../../constants/user/authConstants'; import { useAuth } from '../../hooks/useAuth'; -import { ROUTES } from '../../constants/user/routes'; +import { ROUTES } from '../../constants/routes'; import { useModal } from '../../hooks/useModal'; import Modal from '../../components/common/modal/Modal'; import InputText from '../../components/user/auth/InputText'; @@ -53,7 +53,7 @@ const Login = () => { return ( - logo + logo 로그인
diff --git a/src/pages/login/LoginApi.tsx b/src/pages/login/LoginApi.tsx index 502dd79a..29562201 100644 --- a/src/pages/login/LoginApi.tsx +++ b/src/pages/login/LoginApi.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import useAuthStore from '../../store/authStore'; -import { ROUTES } from '../../constants/user/routes'; +import { ROUTES } from '../../constants/routes'; import * as S from './Login.styled'; import { Spinner } from '../../components/common/loadingSpinner/LoadingSpinner.styled'; import Modal from '../../components/common/modal/Modal'; diff --git a/src/pages/login/LoginSuccess.tsx b/src/pages/login/LoginSuccess.tsx index 9bc05d1d..1a26f4c0 100644 --- a/src/pages/login/LoginSuccess.tsx +++ b/src/pages/login/LoginSuccess.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import useAuthStore from '../../store/authStore'; -import { ROUTES } from '../../constants/user/routes'; +import { ROUTES } from '../../constants/routes'; import * as S from './Login.styled'; import { Spinner } from '../../components/common/loadingSpinner/LoadingSpinner.styled'; import Modal from '../../components/common/modal/Modal'; diff --git a/src/pages/user/changePassword/ChangePassword.tsx b/src/pages/user/changePassword/ChangePassword.tsx index 2f58dc4f..5d7d8903 100644 --- a/src/pages/user/changePassword/ChangePassword.tsx +++ b/src/pages/user/changePassword/ChangePassword.tsx @@ -9,7 +9,7 @@ import { ERROR_MESSAGES } from '../../../constants/user/authConstants'; import { useAuth } from '../../../hooks/useAuth'; import { useModal } from '../../../hooks/useModal'; import useEmailVerification from '../../../hooks/user/useEmailVerification'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import InputText from '../../../components/user/auth/InputText'; import Button from '../../../components/common/Button/Button'; import Modal from '../../../components/common/modal/Modal'; diff --git a/src/pages/user/customerService/notice/Notice.tsx b/src/pages/user/customerService/notice/Notice.tsx index c46ffcba..d52184aa 100644 --- a/src/pages/user/customerService/notice/Notice.tsx +++ b/src/pages/user/customerService/notice/Notice.tsx @@ -5,7 +5,7 @@ import { useGetNotice } from '../../../../hooks/user/useGetNotice'; import { Spinner } from '../../../../components/common/loadingSpinner/LoadingSpinner.styled'; import CustomerServiceHeader from '../../../../components/user/customerService/CustomerServiceHeader'; import ContentBorder from '../../../../components/common/contentBorder/ContentBorder'; -import { ROUTES } from '../../../../constants/user/routes'; +import { ROUTES } from '../../../../constants/routes'; import NoticeList from '../../../../components/user/customerService/notice/NoticeList'; import NoResult from '../../../../components/common/noResult/NoResult'; import Pagination from '../../../../components/common/pagination/Pagination'; diff --git a/src/pages/user/main/HeroSection.tsx b/src/pages/user/main/HeroSection.tsx index a0a5e5c0..34836a97 100644 --- a/src/pages/user/main/HeroSection.tsx +++ b/src/pages/user/main/HeroSection.tsx @@ -2,7 +2,7 @@ import * as S from './HeroSection.styled'; import landimg from '../../../assets/landing.svg'; import DownArrow from '../../../assets/arrow.svg'; import { Link } from 'react-router-dom'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import Button from '../../../components/common/Button/Button'; interface HeroSectionProps { handleScrollToSection: (sectionId: number) => void; diff --git a/src/pages/user/mypage/MyPage.tsx b/src/pages/user/mypage/MyPage.tsx index 8b5f107a..59705db3 100644 --- a/src/pages/user/mypage/MyPage.tsx +++ b/src/pages/user/mypage/MyPage.tsx @@ -7,7 +7,7 @@ import { BellIcon, } from '@heroicons/react/24/outline'; import loadingImg from '../../../assets/loadingImg.svg'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import { useMyProfileInfo } from '../../../hooks/user/useMyInfo'; import Sidebar from '../../../components/common/sidebar/Sidebar'; diff --git a/src/pages/user/projectDetail/ProjectDetail.tsx b/src/pages/user/projectDetail/ProjectDetail.tsx index 95efed20..d395f152 100644 --- a/src/pages/user/projectDetail/ProjectDetail.tsx +++ b/src/pages/user/projectDetail/ProjectDetail.tsx @@ -8,7 +8,7 @@ import useAuthStore from '../../../store/authStore'; import { MODAL_MESSAGE } from '../../../constants/user/modalMessage'; import LoadingSpinner from '../../../components/common/loadingSpinner/LoadingSpinner'; import Modal from '../../../components/common/modal/Modal'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import Avatar from '../../../components/common/avatar/Avatar'; import { formatDate } from '../../../util/formatDate'; import ProjectInformation from '../../../components/user/projectFormComponents/projectInformationText/ProjectInformation'; diff --git a/src/pages/user/register/Register.tsx b/src/pages/user/register/Register.tsx index 912db98a..d3f77148 100644 --- a/src/pages/user/register/Register.tsx +++ b/src/pages/user/register/Register.tsx @@ -15,7 +15,7 @@ import { useAuth } from '../../../hooks/useAuth'; import { useModal } from '../../../hooks/useModal'; import useEmailVerification from '../../../hooks/user/useEmailVerification'; import useNickNameVerification from '../../../hooks/user/useNicknameVerification'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import Title from '../../../components/common/title/Title'; import InputText from '../../../components/user/auth/InputText'; import Button from '../../../components/common/Button/Button'; diff --git a/src/pages/user/userpage/UserPage.tsx b/src/pages/user/userpage/UserPage.tsx index d070afe9..d28ba3aa 100644 --- a/src/pages/user/userpage/UserPage.tsx +++ b/src/pages/user/userpage/UserPage.tsx @@ -2,7 +2,7 @@ import { Outlet, useParams } from 'react-router-dom'; import * as S from '../mypage/MyPage.styled'; import { DocumentTextIcon, UserIcon } from '@heroicons/react/24/outline'; import loadingImg from '../../../assets/loadingImg.svg'; -import { ROUTES } from '../../../constants/user/routes'; +import { ROUTES } from '../../../constants/routes'; import { useUserProfileInfo } from '../../../hooks/user/useUserInfo'; import Sidebar from '../../../components/common/sidebar/Sidebar'; diff --git a/src/routes/AdminRoutes.tsx b/src/routes/AdminRoutes.tsx new file mode 100644 index 00000000..00efc712 --- /dev/null +++ b/src/routes/AdminRoutes.tsx @@ -0,0 +1,29 @@ +import NotFoundPage from '../pages/notFoundPage/NotFoundPage'; +import { lazy } from 'react'; +import { ADMIN_ROUTE } from '../constants/routes'; +import ProtectAdminRoute from './ProtectAdminRoute'; + +const Sidebar = lazy( + () => import('../components/common/admin/sidebar/AdminSidebar') +); + +export const AdminRoutes = () => { + const routeList = [ + { + path: ADMIN_ROUTE.admin, + element: ( + + + + ), + }, + ]; + + const newAdminRoutes = routeList.map((items) => { + return { ...items, errorElement: }; + }); + + return newAdminRoutes; +}; + +export default AdminRoutes; diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index 12365b63..0a26c8d3 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -11,8 +11,8 @@ import useAuthStore from '../store/authStore'; import ProtectRoute from '../components/common/ProtectRoute'; import NotFoundPage from '../pages/notFoundPage/NotFoundPage'; import QueryErrorBoundary from '../components/common/error/QueryErrorBoundary'; -import { ToastProvider } from '../components/common/Toast/ToastProvider'; -import { ROUTES } from '../constants/user/routes'; +import { ADMIN_ROUTE, ROUTES } from '../constants/routes'; + const Login = lazy(() => import('../pages/login/Login')); const LoginSuccess = lazy(() => import('../pages/login/LoginSuccess')); const LoginApi = lazy(() => import('../pages/login/LoginApi')); @@ -101,7 +101,7 @@ const ModifyProject = lazy( ); const Evaluation = lazy(() => import('../pages/user/evaluation/Evaluation')); -const AppRoutes = () => { +export const AppRoutes = () => { const isLoggedIn = useAuthStore((state) => state.isLoggedIn); const routeList = [ @@ -383,19 +383,7 @@ const AppRoutes = () => { }; }); - const router = createBrowserRouter([ - { - element: ( - - - - ), - - children: [...newRouteList, { path: '*', element: }], - }, - ]); - - return ; + return newRouteList; }; export default AppRoutes; diff --git a/src/routes/MergeRoutes.tsx b/src/routes/MergeRoutes.tsx new file mode 100644 index 00000000..dd084465 --- /dev/null +++ b/src/routes/MergeRoutes.tsx @@ -0,0 +1,30 @@ +import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom'; +import AdminRoutes from './AdminRoutes'; +import AppRoutes from './AppRoutes'; +import NotFoundPage from '../pages/notFoundPage/NotFoundPage'; +import { ToastProvider } from '../components/common/Toast/ToastProvider'; +import ProtectAdminRoute from './ProtectAdminRoute'; + +export default function MergeRoutes() { + const router = createBrowserRouter([ + { + element: ( + + + + ), + children: [...AppRoutes()], + }, + { + element: ( + + + + ), + children: [...AdminRoutes()], + }, + { path: '*', element: }, + ]); + + return ; +} diff --git a/src/routes/ProtectAdminRoute.tsx b/src/routes/ProtectAdminRoute.tsx new file mode 100644 index 00000000..cfe01688 --- /dev/null +++ b/src/routes/ProtectAdminRoute.tsx @@ -0,0 +1,56 @@ +import { useNavigate } from 'react-router-dom'; +import useAuthStore from '../store/authStore'; +import { ADMIN_ROUTE, ROUTES } from '../constants/routes'; +import { ReactNode, useEffect } from 'react'; +import { useModal } from '../hooks/useModal'; +import Modal from '../components/common/modal/Modal'; +import { MODAL_MESSAGE } from '../constants/user/modalMessage'; + +interface ProtectAdminRouteProps { + children: ReactNode; +} + +export default function ProtectAdminRoute({ + children, +}: ProtectAdminRouteProps) { + const isLoggedIn = useAuthStore((state) => state.isLoggedIn) ?? false; + const isAdmin = useAuthStore((state) => state.userData?.admin) ?? false; + const logout = useAuthStore((state) => state.logout); + const replace = useAuthStore((state) => state.replace); + const redirectAdminBool = + useAuthStore((state) => state.redirectAdmin) || false; + const navigate = useNavigate(); + const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); + + useEffect(() => { + if (isLoggedIn && !isAdmin) { + handleModalOpen(MODAL_MESSAGE.needAuth); + setTimeout(() => { + navigate(ROUTES.main); + }, 200); + return; + } + if (isLoggedIn && isAdmin && !redirectAdminBool) { + navigate(ADMIN_ROUTE.admin); + replace(); + return; + } + }, [ + redirectAdminBool, + isLoggedIn, + isAdmin, + replace, + navigate, + logout, + handleModalOpen, + ]); + + return ( + <> + {children} + + {message} + + + ); +} diff --git a/src/store/authStore.ts b/src/store/authStore.ts index 25416c9a..3511827d 100644 --- a/src/store/authStore.ts +++ b/src/store/authStore.ts @@ -1,21 +1,18 @@ import { create } from 'zustand'; -import { createJSONStorage, persist } from 'zustand/middleware'; +import { persist } from 'zustand/middleware'; import { decryptData, encryptData } from '../util/cryptoUtils'; import type { UserData } from '../models/auth'; interface AuthState { + redirectAdmin: boolean; isLoggedIn: boolean; userData: UserData | null; accessToken: string | null; login: (accessToken: string, userData: UserData | null) => void; logout: () => void; + replace: () => void; } -export const getStoredUserData = () => { - const encryptedData = localStorage.getItem('userData'); - return encryptedData ? decryptData(encryptedData) : null; -}; - export const getTokens = () => { const accessToken = localStorage.getItem('accessToken'); const refreshToken = localStorage.getItem('refreshToken'); @@ -26,10 +23,16 @@ export const getTokens = () => { const useAuthStore = create( persist( (set) => ({ + redirectAdmin: false, isLoggedIn: false, accessToken: null, userData: null, + replace: () => { + set({ + redirectAdmin: true, + }); + }, login: (accessToken: string, userData: UserData | null) => { set({ isLoggedIn: true, @@ -38,12 +41,17 @@ const useAuthStore = create( }); }, logout: () => { - set({ isLoggedIn: false, accessToken: null, userData: null }); + set({ + redirectAdmin: false, + isLoggedIn: false, + accessToken: null, + userData: null, + }); + useAuthStore.persist.clearStorage(); }, }), { name: 'auth-storage', // 로컬스토리지에 저장될 이름 - storage: createJSONStorage(() => localStorage), } ) );