diff --git a/ticketping/src/App.js b/ticketping/src/App.js index bc82916..1aebc8a 100644 --- a/ticketping/src/App.js +++ b/ticketping/src/App.js @@ -8,6 +8,7 @@ import Login from './pages/Login'; import VerificationInfo from './pages/Join'; import MyPage from './pages/MyPage'; import NotFound from './pages/NotFound'; +import SelectSchedule from './pages/SelectSchedule'; function App() { return ( @@ -18,6 +19,7 @@ function App() { } /> }> }> + } /> }> diff --git a/ticketping/src/component/AppLayout.js b/ticketping/src/component/AppLayout.js index 67f17b2..b21fc0c 100644 --- a/ticketping/src/component/AppLayout.js +++ b/ticketping/src/component/AppLayout.js @@ -2,16 +2,14 @@ import React from "react"; import { useNavigate } from "react-router-dom"; import { useAppContext } from "../store"; import { Button } from "antd"; -import { Logout } from "./Logout"; +import { useLogout } from "./Logout"; import "../style/AppLayout.css"; function AppLayout({ children }) { const navigate = useNavigate(); - const { - store: { isAuthenticated }, - } = useAppContext(); + const {store: { isAuthenticated } } = useAppContext(); - const { handleLogout } = Logout(); + const { logout } = useLogout(); return (
@@ -35,7 +33,7 @@ function AppLayout({ children }) { MyPage {isAuthenticated ? ( - ) : ( diff --git a/ticketping/src/component/EnterQueue.js b/ticketping/src/component/EnterQueue.js new file mode 100644 index 0000000..074d567 --- /dev/null +++ b/ticketping/src/component/EnterQueue.js @@ -0,0 +1,36 @@ +import { axiosInstance } from "../api"; +import { useNavigate } from "react-router-dom"; +import { useAppContext } from "../store"; +import { notification } from "antd"; +import { FrownOutlined } from "@ant-design/icons"; + +export const useEnterQueue = (setIsModalVisible) => { + const navigate = useNavigate(); + const { store: { jwtToken } } = useAppContext(); + const headers = { Authorization: jwtToken }; + + const enterQueue = async (performanceId) => { + try { + const response = await axiosInstance.post(`/api/v1/waiting-queue?performanceId=${performanceId}`, {}, { headers }); + const tokenStatus = response.data.data.tokenStatus; + + if (tokenStatus === "WAITING") { + setIsModalVisible(true); + } else if (tokenStatus === "WORKING") { + navigate(`/performance/${performanceId}/schedule`); + } + } catch (error) { + if (error.response) { + notification.open({ + message: `대기열 진입 실패`, + description: error.response.data.message, + icon: , + }); + } + } + }; + + return { + enterQueue + }; +}; diff --git a/ticketping/src/component/Logout.js b/ticketping/src/component/Logout.js index 56a54ff..7f954f1 100644 --- a/ticketping/src/component/Logout.js +++ b/ticketping/src/component/Logout.js @@ -1,32 +1,32 @@ -import React from "react"; +import { useNavigate } from "react-router-dom"; import { notification } from "antd"; import { MehOutlined } from "@ant-design/icons"; import { axiosInstance } from "../api"; -import { useNavigate } from "react-router-dom"; import { useAppContext, deleteToken } from "../store"; -export const Logout = () => { +export const useLogout = () => { const navigate = useNavigate(); const { dispatch } = useAppContext(); const { store: { jwtToken } } = useAppContext(); - const handleLogout = async () => { + const logout = async () => { const headers = { Authorization: jwtToken }; - + try { await axiosInstance.post("/api/v1/auth/logout", {}, { headers }); - + notification.open({ message: "로그아웃 완료", icon: , }); - + dispatch(deleteToken()); - navigate("/"); + + navigate("/"); } catch (error) { console.error("로그아웃 중 오류 발생:", error); } }; - return { handleLogout }; -}; \ No newline at end of file + return { logout }; +}; diff --git a/ticketping/src/component/QueueInfoModal.js b/ticketping/src/component/QueueInfoModal.js new file mode 100644 index 0000000..808fc0d --- /dev/null +++ b/ticketping/src/component/QueueInfoModal.js @@ -0,0 +1,70 @@ +import React, { useEffect, useState } from 'react'; +import { Modal, Button } from 'antd'; +import { useAppContext } from "../store"; +import { axiosInstance } from "../api"; +import { useNavigate } from "react-router-dom"; +import "../style/QueueInfoModal.css"; + +const QueueInfoModal = ({ visible, onClose, performanceId }) => { + const navigate = useNavigate(); + const { store: { jwtToken } } = useAppContext(); + const headers = { Authorization: jwtToken }; + + const [tokenStatus, setTokenStatus] = useState(null); + const [position, setPosition] = useState(0); + const [totalUsers, setTotalUsers] = useState(0); + + const fetchQueueInfo = async () => { + try { + const response = await axiosInstance.get(`/api/v1/waiting-queue?performanceId=${performanceId}`, { headers }); + setTokenStatus(response.data.data.tokenStatus); + setPosition(response.data.data.position); + setTotalUsers(response.data.data.totalUsers); + + console.log(response.data.data); + + if (tokenStatus === "WORKING") { + navigate(`/performance/${performanceId}/schedule`); + } + } catch (error) { + console.error("대기열 정보 요청 중 오류 발생:", error); + } + }; + + useEffect(() => { + if (visible) { + fetchQueueInfo(); + } + }, [visible]); + + const progressPercentage = totalUsers > 0 ? (position / totalUsers) * 100 : 0; + + return ( + +
+

티켓 예매를 위해 접속 대기중입니다.

+ +

나의 대기 순번: {position}

+ +
+ +

현재 {totalUsers}명이 대기 중에 있습니다.

+ +
+ +

현재 접속량이 많아 대기 중입니다.

+

잠시만 기다려 주시면 예매 페이지로 연결됩니다.

+
+ +
+ ); +}; + +export default QueueInfoModal; diff --git a/ticketping/src/pages/MyPage.js b/ticketping/src/pages/MyPage.js index 629590e..73a75d9 100644 --- a/ticketping/src/pages/MyPage.js +++ b/ticketping/src/pages/MyPage.js @@ -1,5 +1,5 @@ import React from "react"; export default function MyPage() { - return null; + return

마이 페이지

; } diff --git a/ticketping/src/pages/PerformanceDetail.js b/ticketping/src/pages/PerformanceDetail.js index 472bd9b..0bba18b 100644 --- a/ticketping/src/pages/PerformanceDetail.js +++ b/ticketping/src/pages/PerformanceDetail.js @@ -1,23 +1,30 @@ -// PerformanceDetail.js import React, { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { axiosInstance } from "../api"; +import { useEnterQueue } from "../component/EnterQueue"; +import QueueInfoModal from "../component/QueueInfoModal"; +import { useAppContext } from "../store"; +import { notification } from "antd"; +import { FrownOutlined } from "@ant-design/icons"; import "../style/PerformanceDetail.css"; function PerformanceDetail() { const { id } = useParams(); + const {store: { isAuthenticated } } = useAppContext(); + const [performance, setPerformance] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [timeRemaining, setTimeRemaining] = useState(null); + const [isModalVisible, setIsModalVisible] = useState(false); + const { enterQueue } = useEnterQueue(setIsModalVisible); + useEffect(() => { const fetchPerformance = async () => { - console.log("Fetching performance details for ID:", id); try { setIsLoading(true); - const response = await axiosInstance.get(`http://localhost:10001/api/v1/performances/${id}`); - console.log("Performance data fetched successfully:", response.data); + const response = await axiosInstance.get(`/api/v1/performances/${id}`); setPerformance(response.data.data); } catch (err) { console.error("Error fetching performance:", err); @@ -37,7 +44,6 @@ function PerformanceDetail() { useEffect(() => { if (performance) { const updateRemainingTime = () => { - console.log("Updating remaining time..."); const now = new Date(); const bookingDate = new Date(performance.reservationStartDate); const endBookingDate = new Date(performance.reservationEndDate); @@ -69,12 +75,10 @@ function PerformanceDetail() { }, [performance]); const formatPrice = (price) => { - console.log("Formatting price:", price); return price.toLocaleString(); }; if (isLoading) { - console.log("Loading state active..."); return

로딩 중...

; } @@ -83,7 +87,16 @@ function PerformanceDetail() { return

{error}

; } - console.log("Rendering performance details:", performance); + const handleEnterQueue = (performanceId) => { + if (isAuthenticated) { + enterQueue(performanceId); + } else { + notification.open({ + message: "로그인이 필요합니다!", + icon: + }); + } + }; return (
@@ -105,11 +118,15 @@ function PerformanceDetail() { 가격 - {performance.seatCostResponses.map((seat, index) => ( -
- {seat.seatGrade} {formatPrice(seat.cost)}원 -
- ))} + {Array.isArray(performance.seatCostResponses) && performance.seatCostResponses.length > 0 ? ( + performance.seatCostResponses.map((seat, index) => ( +
+ {seat.seatGrade} {formatPrice(seat.cost)}원 +
+ )) + ) : ( +
가격 정보가 없습니다.
+ )} @@ -124,7 +141,18 @@ function PerformanceDetail() { - + + + setIsModalVisible(false)} + performanceId={id} + />
); diff --git a/ticketping/src/pages/SelectSchedule.js b/ticketping/src/pages/SelectSchedule.js new file mode 100644 index 0000000..1a7569f --- /dev/null +++ b/ticketping/src/pages/SelectSchedule.js @@ -0,0 +1,5 @@ +import React from "react"; + +export default function SelectSchedule() { + return

일정 선택 페이지

; +} diff --git a/ticketping/src/style/AppLayout.css b/ticketping/src/style/AppLayout.css index 8959f26..c8545da 100644 --- a/ticketping/src/style/AppLayout.css +++ b/ticketping/src/style/AppLayout.css @@ -8,7 +8,7 @@ .header { background-color: #4A90E2; - padding: 32px 128px 0px 64px; + padding: 12px 128px 0px 64px; display: flex; justify-content: space-between; align-items: center; diff --git a/ticketping/src/style/Login.css b/ticketping/src/style/Login.css index 5c349a9..b33f212 100644 --- a/ticketping/src/style/Login.css +++ b/ticketping/src/style/Login.css @@ -32,7 +32,7 @@ } .button-container { - margin-top: 48px; + margin-top: 24px; display: flex; flex-direction: column; } diff --git a/ticketping/src/style/QueueInfoModal.css b/ticketping/src/style/QueueInfoModal.css new file mode 100644 index 0000000..b3c227d --- /dev/null +++ b/ticketping/src/style/QueueInfoModal.css @@ -0,0 +1,48 @@ +.queue-entry-modal-content { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + color: #000000; + font-family: 'Arial', sans-serif; +} + +.queue-entry-modal-content p { + margin: 3px 0; + color: #555555; +} + +.waiting-message { + font-size: 24px; + margin-bottom: 10px; + color: #555555; +} + +.queue-number { + font-size: 40px; + background-color: rgba(255, 255, 255, 0.2); + padding: 10px; + border-radius: 5px; +} + +.progress-bar { + height: 8px; + background-color: #4caf50; + border-radius: 5px; + margin: 10px 0; + transition: width 0.3s ease; + margin-bottom: 30px; +} + +.modal-divider { + width: 100%; + height: 1px; + background-color: #000; + margin: 10px 0; +} + +.ant-btn { + margin-top: 25px; + width: auto; +} + \ No newline at end of file