diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml index d2ec0c8..700d36e 100644 --- a/.github/auto_assign.yml +++ b/.github/auto_assign.yml @@ -4,7 +4,7 @@ addAssignees: author reviewers: - punchdrunkard - # - Zero-1016 + - Zero-1016 # - seokzin # - saseungmin diff --git a/.github/workflows/_build.yaml b/.github/workflows/_build.yaml index 48c6253..a8bb7e5 100644 --- a/.github/workflows/_build.yaml +++ b/.github/workflows/_build.yaml @@ -7,7 +7,6 @@ on: NEXT_PUBLIC_STAGE: required: true type: string - jobs: build: @@ -16,13 +15,11 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Set up environment variables - run: | - echo "NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL" >> .env - echo "NEXT_PUBLIC_STAGE=$NEXT_PUBLIC_STAGE" >> .env - env: - NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL }} - NEXT_PUBLIC_STAGE: ${{ inputs.NEXT_PUBLIC_STAGE }} + - name: Set up Environment Variable for build + uses: ./.github/workflows/_set_env.yaml + with: + NODE_VERSION: 20.x + NEXT_PUBLIC_STAGE: development - name: Install pnpm uses: pnpm/action-setup@v4 diff --git a/.github/workflows/_set_env.yml b/.github/workflows/_set_env.yml new file mode 100644 index 0000000..4918599 --- /dev/null +++ b/.github/workflows/_set_env.yml @@ -0,0 +1,38 @@ +name: Set Environment Variables + +on: + workflow_call: + inputs: + NEXT_PUBLIC_STAGE: + required: true + type: string + NEXT_PUBLIC_BASE_URL: + required: true + type: string + AUTH_SECRET: + required: true + type: string + AUTH_KAKAO_ID: + required: true + type: string + AUTH_KAKAO_SECRET: + required: true + type: string + +jobs: + set-env-vars: + runs-on: ubuntu-latest + steps: + - name: Set up environment variables + run: | + echo "NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL" >> .env + echo "NEXT_PUBLIC_STAGE=$NEXT_PUBLIC_STAGE" >> .env + echo "AUTH_SECRET=$AUTH_SECRET" >> .env + echo "AUTH_KAKAO_ID=$AUTH_KAKAO_ID" >> .env + echo "AUTH_KAKAO_SECRET=$AUTH_KAKAO_SECRET" >> .env + env: + NEXT_PUBLIC_BASE_URL: ${{ inputs.NEXT_PUBLIC_BASE_URL }} + NEXT_PUBLIC_STAGE: ${{ inputs.NEXT_PUBLIC_STAGE }} + AUTH_SECRET: ${{ inputs.AUTH_SECRET }} + AUTH_KAKAO_ID: ${{ inputs.AUTH_KAKAO_ID }} + AUTH_KAKAO_SECRET: ${{ inputs.AUTH_KAKAO_SECRET }} diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 3f0dfec..08a6d5d 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -39,13 +39,12 @@ jobs: cache: "pnpm" cache-dependency-path: "**/pnpm-lock.yaml" - - name: Set up environment variables - run: | - echo "NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL" >> .env - echo "NEXT_PUBLIC_STAGE=$NEXT_PUBLIC_STAGE" >> .env - env: - NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL }} - NEXT_PUBLIC_STAGE: ${{ inputs.NEXT_PUBLIC_STAGE }} + + - name: Set up Environment Variable for build + uses: ./.github/workflows/_set_env.yaml + with: + NODE_VERSION: 20.x + NEXT_PUBLIC_STAGE: development - name: Install dependencies run: pnpm install diff --git a/src/apis/instance.ts b/src/apis/instance.ts index 7e99be9..11c7f75 100644 --- a/src/apis/instance.ts +++ b/src/apis/instance.ts @@ -22,7 +22,7 @@ export interface FiestaResponse { data: T; } -export async function getUserClientSession() { +export async function getClientSideSession() { const session = await getSession(); return session; } @@ -111,14 +111,14 @@ export class CreateFiestaFetch { const userSession = isServer ? await getServerSideSession() - : await getUserClientSession(); + : await getClientSideSession(); return userSession; } public get = async ( url: string, - options: FiestaFetchOptions, + options: FiestaFetchOptions = {}, ): Promise> => this.fetch(url, "GET", options); public post = async ( @@ -138,7 +138,7 @@ export class CreateFiestaFetch { public delete = async ( url: string, options: FiestaFetchOptions = {}, - ): Promise> => this.fetch(url, "DELETE", { ...options }); + ): Promise> => this.fetch(url, "DELETE", options); } const instance = new CreateFiestaFetch(env.NEXT_PUBLIC_BASE_URL, true); diff --git a/src/apis/review/topReviews/topReviews.ts b/src/apis/review/topReviews/topReviews.ts new file mode 100644 index 0000000..87c584f --- /dev/null +++ b/src/apis/review/topReviews/topReviews.ts @@ -0,0 +1,17 @@ +"use server"; + +import instance from "@/apis/instance"; +import { FIESTA_ENDPOINTS } from "@/config"; + +import { TopReview } from "./topReviewsType"; + +const ENDPOINT = FIESTA_ENDPOINTS.reviews; + +export async function getTopReviews() { + const endpoint = ENDPOINT.mostlike; + const { data } = await instance.get>(endpoint, { + cache: "no-store", + }); + + return data; +} diff --git a/src/apis/review/topReviews/topReviewsType.ts b/src/apis/review/topReviews/topReviewsType.ts new file mode 100644 index 0000000..c2b8ec4 --- /dev/null +++ b/src/apis/review/topReviews/topReviewsType.ts @@ -0,0 +1,15 @@ +export interface TopReview { + reviewId: number; + festivalId: number; + festivalName: string; + content: string; + rating: number; + images?: { + imageId: number; + imageUrl: string; + }; + keywords: { + keywordId: number; + keyword: string; + }[]; +} diff --git a/src/app/(home)/_components/TopReviews.tsx b/src/app/(home)/_components/TopReviews.tsx new file mode 100644 index 0000000..41d291e --- /dev/null +++ b/src/app/(home)/_components/TopReviews.tsx @@ -0,0 +1,23 @@ +import { getTopReviews } from "@/apis/review/topReviews/topReviews"; +import { ReviewTile } from "@/components/core/List"; + +const TopReviews = async () => { + const topReviews = await getTopReviews(); + return ( +
+
+
+ 실시간 핫한 후기 +
+
+ +
+ {topReviews.map((review) => ( + + ))} +
+
+ ); +}; + +export default TopReviews; diff --git a/src/app/(home)/_components/index.ts b/src/app/(home)/_components/index.ts new file mode 100644 index 0000000..a081d7e --- /dev/null +++ b/src/app/(home)/_components/index.ts @@ -0,0 +1,3 @@ +export { default as FestivalHot } from "./FestivalHot"; +export { default as FestivalThisWeek } from "./FestivalThisWeek"; +export { default as TopReviews } from "./TopReviews"; diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 0a018cb..4b8fd22 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -1,16 +1,15 @@ import { HomeHeader } from "@/layout/Mobile/MobileHeader"; import NavigationBar from "@/layout/Mobile/NavigationBar"; -import FestivalHot from "./_components/FestivalHot"; -import FestivalThisWeek from "./_components/FestivalThisWeek"; - +import { FestivalHot, FestivalThisWeek, TopReviews } from "./_components"; export default async function Home() { return (
-
+
+
diff --git a/src/components/core/List/ReviewTile/ReviewTile.stories.tsx b/src/components/core/List/ReviewTile/ReviewTile.stories.tsx index c900385..5f3e093 100644 --- a/src/components/core/List/ReviewTile/ReviewTile.stories.tsx +++ b/src/components/core/List/ReviewTile/ReviewTile.stories.tsx @@ -19,32 +19,54 @@ export const Default: Story = { ), args: { - festivalReview: { - src: "/images/festivalReview.png", - title: "서울 치맥 페스티벌", - content: - "완전 더워요... 선크림 필수.. 빨간 트럭에서파는 치킨 추천합니당 바삭하고 맛있어요. 바삭하고 맛있어요. 바삭하고 맛있어요.", - duration: "8월 1일 ~ 8월 4일", + review: { + reviewId: 6532, + festivalId: 123, + festivalName: "2024 팔레트 뮤직 페스티벌", + content: "음악 듣기 정말 좋은 페스티벌인 것 같아요. 진짜 힐링 그 자체~", rating: 4, - keywords: ["✨쾌적해요", "✨쾌적해요"], + images: { + imageId: 145156, + imageUrl: "/images/festivalReview.png", + }, + keywords: [ + { + keywordId: 12, + keyword: "✨쾌적해요", + }, + { + keywordId: 12, + keyword: "✨쾌적해요", + }, + ], }, }, }; -export const WithOutPhoto: Story = { +export const NoPhoto: Story = { render: (args) => (
), args: { - festivalReview: { - title: "서울 치맥 페스티벌", + review: { + reviewId: 6532, + festivalId: 123, + festivalName: "2024 팔레트 뮤직 페스티벌", content: - "완전 더워요... 선크림 필수.. 빨간 트럭에서파는 치킨 추천합니당 바삭하고 맛있어요. 바삭하고 맛있어요. 바삭하고 맛있어요.", - duration: "8월 1일 ~ 8월 4일", + "완전 더워요... 그냥 더워 죽어요... ㅜㅜ 시원하게 입고가세요... 물이랑 손풍기 필수", rating: 4, - keywords: ["✨쾌적해요", "✨쾌적해요"], + keywords: [ + { + keywordId: 12, + keyword: "✨쾌적해요", + }, + { + keywordId: 12, + keyword: "✨쾌적해요", + }, + ], }, }, }; diff --git a/src/components/core/List/ReviewTile/ReviewTile.tsx b/src/components/core/List/ReviewTile/ReviewTile.tsx index 01c01b3..f3a1afd 100644 --- a/src/components/core/List/ReviewTile/ReviewTile.tsx +++ b/src/components/core/List/ReviewTile/ReviewTile.tsx @@ -1,58 +1,53 @@ +import isEmpty from "lodash/isEmpty"; import Image from "next/image"; -import { FC, HTMLAttributes } from "react"; +import { ComponentPropsWithoutRef, FC } from "react"; +import { TopReview } from "@/apis/review/topReviews/topReviewsType"; import Ratings from "@/components/rating/Ratings"; import { cn } from "@/utils/cn"; -import ReviewTag from "../../Tag/ReviewTag/ReviewTag"; +import { ReviewTag } from "../../Tag"; -interface FestivalReview { - src?: string; - title: string; - content: string; - duration: string; - keywords: string[]; - rating: number; +interface Props extends Omit, "children"> { + review: TopReview; } -interface Props extends Omit, "children"> { - festivalReview: FestivalReview; -} - -const ReviewTile: FC = ({ festivalReview, ...props }) => { - const firstTwoKeywords = festivalReview.keywords.slice(0, 2); - +const ReviewTile: FC = ({ review, className }) => { return (
- {!!festivalReview.src && ( + {!isEmpty(review.images) && (
- {festivalReview.title} + {review.festivalName}
)}
-
-
+
+
- {festivalReview.title} + {review.festivalName} - +
- {festivalReview.content} + {review.content}
- {firstTwoKeywords.map((keyword) => ( - + {review.keywords.map(({ keyword, keywordId }) => ( + ))}
diff --git a/src/components/core/Tag/ReviewTag/ReviewTag.tsx b/src/components/core/Tag/ReviewTag/ReviewTag.tsx index fc2d17a..67facdf 100644 --- a/src/components/core/Tag/ReviewTag/ReviewTag.tsx +++ b/src/components/core/Tag/ReviewTag/ReviewTag.tsx @@ -1,23 +1,21 @@ -import { ButtonHTMLAttributes, FC } from "react"; +import { ComponentPropsWithoutRef, FC } from "react"; import { cn } from "@/utils/cn"; -interface Props - extends Omit, "children"> { +interface Props extends ComponentPropsWithoutRef<"button"> { label: string; } -const ReviewTag: FC = ({ label, ...props }) => { +const ReviewTag: FC = ({ label, className }) => { return ( diff --git a/src/config/apiEndpoints.ts b/src/config/apiEndpoints.ts index e75cb68..210d875 100644 --- a/src/config/apiEndpoints.ts +++ b/src/config/apiEndpoints.ts @@ -38,6 +38,7 @@ const FIESTA_ENDPOINTS = { base: "/reviews", detail: (reviewId: string) => `/reviews/${reviewId}`, like: (reviewId: string) => `/reviews/${reviewId}/like`, + mostlike: "/reviews/mostlike", }, logs: { userLogs: (userId: string) => `/users/${userId}/logs`, diff --git a/src/layout/Mobile/MobileHeader/HomeHeader/HomeHeader.tsx b/src/layout/Mobile/MobileHeader/HomeHeader/HomeHeader.tsx index 225faf2..5c5ac14 100644 --- a/src/layout/Mobile/MobileHeader/HomeHeader/HomeHeader.tsx +++ b/src/layout/Mobile/MobileHeader/HomeHeader/HomeHeader.tsx @@ -8,7 +8,7 @@ interface Props {} const HomeHeader: FC = ({}) => { return ( -
+
{ diff --git a/src/mocks/data/festivalMocks/index.ts b/src/mocks/data/festivalMocks/index.ts new file mode 100644 index 0000000..cfab73a --- /dev/null +++ b/src/mocks/data/festivalMocks/index.ts @@ -0,0 +1,5 @@ +export * from "./hotFestivalHandler"; +export * from "./searchFestivalHandler"; +export * from "./thisWeekFestivalHandler"; +export * from "./topReviewsHandler"; +export * from "./trendingFestivalHandler"; diff --git a/src/mocks/data/festivalMocks/topReviewsHandler.ts b/src/mocks/data/festivalMocks/topReviewsHandler.ts new file mode 100644 index 0000000..9c7cad4 --- /dev/null +++ b/src/mocks/data/festivalMocks/topReviewsHandler.ts @@ -0,0 +1,55 @@ +import { faker } from "@faker-js/faker"; +import { delay, http, HttpResponse } from "msw"; + +import { FIESTA_ENDPOINTS } from "@/config"; +import { env } from "@/env"; + +export const topReviewsHandler = [ + http.get( + `${env.NEXT_PUBLIC_BASE_URL}${FIESTA_ENDPOINTS.reviews.mostlike}`, + + async ({}) => { + await delay(1000); + return HttpResponse.json(GenerateTopReviews()); + }, + ), +]; + +const GenerateTopReviews = () => { + const topReviews = []; + + for (let i = 0; i < 3; i++) { + topReviews.push({ + reviewId: faker.number.int(), + festivalId: faker.number.int(), + festivalName: faker.lorem.text(), + content: faker.lorem.sentences(3), + rating: faker.number.int({ min: 1, max: 5 }), + images: { + imageId: faker.number.int(), + imageUrl: "https://placehold.co/400.png", + }, + keywords: [ + { + keywordId: faker.number.int(), + keyword: `${faker.internet.emoji()} ${faker.word.adverb(5)}`, + }, + { + keywordId: faker.number.int(), + keyword: `${faker.internet.emoji()} ${faker.word.adverb(5)}`, + }, + { + keywordId: faker.number.int(), + keyword: `${faker.internet.emoji()} ${faker.word.adverb(5)}`, + }, + ], + }); + } + + return { + statusCode: 200, + status: "OK", + message: "리뷰 TOP3 조회 성공", + data: topReviews, + }; +}; diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index cca9db1..9f781d1 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,11 +1,15 @@ -import { FestivalHandler } from "./data/festivalMocks/hotFestivalHandler"; -import { SearchFestivalHandler } from "./data/festivalMocks/searchFestivalHandler"; -import { thisWeekFestivalHandler } from "./data/festivalMocks/thisWeekFestivalHandler"; -import { trendingFestivalHandler } from "./data/festivalMocks/trendingFestivalHandler"; +import { + FestivalHandler, + SearchFestivalHandler, + thisWeekFestivalHandler, + topReviewsHandler, + trendingFestivalHandler, +} from "./data/festivalMocks"; export const handlers = [ ...FestivalHandler, ...thisWeekFestivalHandler, ...trendingFestivalHandler, ...SearchFestivalHandler, + ...topReviewsHandler, ];