From e41bb1dc76900d1d9e83563bf848c82bb3b18b2d Mon Sep 17 00:00:00 2001
From: Paul
Date: Sat, 8 Nov 2025 15:45:43 +0900
Subject: [PATCH 1/4] =?UTF-8?q?Feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?=
=?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B0=8F=20=EB=B1=83=EC=A7=80/?=
=?UTF-8?q?=EC=B1=8C=EB=A6=B0=EC=A7=80=20=EC=B6=94=EA=B0=80=20=ED=8F=BC=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/badgeApi.js | 16 ++++++
src/components/badge/BadgeForm.jsx | 57 ++++++++++++++++++++++
src/components/badge/BadgeList.jsx | 49 +++++++++++++++++++
src/components/challenge/ChallengeForm.jsx | 57 ++++++++++++++++++++++
src/components/screens/AdminScreen.jsx | 38 +++++++++++++++
src/components/screens/MyPageScreen.jsx | 15 ++++++
src/types/badge.d.ts | 23 +++++++++
7 files changed, 255 insertions(+)
create mode 100644 src/api/badgeApi.js
create mode 100644 src/components/badge/BadgeForm.jsx
create mode 100644 src/components/badge/BadgeList.jsx
create mode 100644 src/components/challenge/ChallengeForm.jsx
create mode 100644 src/components/screens/AdminScreen.jsx
create mode 100644 src/types/badge.d.ts
diff --git a/src/api/badgeApi.js b/src/api/badgeApi.js
new file mode 100644
index 0000000..4b619e7
--- /dev/null
+++ b/src/api/badgeApi.js
@@ -0,0 +1,16 @@
+import api from './axios';
+
+export async function getBadges() {
+ const res = await api.get('/badge');
+ return res.data.data;
+}
+
+export async function registerBadge(req) {
+ const res = await api.post('/badge', req);
+ return res.data.data;
+}
+
+export async function selectBadge(badgeName) {
+ const res = await api.get('/badge/select', { params: { badgeName } });
+ return res.data.data;
+}
diff --git a/src/components/badge/BadgeForm.jsx b/src/components/badge/BadgeForm.jsx
new file mode 100644
index 0000000..b052317
--- /dev/null
+++ b/src/components/badge/BadgeForm.jsx
@@ -0,0 +1,57 @@
+import React, { useState } from 'react';
+
+const BadgeForm = () => {
+ const [name, setName] = useState('');
+ const [desc, setDesc] = useState('');
+ const [icon, setIcon] = useState('');
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ // TODO: API 연동
+ alert(`뱃지 추가: ${name}, 설명: ${desc}, 아이콘: ${icon}`);
+ setName('');
+ setDesc('');
+ setIcon('');
+ };
+
+ return (
+
+ );
+};
+
+export default BadgeForm;
diff --git a/src/components/badge/BadgeList.jsx b/src/components/badge/BadgeList.jsx
new file mode 100644
index 0000000..3b2a270
--- /dev/null
+++ b/src/components/badge/BadgeList.jsx
@@ -0,0 +1,49 @@
+import React, { useEffect, useState } from 'react';
+import { getBadges, selectBadge } from '../../api/badgeApi';
+
+function BadgeListExample() {
+ const [badges, setBadges] = useState([]);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ getBadges().then(setBadges).catch(setError);
+ }, []);
+
+ const handleSelect = async (name) => {
+ try {
+ await selectBadge(name);
+ setBadges((prev) =>
+ prev.map((b) => ({
+ ...b,
+ isSelected: b.name === name,
+ }))
+ );
+ } catch (err) {
+ setError(String(err));
+ }
+ };
+
+ if (error) return {error}
;
+
+ return (
+
+ {badges.map((badge) => (
+ -
+
+ {badge.name}
+
+
+ ))}
+
+ );
+}
+
+export default BadgeListExample;
diff --git a/src/components/challenge/ChallengeForm.jsx b/src/components/challenge/ChallengeForm.jsx
new file mode 100644
index 0000000..2bb0149
--- /dev/null
+++ b/src/components/challenge/ChallengeForm.jsx
@@ -0,0 +1,57 @@
+import React, { useState } from 'react';
+
+const ChallengeForm = () => {
+ const [title, setTitle] = useState('');
+ const [desc, setDesc] = useState('');
+ const [point, setPoint] = useState('');
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ // TODO: API 연동
+ alert(`챌린지 추가: ${title}, 설명: ${desc}, 포인트: ${point}`);
+ setTitle('');
+ setDesc('');
+ setPoint('');
+ };
+
+ return (
+
+ );
+};
+
+export default ChallengeForm;
diff --git a/src/components/screens/AdminScreen.jsx b/src/components/screens/AdminScreen.jsx
new file mode 100644
index 0000000..d01070b
--- /dev/null
+++ b/src/components/screens/AdminScreen.jsx
@@ -0,0 +1,38 @@
+import React, { useState } from 'react';
+import BadgeForm from '../badge/BadgeForm';
+import ChallengeForm from '../challenge/ChallengeForm';
+
+const AdminScreen = () => {
+ const [activeTab, setActiveTab] = useState('badge');
+
+ return (
+
+
관리자 페이지
+
+
+
+
+ {activeTab === 'badge' ?
:
}
+
+ );
+};
+
+export default AdminScreen;
diff --git a/src/components/screens/MyPageScreen.jsx b/src/components/screens/MyPageScreen.jsx
index 2d1efd4..c67375e 100644
--- a/src/components/screens/MyPageScreen.jsx
+++ b/src/components/screens/MyPageScreen.jsx
@@ -41,6 +41,7 @@ export default function MyPageScreen({ onNavigate }) {
(s) => s.user
);
const { allBadges, earnedIds } = useSelector((state) => state.badge);
+ const [isAdmin, setIsAdmin] = useState(true);
const [showSetting, setShowSetting] = useState(false);
const [showLogoutModal, setShowLogoutModal] = useState(false); // 로그아웃 모달 상태
@@ -237,6 +238,20 @@ export default function MyPageScreen({ onNavigate }) {
메뉴
+ {isAdmin && (
+ -
+
+
+ )}
-
{/* Description 에러 메시지 */}
{descriptionError && (
@@ -318,20 +317,20 @@ const ChallengeForm = () => {
기준, km 단위)
- • 전기차: 전기차 충전 챌린지
- (충전비용 기준, 원 단위)
+ • 전기차: 전기차 충전 챌린지 (충전비용
+ 기준, 원 단위)
- • 수소차: 수소차 충전 챌린지
- (충전비용 기준, 원 단위)
+ • 수소차: 수소차 충전 챌린지 (충전비용
+ 기준, 원 단위)
- • 재활용센터: 재활용센터 방문
- 챌린지 (구매금액 기준, 원 단위)
+ • 재활용센터: 재활용센터 방문 챌린지
+ (구매금액 기준, 원 단위)
- • 제로웨이스트: 제로웨이스트 상점
- 이용 챌린지 (구매금액 기준, 원 단위)
+ • 제로웨이스트: 제로웨이스트 상점 이용
+ 챌린지 (구매금액 기준, 원 단위)
diff --git a/src/components/screens/MyPageScreen.jsx b/src/components/screens/MyPageScreen.jsx
index c67375e..b0b97b8 100644
--- a/src/components/screens/MyPageScreen.jsx
+++ b/src/components/screens/MyPageScreen.jsx
@@ -3,6 +3,7 @@ import { useSelector, useDispatch } from 'react-redux';
import { setActiveTab } from '../../store/slices/appSlice';
import { fetchMyPageData, logout } from '../../store/slices/userSlice';
import { calculateEarnedBadges } from '../../store/slices/badgeSlice';
+import api from '../../api/axios';
const themeColor = '#96cb6f';
@@ -41,11 +42,41 @@ export default function MyPageScreen({ onNavigate }) {
(s) => s.user
);
const { allBadges, earnedIds } = useSelector((state) => state.badge);
- const [isAdmin, setIsAdmin] = useState(true);
+ const [isAdmin, setIsAdmin] = useState(false);
const [showSetting, setShowSetting] = useState(false);
const [showLogoutModal, setShowLogoutModal] = useState(false); // 로그아웃 모달 상태
+ // 관리자 권한 확인
+ const checkAdminStatus = async () => {
+ const token = localStorage.getItem('token');
+ const memberId = localStorage.getItem('memberId');
+
+ // memberId가 1인 경우만 API 호출
+ if (!token || memberId !== '1') {
+ setIsAdmin(false);
+ return;
+ }
+
+ try {
+ const response = await api.get('/admin', {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+
+ if (
+ response.data.status === 'SUCCESS' &&
+ response.data.data.result
+ ) {
+ setIsAdmin(true);
+ } else {
+ setIsAdmin(false);
+ }
+ } catch (err) {
+ console.error('관리자 권한 확인 실패', err.response || err);
+ setIsAdmin(false);
+ }
+ };
+
// 현재 획득한 최고 레벨 뱃지 찾기
const myBadge = useMemo(() => {
const earnedBadges = allBadges.filter((badge) =>
@@ -65,6 +96,11 @@ export default function MyPageScreen({ onNavigate }) {
dispatch(fetchMyPageData());
}, [dispatch]);
+ // 컴포넌트 마운트 시 관리자 권한 확인
+ useEffect(() => {
+ checkAdminStatus();
+ }, []);
+
useEffect(() => {
if (stats.totalPoint !== undefined && stats.totalPoint !== null) {
dispatch(calculateEarnedBadges(stats.totalPoint));
@@ -238,7 +274,7 @@ export default function MyPageScreen({ onNavigate }) {
메뉴
- {isAdmin && (
+ {isAdmin && (
-