Skip to content

Commit fbffae6

Browse files
committed
Merge branch 'develop' of https://github.com/TEAM-BEAT/BEAT-Client into feat/#338/CarouselSlide
2 parents 44a4224 + bc34b91 commit fbffae6

34 files changed

+434
-93
lines changed

api/hi.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export async function GET(request) {
2+
console.log("hi im test");
3+
4+
return new Response(JSON.stringify({ message: "Hi from the server!" }), {
5+
status: 200,
6+
headers: { "Content-Type": "application/json" },
7+
});
8+
}

api/prerender.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Prerenderer from "@prerenderer/prerenderer";
2+
import PuppeteerRenderer from "@prerenderer/renderer-puppeteer";
3+
import chromium from "@sparticuz/chromium-min";
4+
5+
export async function POST(req: Request, res) {
6+
console.log("req is: ", req);
7+
8+
try {
9+
const body = await readBody(req);
10+
11+
const { performanceId } = body;
12+
console.log("performanceId is: ", performanceId);
13+
14+
const chromePath =
15+
process.env.VITE_CHROME_PATH ||
16+
(await chromium.executablePath(
17+
"https://github.com/Sparticuz/chromium/releases/download/v121.0.0/chromium-v121.0.0-pack.tar"
18+
));
19+
20+
// 프리렌더 작업 수행
21+
const prerenderer = new Prerenderer({
22+
staticDir: __dirname, // 정적 파일이 있는 디렉터리 경로
23+
renderer: new PuppeteerRenderer({
24+
launchOptions: {
25+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
26+
ignoreDefaultArgs: ["--disable-extensions"],
27+
defaultViewport: chromium.defaultViewport,
28+
executablePath: chromePath,
29+
ignoreHTTPSErrors: true,
30+
headless: chromium.headless,
31+
},
32+
maxConcurrentRoutes: 1,
33+
renderAfterTime: 500,
34+
}),
35+
});
36+
37+
await prerenderer.initialize();
38+
await prerenderer.renderRoutes([`/gig/${performanceId}`]);
39+
await prerenderer.destroy();
40+
41+
res.status(200).json({ message: "Prerender complete", performanceId });
42+
} catch (error) {
43+
console.error("Error during prerendering:", error);
44+
res.status(500).json({ message: "Error during prerendering", error });
45+
}
46+
}
47+
48+
// Request body를 JSON으로 파싱하는 유틸리티 함수
49+
async function readBody(req) {
50+
const chunks = [];
51+
for await (const chunk of req) {
52+
chunks.push(chunk);
53+
}
54+
const buffer = Buffer.concat(chunks).toString();
55+
return JSON.parse(buffer);
56+
}

public/svgs/not_found_asset.svg

+9
Loading

src/api/hello.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export async function GET(request) {
2+
console.log("hello im test");
3+
4+
return new Response(JSON.stringify({ message: "Hello from the server!" }), {
5+
status: 200,
6+
headers: { "Content-Type": "application/json" },
7+
});
8+
}

