diff --git a/src/assets/components/admin/yagada-rabbit.png b/src/assets/components/admin/yagada-rabbit.png new file mode 100644 index 0000000..f6bb616 Binary files /dev/null and b/src/assets/components/admin/yagada-rabbit.png differ diff --git a/src/commons/svgs/AdminButton.jsx b/src/commons/svgs/AdminButton.jsx new file mode 100644 index 0000000..dcdfe6f --- /dev/null +++ b/src/commons/svgs/AdminButton.jsx @@ -0,0 +1,7 @@ +export const AdminButton = () => { + return ( + + + + ); +}; diff --git a/src/components/admin/AdminHeader.jsx b/src/components/admin/AdminHeader.jsx new file mode 100644 index 0000000..024ee4f --- /dev/null +++ b/src/components/admin/AdminHeader.jsx @@ -0,0 +1,28 @@ +import { + ADMIN_ANNOUNCE_URL, + ADMIN_MANAGE_CUSTOMER_URL, + ADMIN_MANAGE_REPORT_URL, + ADMIN_MANAGE_USER_URL, + ROOT_URL, +} from "constants/url"; +import { Link } from "react-router-dom"; +import logo from "assets/commons/home-logo.png"; +import "styles/components/admin/AdminHeader.scss"; +import Profile from "components/home/Profile"; + +export const AdminHeader = () => { + return ( +
+ + logo + + + +
+ ); +}; diff --git a/src/components/admin/AnnounceManageBar.jsx b/src/components/admin/AnnounceManageBar.jsx new file mode 100644 index 0000000..0d8f654 --- /dev/null +++ b/src/components/admin/AnnounceManageBar.jsx @@ -0,0 +1,31 @@ +import { ADMIN_ANNOUNCE_URL } from "constants/url"; +import { Link, useNavigate } from "react-router-dom"; +import { eraseNotification } from "services/admin/admin"; +import "styles/components/admin/AnnounceManageBar.scss"; + +export const AnnounceManageBar = ({ notificationId }) => { + const navigate = useNavigate(); + + const handleDelete = async () => { + try { + await eraseNotification(notificationId); + navigate(ADMIN_ANNOUNCE_URL); + } catch (error) { + console.error("삭제 실패:", error); + } + }; + + return ( +
+ + 수정 + + +
+ ); +}; diff --git a/src/components/home/EnterAdmin.jsx b/src/components/home/EnterAdmin.jsx new file mode 100644 index 0000000..fd2d913 --- /dev/null +++ b/src/components/home/EnterAdmin.jsx @@ -0,0 +1,31 @@ +import { AdminButton } from "commons/svgs/AdminButton"; +import { ADMIN_URL } from "constants/url"; +import React from "react"; +import { useNavigate } from "react-router-dom"; +import "styles/components/home/EnterAdmin.scss"; + +export const EnterAdmin = () => { + const navigate = useNavigate(); + + const handleClick = () => { + navigate(ADMIN_URL); + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleClick(); + } + }; + + return ( + + ); +}; diff --git a/src/components/home/Home.jsx b/src/components/home/Home.jsx index fbe04f5..bf74f3b 100644 --- a/src/components/home/Home.jsx +++ b/src/components/home/Home.jsx @@ -12,8 +12,8 @@ import { getUserInfoAPI } from "services/user/user"; import "styles/components/home/Home.scss"; import { getAuthToken } from "utils/token"; import { getUserInfo } from "utils/user"; +import { EnterAdmin } from "components/home/EnterAdmin.jsx"; import { RankingPreview } from "components/home/RankingPreview.jsx"; - const Home = () => { const navigate = useNavigate(); const { isSignIn } = useOutletContext(); @@ -84,6 +84,7 @@ const Home = () => { + ); }; diff --git a/src/constants/api.js b/src/constants/api.js index 56be9cc..62884b7 100644 --- a/src/constants/api.js +++ b/src/constants/api.js @@ -70,12 +70,18 @@ export const SOCKET_CHAT_PRIVATE = process.env.REACT_APP_WS_CHAT_PRIVATE_API; // 공지사항 API export const ANNOUNCE_LIST_API = process.env.REACT_APP_ANNOUNCE_LIST_API; +// 관리자 API +export const ADMIN_ANNOUNCE_MANAGE_API = + process.env.REACT_APP_ADMIN_ANNOUNCE_MANAGE_API; +export const ADMIN_QNA_MANAGE_API = process.env.ADMIN_QNA_MANAGE_API; +export const ADMIN_REPORT_API = process.env.REACT_APP_ADMIN_REPORT_API; // 서비스센터 API export const SERVICE_LIST_API = process.env.REACT_APP_SERVICE_LIST_API; export const SERVICE_MY_LIST_API = process.env.REACT_APP_SERVICE_MY_LIST_API; export const SERVICE_QNA_API = process.env.REACT_APP_SERVICE_QNA; + export const RANKING_API = process.env.REACT_APP_RANKING_API; export const RANkING_MY_API = process.env.REACT_APP_MY_RANKING_API; -export const AI_SERVER_IP = process.env.REACT_APP_AI_SERVER_IP; \ No newline at end of file +export const AI_SERVER_IP = process.env.REACT_APP_AI_SERVER_IP; diff --git a/src/constants/url.js b/src/constants/url.js index 447fa5e..8b46777 100644 --- a/src/constants/url.js +++ b/src/constants/url.js @@ -5,6 +5,13 @@ export const SIGN_UP_URL = "/signup"; // 회원가입 export const ACCOUNT_URL = "/find/account"; // 비밀번호 찾기 export const WITHDRAW_URL = "/withdraw"; // 비밀번호 찾기 +export const ADMIN_URL = "/admin"; +export const ADMIN_ANNOUNCE_URL = "/admin/announce"; //공지사항 관리 +export const ADMIN_CREATE_ANNOUNCE_URL = "/admin/announce/create"; //공지 생성 +export const ADMIN_MANAGE_USER_URL = "/admin/user-manage"; // 유저 관리 +export const ADMIN_MANAGE_REPORT_URL = "/admin/report-manage"; //신고 관리 +export const ADMIN_MANAGE_CUSTOMER_URL = "/admin/customer-manage"; //고객센터 1ㄷ1 문의 관리 + export const LOBBY_URL = "/lobby"; // 로비 export const MY_PAGE_URL = "/lobby/myinfo"; // 마이페이지 export const MY_RECORD_URL = "/lobby/myinfo/record"; // 마이페이지 내 전적 diff --git a/src/pages/admin/AdminHome.jsx b/src/pages/admin/AdminHome.jsx new file mode 100644 index 0000000..fc4e6e3 --- /dev/null +++ b/src/pages/admin/AdminHome.jsx @@ -0,0 +1,9 @@ +import "styles/pages/admin/AdminHome.scss"; +import yagadaRabbit from "assets/components/admin/yagada-rabbit.png"; +export const AdminHome = () => { + return ( +
+ +
+ ); +}; diff --git a/src/pages/admin/AdminPage.jsx b/src/pages/admin/AdminPage.jsx new file mode 100644 index 0000000..5bc7c15 --- /dev/null +++ b/src/pages/admin/AdminPage.jsx @@ -0,0 +1,41 @@ +import { AdminHeader } from "components/admin/AdminHeader"; +import ScrollToTop from "components/common/ScrollToTop"; +import { useEffect } from "react"; +import { Outlet } from "react-router-dom"; +import { tokenReissueAPI } from "services/auth/auth"; +import "styles/pages/admin/AdminPage.scss"; +import { getAuthToken } from "utils/token"; + +const AdminPage = () => { + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + return ( +
+ + + +
+ ); +}; + +export default AdminPage; + +export const loader = async () => { + const { accessToken, setAccessToken } = getAuthToken(); + + if (!accessToken) { + // 토큰 재발급 + try { + const res = await tokenReissueAPI(); + const newAccessToken = res.accessToken; + const expireTime = res.expiredAt; + setAccessToken(newAccessToken, expireTime); + } catch (e) { + console.error(e); + return { isSignIn: false }; + } + } + return { isSignIn: true }; +}; diff --git a/src/pages/admin/AnnounceEditPage.jsx b/src/pages/admin/AnnounceEditPage.jsx new file mode 100644 index 0000000..d562cfd --- /dev/null +++ b/src/pages/admin/AnnounceEditPage.jsx @@ -0,0 +1,86 @@ +import CloseButton from "commons/ui/button/CloseButton"; +import { useNavigate, useParams } from "react-router-dom"; +import "styles/pages/admin/AnnounceEditPage.scss"; +import { useEffect, useState } from "react"; +import { getAnnounceDetailAPI } from "services/home/announce"; +import useUserInformationStore from "store/userInformation"; +import { updateAnnounceAPI } from "services/admin/admin"; +import { ADMIN_ANNOUNCE_URL } from "constants/url"; + +const AnnounceEditPage = () => { + const navigate = useNavigate(); + const { id } = useParams(); + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const [loading, setLoading] = useState(true); + + const profile = useUserInformationStore((state) => state.profile); + const isAdminMode = + profile?.role === "ADMIN" && window.location.pathname.includes("admin"); + + const closeEditPage = () => navigate(-1); + + useEffect(() => { + const fetchDetail = async () => { + try { + const res = await getAnnounceDetailAPI(id); + setTitle(res.title); + setContent(res.content); + } catch (err) { + alert("공지사항 정보를 불러오지 못했습니다."); + } finally { + setLoading(false); + } + }; + fetchDetail(); + }, [id]); + + const handleSubmit = async () => { + try { + const notificationInfo = { id, title, content }; + await updateAnnounceAPI(notificationInfo); + navigate(`${ADMIN_ANNOUNCE_URL}/${id}`); + } catch (err) { + console.log(err); + alert("공지 수정에 실패했습니다."); + } + }; + + if (!isAdminMode) return
권한이 없습니다.
; + if (loading) return
로딩 중...
; + + return ( +
+
+
+ +
+ +
+
+ setTitle(e.target.value)} + placeholder="공지 제목을 입력하세요" + /> +
+ +