From 84783ecfa87f32289034374e640dfe68dd59610a Mon Sep 17 00:00:00 2001 From: Eunji <129590633+bianbbc87@users.noreply.github.com> Date: Wed, 8 May 2024 04:13:07 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Feat=20-=20=EC=84=B8=EB=AF=B8?= =?UTF-8?q?=EB=82=98=20=EC=83=81=EC=84=B8=20=EB=85=B8=EC=85=98=20api=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20#44?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/member/route.tsx | 54 ++++++++++++ src/app/api/seminar/review/route.tsx | 59 +++++++++++++ src/app/seminar/[id]/page.tsx | 84 +++++++++---------- .../banner/SeminarDetailBanner.tsx | 19 +++-- .../header/SeminarDetailHeader.tsx | 8 +- .../seminarDetail/pdf/SeminarDetailPdf.tsx | 9 +- .../review/SeminalDetailReviewDetail.tsx | 6 +- .../review/SeminarDetailReview.tsx | 10 ++- src/hooks/seminar/changePathtoNumber.ts | 19 ++--- 9 files changed, 190 insertions(+), 78 deletions(-) create mode 100644 src/app/api/member/route.tsx create mode 100644 src/app/api/seminar/review/route.tsx diff --git a/src/app/api/member/route.tsx b/src/app/api/member/route.tsx new file mode 100644 index 0000000..b002fd9 --- /dev/null +++ b/src/app/api/member/route.tsx @@ -0,0 +1,54 @@ +import { Client } from '@notionhq/client'; +import { NextRequest } from 'next/server'; + +const notion = new Client({ + auth: process.env.NOTION_SECRET_KEY, +}); + +// seminar id를 가진 member 불러오기 +async function queryMemberData(databaseId: string, seminarId: string): Promise { + try { + const response = await notion.databases.query({ + database_id: databaseId, + filter: { + property: 'Seminars', + relation: { + contains: seminarId + } + }, + }); + + return response.results; + } catch (error) { + console.error('Error querying Notion database and fetching member data:', JSON.stringify(error)); + throw error; + } +} + +type Data = { + items?: any[]; + message: string; +}; + +export async function GET(req: NextRequest) { + const url = new URL(req.url); + const seminarId = url.searchParams.get('seminarId') || ''; // 쿼리 파라미터에서 세미나 ID 가져오기 + const databaseId = process.env.NOTION_MEMBER_DATABASE_ID || ''; + + try { + const data = await queryMemberData(databaseId, seminarId); + return new Response(JSON.stringify({ data, message: 'Success' }), { + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + return new Response(JSON.stringify({ message: `Failed: ${error?.toString()}` }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + }, + }); + } +} \ No newline at end of file diff --git a/src/app/api/seminar/review/route.tsx b/src/app/api/seminar/review/route.tsx new file mode 100644 index 0000000..aa24851 --- /dev/null +++ b/src/app/api/seminar/review/route.tsx @@ -0,0 +1,59 @@ +import { Client } from '@notionhq/client'; +import { NextRequest } from 'next/server'; + +const notion = new Client({ + auth: process.env.NOTION_SECRET_KEY, +}); + +// seminar id와 연결된 리뷰 불러오기 +async function queryReviewData(databaseId: string, seminarId: string): Promise { + try { + const response = await notion.databases.query({ + database_id: databaseId, + filter: { + property: 'Seminar', + relation: { + contains: seminarId + } + } + }); + + return response.results; + } catch (error) { + console.error('Error querying Notion database by Seminar ID:', error); + throw error; + } +} + +export async function GET(req: NextRequest) { + const url = new URL(req.url); + const seminarId = url.searchParams.get('seminarId'); // 쿼리 파라미터에서 세미나 ID 가져오기 + + if (!seminarId) { + return new Response(JSON.stringify({ message: 'Seminar ID is required' }), { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + const databaseId = process.env.NOTION_REVIEW_DATABASE_ID || ''; // 리뷰 데이터베이스 ID + + try { + const reviews = await queryReviewData(databaseId, seminarId); + return new Response(JSON.stringify({ reviews, message: 'Success' }), { + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + return new Response(JSON.stringify({ message: `Failed: ${error?.toString()}` }), { + status: 500, + headers: { + 'Content-Type': 'application/json', + }, + }); + } +} \ No newline at end of file diff --git a/src/app/seminar/[id]/page.tsx b/src/app/seminar/[id]/page.tsx index 92c9d79..fc6fb59 100644 --- a/src/app/seminar/[id]/page.tsx +++ b/src/app/seminar/[id]/page.tsx @@ -1,64 +1,56 @@ -'use client'; - -import React, {useEffect} from 'react'; -import { usePathname } from 'next/navigation'; import SeminarDetailHeader from '@/components/seminar/seminarDetail/header/SeminarDetailHeader'; import SeminarDetailBanner from '@/components/seminar/seminarDetail/banner/SeminarDetailBanner'; -import { SEMINAR_DATA } from '@/constants/seminar/seminarData'; -import { OPEN_SEMINAR_DATA } from '@/constants/seminar/openSeminarData'; import SeminarDetailPdf from '@/components/seminar/seminarDetail/pdf/SeminarDetailPdf'; import NotFoundPage from '@/app/not-found'; import SeminarDetailReview from '@/components/seminar/seminarDetail/review/SeminarDetailReview'; -import { SeminarThumbnail } from '@/interfaces/seminar/seminarThumbnail'; -import { changePathtoNumber } from '@/hooks/seminar/changePathtoNumber'; - -const SeminarDetailPage = () => { - const pathname = usePathname(); - let id = changePathtoNumber(pathname || ''); - - let data: SeminarThumbnail | undefined; - - // 일반객체 - data = SEMINAR_DATA.find(seminar => `${seminar.id}` === `${id}`); - +import { refactorSeminarData, refactorSeminarMemberData, refactorSeminarReviewData } from '@/hooks/seminar/notionDataRefactor'; +import { headers } from "next/headers"; +import { changePathtoSeperate } from '@/hooks/seminar/changePathtoNumber'; +import { SEMINAR_MEMBER_DATA } from '@/constants/seminar/seminarMemberData'; + +const SeminarDetailPage = async () => { + // server comp에서 path 가져오기 + const header = headers(); + const pathname = header.get('next-url') + + const seminarId = changePathtoSeperate(pathname ?? ''); + + // seminar data 정의 + const seminarResponse = await fetch('http://localhost:3001/api/seminar'); + const seminarList = await seminarResponse.json(); + const seminars = refactorSeminarData(seminarList.data ?? []); + + // reviews 데이터 정의 + const seminarReviewResponse = await fetch(`http://localhost:3001/api/seminar/review?seminarId=${seminarId}`); + const seminarReviewList = await seminarReviewResponse.json(); + const reviews = refactorSeminarReviewData(seminarReviewList.reviews ?? []); + + // member 데이터 정의 + const memberResponse = await fetch(`http://localhost:3001/api/member?seminarId=${seminarId}`); + const memberList = await memberResponse.json(); + const member = refactorSeminarMemberData(memberList.data[0] ?? SEMINAR_MEMBER_DATA); + + // 세미나 디테일 데이터로 분리 + const seminar = seminars.find(seminar => `${seminar.id}` === `${seminarId}`); // 오픈 세미나 데이터 - if (!data) { - let found = OPEN_SEMINAR_DATA.find(oseminar => - oseminar.seminars.some(seminar => `0${oseminar.id}${seminar.id}` === `${id}`) - ); - - if (found) { - const matchingSeminar = found.seminars.find(seminar => `0${found.id}${seminar.id}` === `${id}`); - console.log('match', matchingSeminar); - - if (matchingSeminar) { - data = matchingSeminar; - } else { - // 일치하는 세미나를 찾지 못한 경우 - return ; - } - } else { - // 일치하는 OpenSeminar를 찾지 못한 경우 + if (!seminar) { return ; } - } - return
- {/* header */} - - - {/* banner */} - + {/* header */} + - {/* pdf file */} - +{/* banner */} + - {/* review */} - +{/* pdf file */} + +{/* review */} +
diff --git a/src/components/seminar/seminarDetail/banner/SeminarDetailBanner.tsx b/src/components/seminar/seminarDetail/banner/SeminarDetailBanner.tsx index 073cff6..685a14a 100644 --- a/src/components/seminar/seminarDetail/banner/SeminarDetailBanner.tsx +++ b/src/components/seminar/seminarDetail/banner/SeminarDetailBanner.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import Image from 'next/image'; import { SeminarThumbnail } from '@/interfaces/seminar/seminarThumbnail'; @@ -5,6 +7,7 @@ import TranslateImg from '@/svg/seminar/translate_img.png'; import { motion } from 'framer-motion'; import { seminarCardVariants } from '@/constants/seminar/seminarCardVariants'; import SeminarDetailBannerInform from './SeminarDetailBannerInform'; +import { SeminarMember } from '@/interfaces/seminar/seminarMember'; /** @@ -18,7 +21,7 @@ import SeminarDetailBannerInform from './SeminarDetailBannerInform'; * Renders the header component for the recruitment section. * @returns The rendered header component. */ -const SeminarDetailBanner = ({ data }: { data: SeminarThumbnail }) => { +const SeminarDetailBanner = ({ seminar, member }: { seminar: SeminarThumbnail, member: SeminarMember }) => { return (
{/* 왼쪽 컨텐츠 */} @@ -31,14 +34,15 @@ const SeminarDetailBanner = ({ data }: { data: SeminarThumbnail }) => { variants={seminarCardVariants} style={{ transformOrigin: '10% 60%' }} > +
{`${data.presenter_name}'s_seminarimage`} +
{/* 오른쪽 컨텐츠 */} @@ -51,12 +55,11 @@ const SeminarDetailBanner = ({ data }: { data: SeminarThumbnail }) => { width={352} height={56} quality={100} - priority /> - - - + + + diff --git a/src/components/seminar/seminarDetail/header/SeminarDetailHeader.tsx b/src/components/seminar/seminarDetail/header/SeminarDetailHeader.tsx index 26a406b..47ff9a5 100644 --- a/src/components/seminar/seminarDetail/header/SeminarDetailHeader.tsx +++ b/src/components/seminar/seminarDetail/header/SeminarDetailHeader.tsx @@ -15,8 +15,8 @@ import BannerImg from '@/svg/seminar/seminar_banner.svg'; * Renders the header component for the recruitment section. * @returns The rendered header component. */ -const SeminarDetailHeader = ({ data }: { data: SeminarThumbnail }) => { - const [categoryData, setCategoryData] = useState([data.type, `${data.flag}st`, data.topic]); +const SeminarDetailHeader = ({ seminar }: { seminar: SeminarThumbnail }) => { + const [categoryData, setCategoryData] = useState([seminar.type, `${seminar.flag}st`, seminar.topic]); return ( <> @@ -31,10 +31,10 @@ const SeminarDetailHeader = ({ data }: { data: SeminarThumbnail }) => { ))}

- {data.title} + {seminar.title}

- {data.description} + {seminar.description}

); diff --git a/src/components/seminar/seminarDetail/pdf/SeminarDetailPdf.tsx b/src/components/seminar/seminarDetail/pdf/SeminarDetailPdf.tsx index 01c89fc..587cdbd 100644 --- a/src/components/seminar/seminarDetail/pdf/SeminarDetailPdf.tsx +++ b/src/components/seminar/seminarDetail/pdf/SeminarDetailPdf.tsx @@ -1,7 +1,10 @@ +'use client'; + import React from 'react'; import PinImg from '@/svg/seminar/pin.svg'; import { motion } from 'framer-motion'; import { seminarCardVariants } from '@/constants/seminar/seminarCardVariants'; +import { SeminarThumbnail } from '@/interfaces/seminar/seminarThumbnail'; /** * @description @@ -14,7 +17,7 @@ import { seminarCardVariants } from '@/constants/seminar/seminarCardVariants'; * Renders the header component for the recruitment section. * @returns The rendered header component. */ -const SeminarDetailPdf = () => { +const SeminarDetailPdf = ({seminar}:{seminar:SeminarThumbnail}) => { return ( { className="flex gap-2 cursor-pointer" > -

세미나 자료 보기

+ + 세미나 자료 보기 +
); diff --git a/src/components/seminar/seminarDetail/review/SeminalDetailReviewDetail.tsx b/src/components/seminar/seminarDetail/review/SeminalDetailReviewDetail.tsx index 1a363f4..1ee20f7 100644 --- a/src/components/seminar/seminarDetail/review/SeminalDetailReviewDetail.tsx +++ b/src/components/seminar/seminarDetail/review/SeminalDetailReviewDetail.tsx @@ -14,11 +14,11 @@ import { seminarCardVariants } from '@/constants/seminar/seminarCardVariants'; * Renders the header component for the recruitment section. * @returns The rendered header component. */ -const SeminarDetailReviewDetail = ({ data }: { data: SeminarReview }) => { +const SeminarDetailReviewDetail = ({ review }: { review: SeminarReview }) => { return (
-

{data.author}

+

{review.author}

{ whileHover={{ scale: 1.05, transition: { duration: 0.2 } }} whileTap={{ scale: 0.8 }} > -

{data.content}

+

{review.content}

); diff --git a/src/components/seminar/seminarDetail/review/SeminarDetailReview.tsx b/src/components/seminar/seminarDetail/review/SeminarDetailReview.tsx index dd2eca8..6b95d6c 100644 --- a/src/components/seminar/seminarDetail/review/SeminarDetailReview.tsx +++ b/src/components/seminar/seminarDetail/review/SeminarDetailReview.tsx @@ -1,4 +1,6 @@ -import React from 'react'; +'use client'; + +import React, {useEffect, useState} from 'react'; import SeminarDetailReviewDetail from './SeminalDetailReviewDetail'; import { motion } from 'framer-motion'; import { seminarCardVariants } from '@/constants/seminar/seminarCardVariants'; @@ -15,7 +17,7 @@ import { SeminarReview } from '@/interfaces/seminar/seminarReview'; * Renders the header component for the recruitment section. * @returns The rendered header component. */ -const SeminarDetailReview = ({ data }: { data: SeminarReview[] }) => { +const SeminarDetailReview = ({reviews}:{reviews:SeminarReview[]}) => { return (
@@ -23,7 +25,7 @@ const SeminarDetailReview = ({ data }: { data: SeminarReview[] }) => { Review

- {data.map((review) => ( + {reviews.map((review) => ( { > + review={review}/> ))}

diff --git a/src/hooks/seminar/changePathtoNumber.ts b/src/hooks/seminar/changePathtoNumber.ts index 4e2481c..0e609cb 100644 --- a/src/hooks/seminar/changePathtoNumber.ts +++ b/src/hooks/seminar/changePathtoNumber.ts @@ -1,13 +1,10 @@ -export const changePathtoNumber = (pathname: string) => { - const match = pathname.match(/\/seminar\/(\d+)/); - const seminar_id = match ? match[1] : null; - return seminar_id; -} +// path 'seminar/${seminar.id} 중 seminar.id만 반환 +export const changePathtoSeperate = (pathname: string) => { + // URL에서 '/seminar/' 다음에 오는 부분을 추출하여 직접 반환 + const basePath = '/seminar/'; + const start = pathname.indexOf(basePath) + basePath.length; + const seminarId = pathname.substring(start); // 이 부분이 바로 seminarId -export const changeOpenPathtoNumber = (pathname:string) => { - const match = pathname.match(/\/seminar\/open\/(\d+)/); - const seminar_id = match ? parseInt(match[1], 10) : null; - - return seminar_id; -} \ No newline at end of file + return seminarId; // 추출된 seminarId 반환 + } \ No newline at end of file