Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ticketping/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -18,6 +19,7 @@ function App() {
<Route path="/performance/:id" element={<PerformanceDetail />} />
<Route path='/login' element={<Login />}></Route>
<Route path='/join' element={<VerificationInfo />}></Route>
<Route path="/performance/:id/schedule" element={<LoginRequiredRoute><SelectSchedule /></LoginRequiredRoute>} />
<Route path='*' element={<NotFound />}></Route>
</Routes>
</AppLayout>
Expand Down
10 changes: 4 additions & 6 deletions ticketping/src/component/AppLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="app">
Expand All @@ -35,7 +33,7 @@ function AppLayout({ children }) {
MyPage
</Button>
{isAuthenticated ? (
<Button type="link" className="auth-button" onClick={handleLogout}>
<Button type="link" className="auth-button" onClick={logout}>
Logout
</Button>
) : (
Expand Down
36 changes: 36 additions & 0 deletions ticketping/src/component/EnterQueue.js
Original file line number Diff line number Diff line change
@@ -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: <FrownOutlined style={{ color: "#ff3333" }} />,
});
}
}
};

return {
enterQueue
};
};
20 changes: 10 additions & 10 deletions ticketping/src/component/Logout.js
Original file line number Diff line number Diff line change
@@ -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: <MehOutlined style={{ color: "#fa8c16" }} />,
});

dispatch(deleteToken());
navigate("/");

navigate("/");
} catch (error) {
console.error("로그아웃 중 오류 발생:", error);
}
};

return { handleLogout };
};
return { logout };
};
70 changes: 70 additions & 0 deletions ticketping/src/component/QueueInfoModal.js
Original file line number Diff line number Diff line change
@@ -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 (
<Modal
visible={visible}
footer={null}
closable={false}
styles={{ mask: { backgroundColor: 'rgba(0, 0, 0, 0.85)' } }}
>
<div className="queue-entry-modal-content">
<h3 className="waiting-message">티켓 예매를 위해 접속 대기중입니다.</h3>

<h1 className="queue-number">나의 대기 순번: {position}</h1>

<div className="progress-bar" style={{ width: `${progressPercentage}%` }}></div>

<h1 className="waiting-message">현재 {totalUsers}명이 대기 중에 있습니다.</h1>

<div className="modal-divider"></div>

<p>현재 접속량이 많아 대기 중입니다.</p>
<p>잠시만 기다려 주시면 예매 페이지로 연결됩니다.</p>
</div>
<Button key="back" onClick={onClose}>
닫기
</Button>
</Modal>
);
};

export default QueueInfoModal;
2 changes: 1 addition & 1 deletion ticketping/src/pages/MyPage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";

export default function MyPage() {
return null;
return <p>마이 페이지</p>;
}
56 changes: 42 additions & 14 deletions ticketping/src/pages/PerformanceDetail.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 <p>로딩 중...</p>;
}

Expand All @@ -83,7 +87,16 @@ function PerformanceDetail() {
return <p>{error}</p>;
}

console.log("Rendering performance details:", performance);
const handleEnterQueue = (performanceId) => {
if (isAuthenticated) {
enterQueue(performanceId);
} else {
notification.open({
message: "로그인이 필요합니다!",
icon: <FrownOutlined style={{ color: "#ff3333" }} />
});
}
};

return (
<div className="performance-detail">
Expand All @@ -105,11 +118,15 @@ function PerformanceDetail() {
<tr>
<th>가격</th>
<td className="price-cell">
{performance.seatCostResponses.map((seat, index) => (
<div key={index}>
{seat.seatGrade} {formatPrice(seat.cost)}원
</div>
))}
{Array.isArray(performance.seatCostResponses) && performance.seatCostResponses.length > 0 ? (
performance.seatCostResponses.map((seat, index) => (
<div key={index}>
{seat.seatGrade} {formatPrice(seat.cost)}원
</div>
))
) : (
<div>가격 정보가 없습니다.</div>
)}
</td>
</tr>
<tr>
Expand All @@ -124,7 +141,18 @@ function PerformanceDetail() {
</tr>
</tbody>
</table>
<button disabled={timeRemaining !== "공연 예매하기"}>{timeRemaining}</button>
<button
disabled={timeRemaining !== "공연 예매하기"}
onClick={() => handleEnterQueue(id)}
>
{timeRemaining}
</button>

<QueueInfoModal
visible={isModalVisible}
onClose={() => setIsModalVisible(false)}
performanceId={id}
/>
</div>
</div>
);
Expand Down
5 changes: 5 additions & 0 deletions ticketping/src/pages/SelectSchedule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from "react";

export default function SelectSchedule() {
return <p>일정 선택 페이지</p>;
}
2 changes: 1 addition & 1 deletion ticketping/src/style/AppLayout.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion ticketping/src/style/Login.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
}

.button-container {
margin-top: 48px;
margin-top: 24px;
display: flex;
flex-direction: column;
}
Expand Down
48 changes: 48 additions & 0 deletions ticketping/src/style/QueueInfoModal.css
Original file line number Diff line number Diff line change
@@ -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;
}

Loading