src/apis/domains/performances/queries.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export const usePostPerformance = () => {
141141

142142
return useMutation({
143143
mutationFn: (formData: PerformanceFormData) => postPerformance(formData),
144-
onSuccess: (res) => {
144+
onSuccess: async (res) => {
145145
queryClient.invalidateQueries({
146146
queryKey: [HOME_QUERY_KEY.LIST, PERFORMANCE_QUERY_KEY.DETAIL],
147147
});
@@ -152,11 +152,29 @@ export const usePostPerformance = () => {
152152
});
153153

154154
if (isPerformanceResponse(res) && res.status === 201) {
155+
// 프리렌더 작업 수행
156+
const prerenderResponse = await fetch(`${import.meta.env.VITE_CLIENT_URL}/api/prerender`, {
157+
method: "POST",
158+
headers: {
159+
"Content-Type": "application/json",
160+
},
161+
body: JSON.stringify({ performanceId: res.data.performanceId }),
162+
});
163+
164+
console.log("prerenderResponse is: ", prerenderResponse);
165+
166+
if (prerenderResponse.ok) {
167+
console.log("Prerender successful");
168+
} else {
169+
console.error("Prerender failed");
170+
}
171+
172+
// 등록 완료 페이지로 이동
155173
navigate("/register-complete", {
156174
state: { performanceId: res.data.performanceId },
157175
});
158176
} else {
159-
console.error("Unexpected response type", res);
177+
console.error("Performance creation failed:", res);
160178
}
161179
},
162180
});

src/apis/domains/tickets/api.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const putTicketUpdate = async (
4949
): Promise<SuccessResponseVoid | null> => {
5050
try {
5151
const response: AxiosResponse<ApiResponseType<SuccessResponseVoid>> = await put(
52-
`tickets/${formData.performanceId}`,
52+
"tickets",
5353
formData
5454
);
5555

@@ -70,7 +70,7 @@ export const deleteTicketDelete = async (
7070
// console.log("fromdata", formData);
7171
try {
7272
const response: AxiosResponse<ApiResponseType<SuccessResponseVoid>> = await del(
73-
`tickets/${formData.performanceId}`,
73+
"tickets",
7474
//DELETE요청의 경우 두번째 인자가 좀 다름. - config 파일을 넣어야 함
7575
{ data: formData }
7676
);

src/assets/svgs/NotFoundAsset.tsx

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as React from "react";
2+
import type { SVGProps } from "react";
3+
const SvgNotFoundAsset = (props: SVGProps<SVGSVGElement>) => (
4+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 150 109" {...props}>
5+
<path
6+
fill="url(#not_found_asset_svg__a)"
7+
d="m60.984 58.346-12.13-38.629L42.907 0l-5.799 19.998-14.514 57.484-8.474-19.136H-10V69.25H7.975l9.403 21.365L26.25 109l5.467-20.355 11.975-45.206 8.102 25.81H166V58.347z"
8+
/>
9+
<defs>
10+
<linearGradient
11+
id="not_found_asset_svg__a"
12+
x1={78}
13+
x2={78}
14+
y1={0}
15+
y2={109}
16+
gradientUnits="userSpaceOnUse"
17+
>
18+
<stop stopColor="#FF006B" />
19+
<stop offset={1} stopColor="#252525" />
20+
</linearGradient>
21+
</defs>
22+
</svg>
23+
);
24+
export default SvgNotFoundAsset;

src/assets/svgs/index.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { default as ButtonDelete24 } from "./ButtonDelete24";
44
export { default as CarouselPartInactive } from "./CarouselPartInactive";
55
export { default as Empty } from "./Empty";
66
export { default as IcHamburgar } from "./IcHamburgar";
7+
export { default as IcOutlinePlace } from "./IcOutlinePlace";
78
export { default as IcomCopy } from "./IcomCopy";
89
export { default as IconArrowDown } from "./IconArrowDown";
910
export { default as IconArrowLeft } from "./IconArrowLeft";
@@ -54,6 +55,6 @@ export { default as IconToss } from "./IconToss";
5455
export { default as IconWoochaegook } from "./IconWoochaegook";
5556
export { default as IconWoori } from "./IconWoori";
5657
export { default as IconXButton } from "./IconXButton";
57-
export { default as IcOutlinePlace } from "./IcOutlinePlace";
58+
export { default as NotFoundAsset } from "./NotFoundAsset";
5859
export { default as Subtract } from "./Subtract";
5960
export { default as Union } from "./Union";

src/components/commons/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export { default as ContextBox } from "./contextBox/ContextBox.tsx";
1414
export { default as Hamburger } from "./hamburger/Hamburger.tsx";
1515
export { default as TextArea } from "./input/textArea/TextArea.tsx";
1616
export { default as TextField } from "./input/textField/TextField.tsx";
17-
export { default as Labal } from "./label/Labal.tsx";
17+
export { default as Label } from "./label/Label.tsx";
1818
export { default as Loading } from "./loading/Loading.tsx";
1919
export { default as Alert } from "./modal/Alert.tsx";
2020
export { default as ModalTextBox } from "./modal/components/ModalTextBox.tsx";

src/components/commons/label/Label.styled.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import styled from "styled-components";
21
import { Subtract } from "@assets/svgs";
2+
import styled from "styled-components";
33

44
export const LabelWrapper = styled.section`
55
position: absolute;

src/components/commons/label/Labal.tsx src/components/commons/label/Label.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { ReactNode } from "react";
21
import * as S from "./Label.styled";
32

43
interface LabelProps {
54
dueDate: number;
65
}
76

8-
const Labal = ({ dueDate }: LabelProps) => {
7+
const Label = ({ dueDate }: LabelProps) => {
98
return (
109
<S.LabelWrapper>
1110
{dueDate === 0 && (
@@ -31,4 +30,4 @@ const Labal = ({ dueDate }: LabelProps) => {
3130
);
3231
};
3332

34-
export default Labal;
33+
export default Label;

src/pages/book/Book.tsx

+29-8
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import {
88
useGetScheduleAvailable,
99
} from "@apis/domains/performances/queries";
1010
import { Button, Context, Loading, OuterLayout, ViewBottomSheet } from "@components/commons";
11+
import MetaTag from "@components/commons/meta/MetaTag";
1112
import { NAVIGATION_STATE } from "@constants/navigationState";
1213
import { useHeader, useLogin, useModal } from "@hooks";
1314
import { BookerInfo, Count, EasyPassEntry, Info, Select, TermCheck } from "@pages/book/components";
1415
import { SHOW_TYPE_KEY } from "@pages/gig/constants";
16+
import NotFound from "@pages/notFound/NotFound";
1517
import * as S from "./Book.styled";
1618
import { getScheduleNumberById } from "./utils";
17-
import MetaTag from "@components/commons/meta/MetaTag";
1819

1920
const Book = () => {
2021
const navigate = useNavigate();
@@ -26,6 +27,24 @@ const Book = () => {
2627
const { isLogin } = useLogin();
2728
const { setHeader } = useHeader();
2829

30+
useEffect(() => {
31+
if (data) {
32+
const nowDate = new Date();
33+
const lastPerformanceDate = new Date(
34+
data.scheduleList[data?.scheduleList.length - 1].performanceDate
35+
);
36+
if (nowDate > lastPerformanceDate) {
37+
openAlert({
38+
title: "종료된 공연입니다.",
39+
okText: "확인",
40+
okCallback: () => {
41+
navigate("/main");
42+
},
43+
});
44+
}
45+
}
46+
}, [data]);
47+
2948
useEffect(() => {
3049
setHeader({
3150
headerStyle: NAVIGATION_STATE.ICON_TITLE_SUB_TEXT,
@@ -160,12 +179,8 @@ const Book = () => {
160179
bookerPhoneNumber: bookerInfo.bookerPhoneNumber,
161180
} as GuestBookingRequest;
162181

163-
console.log(formData);
164-
165182
const res = await memberBook(formData);
166183

167-
console.log(res);
168-
169184
navigate("/book/complete", {
170185
state: {
171186
id: performanceId,
@@ -206,9 +221,15 @@ const Book = () => {
206221
}
207222
}, [isLogin, selectedValue, bookerInfo, easyPassword, isTermChecked]);
208223

209-
return isLoading ? (
210-
<Loading />
211-
) : (
224+
if (isLoading) {
225+
return <Loading />;
226+
}
227+
228+
if (!data) {
229+
return <NotFound />;
230+
}
231+
232+
return (
212233
<S.ContentWrapper>
213234
<MetaTag title="공연 예매" />
214235
{isPending && <Loading />}

src/pages/book/components/info/Info.styled.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@ export const InfoTop = styled.div`
1212
display: flex;
1313
`;
1414

15-
export const InfoPoster = styled.img<{ $imgsrc: string }>`
15+
export const InfoPoster = styled.img`
1616
width: 9.5rem;
1717
height: 12.8rem;
1818
margin-right: 1.4rem;
1919
20-
background-image: url(${({ $imgsrc }) => $imgsrc});
21-
background-size: 100% 100%;
2220
border-radius: 4px;
2321
`;
2422

src/pages/book/components/info/Info.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const Info = ({ genre, title, posterImage, teamName, venue, period }: InfoProps)
2020
return (
2121
<S.InfoContainer>
2222
<S.InfoTop>
23-
<S.InfoPoster $imgsrc={posterImage} />
23+
<S.InfoPoster src={posterImage} />
2424

2525
<S.InfoTextBox>
2626
<ShowType type={getShowTypeText(genre)} />

src/pages/gig/Gig.tsx

+25-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { useGetPerformanceDetail } from "@apis/domains/performances/queries";
22
import { ActionBottomSheet, Button, Loading } from "@components/commons";
33
import OuterLayout from "@components/commons/bottomSheet/OuterLayout";
4+
import MetaTag from "@components/commons/meta/MetaTag";
45
import { NAVIGATION_STATE } from "@constants/navigationState";
56
import { useHeader, useLogin } from "@hooks";
7+
import NotFound from "@pages/notFound/NotFound";
68
import { navigateAtom } from "@stores";
79
import { requestKakaoLogin } from "@utils/kakaoLogin";
810
import { useAtom } from "jotai";
@@ -12,7 +14,6 @@ import Content from "./components/content/Content";
1214
import ShowInfo from "./components/showInfo/ShowInfo";
1315
import { SHOW_TYPE_KEY } from "./constants";
1416
import * as S from "./Gig.styled";
15-
import MetaTag from "@components/commons/meta/MetaTag";
1617

1718
const Gig = () => {
1819
const navigate = useNavigate();
@@ -25,12 +26,18 @@ const Gig = () => {
2526

2627
const [isSheetOpen, setIsSheetOpen] = useState(false);
2728

29+
const nowDate = new Date();
30+
const lastPerformanceDate = new Date(
31+
data?.scheduleList[data?.scheduleList.length - 1]?.performanceDate
32+
);
33+
// 현재 시간이 마지막 공연 시간보다 크면 예매 버튼 비활성화
34+
const isBookDisabled = nowDate > lastPerformanceDate;
35+
2836
const handleBookClick = () => {
2937
if (isLogin) {
3038
navigate(`/book/${performanceId}`);
3139
return;
3240
}
33-
3441
setIsSheetOpen(true);
3542
};
3643

@@ -44,19 +51,25 @@ const Gig = () => {
4451
};
4552

4653
useEffect(() => {
47-
setHeader({
48-
headerStyle: NAVIGATION_STATE.ICON_TITLE,
49-
title: data?.performanceTitle,
50-
leftOnClick: () => {
51-
navigate("/main");
52-
},
53-
});
54+
if (data) {
55+
setHeader({
56+
headerStyle: NAVIGATION_STATE.ICON_TITLE,
57+
title: data?.performanceTitle,
58+
leftOnClick: () => {
59+
navigate("/main");
60+
},
61+
});
62+
}
5463
}, [data]);
5564

5665
if (isLoading) {
5766
return <Loading />;
5867
}
5968

69+
if (!data) {
70+
return <NotFound />;
71+
}
72+
6073
return (
6174
<S.ContentWrapper>
6275
<MetaTag
@@ -86,7 +99,9 @@ const Gig = () => {
8699
staffList={data?.staffList ?? []}
87100
/>
88101
<S.FooterContainer>
89-
<Button onClick={handleBookClick}>예매하기</Button>
102+
<Button onClick={handleBookClick} disabled={isBookDisabled}>
103+
{isBookDisabled ? "종료된 공연은 예매할 수 없습니다." : "예매하기"}
104+
</Button>
90105
</S.FooterContainer>
91106

92107
<ActionBottomSheet

0 commit comments

Comments
 (0)