From 4cc818df9488e1932664452eb58d70112626e1aa Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Sat, 31 Aug 2024 17:42:49 +0900 Subject: [PATCH 01/16] =?UTF-8?q?=F0=9F=94=A8=20settings:=20swiper=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/package.json b/package.json index a74e37d..bb033c8 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "react-daum-postcode": "^3.1.3", "react-dom": "^18", "react-hook-form": "7.52.2", + "swiper": "^11.1.11", "tailwind-merge": "^2.4.0", "tailwindcss-radix": "^3.0.3", "usehooks-ts": "^3.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2592418..55c5b84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: react-hook-form: specifier: 7.52.2 version: 7.52.2(react@18.3.1) + swiper: + specifier: ^11.1.11 + version: 11.1.11 tailwind-merge: specifier: ^2.4.0 version: 2.4.0 @@ -5738,6 +5741,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + swiper@11.1.11: + resolution: {integrity: sha512-077Aw3OrlZpkkBRf/6+44bGh/HZY/vsLEyate2db2KkJgYUIR5TvDgvvhcJtW/puXzw79w5KBc30DauEX6GZYQ==} + engines: {node: '>= 4.7.0'} + synckit@0.8.8: resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -12634,6 +12641,8 @@ snapshots: csso: 5.0.5 picocolors: 1.0.1 + swiper@11.1.11: {} + synckit@0.8.8: dependencies: '@pkgr/core': 0.1.1 From fcdd2b78427402ef03d1ff8167605ab45e7b3006 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Sat, 31 Aug 2024 22:27:52 +0900 Subject: [PATCH 02/16] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=8F=99?= =?UTF-8?q?=EC=A0=81=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=84=A4=EC=9E=84,=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20#34?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/getUserType.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/utils/getUserType.ts diff --git a/src/utils/getUserType.ts b/src/utils/getUserType.ts new file mode 100644 index 0000000..3cb8d7a --- /dev/null +++ b/src/utils/getUserType.ts @@ -0,0 +1,14 @@ +export const UserTypeText: Record = { + 1: "text-secondary-pink-text", + 2: "text-secondary-orange-text", + 3: "text-secondary-yellow-text", + 4: "text-secondary-green-text", + 5: "text-secondary-blue-text", +}; +export const UserTypeImage: Record = { + 1: "/images/romantist.png", + 2: "/images/partypeople.png", + 3: "/images/inspirer.png", + 4: "/images/healer.png", + 5: "/images/explorer.png", +}; From 6f483bdb47209a523dafdec5e9956b231f1d59f6 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Sat, 31 Aug 2024 22:29:15 +0900 Subject: [PATCH 03/16] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EC=B6=95=EC=A0=9C=20api=20=ED=83=80=EC=9E=85=20=EC=84=A0?= =?UTF-8?q?=EC=96=B8=20=EB=B0=8F=20=ED=8E=98=EC=B3=90=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20#34?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recommendFestival/recommendFestival.ts | 30 +++++++++++++++++++ .../recommendFestivalType.ts | 22 ++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/apis/festivals/recommendFestival/recommendFestival.ts create mode 100644 src/apis/festivals/recommendFestival/recommendFestivalType.ts diff --git a/src/apis/festivals/recommendFestival/recommendFestival.ts b/src/apis/festivals/recommendFestival/recommendFestival.ts new file mode 100644 index 0000000..2a1ae9d --- /dev/null +++ b/src/apis/festivals/recommendFestival/recommendFestival.ts @@ -0,0 +1,30 @@ +"use server"; + +import instance from "@/apis/instance"; +import FIESTA_ENDPOINTS from "@/config/apiEndpoints"; +import { generateUrlWithParams } from "@/utils/generateUrlWithParams"; + +import { + PaginationParamter, + RecommendFestivalResponse, +} from "./recommendFestivalType"; + +const defaultParams: PaginationParamter = { page: 0, size: 5 }; +const ENDPOINT = FIESTA_ENDPOINTS.festivals; + +export async function getRecommendFestival( + params: PaginationParamter = defaultParams, +) { + try { + const endpoint = ENDPOINT.recommend; + const { data } = await instance.get( + generateUrlWithParams(endpoint, params), + { + cache: "no-store", + }, + ); + return data; + } catch (error) { + return null; + } +} diff --git a/src/apis/festivals/recommendFestival/recommendFestivalType.ts b/src/apis/festivals/recommendFestival/recommendFestivalType.ts new file mode 100644 index 0000000..2c605d4 --- /dev/null +++ b/src/apis/festivals/recommendFestival/recommendFestivalType.ts @@ -0,0 +1,22 @@ +export type PaginationParamter = { + page: number; + size: number; +}; + +export type RecommendFestivalResponse = { + festivals: Array; + userType: { + userTypeId: number; + name: string; + }; +}; + +export interface RecommendFestivalModel { + festivalId: number; + name: string; + sido: string; + sigungu: string; + thumbnailImage: string; + startDate: string; + endDate: string; +} From 3454c0c2eba09a241c939dd14de88afa6c3f9b67 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Sat, 31 Aug 2024 22:30:50 +0900 Subject: [PATCH 04/16] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EC=B6=95=EC=A0=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20#34?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(home)/page.tsx | 5 +- .../RecommendFestival.stories.tsx | 92 ++++++++++++++++ .../RecommendFestivalFallbackUI.tsx | 38 +++++++ .../RecommendFestivalHeader.tsx | 34 ++++++ .../RecommendFestivalList.tsx | 101 ++++++++++++++++++ .../Swiper/RecommendFestival/index.tsx | 16 +++ src/utils/index.ts | 1 + 7 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx create mode 100644 src/components/Swiper/RecommendFestival/RecommendFestivalFallbackUI.tsx create mode 100644 src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx create mode 100644 src/components/Swiper/RecommendFestival/RecommendFestivalList.tsx create mode 100644 src/components/Swiper/RecommendFestival/index.tsx diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 2b2159e..82a55ea 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -1,15 +1,18 @@ import { getServerSideSession } from "@/apis/auth/auth"; import { FloatingButton } from "@/components/core/Button"; +import RecommendFestival from "@/components/Swiper/RecommendFestival"; import { HomeHeader } from "@/layout/Mobile/MobileHeader"; import NavigationBar from "@/layout/Mobile/NavigationBar"; import { FestivalHot, FestivalThisWeek, TopReviews } from "./_components"; + export default async function Home() { const session = await getServerSideSession(); return (
-
+ +
diff --git a/src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx b/src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx new file mode 100644 index 0000000..34ba8e8 --- /dev/null +++ b/src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx @@ -0,0 +1,92 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import RecommendFestivalFallbackUI from "./RecommendFestivalFallbackUI"; +import RecoomendFestivalList from "./RecommendFestivalList"; + +const meta: Meta = { + title: "Swiper/RecommendFestival", + component: RecoomendFestivalList, + parameters: { + controls: { include: [] }, + }, + argTypes: {}, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + recommendFestivals: { + festivals: [ + { + festivalId: 64, + name: "dfdfd", + sido: "충청남도", + sigungu: "천안시 동남구", + thumbnailImage: + "https://fiesta-image.s3.ap-northeast-2.amazonaws.com/festival/6869294f-f5ef-4891-a3ca-60fac98e2873my-festival-type%20%282%29.png", + startDate: "2024-08-29", + endDate: "2024-08-31", + }, + { + festivalId: 43, + name: "광주 버스킹 월드컵", + sido: "광주", + sigungu: "동구", + thumbnailImage: + "https://fiesta-image.s3.ap-northeast-2.amazonaws.com/festival/fd48e6eb-5ff6-457d-affd-d3cc659042503112130_image2_1.jpeg", + startDate: "2024-10-02", + endDate: "2024-10-06", + }, + { + festivalId: 44, + name: "광주 추억의 충장축제", + sido: "광주", + sigungu: "동구", + thumbnailImage: + "https://fiesta-image.s3.ap-northeast-2.amazonaws.com/festival/00d385e7-5048-4265-abaf-8fd001ecff7711111111.jpg", + startDate: "2024-10-02", + endDate: "2024-10-06", + }, + { + festivalId: 45, + name: "광주프린지페스티벌", + sido: "광주", + sigungu: "북구", + thumbnailImage: + "https://fiesta-image.s3.ap-northeast-2.amazonaws.com/festival/f54a1b88-0873-4696-b3a0-ca6e15b949ae3343042_image2_1.jpg", + startDate: "2024-09-21", + endDate: "2024-09-29", + }, + { + festivalId: 54, + name: "국가유산 미디어아트 고흥분청사기요지", + sido: "전라남도", + sigungu: "고흥군", + thumbnailImage: + "https://fiesta-image.s3.ap-northeast-2.amazonaws.com/festival/d52dee4c-edbc-46c0-8236-b801411cad0d3340080_image2_1.jpg", + startDate: "2024-09-13", + endDate: "2024-10-06", + }, + ], + userType: { + userTypeId: 2, + name: "파티피플러", + }, + }, + }, + render: (args) => ( +
+ +
+ ), +}; +export const NoSession: Story = { + args: {}, + render: (args) => ( +
+ +
+ ), +}; diff --git a/src/components/Swiper/RecommendFestival/RecommendFestivalFallbackUI.tsx b/src/components/Swiper/RecommendFestival/RecommendFestivalFallbackUI.tsx new file mode 100644 index 0000000..dd306ec --- /dev/null +++ b/src/components/Swiper/RecommendFestival/RecommendFestivalFallbackUI.tsx @@ -0,0 +1,38 @@ +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +import RoundButton from "@/components/core/Button/RoundButton/RoundButton"; + +const RecommendFestivalFallbackUI = () => { + return ( +
+
+
+ + 로그인을 통해 + + + 페스티벌을 추천받아요 + +
+ + + +
+
+ romantist +
+
+ ); +}; + +export default RecommendFestivalFallbackUI; diff --git a/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx b/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx new file mode 100644 index 0000000..4d8dd24 --- /dev/null +++ b/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx @@ -0,0 +1,34 @@ +import Image from "next/image"; + +import { UserTypeImage, UserTypeText } from "@/utils"; + +const RecommendFestivalHeader = ({ + userType, +}: { + userType: { + userTypeId: number; + name: string; + }; +}) => { + const userTypeTextColor = UserTypeText[userType.userTypeId ?? 1]; + const userTypeImage = UserTypeImage[userType.userTypeId ?? 1]; + + return ( +
+
+ {userType.name} + 들을 + 위한 페스티벌이에요! +
+ {userType.name} +
+ ); +}; + +export default RecommendFestivalHeader; diff --git a/src/components/Swiper/RecommendFestival/RecommendFestivalList.tsx b/src/components/Swiper/RecommendFestival/RecommendFestivalList.tsx new file mode 100644 index 0000000..93468af --- /dev/null +++ b/src/components/Swiper/RecommendFestival/RecommendFestivalList.tsx @@ -0,0 +1,101 @@ +"use client"; + +import "swiper/css"; +import "swiper/css/pagination"; + +import Image from "next/image"; +import Link from "next/link"; +import { Pagination } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; + +import { RecommendFestivalResponse } from "@/apis/festivals/recommendFestival/recommendFestivalType"; +import { formatToKoreanDate } from "@/lib/dayjs"; + +import RecommendFestivalHeader from "./RecommendFestivalHeader"; + +const RecommendFestivalList = ({ + recommendFestivals, +}: { + recommendFestivals: RecommendFestivalResponse; +}) => { + return ( +
+ + ' + + " / " + + '' + ); + }, + }} + // navigation={{ + // enabled: true, + // prevEl: ".nav_prev", + // nextEl: ".nav_next", + // }} + // modules={[Pagination, Navigation]} + modules={[Pagination]} + className="relative flex h-[289px] w-full items-center justify-center rounded-[18px]" + > + {recommendFestivals.festivals.map((festival) => ( + + image + +

+ {`${festival.sido} ${festival.sigungu} | ${formatToKoreanDate(festival.startDate)} - ${formatToKoreanDate(festival.endDate)}`} +

+

+ {festival.name} +

+ +
+ ))} + + {/* + */} +
+
+ ); +}; + +export default RecommendFestivalList; diff --git a/src/components/Swiper/RecommendFestival/index.tsx b/src/components/Swiper/RecommendFestival/index.tsx new file mode 100644 index 0000000..338f131 --- /dev/null +++ b/src/components/Swiper/RecommendFestival/index.tsx @@ -0,0 +1,16 @@ +import { getRecommendFestival } from "@/apis/festivals/recommendFestival/recommendFestival"; + +import RecommendFestivalFallbackUI from "./RecommendFestivalFallbackUI"; +import RecommendFestivalList from "./RecommendFestivalList"; + +const RecommendFestival = async () => { + const festivals = await getRecommendFestival(); + + if (!festivals) { + return ; + } + + return ; +}; + +export default RecommendFestival; diff --git a/src/utils/index.ts b/src/utils/index.ts index 3d20949..549726f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,6 +6,7 @@ export * from "./extractKeyFromArray"; export * from "./generateQueryString"; export * from "./generateUrlWithParams"; export * from "./getSettledValue"; +export * from "./getUserType"; export * from "./isEmpty"; export * from "./isMatchPath"; export * from "./log"; From ec7bc4459b8ecd98ab852a256dfb33d26fb042bd Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Sun, 1 Sep 2024 23:48:54 +0900 Subject: [PATCH 05/16] =?UTF-8?q?=F0=9F=92=85=20style:=20secondary=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=84=A4?= =?UTF-8?q?=EC=9E=84=EC=9D=84=20=EB=8F=99=EC=A0=81=20=ED=95=A0=EB=8B=B9?= =?UTF-8?q?=EC=9D=84=20=ED=95=98=EA=B8=B0=EC=9C=84=ED=95=9C=20safelist=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EC=B6=94=EA=B0=80=20#42?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tailwind.config.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tailwind.config.ts b/tailwind.config.ts index 2da076e..653214c 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -12,6 +12,11 @@ const config: Config = { "./src/layout/*.{js,ts,jsx,tsx,mdx}", "./src/layout/**/*.{js,ts,jsx,tsx,mdx}", ], + safelist: [ + { + pattern: /.*secondary.*/, + }, + ], theme: { screens: { sm: "320px", From 1084baab615f3931e8d8349e3e175e00ef976c82 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Mon, 2 Sep 2024 00:07:38 +0900 Subject: [PATCH 06/16] =?UTF-8?q?=E2=9C=85=20test:=20refresh=20token=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9E=84=EC=8B=9C=20=EC=BD=94=EB=A9=98?= =?UTF-8?q?=ED=8A=B8=20=EC=95=84=EC=9B=83=20#42?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth.ts | 59 ++++++++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/auth.ts b/auth.ts index c3ac261..c6b94e3 100644 --- a/auth.ts +++ b/auth.ts @@ -1,18 +1,13 @@ // ? Reference https://github.dev/nextauthjs/next-auth-example/blob/main/app/api/protected/route.ts // ? Reference https://www.heropy.dev/p/MI1Khc -import { decodeJwt } from "jose"; import NextAuth, { Account, NextAuthConfig, Session, User } from "next-auth"; import { AdapterUser } from "next-auth/adapters"; import { JWT } from "next-auth/jwt"; import { ProviderType } from "next-auth/providers"; import Kakao from "next-auth/providers/kakao"; -import { - getRefreshToken, - getServerSideSession, - postOauthLogin, -} from "@/apis/auth/auth"; +import { getServerSideSession, postOauthLogin } from "@/apis/auth/auth"; import { SocialLoginRequest } from "@/apis/auth/authType"; import { isMatchPath } from "@/utils"; @@ -69,40 +64,40 @@ const config = { user?: User | AdapterUser; account?: Account | null; }) => { - if (token.accessToken) { - const decodedToken = decodeJwt(token.accessToken); - - if (decodedToken.exp && decodedToken.exp * 1000 < Date.now()) { - const { accessToken, refreshToken } = await getRefreshToken( - token.refreshToken as string, - ); - token.accessToken = accessToken; - token.refreshToken = refreshToken; - return token; - } - - return token; - } - - if (!user?.id || !user.email || !account?.access_token) { - throw Error("Login Failed"); - } - const body: SocialLoginRequest = { - id: user?.id, - email: user?.email, - accessToken: account?.access_token, - }; + // ! refresh Token 로직 수정 필요 + // if (token.accessToken) { + // const decodedToken = decodeJwt(token.accessToken); + + // if (decodedToken.exp && decodedToken.exp * 1000 < Date.now()) { + // const { accessToken, refreshToken } = await getRefreshToken( + // token.refreshToken as string, + // ); + // token.accessToken = accessToken; + // token.refreshToken = refreshToken; + // return token; + // } + + // return token; + // } let response: JWT = token; - if (!Object.hasOwn(token, "isProfileRegistered")) { - response = await postOauthLogin(body); + if (!!user && !!account) { + const body: SocialLoginRequest = { + id: user.id as string, + email: user.email as string, + accessToken: account.access_token as string, + }; + + if (!Object.hasOwn(token, "isProfileRegistered")) { + response = await postOauthLogin(body); + } } token.accessToken = response?.accessToken; token.refreshToken = response.refreshToken; token.isProfileRegistered = response.isProfileRegistered; - token.email = user.email; + token.email = user?.email; return token; }, From 29de13b51e7eac2bcb42d5c71fe5dadc97e2fc69 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Mon, 2 Sep 2024 00:12:59 +0900 Subject: [PATCH 07/16] =?UTF-8?q?=F0=9F=94=A7=20chore:=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83=20=EC=9E=84=EC=8B=9C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=83=9D=EC=84=B1=20#42?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mypage/view.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/mypage/view.tsx b/src/app/mypage/view.tsx index 0c0b4ce..03db9a0 100644 --- a/src/app/mypage/view.tsx +++ b/src/app/mypage/view.tsx @@ -1,7 +1,12 @@ +import { postSignOut } from "@/apis/auth/auth"; + const MyPageView = () => { return ( -
-
+
+
@@ -10,7 +15,7 @@ const MyPageView = () => {
-
+ ); }; From 40be5d143763a1772945700793c66e9d6d4596f4 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Mon, 2 Sep 2024 00:13:42 +0900 Subject: [PATCH 08/16] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=EB=B3=84=20=EC=B6=94=EC=B2=9C=20=ED=8E=98=EC=8A=A4=ED=8B=B0?= =?UTF-8?q?=EB=B2=8C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20UI=20?= =?UTF-8?q?=EB=8B=A8=20=EA=B5=AC=ED=98=84=20#42?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recommendFestivalType.ts | 10 +++--- src/app/onboarding/page.tsx | 2 +- .../RecommendFestivalHeader.tsx | 24 ++++++------- .../RecommendFestivalList.tsx | 35 ++++--------------- 4 files changed, 26 insertions(+), 45 deletions(-) diff --git a/src/apis/festivals/recommendFestival/recommendFestivalType.ts b/src/apis/festivals/recommendFestival/recommendFestivalType.ts index 2c605d4..a7d1a4c 100644 --- a/src/apis/festivals/recommendFestival/recommendFestivalType.ts +++ b/src/apis/festivals/recommendFestival/recommendFestivalType.ts @@ -3,12 +3,14 @@ export type PaginationParamter = { size: number; }; +export type UserType = { + userTypeId: number; + name: string; +}; + export type RecommendFestivalResponse = { festivals: Array; - userType: { - userTypeId: number; - name: string; - }; + userType: UserType; }; export interface RecommendFestivalModel { diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx index 68337e6..7185036 100644 --- a/src/app/onboarding/page.tsx +++ b/src/app/onboarding/page.tsx @@ -8,7 +8,7 @@ import OnBoardingView from "./view"; export default async function OnBoarding() { const session = await getServerSideSession(); - if (session?.isProfileRegistered) { + if (!session || session?.isProfileRegistered) { redirect("/"); } diff --git a/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx b/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx index 4d8dd24..15caad3 100644 --- a/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx +++ b/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx @@ -1,28 +1,28 @@ import Image from "next/image"; +import { FC } from "react"; +import { UserType } from "@/apis/festivals/recommendFestival/recommendFestivalType"; import { UserTypeImage, UserTypeText } from "@/utils"; -const RecommendFestivalHeader = ({ - userType, -}: { - userType: { - userTypeId: number; - name: string; - }; -}) => { - const userTypeTextColor = UserTypeText[userType.userTypeId ?? 1]; - const userTypeImage = UserTypeImage[userType.userTypeId ?? 1]; +interface Props { + userType: UserType; +} +const RecommendFestivalHeader: FC = ({ userType }) => { return (
- {userType.name} + + {userType.name} + 들을 위한 페스티벌이에요!
{userType.name} {recommendFestivals.festivals.map((festival) => ( @@ -73,26 +72,6 @@ const RecommendFestivalList = ({ - {/* - */} ); From 65d1e8d104e9d5ee2a6a35b39c2783efc886da15 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Mon, 2 Sep 2024 01:39:29 +0900 Subject: [PATCH 09/16] =?UTF-8?q?=F0=9F=90=9B=20fix:=20memo=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=ED=95=98=EC=97=AC=20re-rendering=20=EC=B5=9C?= =?UTF-8?q?=EC=86=8C=ED=99=94=20#42?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/CreateFestivalFirstStep.tsx | 5 +- .../_components/CreateFestivalSecondStep.tsx | 75 ++++++++----------- .../new/_components/DurationFestivalInput.tsx | 32 ++++++++ .../core/Input/AddressInput/AddressInput.tsx | 4 +- .../Input/DurationInput/DurationInput.tsx | 4 +- .../core/Input/TimeInput/TimeInput.tsx | 11 +-- src/validations/CreateFestivalSchema.ts | 10 +-- 7 files changed, 78 insertions(+), 63 deletions(-) create mode 100644 src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx diff --git a/src/app/(home)/festivals/new/_components/CreateFestivalFirstStep.tsx b/src/app/(home)/festivals/new/_components/CreateFestivalFirstStep.tsx index 9f7d4cb..74f996d 100644 --- a/src/app/(home)/festivals/new/_components/CreateFestivalFirstStep.tsx +++ b/src/app/(home)/festivals/new/_components/CreateFestivalFirstStep.tsx @@ -7,10 +7,7 @@ import { CREATE_FESTIVAL_SETTING } from "@/config"; import { CreateFestivalType } from "@/validations/CreateFestivalSchema"; const CreateFestivalFirstStep = () => { - const { - control, - formState: { dirtyFields }, - } = useFormContext(); + const { control } = useFormContext(); return (
diff --git a/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx b/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx index 412c971..3be76ef 100644 --- a/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx +++ b/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx @@ -1,5 +1,5 @@ -import { FC } from "react"; -import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { FC, useCallback } from "react"; +import { Controller, useFormContext } from "react-hook-form"; import { FestivalCategory, @@ -9,13 +9,14 @@ import { AddressInput, CategoryKeywordInput, DescriptionInput, - DurationInput, MoodKeywordInput, TextInput, TimeInput, } from "@/components/core/Input"; import { CreateFestivalType } from "@/validations/CreateFestivalSchema"; +import DurationFestivalInput from "./DurationFestivalInput"; + interface Props { moods: Array; categories: Array; @@ -29,15 +30,6 @@ const CreateFestivalSecondStep: FC = ({ moods, categories }) => { formState: { errors, submitCount }, } = useFormContext(); - const startDate = useWatch({ control, name: "startDate" }); - const endDate = useWatch({ control, name: "endDate" }); - const time = useWatch({ control, name: "playtime" }); - - const handleCalendarConfirm = (start: string | null, end: string | null) => { - setValue("startDate", start ?? "", { shouldValidate: true }); - setValue("endDate", end ?? "", { shouldValidate: true }); - }; - const handleAddress = ( address: string, sido: string, @@ -45,40 +37,33 @@ const CreateFestivalSecondStep: FC = ({ moods, categories }) => { latitude: string, longitude: string, ) => { - setValue("address", address ?? "", { shouldValidate: true }); - setValue("sido", sido ?? "", { shouldValidate: true }); - setValue("sigungu", sigungu ?? "", { shouldValidate: true }); - setValue("latitude", latitude ?? "", { shouldValidate: true }); - setValue("longitude", longitude ?? "", { shouldValidate: true }); + setValue("address", address ?? ""); + setValue("sido", sido ?? ""); + setValue("sigungu", sigungu ?? ""); + setValue("latitude", latitude ?? ""); + setValue("longitude", longitude ?? ""); }; - const handleTimeChange = (time: string) => { - setValue("playtime", time, { shouldValidate: true }); - }; + const handleGetError = useCallback( + (name: keyof CreateFestivalType) => { + if (submitCount < 2) { + return undefined; + } - const handleGetError = (name: keyof CreateFestivalType) => { - if (submitCount < 2) { - return undefined; - } - - const errorMessage = errors[name]?.message ?? undefined; + const errorMessage = errors[name]?.message ?? undefined; - if (typeof errorMessage === "string") { - return errorMessage; - } + if (typeof errorMessage === "string") { + return errorMessage; + } - return undefined; - }; + return undefined; + }, + [errors], + ); return (
- + = ({ moods, categories }) => { error={handleGetError("address")} /> - ( + + )} /> diff --git a/src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx b/src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx new file mode 100644 index 0000000..8fcd688 --- /dev/null +++ b/src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx @@ -0,0 +1,32 @@ +import { FC } from "react"; +import { useFormContext, useWatch } from "react-hook-form"; + +import { DurationInput } from "@/components/core/Input"; +import { CreateFestivalType } from "@/validations/CreateFestivalSchema"; + +interface Props { + handleGetError: (name: keyof CreateFestivalType) => string | undefined; +} + +const DurationFestivalInput: FC = ({ handleGetError }) => { + const { control, setValue } = useFormContext(); + + const startDate = useWatch({ control, name: "startDate" }); + const endDate = useWatch({ control, name: "endDate" }); + + const handleCalendarConfirm = (start: string | null, end: string | null) => { + setValue("startDate", start ?? ""); + setValue("endDate", end ?? ""); + }; + return ( + + ); +}; + +export default DurationFestivalInput; diff --git a/src/components/core/Input/AddressInput/AddressInput.tsx b/src/components/core/Input/AddressInput/AddressInput.tsx index b04b5d6..e9fed7a 100644 --- a/src/components/core/Input/AddressInput/AddressInput.tsx +++ b/src/components/core/Input/AddressInput/AddressInput.tsx @@ -1,4 +1,4 @@ -import { FC, HtmlHTMLAttributes, useState } from "react"; +import { FC, HtmlHTMLAttributes, memo, useState } from "react"; import { Address, useDaumPostcodePopup } from "react-daum-postcode"; import { ErrorIcon, PinLocationIcon } from "@/components/icons"; @@ -132,4 +132,4 @@ const AddressInput: FC = ({ ); }; -export default AddressInput; +export default memo(AddressInput); diff --git a/src/components/core/Input/DurationInput/DurationInput.tsx b/src/components/core/Input/DurationInput/DurationInput.tsx index 7b653b1..275ac47 100644 --- a/src/components/core/Input/DurationInput/DurationInput.tsx +++ b/src/components/core/Input/DurationInput/DurationInput.tsx @@ -1,4 +1,4 @@ -import { ButtonHTMLAttributes, FC, useState } from "react"; +import { ButtonHTMLAttributes, FC, memo, useState } from "react"; import { CalendarDialog } from "@/components/Dialog"; import { CalendarIcon, ErrorIcon } from "@/components/icons"; @@ -119,4 +119,4 @@ const DurationInput: FC = ({ ); }; -export default DurationInput; +export default memo(DurationInput); diff --git a/src/components/core/Input/TimeInput/TimeInput.tsx b/src/components/core/Input/TimeInput/TimeInput.tsx index 4394793..65dcde9 100644 --- a/src/components/core/Input/TimeInput/TimeInput.tsx +++ b/src/components/core/Input/TimeInput/TimeInput.tsx @@ -2,6 +2,7 @@ import { ChangeEvent, ComponentPropsWithoutRef, FC, + memo, useEffect, useRef, useState, @@ -47,12 +48,12 @@ const TimeInput: FC = ({ onChange(`${start ?? startTime} ~ ${end ?? endTime}`); }; - const handleStartTImeChange = (e: ChangeEvent) => { + const handleStartTimeChange = (e: ChangeEvent) => { const time = e.target.value; setStartTime(time); handleOnChange(time); }; - const handleEndTImeChange = (e: ChangeEvent) => { + const handleEndTimeChange = (e: ChangeEvent) => { const time = e.target.value; setEndTime(time); handleOnChange(undefined, time); @@ -99,7 +100,7 @@ const TimeInput: FC = ({ !!startTime ? "text-primary-01" : "text-gray-400") } onClick={() => handleOpenPicker(startTimeRef)} - onChange={handleStartTImeChange} + onChange={handleStartTimeChange} /> = ({ !!endTime ? "text-primary-01" : "text-gray-400", )} onClick={() => handleOpenPicker(endTimeRef)} - onChange={handleEndTImeChange} + onChange={handleEndTimeChange} />
@@ -137,4 +138,4 @@ const TimeInput: FC = ({ ); }; -export default TimeInput; +export default memo(TimeInput); diff --git a/src/validations/CreateFestivalSchema.ts b/src/validations/CreateFestivalSchema.ts index a148838..8644a90 100644 --- a/src/validations/CreateFestivalSchema.ts +++ b/src/validations/CreateFestivalSchema.ts @@ -48,14 +48,8 @@ const CreateFestivalSchema = z.object({ instagramUrl: z.string().url(VALIDATION_ERROR_MESSAGES.url), ticketLink: z.string().url(VALIDATION_ERROR_MESSAGES.url), fee: z.string(), - categoryIds: z - .array(z.number()) - .min(1, VALIDATION_ERROR_MESSAGES.min(1)) - .max(2, VALIDATION_ERROR_MESSAGES.min(2)), - moodIds: z - .array(z.number()) - .min(1, VALIDATION_ERROR_MESSAGES.min(1)) - .max(2, VALIDATION_ERROR_MESSAGES.min(2)), + categoryIds: z.array(z.number()), + moodIds: z.array(z.number()), tip: z.string(), }); From feb3ce7c5549b96ab62a8f98f0baa82f0208c9ab Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Mon, 2 Sep 2024 01:44:01 +0900 Subject: [PATCH 10/16] =?UTF-8?q?=F0=9F=94=A7=20chore:=20usecallback=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/CreateFestivalSecondStep.tsx | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx b/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx index 3be76ef..9fb1500 100644 --- a/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx +++ b/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback } from "react"; +import { FC } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { @@ -44,22 +44,19 @@ const CreateFestivalSecondStep: FC = ({ moods, categories }) => { setValue("longitude", longitude ?? ""); }; - const handleGetError = useCallback( - (name: keyof CreateFestivalType) => { - if (submitCount < 2) { - return undefined; - } + const handleGetError = (name: keyof CreateFestivalType) => { + if (submitCount < 2) { + return undefined; + } - const errorMessage = errors[name]?.message ?? undefined; + const errorMessage = errors[name]?.message ?? undefined; - if (typeof errorMessage === "string") { - return errorMessage; - } + if (typeof errorMessage === "string") { + return errorMessage; + } - return undefined; - }, - [errors], - ); + return undefined; + }; return (
From 9048e1d64588ce48a68350b1cb401158ef77f1be Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Mon, 2 Sep 2024 04:05:32 +0900 Subject: [PATCH 11/16] =?UTF-8?q?=F0=9F=91=B7=20cicd:=20correct=20the=20na?= =?UTF-8?q?me=20of=20=5Fset=5Fenv.yaml=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/chromatic.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 08a6d5d..d4e335b 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -40,8 +40,8 @@ jobs: cache-dependency-path: "**/pnpm-lock.yaml" - - name: Set up Environment Variable for build - uses: ./.github/workflows/_set_env.yaml + - name: Set up Environment Variable for build + uses: ./.github/workflows/_set_env.yml with: NODE_VERSION: 20.x NEXT_PUBLIC_STAGE: development From 76883106544c6bf96619d6c31d0fde08cc261064 Mon Sep 17 00:00:00 2001 From: Jeongmin Lee Date: Mon, 2 Sep 2024 21:00:43 +0900 Subject: [PATCH 12/16] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=20=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20default=20?= =?UTF-8?q?=EA=B0=92=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../new/_components/CreateFestivalSecondStep.tsx | 10 +++++----- .../new/_components/DurationFestivalInput.tsx | 4 ++-- src/validations/CreateFestivalSchema.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx b/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx index 9fb1500..9a82723 100644 --- a/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx +++ b/src/app/(home)/festivals/new/_components/CreateFestivalSecondStep.tsx @@ -37,11 +37,11 @@ const CreateFestivalSecondStep: FC = ({ moods, categories }) => { latitude: string, longitude: string, ) => { - setValue("address", address ?? ""); - setValue("sido", sido ?? ""); - setValue("sigungu", sigungu ?? ""); - setValue("latitude", latitude ?? ""); - setValue("longitude", longitude ?? ""); + setValue("address", address); + setValue("sido", sido); + setValue("sigungu", sigungu); + setValue("latitude", latitude); + setValue("longitude", longitude); }; const handleGetError = (name: keyof CreateFestivalType) => { diff --git a/src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx b/src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx index 8fcd688..e7d1889 100644 --- a/src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx +++ b/src/app/(home)/festivals/new/_components/DurationFestivalInput.tsx @@ -15,8 +15,8 @@ const DurationFestivalInput: FC = ({ handleGetError }) => { const endDate = useWatch({ control, name: "endDate" }); const handleCalendarConfirm = (start: string | null, end: string | null) => { - setValue("startDate", start ?? ""); - setValue("endDate", end ?? ""); + setValue("startDate", start); + setValue("endDate", end); }; return ( Date: Tue, 3 Sep 2024 23:38:38 +0900 Subject: [PATCH 13/16] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=ED=8E=98=EC=8A=A4=ED=8B=B0=ED=8C=94=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecommendFestivalFallbackUI.tsx | 0 .../RecommendFestivalHeader.tsx | 0 .../RecommendFestivalSkeleton.tsx | 20 ++++++++++++++++ .../RecommendFestival.stories.tsx | 8 +++---- ...FestivalList.tsx => RecommendFestival.tsx} | 24 +++++++++++++------ .../Swiper/RecommendFestival/index.tsx | 16 ------------- 6 files changed, 41 insertions(+), 27 deletions(-) rename src/{components/Swiper/RecommendFestival => app/(home)/_components/FestivalRecommend}/RecommendFestivalFallbackUI.tsx (100%) rename src/{components/Swiper/RecommendFestival => app/(home)/_components/FestivalRecommend}/RecommendFestivalHeader.tsx (100%) create mode 100644 src/app/(home)/_components/FestivalRecommend/RecommendFestivalSkeleton.tsx rename src/components/Swiper/RecommendFestival/{RecommendFestivalList.tsx => RecommendFestival.tsx} (72%) delete mode 100644 src/components/Swiper/RecommendFestival/index.tsx diff --git a/src/components/Swiper/RecommendFestival/RecommendFestivalFallbackUI.tsx b/src/app/(home)/_components/FestivalRecommend/RecommendFestivalFallbackUI.tsx similarity index 100% rename from src/components/Swiper/RecommendFestival/RecommendFestivalFallbackUI.tsx rename to src/app/(home)/_components/FestivalRecommend/RecommendFestivalFallbackUI.tsx diff --git a/src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx b/src/app/(home)/_components/FestivalRecommend/RecommendFestivalHeader.tsx similarity index 100% rename from src/components/Swiper/RecommendFestival/RecommendFestivalHeader.tsx rename to src/app/(home)/_components/FestivalRecommend/RecommendFestivalHeader.tsx diff --git a/src/app/(home)/_components/FestivalRecommend/RecommendFestivalSkeleton.tsx b/src/app/(home)/_components/FestivalRecommend/RecommendFestivalSkeleton.tsx new file mode 100644 index 0000000..00e23c0 --- /dev/null +++ b/src/app/(home)/_components/FestivalRecommend/RecommendFestivalSkeleton.tsx @@ -0,0 +1,20 @@ +const RecommendFestivalSkeleton = () => { + return ( +
+
+
+
+
+
+
+
+
+ +
+
+ Loading... +
+ ); +}; + +export default RecommendFestivalSkeleton; diff --git a/src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx b/src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx index 34ba8e8..bcb4387 100644 --- a/src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx +++ b/src/components/Swiper/RecommendFestival/RecommendFestival.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; -import RecommendFestivalFallbackUI from "./RecommendFestivalFallbackUI"; -import RecoomendFestivalList from "./RecommendFestivalList"; +import RecommendFestivalFallbackUI from "../../../app/(home)/_components/FestivalRecommend/RecommendFestivalFallbackUI"; +import RecoomendFestivalList from "./RecommendFestival"; const meta: Meta = { title: "Swiper/RecommendFestival", @@ -17,7 +17,7 @@ type Story = StoryObj; export const Default: Story = { args: { - recommendFestivals: { + initialData: { festivals: [ { festivalId: 64, @@ -78,7 +78,7 @@ export const Default: Story = { }, render: (args) => (
- +
), }; diff --git a/src/components/Swiper/RecommendFestival/RecommendFestivalList.tsx b/src/components/Swiper/RecommendFestival/RecommendFestival.tsx similarity index 72% rename from src/components/Swiper/RecommendFestival/RecommendFestivalList.tsx rename to src/components/Swiper/RecommendFestival/RecommendFestival.tsx index 87d8f94..94f1fb7 100644 --- a/src/components/Swiper/RecommendFestival/RecommendFestivalList.tsx +++ b/src/components/Swiper/RecommendFestival/RecommendFestival.tsx @@ -3,27 +3,36 @@ import "swiper/css"; import "swiper/css/pagination"; +import { useSuspenseQuery } from "@tanstack/react-query"; import Image from "next/image"; import Link from "next/link"; import { Autoplay, Pagination } from "swiper/modules"; import { Swiper, SwiperSlide } from "swiper/react"; +import { getRecommendFestival } from "@/apis/festivals/recommendFestival/recommendFestival"; +import { recommendFestivalKeys } from "@/apis/festivals/recommendFestival/recommendFestivalKeys"; import { RecommendFestivalResponse } from "@/apis/festivals/recommendFestival/recommendFestivalType"; import { formatToKoreanDate } from "@/lib/dayjs"; -import RecommendFestivalHeader from "./RecommendFestivalHeader"; +import RecommendFestivalHeader from "../../../app/(home)/_components/FestivalRecommend/RecommendFestivalHeader"; -const RecommendFestivalList = ({ - recommendFestivals, +const RecommendFestival = ({ + initialData, }: { - recommendFestivals: RecommendFestivalResponse; + initialData: RecommendFestivalResponse; }) => { + const { data: recommendFestivals } = useSuspenseQuery({ + queryKey: recommendFestivalKeys.all, + queryFn: () => getRecommendFestival(), + initialData, + }); + return (

{`${festival.sido} ${festival.sigungu} | ${formatToKoreanDate(festival.startDate)} - ${formatToKoreanDate(festival.endDate)}`} @@ -67,6 +76,7 @@ const RecommendFestivalList = ({ {festival.name}

+
))}