-
Notifications
You must be signed in to change notification settings - Fork 1
branch: 관리자페이지 진입 및 공지사항 파트 UI + API 연동 #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
f154ab1
5e727f2
08cf0e4
f011184
97bc9e1
ece4dcc
838a117
9d78792
e1a1ebb
52fca7f
9d1b129
be46fa6
0810baa
c47d122
85e2cec
3a41c06
4d3fe64
0afe120
7e778ca
701db01
a4d969f
3678c8b
abe5e7b
5589d9f
5f8c14b
bee8235
09a27f2
b245bf8
bcb32ab
cbdb8cf
1bfebc5
d0a0d7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const AdminButton = () => { | ||
| return ( | ||
| <svg viewBox="0 0 24 24" fill="currentColor"> | ||
| <path d="M12 2 2 7v6c0 5 5 9 10 9s10-4 10-9V7l-10-5zM12 18a2.5 2.5 0 110-5 2.5 2.5 0 010 5zm4-8H8V9a4 4 0 018 0v1z" /> | ||
| </svg> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <header className="admin-page-header"> | ||
| <Link to={ROOT_URL}> | ||
| <img src={logo} alt="logo" className="home-logo-img" /> | ||
| </Link> | ||
| <nav className="admin-page-nav-container"> | ||
| <Link to={ADMIN_ANNOUNCE_URL}>공지 관리</Link> | ||
| <Link to={ADMIN_MANAGE_USER_URL}>유저 관리</Link> | ||
| <Link to={ADMIN_MANAGE_REPORT_URL}>신고 관리</Link> | ||
| <Link to={ADMIN_MANAGE_CUSTOMER_URL}>고객센터</Link> | ||
| </nav> | ||
| <Profile /> | ||
| </header> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <div className="announce-manage-button-bar"> | ||
| <Link | ||
| to={`${ADMIN_ANNOUNCE_URL}/${notificationId}/edit`} | ||
| className="button--edit" | ||
| > | ||
| 수정 | ||
| </Link> | ||
| <button className="button--delete" onClick={handleDelete}> | ||
| 삭제 | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <button | ||
| className="admin-button" | ||
| type="button" | ||
| onClick={handleClick} | ||
| onKeyDown={handleKeyDown} | ||
| > | ||
| <AdminButton /> | ||
| </button> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import "styles/pages/admin/AdminHome.scss"; | ||
| import yagadaRabbit from "assets/components/admin/yagada-rabbit.png"; | ||
| export const AdminHome = () => { | ||
| return ( | ||
| <div className="admin-home-container"> | ||
| <img src={yagadaRabbit} /> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <div className="admin-page-container"> | ||
| <ScrollToTop /> | ||
| <AdminHeader /> | ||
| <Outlet /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| 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 }; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"); | ||
|
Comment on lines
+18
to
+19
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이전 리뷰에 있듯 page loader 단에서 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("공지 수정에 실패했습니다."); | ||
| } | ||
| }; | ||
|
Comment on lines
+21
to
+47
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 수정 기능을 확인해보니, 이 경우 수정 완료 시 새로운 히스토리가 추가되어, |
||
|
|
||
| if (!isAdminMode) return <div>권한이 없습니다.</div>; | ||
| if (loading) return <div className="announce-edit-loading">로딩 중...</div>; | ||
|
|
||
| return ( | ||
| <div className="announce-edit-page-container"> | ||
| <article className="announce-edit-container"> | ||
| <header className="announce-edit-header"> | ||
| <CloseButton onClick={closeEditPage} /> | ||
| </header> | ||
|
|
||
| <div className="announce-edit-content"> | ||
| <div className="content-title"> | ||
| <input | ||
| type="text" | ||
| className="edit-title-input" | ||
| value={title} | ||
| onChange={(e) => setTitle(e.target.value)} | ||
| placeholder="공지 제목을 입력하세요" | ||
| /> | ||
| </div> | ||
|
|
||
| <textarea | ||
| className="edit-content-input" | ||
| value={content} | ||
| onChange={(e) => setContent(e.target.value)} | ||
| placeholder="공지 내용을 입력하세요" | ||
| /> | ||
|
|
||
| <button className="save-button" onClick={handleSubmit}> | ||
| 수정 완료 | ||
| </button> | ||
| </div> | ||
| </article> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default AnnounceEditPage; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import React, { useState } from "react"; | ||
| import { useNavigate } from "react-router-dom"; | ||
| import { postAnnounceAPI } from "services/admin/admin"; | ||
| import "styles/pages/admin/CreateAnnouncePage.scss"; | ||
|
|
||
| export const CreateAnnouncePage = () => { | ||
| const [title, setTitle] = useState(""); | ||
| const [content, setContent] = useState(""); | ||
| const navigate = useNavigate(); | ||
| const handleSubmit = async () => { | ||
| if (!title || !content) { | ||
| return; | ||
| } | ||
| const notification = { title, content }; | ||
| try { | ||
| await postAnnounceAPI(notification); | ||
| navigate(-1); | ||
| } catch (error) { | ||
| console.error(error); | ||
| alert("등록 중 오류가 발생했습니다."); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="announce-create-container"> | ||
| <input | ||
| className="announce-create-title" | ||
| type="text" | ||
| placeholder="공지사항 제목 입력" | ||
| value={title} | ||
| onChange={(e) => setTitle(e.target.value)} | ||
| /> | ||
|
|
||
| <textarea | ||
| className="announce-create-content" | ||
| placeholder="내용을 입력하세요" | ||
| value={content} | ||
| onChange={(e) => setContent(e.target.value)} | ||
| /> | ||
|
|
||
| <button className="announce-submit" onClick={handleSubmit}> | ||
| 등록 | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-- 두개로 하신 이유가 있을까요?