Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f154ab1
chore: 관리자 진입 버튼 svg 추가
sernan96 Nov 17, 2025
5e727f2
feat: 관리자 진입 버튼 구현 및 home 화면 배치
sernan96 Nov 17, 2025
08cf0e4
design: 관리자 버튼 스타일 속성 추가
sernan96 Nov 17, 2025
f011184
feat: admin 라우팅 구현
sernan96 Nov 17, 2025
97bc9e1
feat: 공지사항 페이지 관리자 모드로 들어갔을때의 동작 구현
sernan96 Nov 17, 2025
ece4dcc
feat: role에 따라 이동할 주소 return하는 util함수 구현
sernan96 Nov 17, 2025
838a117
refactor: 공지사항 페이지에서 관리자 모드임을 확인하는 조건 추가
sernan96 Nov 17, 2025
9d78792
design: 공지사항 수정 삭제 바 디자인 추가
sernan96 Nov 17, 2025
e1a1ebb
refactor: 관리자 입장 버튼 수정
sernan96 Nov 17, 2025
52fca7f
refactor: 기존 페이지 재활용 어드민 동작 추가
sernan96 Nov 17, 2025
9d1b129
feat: 공지사항 삭제 api 연동
sernan96 Nov 17, 2025
be46fa6
design: admin 페이지 디자인 적용
sernan96 Nov 17, 2025
0810baa
feat: 공지사항 수정 삭제 bar 구현
sernan96 Nov 17, 2025
c47d122
feat: 공지사항 수정, 게시 api 연결
sernan96 Nov 18, 2025
85e2cec
chore: 관리자 api 추가
sernan96 Nov 18, 2025
3a41c06
feat: 삭제 후 뒤로가기 추가
sernan96 Nov 18, 2025
4d3fe64
refactor: 공지사항 관리바 props추가
sernan96 Nov 18, 2025
0afe120
chore: 관리자 url 추가
sernan96 Nov 18, 2025
7e778ca
feat: 공지사항 수정 페이지 구현
sernan96 Nov 18, 2025
701db01
design: 공지사항 생성 버튼 디자인 추가
sernan96 Nov 18, 2025
a4d969f
feat: 공지사항 생성 버튼 추가 (임시)
sernan96 Nov 18, 2025
3678c8b
feat: 공지 생성 페이지 구현 및 api 연결
sernan96 Nov 18, 2025
abe5e7b
feat: 공지사항 관리 라우팅 추가
sernan96 Nov 18, 2025
5589d9f
feat: 관리자 홈 추가 및 야가다 토끼 사진 추가
sernan96 Nov 18, 2025
5f8c14b
refactor: 함수형 컴포넌트 변경
sernan96 Nov 18, 2025
bee8235
chore: url 수정 및 절대 경로 추가
sernan96 Nov 18, 2025
09a27f2
feat: 각 관리 url 추가
sernan96 Nov 18, 2025
b245bf8
Merge branch 'develop' into feature/manager
sernan96 Nov 18, 2025
bcb32ab
design: 공지사항 작성 마진 수정
sernan96 Nov 18, 2025
cbdb8cf
Merge branch 'feature/manager' of https://github.com/GotchaAI/FE-WEB …
sernan96 Nov 18, 2025
1bfebc5
refactor: 스크롤 초기화 예외상황 커버
sernan96 Nov 18, 2025
d0a0d7a
Merge branch 'develop' into feature/manager
sernan96 Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added src/assets/components/admin/yagada-rabbit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/commons/svgs/AdminButton.jsx
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>
);
};
28 changes: 28 additions & 0 deletions src/components/admin/AdminHeader.jsx
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>
);
};
31 changes: 31 additions & 0 deletions src/components/admin/AnnounceManageBar.jsx
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>
Comment on lines +19 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-- 두개로 하신 이유가 있을까요?

);
};
31 changes: 31 additions & 0 deletions src/components/home/EnterAdmin.jsx
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>
);
};
3 changes: 2 additions & 1 deletion src/components/home/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -84,6 +84,7 @@ const Home = () => {
<IntroduceCharacterPreview />
</div>
</div>
<EnterAdmin />
</div>
);
};
Expand Down
8 changes: 7 additions & 1 deletion src/constants/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
export const AI_SERVER_IP = process.env.REACT_APP_AI_SERVER_IP;
7 changes: 7 additions & 0 deletions src/constants/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"; // 마이페이지 내 전적
Expand Down
9 changes: 9 additions & 0 deletions src/pages/admin/AdminHome.jsx
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>
);
};
41 changes: 41 additions & 0 deletions src/pages/admin/AdminPage.jsx
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 };
};
86 changes: 86 additions & 0 deletions src/pages/admin/AnnounceEditPage.jsx
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
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 수정 기능을 확인해보니,
navigate(url/id)로 수정 완료 시 상세 페이지로 이동하고,
나가기 버튼에서는 navigate(-1)을 사용하고 있습니다.

이 경우 수정 완료 시 새로운 히스토리가 추가되어,
나가기 버튼을 눌러도 동일한 상세 페이지가 연속해서 나타나는 현상이 발생합니다.
혹시 이런 히스토리 중첩이 의도된 동작인지 궁금합니다.


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;
46 changes: 46 additions & 0 deletions src/pages/admin/CreateAnnouncePage.jsx
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>
);
};
7 changes: 7 additions & 0 deletions src/pages/home/AnnounceDetailPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import "styles/pages/home/AnnounceDetailPage.scss";
import { formatDate } from "utils/time";
import { useEffect, useState } from "react";
import { getAnnounceDetailAPI } from "services/home/announce";
import useUserInformationStore from "store/userInformation";
import { AnnounceManageBar } from "components/admin/AnnounceManageBar";

/** 공통 상태 컴포넌트 */
const AnnounceStatus = ({ type, message }) => (
Expand All @@ -20,6 +22,10 @@ const AnnounceDetailPage = () => {
const [notice, setNotice] = useState(null);
const [status, setStatus] = useState({ loading: true, error: null });

const profile = useUserInformationStore((state) => state.profile);
const isAdminMode =
profile?.role === "ADMIN" && window.location.href.includes("admin");

const closeDetailPage = () => navigate(-1);

useEffect(() => {
Expand Down Expand Up @@ -67,6 +73,7 @@ const AnnounceDetailPage = () => {
{notice.title}
</div>
<div className="content-description">{notice.content}</div>
{isAdminMode && <AnnounceManageBar notificationId={id} />}
</div>
</article>
</div>
Expand Down
Loading