diff --git a/frontend/package.json b/frontend/package.json index 56b3680..142f4f5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,6 @@ { "name": "piroinfront", + "proxy": "http://localhost:8080", "version": "0.1.0", "private": true, "dependencies": { @@ -16,7 +17,6 @@ "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, - "proxy": "http://13.209.73.127:8080", "scripts": { "start": "react-scripts start", "build": "react-scripts build", diff --git a/frontend/src/pages/OnboardingPage.js b/frontend/src/pages/OnboardingPage.js index b819e21..933057d 100644 --- a/frontend/src/pages/OnboardingPage.js +++ b/frontend/src/pages/OnboardingPage.js @@ -7,10 +7,15 @@ function OnboardingPage() { const navigate = useNavigate(); useEffect(() => { - const timer = setTimeout(() => { - navigate('/login'); - }, 2000); - return () => clearTimeout(timer); + const timer = setTimeout(() => { + const token = localStorage.getItem('token'); + if (token) { + navigate('/pirocheck'); + } else { + navigate('/login'); + } + }, 2000); + return () => clearTimeout(timer); }, []); return ( diff --git a/frontend/src/pages/login/LoginPage.js b/frontend/src/pages/login/LoginPage.js index a232d89..bac5e88 100644 --- a/frontend/src/pages/login/LoginPage.js +++ b/frontend/src/pages/login/LoginPage.js @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; // 추가 +import { useNavigate } from 'react-router-dom'; +import { authFetch } from '../../utils/Api'; import styles from './LoginPage.module.css'; function LoginPage() { @@ -15,7 +16,7 @@ function LoginPage() { const handleLogin = async () => { try { - const response = await fetch('/api/auth/login', { + const response = await authFetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form), diff --git a/frontend/src/pages/pirocheck/PIroCheckMain.js b/frontend/src/pages/pirocheck/PIroCheckMain.js index 678c45a..8faf3a2 100644 --- a/frontend/src/pages/pirocheck/PIroCheckMain.js +++ b/frontend/src/pages/pirocheck/PIroCheckMain.js @@ -1,4 +1,3 @@ -import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import styles from './PIroCheckMain.module.css'; diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.js b/frontend/src/pages/pirocheck/assignment/Assignment.js index 9f1644a..81a0f13 100644 --- a/frontend/src/pages/pirocheck/assignment/Assignment.js +++ b/frontend/src/pages/pirocheck/assignment/Assignment.js @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { authFetch } from '../../../utils/Api'; import styles from './Assignment.module.css'; import LogoImg from '../../../assets/images/logo.png'; import EditIcon from '../../../assets/images/icon_edit.svg'; @@ -14,26 +15,7 @@ const dayMap = { SATURDAY: 'SAT', }; -// 임시 데이터 (백엔드 연동 전) -const MOCK_DATA = [ - { - week: '1', - assignments: [ - { assignmentId: 1, title: '코딩앵무 클론 코딩', week: '1', day: 'TUESDAY', sessionDate: 'HTML/CSS, Git 기초', submitted: 'SUBMITTED' }, - { assignmentId: 2, title: '피로그래밍 페이지 클론 코딩', week: '1', day: 'TUESDAY', sessionDate: 'HTML/CSS, Git 기초', submitted: 'NOT_SUBMITTED' }, - { assignmentId: 3, title: '코딩앵무 클론 코딩', week: '1', day: 'THURSDAY', sessionDate: 'JS', submitted: 'SUBMITTED' }, - { assignmentId: 4, title: '숫자야구 게임', week: '1', day: 'THURSDAY', sessionDate: 'JS', submitted: 'LATE' }, - { assignmentId: 5, title: '파이썬 코딩도장', week: '1', day: 'THURSDAY', sessionDate: 'JS', submitted: 'NOT_SUBMITTED' }, - { assignmentId: 6, title: '아르사 팀플', week: '1', day: 'SATURDAY', sessionDate: 'DB 개론', submitted: 'SUBMITTED' }, - ], - }, - { week: '2', assignments: [] }, - { week: '3', assignments: [] }, - { week: '4', assignments: [] }, - { week: '5', assignments: [] }, -]; - -const IS_MOCK = true; // 백엔드 연동 시 false로 변경 +const IS_MOCK = false; // 제출 상태 아이콘 (부원용) function StatusIcon({ status }) { @@ -69,7 +51,7 @@ function AssignmentModal({ item, onClose, onSave }) { ? `/api/assignments/modify/${item.assignmentId}` : '/api/assignments/create'; const method = isEdit ? 'PATCH' : 'POST'; - await fetch(url, { + await authFetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: form.title, week: form.week, day: form.day }), @@ -164,13 +146,9 @@ function Assignment() { const [modalItem, setModalItem] = useState(undefined); // undefined=닫힘, null=생성, object=수정 const fetchAll = async () => { - if (IS_MOCK) { - setWeeks(MOCK_DATA); - return; - } const results = await Promise.all( ['1', '2', '3', '4', '5'].map(w => - fetch(`/api/assignments/me/${w}`) + authFetch(`/api/assignments/me/${w}`) .then(r => r.json()) .catch(() => ({ week: w, assignments: [] })) ) @@ -182,17 +160,12 @@ function Assignment() { const handleDelete = async (assignmentId) => { if (!window.confirm('삭제하시겠습니까?')) return; - if (!IS_MOCK) { - await fetch(`/api/assignments/${assignmentId}`, { method: 'DELETE' }); - } + await authFetch(`/api/assignments/${assignmentId}`, { method: 'DELETE' }); fetchAll(); - }; + }; return (
- {IS_MOCK && ( -
⚠️ 현재 임시 데이터로 표시 중입니다. 백엔드 연동 후 IS_MOCK을 false로 변경하세요.
- )}
ASSIGNMENT CHECK
{weeks.map((w, i) => ( diff --git a/frontend/src/pages/pirocheck/assignment/Assignment.module.css b/frontend/src/pages/pirocheck/assignment/Assignment.module.css index 8e38911..5ca20b9 100644 --- a/frontend/src/pages/pirocheck/assignment/Assignment.module.css +++ b/frontend/src/pages/pirocheck/assignment/Assignment.module.css @@ -103,7 +103,7 @@ .dayLabel { color: var(--dark); - font-family: var(--font-title); + font-family: var(--font-main); font-size: 1.2rem; font-weight: 700; } @@ -212,7 +212,7 @@ } .modalTitle { - font-family: var(--font-title); + font-family: var(--font-main); font-size: 3rem; font-weight: 800; color: var(--main); diff --git a/frontend/src/pages/pirocheck/attendance/Attendance.js b/frontend/src/pages/pirocheck/attendance/Attendance.js index 64d25e2..fe8f350 100644 --- a/frontend/src/pages/pirocheck/attendance/Attendance.js +++ b/frontend/src/pages/pirocheck/attendance/Attendance.js @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { authFetch } from '../../../utils/Api'; import styles from './Attendance.module.css'; import CloverGreen from '../../../assets/images/CloverGreen.svg'; import CloverRed from '../../../assets/images/CloverRed.svg'; @@ -14,8 +15,11 @@ function cloverForSlot(status) { return 미정; } -function slotIcon(status) { - if (status === true) return 출석; +function historyIcon(slots) { + const successCount = slots.filter(s => s.status === true).length; + if (successCount === 3) return 3회 출석; + if (successCount === 2) return 2회 출석; + if (successCount === 1) return 1회 출석; return 결석; } @@ -27,7 +31,7 @@ function AdminView() { useEffect(() => { const fetchActiveCode = async () => { try { - const res = await fetch('/api/admin/attendance/active-code'); + const res = await authFetch('/api/admin/attendance/active-code'); if (res.ok) { const data = await res.json(); if (!data.isExpired) { @@ -41,14 +45,14 @@ function AdminView() { }, []); const handleGenerate = async () => { - const res = await fetch('/api/admin/attendance/start', { method: 'POST' }); + const res = await authFetch('/api/admin/attendance/start', { method: 'POST' }); const data = await res.json(); setCode(data.code); setHasCode(true); }; const handleExpire = async () => { - await fetch('/api/admin/attendance/active-code/expire', { method: 'PUT' }); + await authFetch('/api/admin/attendance/active-code/expire', { method: 'PUT' }); setCode(null); setHasCode(false); }; @@ -72,7 +76,7 @@ function AdminView() { 종료 )} - 출석 관리 + 출석 관리
); @@ -96,7 +100,7 @@ function MemberView() { ] })); - fetch('/api/attendance/user') + authFetch('/api/attendance/user') .then(r => r.json()) .then(data => { const apiData = data.data || []; @@ -112,7 +116,7 @@ function MemberView() { const handleSubmit = async () => { if (!inputCode.trim()) return; - const res = await fetch('/api/attendance/mark', { + const res = await authFetch('/api/attendance/mark', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code: inputCode }), @@ -123,7 +127,7 @@ function MemberView() { if (result.statusCode === 'SUCCESS') { setMessage('출석 성공!'); const today = new Date().toISOString().split('T')[0]; - fetch(`/api/attendance/user/date?date=${today}`) + authFetch(`/api/attendance/user/date?date=${today}`) .then(r => r.json()) .then(d => setTodaySlots(d.data || [])); } else if (result.statusCode === 'INVALID_CODE') { @@ -166,10 +170,7 @@ function MemberView() {
{row.week}주차
- {[0, 1, 2].map(j => { - const slot = row.slots[j] ?? { status: false }; - return
{slotIcon(slot.status)}
; - })} + {historyIcon(row.slots)}
))} diff --git a/frontend/src/pages/pirocheck/deposit/Deposit.js b/frontend/src/pages/pirocheck/deposit/Deposit.js index 8b7f2f9..07a1ba2 100644 --- a/frontend/src/pages/pirocheck/deposit/Deposit.js +++ b/frontend/src/pages/pirocheck/deposit/Deposit.js @@ -1,40 +1,23 @@ import { useState, useEffect } from 'react'; +import { authFetch } from '../../../utils/Api'; import styles from './Deposit.module.css'; -// 임시 데이터 -const MOCK_DATA = { - amount: 100000, - descentAssignment: 10000, - descentAttendance: 10000, - ascentDefence: 10000, -}; - -const IS_MOCK = true; // 백엔드 연동 시 false로 변경 +const IS_MOCK = false function Deposit() { const [deposit, setDeposit] = useState(null); useEffect(() => { - if (IS_MOCK) { - setDeposit(MOCK_DATA); - return; - } - fetch('/api/deposit/me') + authFetch('/api/deposit/me') .then(r => r.json()) .then(data => setDeposit(data)) - .catch(() => setDeposit(MOCK_DATA)); + .catch(() => {}); }, []); if (!deposit) return null; return (
- {IS_MOCK && ( -
- ⚠️ 현재 임시 데이터로 표시 중입니다. 백엔드 연동 후 IS_MOCK을 false로 변경하세요. -
- )} -
DEPOSIT CHECK
diff --git a/frontend/src/pages/pirocheck/students/StudentDetail.js b/frontend/src/pages/pirocheck/students/StudentDetail.js index 6c31628..469c2f7 100644 --- a/frontend/src/pages/pirocheck/students/StudentDetail.js +++ b/frontend/src/pages/pirocheck/students/StudentDetail.js @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { useParams, useLocation } from 'react-router-dom'; +import { authFetch } from '../../../utils/Api'; import styles from './StudentDetail.module.css'; import ProfileImg from '../../../assets/images/profile.svg'; import Logo2 from '../../../assets/images/logo2.svg'; @@ -191,7 +192,7 @@ function StudentDetail() { // TODO: GET /api/admin/admin/student/{userId}/status/{week} (1~5주차) // 커리큘럼에서 세션 제목 가져오기 - const curriculumRes = await fetch('/api/curriculums'); + const curriculumRes = await authFetch('/api/curriculums'); const curriculums = await curriculumRes.json(); // weeks 데이터에 sessionTitles 추가 diff --git a/frontend/src/pages/pirocheck/students/StudentList.js b/frontend/src/pages/pirocheck/students/StudentList.js index 98790c8..802bfb4 100644 --- a/frontend/src/pages/pirocheck/students/StudentList.js +++ b/frontend/src/pages/pirocheck/students/StudentList.js @@ -1,17 +1,10 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { authFetch } from '../../../utils/Api'; import styles from './StudentList.module.css'; import ArrowRight from '../../../assets/images/icon_arrow_right.svg'; -const MOCK_STUDENTS = [ - { userId: 1, name: '김피로' }, - { userId: 2, name: '이피로' }, - { userId: 3, name: '박피로' }, - { userId: 4, name: '최피로' }, - { userId: 5, name: '정피로' }, -]; - -const IS_MOCK = true; +const IS_MOCK = false; function StudentList() { const navigate = useNavigate(); @@ -19,20 +12,14 @@ function StudentList() { const [search, setSearch] = useState(''); const fetchStudents = async (keyword = '') => { - if (IS_MOCK) { - if (keyword) { - setStudents(MOCK_STUDENTS.filter(s => s.name.includes(keyword))); - } else { - setStudents(MOCK_STUDENTS); - } - return; - } - const url = keyword - ? `/api/admin/studentlist/search?name=${keyword}` - : '/api/admin/studentlist'; - const res = await fetch(url); - const data = await res.json(); - setStudents(data); + try { + const url = keyword + ? `/api/admin/studentlist/search?name=${keyword}` + : '/api/admin/studentlist'; + const res = await authFetch(url); + const data = await res.json(); + setStudents(Array.isArray(data) ? data : data.data || []); + } catch (e) {} }; useEffect(() => { fetchStudents(); }, []); @@ -41,11 +28,6 @@ function StudentList() { return (
- {IS_MOCK && ( -
- ⚠️ 현재 임시 데이터로 표시 중입니다. -
- )}
PIROGRAMMER
diff --git a/frontend/src/utils/Api.js b/frontend/src/utils/Api.js new file mode 100644 index 0000000..91cdd7c --- /dev/null +++ b/frontend/src/utils/Api.js @@ -0,0 +1,11 @@ +export function authFetch(url, options = {}) { + const token = localStorage.getItem('token'); + return fetch(url, { + ...options, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + ...options.headers, + }, + }); +} \ No newline at end of file