Skip to content

Commit

Permalink
�feat#152/ 전역 에러 핸들러, 티켓 구매 관련 api 연결 (#154)
Browse files Browse the repository at this point in the history
* feat: 대기열 진입 페이지 api 연결

* feat: 502 전역 에러핸들러 설정

정상 작동하는지 테스트 필요

* fix: waitClient baseUrl 상대경로로 수정

* feat: 메인페이지 문구 수정
  • Loading branch information
kkyu0718 authored Aug 28, 2024
1 parent 9e8c55a commit 65de5bb
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 21 deletions.
10 changes: 10 additions & 0 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ import CreateFestivalPage from './pages/festival/CreateFestivalPage';
import FestivalManagement from './pages/admin/FestivalManagement';
import PaymentProcessPage from './pages/festival/PaymentProcessPage';
import TicketQueuePage from './pages/festival/TicketQueuePage';
import setupAxiosInterceptors from "./components/exception/SetupAxiosInterceptors";
import {useEffect} from "react";
import ErrorBoundary from "./components/exception/ErrorBoundary";
import ErrorPage from "./components/exception/ErrorPage";

function App() {
useEffect(() => {
setupAxiosInterceptors();
}, []);

return (
<ErrorBoundary fallback={<Layout><ErrorPage /></Layout>}>
<div>
<AuthProvider>
<Routes>
Expand All @@ -32,6 +41,7 @@ function App() {
</Routes>
</AuthProvider>
</div>
</ErrorBoundary>
);
}

Expand Down
27 changes: 27 additions & 0 deletions frontend/src/components/exception/ErrorBoundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
console.error("Uncaught error:", error, errorInfo);
}

render() {
if (this.state.hasError) {
return <h1>오류가 발생했습니다.</h1>;
}

return this.props.children;
}
}

export default ErrorBoundary;
39 changes: 39 additions & 0 deletions frontend/src/components/exception/ErrorPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import {XCircleIcon} from '@heroicons/react/solid';

const ErrorPage = () => {
return (

<div className="flex flex-col items-center justify-center h-full px-4 py-16 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8 text-center">
<XCircleIcon className="mx-auto h-24 w-24 text-red-500"/>
<h2 className="mt-6 text-3xl font-extrabold text-gray-900">
Oops! 문제가 발생했습니다
</h2>
<p className="mt-2 text-sm text-gray-600">
죄송합니다. 현재 서버에 일시적인 문제가 발생했습니다.
</p>
<div className="mt-8 space-y-4">
<button
onClick={() => window.location.reload()}
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
페이지 새로고침
</button>

<a href="/"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm
text-sm font-medium text-indigo-600 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2
focus:ring-offset-2 focus:ring-indigo-500"
>
홈페이지로 돌아가기
</a>
</div>
</div>
</div>

)
;
};

export default ErrorPage;
16 changes: 16 additions & 0 deletions frontend/src/components/exception/SetupAxiosInterceptors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import axios from 'axios';

const setupAxiosInterceptors = () => {
axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 502) {
// 502 에러 발생 시 특정 URL로 리다이렉트
window.location.href = 'https://www.naver.com';
}
// return Promise.reject(error);
}
);
};

export default setupAxiosInterceptors;
2 changes: 1 addition & 1 deletion frontend/src/pages/festival/FestivalDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const FestivalDetail = () => {
};

const handlePurchase = (ticketId) => {
navigate(`/festivals/${festivalId}/tickets/${ticketId}/purchase`);
navigate(`/festivals/${festivalId}/tickets/${ticketId}/queue`);
};

