Skip to content

Commit 6c19f8b

Browse files
committed
fix: guest 티켓 entry 추가
1 parent ace1910 commit 6c19f8b

8 files changed

Lines changed: 267 additions & 0 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.container {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 12px;
5+
justify-content: center;
6+
max-width: 600px;
7+
height: 100vh;
8+
margin: auto;
9+
padding: 0 4px;
10+
}
11+
12+
.event_info {
13+
display: flex;
14+
flex-direction: column;
15+
gap: 8px;
16+
justify-content: center;
17+
padding-bottom: 32px;
18+
}
19+
20+
.logo {
21+
position: absolute;
22+
top: 0;
23+
left: 0;
24+
width: 115.43px;
25+
height: 17px;
26+
margin: 12px;
27+
28+
// Mobile responsive - 데스크톱과 동일한 크기 유지
29+
@media (max-width: 768px) {
30+
width: 115.43px;
31+
height: 17px;
32+
}
33+
}
34+
35+
.logo_image {
36+
width: 100%;
37+
height: 100%;
38+
object-fit: contain;
39+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"use client";
2+
3+
import Image from "next/image";
4+
import Link from "next/link";
5+
import classNames from "classnames/bind";
6+
import permitLogo from "public/assets/png/permit_logo.png";
7+
8+
import { Button, Flex, TextField, Typography } from "@permit/design-system";
9+
import { useTextField } from "@permit/design-system/hooks";
10+
import { useGuestTicketDoorValidationQuery } from "@/data/tickets/getGuestTicketDoorValidation/queries";
11+
import { useGuestTicketStaffConfirmMutation } from "@/data/tickets/postGuestTicketStaffConfirm/mutation";
12+
import { LoadingWithLayout } from "@/shared/components/LoadingWithLayout";
13+
import { isAxiosErrorResponse } from "@/shared/types/axioxError";
14+
15+
import styles from "./index.module.scss";
16+
17+
const cx = classNames.bind(styles);
18+
19+
const NO_ENTRY_TIME = 40013;
20+
const ALREADY_USED_TICKET = 40906;
21+
const CANCELED_TICKET = 40015;
22+
23+
type Props = {
24+
ticketCode: string;
25+
};
26+
27+
export const EntryClient = ({ ticketCode }: Props) => {
28+
const { data, isLoading, error } = useGuestTicketDoorValidationQuery({
29+
ticketCode,
30+
options: { refetchOnWindowFocus: true, throwOnError: false },
31+
});
32+
33+
const { mutateAsync: mutateCheckEntryCode, isPending: isCheckEntryCodePending } =
34+
useGuestTicketStaffConfirmMutation();
35+
36+
const checkCodeField = useTextField({
37+
initialValue: "",
38+
validate: (value) => {
39+
if (!value) return "Please input check code.";
40+
41+
return undefined;
42+
},
43+
});
44+
45+
const handleCheckConfirm = async () => {
46+
if (!checkCodeField.validateValue()) {
47+
return;
48+
}
49+
50+
try {
51+
await mutateCheckEntryCode({ ticketCode, checkCode: checkCodeField.value });
52+
alert("확인되었습니다.");
53+
} catch (e) {
54+
if (isAxiosErrorResponse(e)) {
55+
alert(e.message || "An error occurred while verifying the code.");
56+
}
57+
}
58+
};
59+
60+
if (isLoading) {
61+
return <LoadingWithLayout />;
62+
}
63+
64+
if (error?.code === NO_ENTRY_TIME) {
65+
return (
66+
<Flex direction="column" justify="center" align="center" gap={16} style={{ height: "100vh" }}>
67+
<Link className={cx("logo")} href="/">
68+
<Image src={permitLogo} alt="PERMIT" className={cx("logo_image")} />
69+
</Link>
70+
<Typography type="title20" weight="bold" color="white">
71+
Ticket Not Valid at This Time
72+
</Typography>
73+
<Typography type="body16" color="gray300">
74+
해당 티켓의 유효 시간이 아닙니다.
75+
</Typography>
76+
</Flex>
77+
);
78+
}
79+
80+
if (error?.code === ALREADY_USED_TICKET) {
81+
return (
82+
<Flex direction="column" justify="center" align="center" style={{ height: "100vh" }}>
83+
<Link className={cx("logo")} href="/">
84+
<Image src={permitLogo} alt="PERMIT" className={cx("logo_image")} />
85+
</Link>
86+
<Typography type="title20" weight="bold" color="white">
87+
이미 사용한 티켓입니다.
88+
</Typography>
89+
</Flex>
90+
);
91+
}
92+
93+
if (error?.code === CANCELED_TICKET) {
94+
return (
95+
<Flex direction="column" justify="center" align="center" style={{ height: "100vh" }}>
96+
<Link className={cx("logo")} href="/">
97+
<Image src={permitLogo} alt="PERMIT" className={cx("logo_image")} />
98+
</Link>
99+
<Typography type="title20" weight="bold" color="white">
100+
취소된 티켓입니다.
101+
</Typography>
102+
</Flex>
103+
);
104+
}
105+
106+
if (error) {
107+
throw error;
108+
}
109+
110+
return (
111+
<div className={cx("container")}>
112+
<div className={cx("event_info")}>
113+
<Typography type="title24">Guest Ticket</Typography>
114+
<Typography type="title20" weight="bold">
115+
{data?.eventName}
116+
</Typography>
117+
<Typography type="body16" weight="bold" color="gray300">
118+
{data?.ticketName}
119+
</Typography>
120+
</div>
121+
122+
<Typography type="body14">Event Verification Code</Typography>
123+
<TextField
124+
placeholder="코드를 입력해주세요"
125+
fullWidth
126+
value={checkCodeField.value}
127+
onChange={checkCodeField.handleChange}
128+
error={checkCodeField.error}
129+
/>
130+
<Button
131+
fullWidth
132+
variant="cta"
133+
isLoading={isCheckEntryCodePending}
134+
onClick={handleCheckConfirm}
135+
>
136+
Check
137+
</Button>
138+
</div>
139+
);
140+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { EntryClient } from "./_clientBounday/EntryClient";
2+
3+
type Props = {
4+
params: Promise<{ ticketCode: string }>;
5+
};
6+
7+
const EntryPage = async ({ params }: Props) => {
8+
const { ticketCode } = await params;
9+
10+
return <EntryClient ticketCode={ticketCode} />;
11+
};
12+
13+
export default EntryPage;

apps/ticket/src/data/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ export const API_URL = {
6060
TICKET: {
6161
/** 도어용 유저 티켓 유효성 검증 API */
6262
VALIDATION: "/api/tickets/door/validation/:ticketCode",
63+
/** 도어용 게스트 티켓 유효성 검증 API */
64+
GUEST_VALIDATION: "/api/guests/tickets/door/validation/:ticketCode",
6365
/** 도어용 유저 스탭 티켓 확인 API */
6466
CONFIRM: "/api/tickets/door/staff/confirm",
67+
/** 도어용 게스트 스탭 티켓 확인 API */
68+
GUEST_CONFIRM: "/api/guests/tickets/door/staff/confirm",
6569
},
6670
} as const;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
3+
import { API_URL } from "@/data/constants";
4+
import { instance } from "@/lib/axios";
5+
import { getPathUrl } from "@/shared/helpers/getPathUrl";
6+
import {
7+
OptionsObject,
8+
PermitQueryOptions,
9+
UsePermitQueryOptions,
10+
} from "@/shared/types/queryOptions";
11+
12+
import { GuestTicketDoorValidationParams, GuestTicketDoorValidationResponse } from "./types";
13+
14+
/** 도어용 게스트 티켓 유효성 검증 API */
15+
export const guestTicketDoorValidationOptions = (
16+
params: GuestTicketDoorValidationParams,
17+
): PermitQueryOptions<GuestTicketDoorValidationResponse> => {
18+
const url = getPathUrl(API_URL.TICKET.GUEST_VALIDATION, { ticketCode: params.ticketCode });
19+
20+
return {
21+
queryKey: [url],
22+
queryFn: () => {
23+
return instance.get<GuestTicketDoorValidationResponse>(url).then((res) => res.data);
24+
},
25+
};
26+
};
27+
28+
export const useGuestTicketDoorValidationQuery = ({
29+
ticketCode,
30+
options,
31+
}: GuestTicketDoorValidationParams &
32+
OptionsObject<UsePermitQueryOptions<GuestTicketDoorValidationResponse>>) => {
33+
return useQuery({
34+
...guestTicketDoorValidationOptions({ ticketCode }),
35+
...options,
36+
});
37+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type GuestTicketDoorValidationParams = {
2+
/** 티켓고유코드 */
3+
ticketCode: string;
4+
};
5+
6+
export type GuestTicketDoorValidationResponse = {
7+
eventName: string;
8+
ticketName: string;
9+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useMutation } from "@tanstack/react-query";
2+
3+
import { API_URL } from "@/data/constants";
4+
import { instance } from "@/lib/axios";
5+
import { AxiosErrorResponse } from "@/shared/types/axioxError";
6+
import { UsePermitMutationOptions } from "@/shared/types/queryOptions";
7+
8+
import { GuestTicketConfirmRequest } from "./types";
9+
10+
/** 도어용 게스트 티켓 스텝 확인 API */
11+
export const useGuestTicketStaffConfirmMutation = (
12+
options?: UsePermitMutationOptions<null, AxiosErrorResponse, GuestTicketConfirmRequest>,
13+
) => {
14+
return useMutation({
15+
mutationFn: (params) =>
16+
instance.post(API_URL.TICKET.GUEST_CONFIRM, params).then((res) => res.data),
17+
...options,
18+
});
19+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type GuestTicketConfirmRequest = {
2+
/** 티켓고유코드 */
3+
ticketCode: string;
4+
/** 행사티켓확인코드 */
5+
checkCode: string;
6+
};

0 commit comments

Comments
 (0)