Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
30 changes: 27 additions & 3 deletions src/features/ticket/api/order.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
import { axiosClient } from "../../../shared/types/api/http-client"
export const readMyTickets = {
get: async (page: number = 0, size: number = 10) => {
import { OrderTicketRequest } from "../model/OrderCreation";

export const readTicket = {
// 주문 티켓 전체 조회
getAll: async (page: number = 0, size: number = 10) => {
const response = await axiosClient.get("/orders", {
params: { page, size },
});
return response.data;
},
}
// 주문 상세 조회
getDetail: async(ticketId: number, eventId: number) => {
const response = await axiosClient.get('/orders/purchase-confirmation',{
params: {ticketId, eventId}
});
return response.data;
}
}

// 티켓 구매
export const orderTickets = async (data: OrderTicketRequest) => {
const response = await axiosClient.post("/orders", data);
return response.data;
}

// 티켓 취소
export const cancleTickets = async (orderId: number) => {
const response = await axiosClient.post(`/orders/cancel?orderId=${orderId}`);
return response.data;
}


5 changes: 5 additions & 0 deletions src/features/ticket/model/OrderCreation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface OrderTicketRequest {
ticketId: number;
eventId: number;
ticketCnt: number;
}
79 changes: 67 additions & 12 deletions src/pages/dashboard/ui/ticket/TIcketConfirmPage.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,65 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import Header from '../../../../../design-system/ui/Header';
import Search from '../../../../../design-system/icons/Search.svg';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import EmailDeleteMoal from '../../../../widgets/dashboard/ui/EmailDeleteModal';
import PurchaseBanner from '../../../../widgets/dashboard/ui/TicketConfirmPage/PurchaseBanner';
import OrganizerInfo from '../../../../widgets/dashboard/ui/TicketConfirmPage/OrganizerInfo';
import LocationInfo from '../../../../widgets/dashboard/ui/TicketConfirmPage/LocationInfo';
import OrganizerInfo from '../../../../widgets/event/ui/OrganizerInfo';
import KakaoMap from '../../../../shared/ui/KakaoMap';
import { cancleTickets, readTicket } from '../../../../features/ticket/api/order';

type Ticket = {
id: number;
title: string;
startDate: string;
startTime: string;
ticketName: string;
ticketCnt: number;
hostChannelName: string;
hostChannelDescription: string;
organizerEmail: string;
organizerPhoneNumber: string;
eventAddress: string;
location: { lng: number, lat: number };
remainDays: string;
ticketQrCode: string;
orderStatus: "COMPLETED" | "PENDING" | "CANCELED";
};

const TicketConfirmPage = () => {
const navigate = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const location = useLocation();
const orderIds: number[] = location.state?.orderIds || [];
const eventId = location.state?.eventId;
const ticketId = location.state?.ticketId;

console.log(ticketId, eventId, orderIds);
const [ticket, setTicket] = useState<Ticket | null>(null);
useEffect(() => {
const fetchOrderTicket = async () => {
try {
const response = await readTicket.getDetail(ticketId, eventId);
setTicket(response.result || []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

API 응답 처리 검증 필요

API 응답에서 response.result || []를 사용하고 있는데, Ticket 타입은 객체이지만 빈 배열을 기본값으로 설정하고 있습니다. 이는 타입 불일치를 발생시킬 수 있습니다.

- setTicket(response.result || []);
+ setTicket(response.result || null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTicket(response.result || []);
setTicket(response.result || null);

} catch (error) {
console.error("구매한 티켓 정보 불러오기 실패:", error);
}
};
fetchOrderTicket();
}, []);
Comment on lines +37 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

useEffect 의존성 배열에 ticketId와 eventId를 추가해야 합니다.

useEffect의 의존성 배열이 비어 있어 컴포넌트가 마운트될 때만 한 번 실행됩니다. ticketId나 eventId가 변경될 경우에도 티켓 정보를 다시 불러와야 합니다.

  useEffect(() => {
+   if (!ticketId || !eventId) return;
+   
    const fetchOrderTicket = async () => {
      try {
        const response = await readTicket.getDetail(ticketId, eventId);
        setTicket(response.result || []);
      } catch (error) {
        console.error("구매한 티켓 정보 불러오기 실패:", error);
      }
    };
    fetchOrderTicket();
- }, []);
+ }, [ticketId, eventId]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [ticket, setTicket] = useState<Ticket | null>(null);
useEffect(() => {
const fetchOrderTicket = async () => {
try {
const response = await readTicket.getDetail(ticketId, eventId);
setTicket(response.result || []);
} catch (error) {
console.error("구매한 티켓 정보 불러오기 실패:", error);
}
};
fetchOrderTicket();
}, []);
const [ticket, setTicket] = useState<Ticket | null>(null);
useEffect(() => {
if (!ticketId || !eventId) return;
const fetchOrderTicket = async () => {
try {
const response = await readTicket.getDetail(ticketId, eventId);
setTicket(response.result || []);
} catch (error) {
console.error("구매한 티켓 정보 불러오기 실패:", error);
}
};
fetchOrderTicket();
}, [ticketId, eventId]);

const handlePreviousButton = () => {
navigate(-1);
};

const cancleOrderTicket = async (orderIds: number[]) => {
for (const orderId of orderIds) {
try {
const response = await cancleTickets(orderId);
console.log("티켓 취소 API 응답:", response);
} catch (error) {
console.error(`orderId ${orderId} 취소 실패:`, error);
}
}
}
return (
<>
<Header
Expand All @@ -24,18 +69,28 @@ const TicketConfirmPage = () => {
centerContent="티켓 구매 확인"
rightContent={<img src={Search} alt="검색" className="w-4" />}
/>
<div className="bg-gray-100 p-3 min-h-screen flex flex-col gap-3">
<PurchaseBanner setIsModalOpen={setIsModalOpen} />
<OrganizerInfo />
<LocationInfo />
</div>
{ticket ? (
<>
<div className="bg-gray-100 p-3 min-h-screen flex flex-col gap-3">
<PurchaseBanner setIsModalOpen={setIsModalOpen} title={ticket.title} startDate={ticket.startDate} startTime={ticket.startTime} ticketName={ticket.ticketName} quantity={orderIds.length} />
<OrganizerInfo name={ticket.hostChannelName} description={ticket.hostChannelDescription} phone={ticket.organizerPhoneNumber} email={ticket.organizerEmail} bgColor='bg-white' />
<div className="p-5 bg-white flex flex-col gap-2 rounded-[10px]">
<p className="font-bold md:text-2xl text-xl">오시는 길</p>
<p>{ticket.eventAddress}</p>
<KakaoMap lat={ticket.location.lat} lng={ticket.location.lng} />
</div>
</div>
</>
) : (
<p className="text-center text-gray-500">티켓 정보를 불러오는 중...</p>
)}
{isModalOpen && (
<EmailDeleteMoal
mainText="WOOACON 2024의 일반 티켓 2매 구매를 취소하시겠습니까?. 취소 후에는 복구가 불가능합니다."
mainText={`${ticket?.title}의 ${ticket?.ticketName} ${orderIds.length}매 구매를 취소하시겠습니까?. 취소 후에는 복구가 불가능합니다.`}
approveButtonText="티켓 취소"
rejectButtonText="뒤로가기"
onClose={() => setIsModalOpen(false)}
onClick={() => navigate('/menu/myticket')}
onClick={() => { cancleOrderTicket(orderIds),navigate('/menu/myticket') }}
/>
)}
</>
Expand Down
1 change: 0 additions & 1 deletion src/pages/event/ui/EventDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ const EventDetailsPage = () => {
fetchEventDetail();
}, []);


return (
<>
<Header
Expand Down
8 changes: 4 additions & 4 deletions src/pages/menu/ui/MyTicketPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import QrModal from '../../../../design-system/ui/modals/QrModal';
import QRbackground from '../../../../design-system/icons/QRbackground.svg';
import EventCard from '../../../shared/ui/EventCard';
import { readMyTickets } from '../../../features/ticket/api/order';
import { readTicket } from '../../../features/ticket/api/order';
import completedImg from '../../../../public/assets/menu/Completed.svg';
import pendingImg from '../../../../public/assets/menu/Pending.svg';
import ticketImg from '../../../../public/assets/menu/Ticket.svg';
Expand Down Expand Up @@ -37,7 +37,7 @@ const MyTicketPage = () => {
useEffect(() => {
const fetchMyTickets = async () => {
try {
const response = await readMyTickets.get(0, 10);
const response = await readTicket.getAll(0, 10);
setMyTickets(response.result || []);
console.log(response.result);
} catch (error) {
Expand All @@ -51,9 +51,9 @@ const MyTicketPage = () => {
<TicketHostLayout image={TicketLogo} centerContent="내 티켓" showText={true}>
{/* 이벤트 카드 목록 */}
<div className="grid grid-cols-2 gap-4 mx-6 mt-28 md:grid-cols-2 lg:grid-cols-2 pb-4">
{myTickets.map((ticket, index) => (
{myTickets.map((ticket) => (
<EventCard
key={index}
id={ticket.id}
img={ticket.event.bannerImageUrl}
eventTitle={ticket.event.title}
dDay={ticket.event.remainDays}
Expand Down
4 changes: 3 additions & 1 deletion src/shared/types/api/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ axiosClient.interceptors.request.use(
axiosClient.interceptors.response.use(
(response: AxiosResponse) => {
if (response.config.method === 'post' || response.config.method === 'put') {
console.log('서버로 전송된 데이터:', JSON.parse(response.config.data));
if (response.config.data) {
console.log('서버로 전송된 데이터:', JSON.parse(response.config.data));
}
}
return response;
},
Expand Down
19 changes: 0 additions & 19 deletions src/widgets/dashboard/ui/TicketConfirmPage/LocationInfo.tsx

This file was deleted.

25 changes: 0 additions & 25 deletions src/widgets/dashboard/ui/TicketConfirmPage/OrganizerInfo.tsx

This file was deleted.

16 changes: 12 additions & 4 deletions src/widgets/dashboard/ui/TicketConfirmPage/PurchaseBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import React from 'react';
import { useNavigate } from 'react-router-dom';
import CheckIcon from '../../../../../public/assets/dashboard/ticket/checkIcon.svg';

const PurchaseBanner = ({ setIsModalOpen }: { setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>> }) => {
interface PurchaseBannerPrpos {
title: string;
startDate: string;
startTime: string;
ticketName: string;
quantity: number;
setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
const PurchaseBanner = ({ title,startDate, startTime, ticketName, quantity, setIsModalOpen }: PurchaseBannerPrpos) => {
const navigate = useNavigate();
//@TODO:api연동하면서 props 변경

Expand All @@ -18,14 +26,14 @@ const PurchaseBanner = ({ setIsModalOpen }: { setIsModalOpen: React.Dispatch<Rea
<div className="w-full h-3 bg-white clip-bottom-banner"></div>
<div className="bg-white p-5 flex flex-col gap-5">
<div>
<p className="text-pink-500 font-bold md:text-2xl text-xl">WOOACON 2024</p>
<p className="text-pink-500 font-bold md:text-2xl text-xl">{title}</p>
<p className="text-gray-700 mt-5 flex gap-3">
<span className="font-semibold text-gray-400">일정</span>
2025년 4월 24일, 14시 20분
{startDate}, {startTime}
</p>
<p className="text-gray-700 flex gap-3">
<span className="font-semibold text-gray-400">상품</span>
일반 티켓, 2인
{ticketName}, {quantity}인
</p>
</div>

Expand Down
5 changes: 3 additions & 2 deletions src/widgets/event/ui/OrganizerInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ interface OrganizerInfoProps {
description: string;
phone: string;
email: string;
bgColor?: string;
}

const OrganizerInfo = ({ name, description, phone, email}: OrganizerInfoProps) => {
const OrganizerInfo = ({ name, description, phone, email, bgColor = "bg-gray-100"}: OrganizerInfoProps) => {

return (
<div className="flex flex-col w-full h-full bg-gray-100 px-4 py-3 md:px-6 md:py-4 rounded-[10px] gap-1">
<div className={`flex flex-col w-full h-full px-4 py-3 md:px-6 md:py-4 rounded-[10px] gap-1 ${bgColor}`}>
<h1 className="font-bold text-lg">주최자</h1>
<span className="font-semibold">{name}</span>
<span className="text-sm md:text-base">{description}</span>
Expand Down
49 changes: 48 additions & 1 deletion src/widgets/event/ui/TicketInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton'
import TextButton from '../../../../design-system/ui/buttons/TextButton';
import { readTicket } from '../../../features/ticket/api/ticket';
import { ReadTicket } from '../../../pages/dashboard/ui/ticket/TicketListPage';
import { OrderTicketRequest } from '../../../features/ticket/model/OrderCreation';
import { orderTickets } from '../../../features/ticket/api/order';
import { useNavigate } from 'react-router-dom';

const TicketInfo = ({ eventId }: { eventId: number }) => {
const limitNum = 4;
const [tickets, setTickets] = useState<ReadTicket[]>([]);
const [quantity, setQuantity] = useState<{ [key: number]: number }>({});
const navigate = useNavigate();

useEffect(() => {
const fetchTickets = async () => {
Expand Down Expand Up @@ -48,6 +52,49 @@ const TicketInfo = ({ eventId }: { eventId: number }) => {
}));
};

// 구매 API 호출
const orderTicket = async (ticketId: number, eventId: number, ticketCnt: number) => {
try {
const requestData: OrderTicketRequest = {
ticketId,
eventId,
ticketCnt,
};

const response = await orderTickets(requestData);

console.log("API 응답:", response);

if (response.isSuccess) {
let orderIds: number[] = [];

if (typeof response.result === "string") {

const match = response.result.match(/\[.*?\]/);
if (match) {
orderIds = match[0]
.replace(/\[|\]/g, "")
.split(",")
.map((id: string) => Number(id.trim()));
}
}
if (orderIds.length > 0) {
navigate('/payment/ticket-confirm', { state: { orderIds,ticketId, eventId } });
} else {
console.error("orderId를 파싱할 수 없습니다.", response.result);
alert("주문 정보를 불러올 수 없습니다.");
}

} else {
console.error("티켓 구매 실패:", response.message);
alert(`구매 실패: ${response.message}`);
}
} catch (error) {
console.error("티켓 구매 중 오류 발생:", error);
alert("티켓 구매 중 오류가 발생했습니다.");
}
};

return (
<div className="w-full h-full">
{tickets.map(ticket => (
Expand Down Expand Up @@ -78,7 +125,7 @@ const TicketInfo = ({ eventId }: { eventId: number }) => {
className="flex justify-center items-center bg-white w-6 h-6 md:w-7 md:h-7"
/>
</div>
<TertiaryButton label="구매하기" type="button" color="black" size="large" className="w-22 h-8" />
<TertiaryButton label="구매하기" type="button" color="black" size="large" className="w-22 h-8" onClick={() => orderTicket(ticket.ticketId, eventId, quantity[ticket.ticketId])} />
</div>
</div>
</div>
Expand Down