diff --git a/BackEnd/src/main/java/com/team5/nbe341team05/Nbe341Team05Application.java b/BackEnd/src/main/java/com/team5/nbe341team05/Nbe341Team05Application.java index 5f39610fe..913ce69e6 100644 --- a/BackEnd/src/main/java/com/team5/nbe341team05/Nbe341Team05Application.java +++ b/BackEnd/src/main/java/com/team5/nbe341team05/Nbe341Team05Application.java @@ -3,11 +3,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.scheduling.annotation.EnableScheduling; +import static org.springframework.data.web.config.EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO; + @SpringBootApplication @EnableJpaAuditing @EnableScheduling // 스케줄링 활성화 +@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO) public class Nbe341Team05Application { public static void main(String[] args) { diff --git a/BackEnd/src/main/java/com/team5/nbe341team05/common/security/SecurityConfig.java b/BackEnd/src/main/java/com/team5/nbe341team05/common/security/SecurityConfig.java index 509db0192..aab1a2fae 100644 --- a/BackEnd/src/main/java/com/team5/nbe341team05/common/security/SecurityConfig.java +++ b/BackEnd/src/main/java/com/team5/nbe341team05/common/security/SecurityConfig.java @@ -1,5 +1,7 @@ package com.team5.nbe341team05.common.security; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -7,6 +9,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -14,6 +17,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.cors.CorsConfiguration; @@ -21,6 +25,8 @@ import java.util.Collections; import java.util.List; +import java.io.IOException; + @Configuration @EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true) // @PreAuthorize 사용 @@ -42,7 +48,20 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .formLogin(login -> login .loginPage("/login") // 로그인 페이지 설정 .loginProcessingUrl("/perform_login") - .defaultSuccessUrl("/admin/order", true) // 로그인 성공 후 리다이렉트 + .successHandler(new AuthenticationSuccessHandler() { + @Override + public void onAuthenticationSuccess(HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) throws IOException { + String redirectUrl = request.getParameter("redirectUrl"); + if (redirectUrl != null && redirectUrl.startsWith("/admin/")) { + response.sendRedirect(redirectUrl); + } else { + response.sendRedirect("/admin/order"); + } + + } + }) // 로그인 성공 후 리다이렉트 .permitAll() ) .logout(logout -> logout diff --git a/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/controller/MenuAdminController.java b/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/controller/MenuAdminController.java index e21f17e63..ac3885b87 100644 --- a/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/controller/MenuAdminController.java +++ b/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/controller/MenuAdminController.java @@ -47,7 +47,6 @@ public ResponseMessage getMenuById(@PathVariable("id") Long id) ); } - @Transactional @PostMapping public ResponseMessage createMenu(@RequestPart("menu") MenuRequestDto menuRequestDto , @RequestPart("image")MultipartFile image @@ -64,7 +63,7 @@ public ResponseMessage createMenu(@RequestPart("menu") MenuRequ @Transactional @PostMapping("/{id}") - public ResponseMessage updateMenu(@PathVariable("id") Long id, @RequestBody MenuRequestDto menuRequestDto, MultipartFile image) throws IOException { + public ResponseMessage updateMenu(@PathVariable("id") Long id, @RequestPart(value = "menuRequestDto") MenuRequestDto menuRequestDto, @RequestPart(value = "image") MultipartFile image) throws IOException { Menu menu = this.menuService.updateMenu(id, menuRequestDto,image); return new ResponseMessage<>("상품이 성공적으로 수정되었습니다.", String.valueOf(HttpStatus.OK.value()), menu); diff --git a/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/service/MenuService.java b/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/service/MenuService.java index c8bd023d6..3f20065f5 100644 --- a/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/service/MenuService.java +++ b/BackEnd/src/main/java/com/team5/nbe341team05/domain/menu/service/MenuService.java @@ -112,6 +112,6 @@ public String saveImage(MultipartFile imageFile) throws IOException { Path filePath = Paths.get(uploadDir, originalFilename); imageFile.transferTo(filePath.toFile()); - return originalFilename; + return savedFilename; } } \ No newline at end of file diff --git a/frontend/src/App.js b/frontend/src/App.js index c05b2858a..69cbba66a 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -12,37 +12,40 @@ import EventsScreen from './screens/EventsScreen'; import './App.css'; import LoginPage from "./components/login"; -import AddMenuPage from "./screens/addMenu"; -import AdminOrderDetail from "./screens/AdminOrderDetail"; -import AdminOrderList from './screens/AdminOrderList'; -import Login from './components/login'; +import AddMenuPage from "./screens/admin/AddMenu"; +import AdminOrderDetail from "./screens/admin/AdminOrderDetail"; +import AdminOrderList from './screens/admin/AdminOrderList'; +import AdminMenuScreen from './screens/admin/AdminMenuScreen'; +import ModifyMenuPage from './screens/admin/ModifyMenu'; +import Layout from './components/Layout'; function App() { - return ( - - - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - }/> - {/* /login 경로에 LoginPage 컴포넌트를 렌더링 */} - }/> - {/* 기본 경로 설정 */} - {/* Welcome to the Home Page} /> */} - }/> - }/> - }/> - }/> - }/> - }/> - - - ); + return ( + + + }/> + }/> + }/> + } /> + } /> + } /> + } /> + } /> + + {/* /login 경로에 LoginPage 컴포넌트를 렌더링 */} + } /> + {/* 기본 경로 설정 */} + {/* Welcome to the Home Page} /> */} + }/> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); } export default App; \ No newline at end of file diff --git a/frontend/src/DL/api.js b/frontend/src/DL/api.js index e90073e42..b5fa37f6d 100644 --- a/frontend/src/DL/api.js +++ b/frontend/src/DL/api.js @@ -4,87 +4,137 @@ const API_BASE_URL = 'http://localhost:8080'; // 백엔드 API 주소 const api = axios.create({ baseURL: API_BASE_URL, // baseURL을 사용하여 기본 API 주소 설정 - headers:{ - "Content-Type" : "application/json", + headers: { + "Content-Type": "application/json", }, withCredentials: true, // 세션 기반 인증에 필요한 설정 (쿠키 포함) }); // 요청 인터셉터 - 토큰 추가 api.interceptors.request.use( - (config) => { - const token = localStorage.getItem('token'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; - }, - (error) => { - return Promise.reject(error); + (config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; } + return config; + }, + (error) => { + return Promise.reject(error); + } ); -export const uploadImage = async (image) => { - if (!image) { - alert("이미지를 선택하세요."); - return; +// 응답 인터셉터 추가 +api.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + console.error('API 에러:', error.response); + if (error.response?.status === 302) { + // 로그인 페이지로 리다이렉트 + window.location.href = '/login'; + } + return Promise.reject(error); } +); +export const addMenu = async (menuData, image) => { try { - const formData = new FormData(); - formData.append("image", image); - const response = await api.post(`/admin/menus`, formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }); - - alert("이미지 업로드 성공."); - return response.data; // 서버에서 반환된 이미지 URL - } catch (err) { - console.error("이미지 업로드 실패:", err); - throw new Error("이미지 업로드에 실패했습니다."); - } -}; + const menuRequestDto = { + productName: menuData.productName, + description: menuData.description, + price: Number(menuData.price), + stock: Number(menuData.stock) + }; -export const addMenu = async (menuData,image) => { - try { - const formData = new FormData(); - formData.append("menu", JSON.stringify(menuData)); // JSON으로 변환 후 추가 + formData.append("menu", new Blob([JSON.stringify(menuRequestDto)], { + type: 'application/json' + })); formData.append("image", image); - + formData.forEach((value, key) => { + console.log(`${key}:`, value); + }); const response = await api.post(`/admin/menus`, formData, { headers: { - "Content-Type": "application/form-data", + "Content-Type": "multipart/form-data", }, + withCredentials: true, }); + console.log("메뉴 생성 성공:", response.data); - return response.data; // 서버 응답 데이터 + return response.data; } catch (err) { - console.error("메뉴 추가 실패:", err); - throw new Error("메뉴 추가에 실패했습니다."); + if (err.response?.status === 302) { + window.location.href = '/login'; + throw new Error('로그인이 필요합니다.'); + } + throw new Error(err.response?.data?.message || "메뉴 추가에 실패했습니다."); } }; export const adminApi = { - getAllOrders: () => api.get(`${API_BASE_URL}/admin/order`), - getOrderById: (id) => api.get(`${API_BASE_URL}/admin/order/${id}`), - cancelOrder: (id) => api.delete(`${API_BASE_URL}/admin/order/${id}`) + getAllOrders: async () => { + try { + // API 경로 수정 + const response = await api.get('/admin/order'); // 또는 실제 백엔드 경로에 맞게 수정 + return response; + } catch (error) { + console.error('getAllOrders Error:', error); + throw error; + } + }, + getOrderDetail: async (orderId) => { + try { + // API 경로 수정 + const response = await api.get(`/admin/order/${orderId}`); // 또는 실제 백엔드 경로에 맞게 수정 + return response; + } catch (error) { + console.error('getOrderDetail Error:', error); + throw error; + } + }, + modifyMenu: async (menuId, menuData, image) => { + try { + const formData = new FormData(); + + formData.append('menuRequestDto', new Blob([JSON.stringify(menuData)], { + type: 'application/json' + })); + + // 새로운 이미지가 있는 경우에만 추가 + if (image instanceof File) { + formData.append('image', image); + } + const response = await api.post(`/admin/menus/${menuId}`, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + withCredentials: true, + }); + return response.data; + } catch (error) { + console.error('modifyMenu Error:', error); + throw error; + } + }, + cancelOrder: (orderId) => api.delete(`/admin/order/${orderId}`), + cancelMenu: (menuId) => api.delete(`/admin/menus/${menuId}`) }; // 주문 생성 export const createOrder = async (orderData) => { - try { - const response = await api.post(`/order`, orderData); - return response.data; // 성공적으로 받은 데이터 반환 - } catch (error) { - // 오류 처리 - console.error('주문 생성 실패:', error.response?.data || error.message); - throw error; // 오류를 호출한 쪽으로 다시 던짐 - } - }; + try { + const response = await api.post(`/order`, orderData); + return response.data; // 성공적으로 받은 데이터 반환 + } catch (error) { + // 오류 처리 + console.error('주문 생성 실패:', error.response?.data || error.message); + throw error; // 오류를 호출한 쪽으로 다시 던짐 + } +}; // 주문 조회 export const orderList = (email) => api.get(`/order/${email}`); diff --git a/frontend/src/components/Layout.js b/frontend/src/components/Layout.js new file mode 100644 index 000000000..26678ca87 --- /dev/null +++ b/frontend/src/components/Layout.js @@ -0,0 +1,24 @@ +import React from 'react'; +import Navbar from './Navbar'; +import Sidebar from './Sidebar'; + +const Layout = ({ children, showSidebar = true }) => { + return ( +
+ {/* Navbar */} + + + {/* Main Content Area */} +
{/* Navbar 높이만큼 상단 여백 */} + {showSidebar && } + + {/* Main Content */} +
{/* Sidebar 너비만큼 왼쪽 여백 */} + {children} +
+
+
+ ); +}; + +export default Layout; \ No newline at end of file diff --git a/frontend/src/components/Navbar.js b/frontend/src/components/Navbar.js index a68f888df..3fce066ee 100644 --- a/frontend/src/components/Navbar.js +++ b/frontend/src/components/Navbar.js @@ -1,8 +1,33 @@ import React, { useState } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; const Navbar = () => { const [showDropdown, setShowDropdown] = useState(false); + const navigate = useNavigate(); + + const handleAdminMenuClick = async (path) => { + try { + const response = await fetch('/api/auth/check', { + credentials: 'include' + }); + + if (!response.ok) { + // state에 리다이렉트 경로 저장 + navigate('/login', { + state: { redirectTo: path }, + replace: true // 히스토리 스택에 쌓이지 않도록 설정 + }); + } else { + navigate(path); + } + } catch (error) { + console.error('인증 확인 실패:', error); + navigate('/login', { + state: { redirectTo: path }, + replace: true + }); + } + }; return (