diff --git a/back/app.js b/back/app.js index fea3b5f..a5e9e50 100644 --- a/back/app.js +++ b/back/app.js @@ -21,19 +21,12 @@ const app = express(); // origin: 'http://localhost:3000', // credentials: true // })); -// app.use(cors({ -// origin: ['http://localhost:3000', 'https://st-available-room.netlify.app'], -// methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], -// allowedHeaders: ['Content-Type', 'Authorization'], -// credentials: true -// })); app.use(cors({ - origin: ['http://localhost:3000','https://st-available-room.netlify.app'], // ✅ 프론트 배포 URL 정확히 명시 - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'], - credentials: true // ✅ 쿠키/헤더 인증 정보 허용 -})); - + origin: 'http://localhost:3000', // 프론트 주소 + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true + })); app.use(express.json()); app.use('/api/users', userRoutes); diff --git a/front/src/api/axios.js b/front/src/api/axios.js new file mode 100644 index 0000000..36869b6 --- /dev/null +++ b/front/src/api/axios.js @@ -0,0 +1,8 @@ +import axios from 'axios'; + +const instance = axios.create({ + baseURL: 'http://localhost:8080/api', // 변경 시 여기만 수정 + withCredentials: true +}); + +export default instance; \ No newline at end of file diff --git a/front/src/api/instance.js b/front/src/api/instance.js deleted file mode 100644 index c2770e1..0000000 --- a/front/src/api/instance.js +++ /dev/null @@ -1,9 +0,0 @@ -// front/src/api/instance.js -import axios from 'axios'; - -const instance = axios.create({ - baseURL: `${process.env.REACT_APP_API_URL}/api`, - withCredentials: true, -}); - -export default instance; diff --git a/front/src/pages/HotspotPage.js b/front/src/pages/HotspotPage.js index ff84489..15f6fdb 100644 --- a/front/src/pages/HotspotPage.js +++ b/front/src/pages/HotspotPage.js @@ -4,7 +4,7 @@ import Footer from '../components/Footer'; import HotspotCard from '../components/HotspotCard'; import RoomSelectModal from '../components/RoomSelectModal'; import { useNavigate } from 'react-router-dom'; -import api from '../api/instance'; +import axios from 'axios'; import '../styles/HotspotPage.css'; const CATEGORIES = [ @@ -31,16 +31,16 @@ const HotspotPage = () => { try { let response; if (category === 'Auditorium Size / Large Hall') { - response = await api.get('/analytics/popular-buildings/by-large-group'); + response = await axios.get('http://localhost:8080/api/analytics/popular-buildings/by-large-group'); } else if (category === 'Study Friendly') { - response = await api.get('/analytics/popular-buildings/by-purpose?purpose=Study'); + response = await axios.get('http://localhost:8080/api/analytics/popular-buildings/by-purpose?purpose=Study'); } else if (category === 'Meeting & Presentation / Collab Zones') { - response = await api.get('/analytics/popular-buildings/by-purpose?purpose=Meeting'); + response = await axios.get('http://localhost:8080/api/analytics/popular-buildings/by-purpose?purpose=Meeting'); } const hotspotData = Array.isArray(response.data) ? response.data : [response.data]; - const allBuildingsRes = await api.get('/buildings'); + const allBuildingsRes = await axios.get('http://localhost:8080/api/buildings'); const allBuildings = allBuildingsRes.data.buildings; const matched = hotspotData.map((item, i) => { @@ -87,7 +87,7 @@ const HotspotPage = () => { const handleReserve = async (building) => { try { - const res = await api.get(`/buildings/rooms?buildingNo=${building.id}`); + const res = await axios.get(`http://localhost:8080/api/buildings/rooms?buildingNo=${building.id}`); const availableRooms = res.data.rooms.map(room => ({ room: `Room ${room}`, time: '8:00 - 17:50', @@ -169,4 +169,4 @@ const HotspotPage = () => { ); }; -export default HotspotPage; +export default HotspotPage; \ No newline at end of file diff --git a/front/src/pages/LoginPage.js b/front/src/pages/LoginPage.js index 987740f..7d1f69e 100644 --- a/front/src/pages/LoginPage.js +++ b/front/src/pages/LoginPage.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import api from '../api/instance'; +import axios from 'axios'; import { useNavigate } from 'react-router-dom'; import Header from '../components/Header'; import Footer from '../components/Footer'; @@ -17,10 +17,10 @@ const LoginPage = () => { const handleLogin = async () => { try { - const res = await api.post('/users/login', form); + const res = await axios.post('http://localhost:8080/api/users/login', form); localStorage.setItem('token', res.data.token); localStorage.setItem('user', JSON.stringify(res.data.user)); - navigate('/'); // 메인 페이지로 이동 + navigate('/'); // ✅ 메인 페이지로 이동 } catch (err) { console.warn('⚠️ 백엔드 로그인 실패 - mock 처리로 우회'); @@ -39,8 +39,8 @@ const LoginPage = () => { }; const handleSubmit = (e) => { - e.preventDefault(); // 새로고침 방지 - handleLogin(); // 로그인 실행 + e.preventDefault(); // ⛔ 새로고침 방지 + handleLogin(); // ✅ 로그인 실행 }; return ( @@ -56,7 +56,7 @@ const LoginPage = () => { - {/*form으로 감싸고 onSubmit 적용 */} + {/* ✅ form으로 감싸고 onSubmit 적용 */}
@@ -95,4 +95,4 @@ const LoginPage = () => { ); }; -export default LoginPage; +export default LoginPage; \ No newline at end of file diff --git a/front/src/pages/MainPage.js b/front/src/pages/MainPage.js index 388f1b7..dd78385 100644 --- a/front/src/pages/MainPage.js +++ b/front/src/pages/MainPage.js @@ -63,4 +63,4 @@ const MainPage = () => { ); }; -export default MainPage; +export default MainPage; \ No newline at end of file diff --git a/front/src/pages/MyReservationPage.js b/front/src/pages/MyReservationPage.js index c7067f1..747eac3 100644 --- a/front/src/pages/MyReservationPage.js +++ b/front/src/pages/MyReservationPage.js @@ -1,11 +1,11 @@ import React, { useEffect, useState } from 'react'; -import api from '../api/instance'; +import axios from 'axios'; import Header from '../components/Header'; import Footer from '../components/Footer'; import Modal from '../components/Modal'; import '../styles/MyReservationPage.css'; -// 이미지 경로 매핑 +// ✅ 이미지 경로 매핑 const getBuildingImage = (number) => { try { return require(`../assets/buildings img/${number}.png`); @@ -21,10 +21,10 @@ const MyReservationPage = () => { const [modalStep, setModalStep] = useState('confirm'); const [selectedReservation, setSelectedReservation] = useState(null); - // 건물 리스트 받아오기 + // 🧭 건물 리스트 받아오기 const fetchBuildings = async () => { try { - const res = await api.get('/buildings'); + const res = await axios.get('http://localhost:8080/api/buildings'); setBuildings(res.data.buildings || []); } catch (err) { console.warn('⚠️ 건물 정보 fetch 실패, fallback mock 사용'); @@ -35,11 +35,11 @@ const MyReservationPage = () => { } }; - // 예약 정보 불러오기 + // 🗓️ 예약 정보 불러오기 const fetchReservations = async () => { try { const token = localStorage.getItem('token'); - const res = await api.get('/reservations/my', { + const res = await axios.get('http://localhost:8080/api/reservations/my', { headers: { Authorization: `Bearer ${token}` } }); setReservations(res.data); @@ -85,7 +85,7 @@ const MyReservationPage = () => { const confirmCancel = async () => { try { const token = localStorage.getItem('token'); - await api.delete(`/reservations/${selectedReservation._id}`, { + await axios.delete(`http://localhost:8080/api/reservations/${selectedReservation._id}`, { headers: { Authorization: `Bearer ${token}` } }); setReservations(reservations.filter(r => r._id !== selectedReservation._id)); @@ -163,4 +163,4 @@ const MyReservationPage = () => { ); }; -export default MyReservationPage; +export default MyReservationPage; \ No newline at end of file diff --git a/front/src/pages/ProfilePage.js b/front/src/pages/ProfilePage.js index 39d3a8a..22350ff 100644 --- a/front/src/pages/ProfilePage.js +++ b/front/src/pages/ProfilePage.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import api from '../api/instance'; +import axios from 'axios'; import Header from '../components/Header'; import Footer from '../components/Footer'; import BuildingCard from '../components/BuildingCard'; @@ -37,7 +37,7 @@ const MOCK_BUILDINGS = [ }, ]; -// mock 유저 미리 설정 +// ✅ mock 유저 미리 설정 const MOCK_USER = { name: '홍길동', studentNumber: '202312345', @@ -58,23 +58,23 @@ const ProfilePage = () => { const token = localStorage.getItem('token'); if (!token) throw new Error('No token'); - const res = await api.get('/users/me', { + const res = await axios.get('http://localhost:8080/api/users/me', { headers: { Authorization: `Bearer ${token}` } }); const userData = res.data.user; setUser(userData); - // 서버에서 전체 건물 목록 받아오기 - const buildingsRes = await api.get('/api/buildings'); + // ✅ 서버에서 전체 건물 목록 받아오기 + const buildingsRes = await axios.get('http://localhost:8080/api/buildings'); const buildingData = buildingsRes.data.buildings; - // building.name과 userData.favorites 비교 후 매칭 + // ✅ building.name과 userData.favorites를 비교해 매칭 const matched = await Promise.all( buildingData .filter(b => userData.favorites.includes(b.buildingName)) .map(async (b) => { - const roomRes = await api.get(`/buildings/rooms?buildingNo=${b.buildingNo}`); + const roomRes = await axios.get(`http://localhost:8080/api/buildings/rooms?buildingNo=${b.buildingNo}`); const availableRooms = roomRes.data.rooms || []; return { @@ -123,13 +123,13 @@ const ProfilePage = () => { if (token) { if (isAlreadyFavorite) { - await api.delete('/users/favorites', { + await axios.delete('http://localhost:8080/api/users/favorites', { headers: { Authorization: `Bearer ${token}` }, data: { building: buildingName }, }); updatedFavorites = user.favorites.filter((n) => n !== buildingName); } else { - await api.post('/users/favorites', { building: buildingName }, { + await axios.post('http://localhost:8080/api/users/favorites', { building: buildingName }, { headers: { Authorization: `Bearer ${token}` }, }); updatedFavorites = [...user.favorites, buildingName]; @@ -199,4 +199,4 @@ const ProfilePage = () => { ); }; -export default ProfilePage; +export default ProfilePage; \ No newline at end of file diff --git a/front/src/pages/ReservePage.js b/front/src/pages/ReservePage.js index 4935dc2..8e1b002 100644 --- a/front/src/pages/ReservePage.js +++ b/front/src/pages/ReservePage.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import api from '../api/instance'; +import axios from 'axios'; import Header from '../components/Header'; import Footer from '../components/Footer'; import BuildingCard from '../components/BuildingCard'; @@ -54,8 +54,8 @@ const ReservePage = () => { try { const token = localStorage.getItem('token'); const [buildingsRes, userRes] = await Promise.all([ - api.get('/buildings'), - api.get('/users/me', { + axios.get('http://localhost:8080/api/buildings'), + axios.get('http://localhost:8080/api/users/me', { headers: { Authorization: `Bearer ${token}` } }), ]); @@ -65,7 +65,7 @@ const ReservePage = () => { const buildingList = await Promise.all( buildingData.map(async (b) => { - const roomRes = await api.get(`/buildings/rooms?buildingNo=${b.buildingNo}`); + const roomRes = await axios.get(`http://localhost:8080/api/buildings/rooms?buildingNo=${b.buildingNo}`); const availableRooms = roomRes.data.rooms || []; return { id: String(b.buildingNo), @@ -99,7 +99,7 @@ const ReservePage = () => { try { if (isAlreadyFavorite) { - await api.delete('/users/favorites', { + await axios.delete('http://localhost:8080/api/users/favorites', { headers: { Authorization: `Bearer ${token}` }, data: { building: buildingName }, }); @@ -107,7 +107,7 @@ const ReservePage = () => { setFavoriteIds(updated); localStorage.setItem('favorites', JSON.stringify(updated)); } else { - await api.post('/users/favorites', { building: buildingName }, { + await axios.post('http://localhost:8080/api/users/favorites', { building: buildingName }, { headers: { Authorization: `Bearer ${token}` }, }); const updated = [...favoriteIds, buildingName]; @@ -133,7 +133,7 @@ const ReservePage = () => { const filteredBuildings = buildings .filter(b => b.name.toLowerCase().includes(searchTerm.toLowerCase())) - .filter(b => b.availableRooms.length > 0); // 방 없는 건물 제외 + .filter(b => b.availableRooms.length > 0); // ✅ 방이 없는 건물 제외 const favoriteBuildings = filteredBuildings.filter(b => favoriteIds.includes(b.name)); const nonFavoriteBuildings = filteredBuildings.filter(b => !favoriteIds.includes(b.name)); @@ -195,4 +195,4 @@ const ReservePage = () => { ); }; -export default ReservePage; +export default ReservePage; \ No newline at end of file diff --git a/front/src/pages/RoomDetailPage.js b/front/src/pages/RoomDetailPage.js index 625d230..1b20629 100644 --- a/front/src/pages/RoomDetailPage.js +++ b/front/src/pages/RoomDetailPage.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import api from '../api/instance'; +import axios from 'axios'; import Header from '../components/Header'; import Footer from '../components/Footer'; import Modal from '../components/Modal'; @@ -84,7 +84,7 @@ const RoomDetailPage = () => { const fetchAvailability = async () => { try { - const res = await api.get('/timetable/availability', { + const res = await axios.get('http://localhost:8080/api/timetable/availability', { params: { building, room, week: formatDate(startOfWeek) } }); applyGridFromAvailability(res.data.availability); @@ -148,8 +148,8 @@ const RoomDetailPage = () => { const endTime = startTimes[r + selected.length] || '18:00'; try { - await api.post( - '/reservations', + await axios.post( + 'http://localhost:8080/api/reservations', { building, room, @@ -168,7 +168,7 @@ const RoomDetailPage = () => { } catch (err) { setShowConfirm(false); alert(err.response?.status === 409 - ? 'Some of the selected periods have already been reserved or were booked by you today—cannot reserve again. ' + ? 'Some of the selected periods have already been reserved' : 'Reservation failed. Try again.'); } }; @@ -278,4 +278,4 @@ const RoomDetailPage = () => { ); }; -export default RoomDetailPage; +export default RoomDetailPage; \ No newline at end of file diff --git a/front/src/pages/SignupPage.js b/front/src/pages/SignupPage.js index 6336ed3..2edef66 100644 --- a/front/src/pages/SignupPage.js +++ b/front/src/pages/SignupPage.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import api from '../api/instance'; +import axios from 'axios'; import Header from '../components/Header'; import Footer from '../components/Footer'; import '../styles/SignupPage.css'; @@ -35,7 +35,7 @@ const SignupPage = () => { const handleSignup = async () => { try { - await api.post('/users/register', form); + await axios.post('http://localhost:8080/api/users/register', form); alert('회원가입 성공!'); window.location.href = '/login'; } catch (err) { @@ -112,4 +112,4 @@ const SignupPage = () => { ); }; -export default SignupPage; +export default SignupPage; \ No newline at end of file diff --git a/front/src/test/ReservePage.test.js b/front/src/test/ReservePage.test.js index 2ac6047..bf7f270 100644 --- a/front/src/test/ReservePage.test.js +++ b/front/src/test/ReservePage.test.js @@ -3,10 +3,7 @@ import { render, screen } from '@testing-library/react'; import ReservePage from '../pages/ReservePage'; import { BrowserRouter } from 'react-router-dom'; -// ✅ api 인스턴스를 mocking -import api from '../api/instance'; - -jest.mock('../api/instance', () => ({ +jest.mock('axios', () => ({ get: jest.fn(() => Promise.resolve({ data: { buildings: [] } })), })); @@ -23,4 +20,4 @@ test('Make sure your favorite building is displayed at the top', async () => { const title = await screen.findByText(/Favorite Classrooms/i); expect(title).toBeInTheDocument(); -}); +}); \ No newline at end of file diff --git a/front/src/test/RoomDetailPage.test.js b/front/src/test/RoomDetailPage.test.js index 5f8a8a0..e42851e 100644 --- a/front/src/test/RoomDetailPage.test.js +++ b/front/src/test/RoomDetailPage.test.js @@ -3,21 +3,16 @@ import { render, screen } from '@testing-library/react'; import RoomDetailPage from '../pages/RoomDetailPage'; import { BrowserRouter } from 'react-router-dom'; -// ✅ axios 대신 api 인스턴스를 mocking -import api from '../api/instance'; - -jest.mock('../api/instance', () => ({ - get: jest.fn(() => - Promise.resolve({ - data: { - availability: { - Mon: { - 'Period 0': { status: 'unavailable', subject: 'Math' }, - }, +jest.mock('axios', () => ({ + get: jest.fn(() => Promise.resolve({ + data: { + availability: { + Mon: { + 'Period 0': { status: 'unavailable', subject: 'Math' }, }, }, - }) - ), + }, + })), })); test('Unavailable cells are marked with unbookable', async () => { @@ -29,4 +24,4 @@ test('Unavailable cells are marked with unbookable', async () => { const cell = await screen.findByText(/📘 Math/i); expect(cell).toHaveClass('unavailable'); -}); +}); \ No newline at end of file