if (isLoading) {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/festival/FestivalList.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ export default function FestivalList() {
<div className="flex flex-col h-full">
<div className="flex-grow overflow-y-auto px-4 py-8">
<div className="max-w-6xl mx-auto space-y-8">
<h1 className="text-3xl font-bold text-teel-500">Events Board</h1>
<p className="text-sm text-gray-600">이벤트 보드에서 페스타의 이벤트를 한눈에 볼 수 있습니다.</p>
<h1 className="text-3xl font-bold text-teel-500">이벤트 둘러보기</h1>
<p className="text-sm text-gray-600">축제의 민족에게 어울리는 이벤트를 찾아보세요</p>

{error && <p className="text-red-500 text-center mb-4">{error}</p>}

Expand Down
6 changes: 4 additions & 2 deletions frontend/src/pages/festival/PaymentProcessPage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
import apiClient from '../../utils/apiClient';
import { Loader2, AlertCircle, CheckCircle } from 'lucide-react';
import { Button } from '../../components/ui/button';
Expand All @@ -12,10 +12,12 @@ const PaymentProcessPage = () => {
const [isLoading, setIsLoading] = useState(true);
const [elapsedTime, setElapsedTime] = useState(0);
const navigate = useNavigate();
const [searchParams] = useSearchParams();

const startPayment = useCallback(async () => {
try {
const response = await apiClient.post(`/festivals/${festivalId}/tickets/${ticketId}/purchase`);
const purchaseSession = searchParams.get('purchaseSession');
const response = await apiClient.post(`/festivals/${festivalId}/tickets/${ticketId}/purchase/${purchaseSession}`);
console.log('Payment started:', response.data);
setPaymentId(response.data.data.paymentId);
setPaymentStatus('PENDING');
Expand Down
14 changes: 9 additions & 5 deletions frontend/src/pages/festival/TicketPurchasePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const TicketPurchasePage = () => {
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isReserved, setIsReserved] = useState(false);
const [purchaseSession, setPurchaseSession] = useState(null)

useEffect(() => {
const checkPurchaseAvailability = async () => {
Expand All @@ -20,14 +21,17 @@ const TicketPurchasePage = () => {
setError('현재 이 티켓은 구매할 수 없습니다.');
return;
}
await fetchPurchaseInfo();

const { purchaseSession } = response.data.data;
setPurchaseSession(purchaseSession);
await fetchPurchaseInfo(purchaseSession);
} catch (err) {
if (err.response) {
if (err.response.status === 401) {
navigate('/login', { state: { from: `/festivals/${festivalId}/tickets/${ticketId}/purchase` } });
} else if (err.response.data.errorCode === 'TK-0005') {
setIsReserved(true);
await fetchPurchaseInfo();
// await fetchPurchaseInfo();
} else if (err.response.status === 400) {
setError('이미 구매한 티켓입니다.');
} else if (err.response.status === 404) {
Expand All @@ -43,9 +47,9 @@ const TicketPurchasePage = () => {
}
};

const fetchPurchaseInfo = async () => {
const fetchPurchaseInfo = async (purchaseSession) => {
try {
const response = await apiClient.get(`/festivals/${festivalId}/tickets/${ticketId}/purchase`);
const response = await apiClient.get(`/festivals/${festivalId}/tickets/${ticketId}/purchase/${purchaseSession}`);
setPurchaseInfo(response.data.data);
} catch (err) {
setError('티켓 정보를 불러오는 중 오류가 발생했습니다.');
Expand All @@ -71,7 +75,7 @@ const TicketPurchasePage = () => {
const handleSubmit = async (e) => {
e.preventDefault();
try {
navigate(`/festivals/${festivalId}/tickets/${ticketId}/payment`);
navigate(`/festivals/${festivalId}/tickets/${ticketId}/payment?purchaseSession=${purchaseSession}`);
} catch (err) {
setError('결제 처리 중 오류가 발생했습니다.');
}
Expand Down
28 changes: 17 additions & 11 deletions frontend/src/pages/festival/TicketQueuePage.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import apiClient from '../../utils/apiClient';
import { Loader2 } from 'lucide-react';
import waitClient from '../../utils/waitClient'

const TicketQueuePage = () => {
const { festivalId, ticketId } = useParams();
const navigate = useNavigate();
const [waitOrder, setWaitOrder] = useState(null);
const [isPurchasable, setIsPurchasable] = useState(false);
const [relativeWaitOrder, setRelativeWaitOrder] = useState(null);
const [absoluteWaitOrder, setAbsoluteWaitOrder] = useState(null);
const [error, setError] = useState(null);

const checkQueueStatus = useCallback(async () => {
try {
const response = await apiClient.get(`/api/v1/festivals/${festivalId}/tickets/${ticketId}/purchase/wait`);
const { purchasable, waitOrder } = response.data.data;
setWaitOrder(waitOrder);
setIsPurchasable(purchasable);
const url = `/festivals/${festivalId}/tickets/${ticketId}/purchase/wait`;
const fullUrl = absoluteWaitOrder !== null ? `${url}?waitOrder=${absoluteWaitOrder}` : url;

const response = await waitClient.get(fullUrl);
const { purchasable, relativeWaitOrder, absoluteWaitOrder: newAbsoluteWaitOrder } = response.data.data;

console.log('대기열 상태:', response.data.data);

setRelativeWaitOrder(relativeWaitOrder);
setAbsoluteWaitOrder(newAbsoluteWaitOrder);

if (purchasable) {
navigate(`/festivals/${festivalId}/tickets/${ticketId}/purchase`);
Expand All @@ -24,7 +30,7 @@ const TicketQueuePage = () => {
setError('대기열 상태 확인 중 오류가 발생했습니다.');
console.error('대기열 상태 확인 오류:', err);
}
}, [festivalId, ticketId, navigate]);
}, [festivalId, ticketId, navigate, absoluteWaitOrder]);

useEffect(() => {
checkQueueStatus(); // 초기 호출
Expand All @@ -47,13 +53,13 @@ const TicketQueuePage = () => {
<h1 className="text-2xl font-bold text-teal-600 mb-4">티켓 구매 대기열</h1>
<div className="bg-white shadow-md rounded-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4">현재 대기 상태</h2>
{waitOrder !== null ? (
{relativeWaitOrder !== null ? (
<>
<p className="text-lg mb-4">대기 순서: {waitOrder}번째</p>
<p className="text-lg mb-4">대기 순서: {relativeWaitOrder}번째</p>
<div className="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
<div
className="bg-teal-600 h-2.5 rounded-full"
style={{ width: `${Math.max(0, 100 - waitOrder)}%` }}
style={{ width: `${Math.max(0, 100 - relativeWaitOrder)}%` }}
></div>
</div>
<p className="mt-2 text-sm text-gray-500">잠시만 기다려주세요. 곧 구매 페이지로 이동합니다.</p>
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/utils/waitClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from 'axios';

const waitClient = axios.create({
baseURL: '/api/v1',
withCredentials: true,
});

waitClient.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('isAuthenticated');
window.location.href = '/login';
}
return Promise.reject(error);
}
);

export default waitClient;

0 comments on commit 65de5bb

Please sign in to comment.