diff --git a/package.json b/package.json index f7efef7..6b460fe 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,13 @@ "make-sprite": "rm public/sprite/sprite.svg && svgstore -o public/sprite/sprite.svg public/sprite/icons/**/*.svg" }, "dependencies": { + "@notionhq/client": "^3.1.3", "@suspensive/react": "^1.13.0", "@suspensive/react-query": "^1.13.0", "@tanstack/react-query": "^4.32.6", "axios": "^1.4.0", "clsx": "^2.0.0", + "dotenv": "^16.5.0", "es-toolkit": "^1.15.1", "eslint": "^8.46.0", "eslint-config-next": "^13.4.12", @@ -54,5 +56,18 @@ "prettier-plugin-tailwindcss": "^0.4.1", "svgstore-cli": "^2.0.1", "tailwindcss": "^3.3.3" - } + }, + "description": "", + "main": ".eslintrc.js", + "repository": { + "type": "git", + "url": "git+https://github.com/dnd-side-project/dnd-9th-5-frontend.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/dnd-side-project/dnd-9th-5-frontend/issues" + }, + "homepage": "https://github.com/dnd-side-project/dnd-9th-5-frontend#readme" } diff --git a/src/apis/apis.ts b/src/apis/apis.ts index a90b485..fe0a2fa 100644 --- a/src/apis/apis.ts +++ b/src/apis/apis.ts @@ -4,37 +4,12 @@ import { PoseDetailResponse, PoseFeedContents, PoseFeedResponse, - PosePickResponse, - PoseTalkResponse, RegisterResponse, } from '.'; import privateApi from './config/privateApi'; import publicApi from './config/publicApi'; import { KAKAO_REDIRECT_URI } from '@/constants'; -export const getPosePick = (peopleCount: number) => - publicApi.get(`/pose/pick/${peopleCount}`); - -export const getPoseDetail = (poseId: number) => - privateApi.get(`/pose/${poseId}`); - -export const getPoseTalk = () => publicApi.get('/pose/talk'); - -export const getPoseFeed = async ( - peopleCount: number, - frameCount: number, - tags: string, - pageNumber: number -) => - await privateApi.get(`/pose`, { - params: { - frameCount, - pageNumber, - peopleCount, - tags, - }, - }); - export const getFilterTag = () => publicApi.get('/pose/tags'); export const getRegister = (code: string) => diff --git a/src/apis/queries.ts b/src/apis/queries.ts index cd370ff..93fc5e6 100644 --- a/src/apis/queries.ts +++ b/src/apis/queries.ts @@ -8,56 +8,11 @@ import { import { FilterTagsResponse, MyposeCountResponse, - PoseDetailResponse, PoseFeedContents, - PoseFeedResponse, - PosePickResponse, - PoseTalkResponse, getBookmarkFeed, getFilterTag, getMyposeCount, - getPoseDetail, - getPoseFeed, - getPosePick, - getPoseTalk, } from '.'; -import { FilterState } from '@/hooks/useFilterState'; - -export const usePoseDetailQuery = ( - { poseId }: { poseId: number }, - options?: UseQueryOptions -) => useQuery(['poseId', poseId], () => getPoseDetail(poseId), options); - -export const usePosePickQuery = ( - peopleCount: number, - options?: UseQueryOptions -) => - useSuspenseQuery(['posePick', peopleCount], () => getPosePick(peopleCount), { - enabled: false, - ...options, - }); - -export const usePoseTalkQuery = (options?: UseQueryOptions) => - useSuspenseQuery(['poseTalk'], getPoseTalk, { enabled: false, ...options }); - -export const usePoseFeedQuery = ( - { peopleCount, frameCount, tags }: FilterState, - options?: UseInfiniteQueryOptions -) => { - return useSuspenseInfiniteQuery( - ['poseFeed', peopleCount, frameCount, tags], - ({ pageParam = 0 }) => getPoseFeed(peopleCount, frameCount, tags.join(','), pageParam), - { - getNextPageParam: (lastPage) => { - const target = lastPage.recommendation - ? lastPage.recommendedContents - : lastPage.filteredContents; - return target.last ? undefined : target.number + 1; - }, - ...options, - } - ); -}; export const useBookmarkFeedQuery = (options?: UseInfiniteQueryOptions) => useSuspenseInfiniteQuery( diff --git a/src/apis/type.ts b/src/apis/type.ts index 5d9fcc9..910f190 100644 --- a/src/apis/type.ts +++ b/src/apis/type.ts @@ -63,15 +63,6 @@ export interface PoseDetailResponse { poseInfo: PoseInfo; } -export interface PoseTalkResponse { - poseWord: { - content: string; - createdAt: string; - updateAt: string; - wordId: number; - }; -} - // 로그인 export interface RegisterResponse { id: number; diff --git a/src/app/(Main)/feed/FeedContent.tsx b/src/app/(Main)/feed/FeedContent.tsx index 480446f..02e9bbf 100644 --- a/src/app/(Main)/feed/FeedContent.tsx +++ b/src/app/(Main)/feed/FeedContent.tsx @@ -1,18 +1,37 @@ +'use client'; + import Link from 'next/link'; -import { usePoseFeedQuery } from '@/apis'; import { PrimaryButton } from '@/components/Button'; import EmptyCase from '@/components/Feed/EmptyCase'; import FeedSection from '@/components/Feed/FeedSection'; import { URL } from '@/constants'; import { useFilterState } from '@/hooks'; +import { useEffect, useState } from 'react'; +import { PoseFeedResponseI } from '@/server/type'; +import { getPoseFeed } from '@/server/api'; export default function FeedContent() { const { filterState } = useFilterState(); - const query = usePoseFeedQuery(filterState); + // const query = usePoseFeedQuery(filterState); + const [data, setData] = useState(null); + + async function fetchPoseFeed() { + const res = await getPoseFeed( + filterState.peopleCount, + filterState.frameCount, + filterState.tags.join(',') + ); + setData(res.data); + } + + useEffect(() => { + fetchPoseFeed(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filterState]); return ( - + -
+ {/*
태그
@@ -70,7 +70,7 @@ export default function FilterSheet() { setState={setTagState} /> )} -
+
*/}
+ ); diff --git a/src/app/(Main)/pick/PickComponent.tsx b/src/app/(Main)/pick/PickComponent.tsx index d8c74a7..bfb0acb 100644 --- a/src/app/(Main)/pick/PickComponent.tsx +++ b/src/app/(Main)/pick/PickComponent.tsx @@ -6,20 +6,20 @@ import Lottie from 'react-lottie-player'; import lottiePick from '#/lotties/pick.json'; import { MainFooter } from '../MainFooter'; -import { usePosePickQuery } from '@/apis'; import { PrimaryButton } from '@/components/Button'; import PoseImage from '@/components/Modal/PoseImage'; import { SelectionBasic } from '@/components/Selection'; import { PEOPLE_COUNT_LIST } from '@/constants'; import { useDidMount } from '@/hooks'; +import { getPosePick } from '@/server/api'; -const DEFAULT_IMAGE = '/images/image-frame.png'; +const DEFAULT_IMAGE = '/images/image-frame.png' as const; export default function PickComponent() { const [countState, setCountState] = useState(1); + const [isLoading, setisLoading] = useState(false); const [isLottiePlaying, setIsLottiePlaying] = useState(true); - const { refetch, data } = usePosePickQuery(countState); - const imageSrc = data?.poseInfo?.imageKey || DEFAULT_IMAGE; + const [imageSrc, setImageSrc] = useState(DEFAULT_IMAGE); useDidMount(async () => { await delay(2200); @@ -27,9 +27,10 @@ export default function PickComponent() { }); const handlePickClick = async () => { - refetch(); + setisLoading(true); setIsLottiePlaying(true); - await delay(900); + const imageUrl = (await getPosePick(countState)).data.imageUrl; + setImageSrc(imageUrl); setIsLottiePlaying(false); }; @@ -43,13 +44,13 @@ export default function PickComponent() { />
- {isLottiePlaying && ( + {(isLottiePlaying || isLoading) && (
)}
- + setisLoading(false)} />
diff --git a/src/app/(Main)/talk/TalkSection.tsx b/src/app/(Main)/talk/TalkSection.tsx index 4ff9778..05acab4 100644 --- a/src/app/(Main)/talk/TalkSection.tsx +++ b/src/app/(Main)/talk/TalkSection.tsx @@ -1,15 +1,14 @@ 'use client'; -import { delay } from 'es-toolkit'; import { useState } from 'react'; import Lottie from 'react-lottie-player'; import lottieTalkAfterClick from '#/lotties/talk_after_click.json'; import lottieTalkBeforeClick from '#/lotties/talk_before_click.json'; import { MainFooter } from '../MainFooter'; -import { usePoseTalkQuery } from '@/apis'; import { PrimaryButton } from '@/components/Button'; import { Spacing } from '@/components/Spacing'; +import { getPoseTalk } from '@/server/api'; const INITIAL_TALK_WORD = `제시어에 맞춰\n포즈를 취해요!`; @@ -18,22 +17,16 @@ export default function TalkWordSection() { const [isLoading, setIsLoading] = useState(true); const isWordLoaded = talkWord !== INITIAL_TALK_WORD; - const { refetch } = usePoseTalkQuery({ - onSuccess: async (data) => { - await delay(1000); - setTalkWord(data.poseWord.content); - setIsLoading(false); - }, - }); - - const handleTalkClick = () => { + const handleTalkClick = async () => { setIsLoading(true); - refetch(); + const keyword = (await getPoseTalk()).data.keyword; + setTalkWord(keyword); + setIsLoading(false); }; return (
-

{talkWord}

+

{talkWord}

diff --git a/src/app/(Sub)/auth/AuthComponent.tsx b/src/app/(Sub)/auth/AuthComponent.tsx index 1e5a702..b3f9cf3 100644 --- a/src/app/(Sub)/auth/AuthComponent.tsx +++ b/src/app/(Sub)/auth/AuthComponent.tsx @@ -15,22 +15,23 @@ export default function AuthComponent({ code }: AuthComponentProps) { const router = useRouter(); useDidMount(async () => { - try { - const { - token: { accessToken }, - email, - nickname, - } = await getRegister(code); - - setClientCookie(COOKIE_ACCESS_TOKEN, accessToken); - setClientCookie(COOKIE_EMAIL, email); - setClientCookie(COOKIE_NICKNAME, nickname); - - alert(`로그인에 성공했어요!`); - router.push('/'); - } catch (error) { - router.push('/'); - } + // try { + // const { + // token: { accessToken }, + // email, + // nickname, + // } = await getRegister(code); + + // setClientCookie(COOKIE_ACCESS_TOKEN, accessToken); + // setClientCookie(COOKIE_EMAIL, email); + // setClientCookie(COOKIE_NICKNAME, nickname); + + // alert(`로그인에 성공했어요!`); + // router.push('/'); + // } catch (error) { + // router.push('/'); + // } + alert('로그인 기능은 준비중이에요'); }); return null; diff --git a/src/app/(Sub)/detail/[id]/DetailSection.tsx b/src/app/(Sub)/detail/[id]/DetailSection.tsx index 297cec9..1f3f1ae 100644 --- a/src/app/(Sub)/detail/[id]/DetailSection.tsx +++ b/src/app/(Sub)/detail/[id]/DetailSection.tsx @@ -1,11 +1,10 @@ 'use client'; import { usePathname } from 'next/navigation'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Source from './Source'; import TagButton from './TagButton'; -import { usePoseDetailQuery } from '@/apis'; import { MainFooter } from '@/app/(Main)/MainFooter'; import { PrimaryButton } from '@/components/Button'; import BookmarkButton from '@/components/Feed/BookmarkButton'; @@ -16,22 +15,32 @@ import { useOverlay } from '@/components/Overlay/useOverlay'; import { BASE_SITE_URL } from '@/constants'; import { useKakaoShare } from '@/hooks'; import { copy } from '@/utils/copy'; +import { PoseDataI } from '@/server/type'; +import { getPoseDetail } from '@/server/api'; interface DetailSectionProps { poseId: number; } export default function DetailSection({ poseId }: DetailSectionProps) { - const { data } = usePoseDetailQuery({ poseId }); + const [data, setData] = useState(null); + // const { data } = usePoseDetailQuery({ poseId }); const { shareKakao } = useKakaoShare(); const { open } = useOverlay(); const pathname = usePathname(); const [isRendered, setIsRendered] = useState(false); - if (!data) return null; - const { imageKey, tagAttributes, source, sourceUrl, peopleCount, frameCount, bookmarkCheck } = - data.poseInfo; + useEffect(() => { + fetchDetail(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [poseId]); + + // region method + async function fetchDetail() { + const res = await getPoseDetail(poseId + ''); + setData(res.data); + } const handleShareLink = async () => { await copy(BASE_SITE_URL + pathname); @@ -42,6 +51,18 @@ export default function DetailSection({ poseId }: DetailSectionProps) { )); }; + // region return + if (!data) return null; + const { + image: imageKey, + tags: tagAttributes, + people: peopleCount, + cut: frameCount, + source, + sourceUrl, + } = data; + const bookmarkCheck = false; + return (
} /> - {source && } + {source && sourceUrl && }
{isRendered ||
} setIsRendered(true)} /> @@ -59,14 +80,14 @@ export default function DetailSection({ poseId }: DetailSectionProps) { {tagAttributes?.split(',').map((tag, index) => )}
- + - shareKakao(poseId)} /> + {/* shareKakao(poseId)} /> */}
); diff --git a/src/app/api/pose/[id]/route.ts b/src/app/api/pose/[id]/route.ts new file mode 100644 index 0000000..bcbd125 --- /dev/null +++ b/src/app/api/pose/[id]/route.ts @@ -0,0 +1,40 @@ +import { PageObjectResponse } from '@notionhq/client'; +import { NextRequest, NextResponse } from 'next/server'; + +import { NOTION_DATABASE, notionClient } from '@/database'; +import { ApiResponse, PoseDataI } from '@/server/type'; +import { refinePoseDataFromPage } from '@/server/utils'; + +interface Params { + id: string; +} +// region GET +export async function GET( + req: NextRequest, + { params: { id } }: { params: Params } +): Promise> { + try { + const response = await notionClient.databases.query({ + database_id: NOTION_DATABASE.data, + filter: { property: 'id', unique_id: { equals: parseInt(id) } }, + page_size: 10, + }); + + const results = response.results as PageObjectResponse[]; + + if (results.length === 0) { + return NextResponse.json({ error: `No Data : ${id}` }, { status: 400 }); + } + + const data = refinePoseDataFromPage(results[0]); + + if (!data) { + return NextResponse.json({ error: `No Image : ${id}` }, { status: 400 }); + } + + return NextResponse.json(data); + } catch (error) { + console.error('[NOTION_API_ERROR]', error); + return NextResponse.json({ error: 'Failed to fetch data' }, { status: 500 }); + } +} diff --git a/src/app/api/pose/pick/[peopleCount]/route.ts b/src/app/api/pose/pick/[peopleCount]/route.ts new file mode 100644 index 0000000..22176dd --- /dev/null +++ b/src/app/api/pose/pick/[peopleCount]/route.ts @@ -0,0 +1,59 @@ +import { BlockObjectResponse } from '@notionhq/client'; +import { NextRequest, NextResponse } from 'next/server'; + +import { NOTION_DATABASE, notionClient } from '@/database'; +import { ApiResponse, PosePickResponseI } from '@/server/type'; + +const PAGE_SIZE = 10; + +interface Params { + peopleCount: string; +} + +// region GET +export async function GET( + req: NextRequest, + { params: { peopleCount } }: { params: Params } +): Promise> { + try { + const response = await notionClient.databases.query({ + database_id: NOTION_DATABASE.data, + page_size: PAGE_SIZE, + filter: { + property: 'people', + number: { + equals: parseInt(peopleCount), + }, + }, + }); + const resultPages = response.results; + + if (resultPages.length === 0) { + return NextResponse.json({ error: 'No data found' }, { status: 404 }); + } + + const RANDOM_INDEX = Math.floor(Math.random() * resultPages.length); + const randomPage = resultPages[RANDOM_INDEX]; + + const contentBlocks = ( + await notionClient.blocks.children.list({ + block_id: randomPage.id, + }) + ).results as BlockObjectResponse[]; + + const imageBlock = contentBlocks.find((block) => block.type === 'image'); + + if (!(imageBlock && 'image' in imageBlock && 'file' in imageBlock.image)) { + return NextResponse.json({ error: `No Image : ${randomPage.id}` }, { status: 500 }); + } + + const imageUrl = imageBlock.image.file.url; + + return NextResponse.json({ + imageUrl, + }); + } catch (error) { + console.error('[NOTION_API_ERROR]', error); + return NextResponse.json({ error: 'Failed to fetch data' }, { status: 500 }); + } +} diff --git a/src/app/api/pose/route.ts b/src/app/api/pose/route.ts new file mode 100644 index 0000000..d8b2310 --- /dev/null +++ b/src/app/api/pose/route.ts @@ -0,0 +1,71 @@ +import { PageObjectResponse } from '@notionhq/client'; +import { NextRequest, NextResponse } from 'next/server'; + +import { NOTION_DATABASE, notionClient } from '@/database'; +import { ApiResponse, PoseDataI, PoseFeedResponseI } from '@/server/type'; +import { refinePoseDataFromPage } from '@/server/utils'; + +// region GET +export async function GET(req: NextRequest): Promise> { + const searchParams = req.nextUrl.searchParams; + + const people = searchParams.get('people'); + const cut = searchParams.get('cut'); + const tags = searchParams.get('tags')?.split(','); + + const andFilters = []; + if (people && people !== '0') { + andFilters.push({ + property: 'people', + number: { + equals: parseInt(people), + }, + }); + } + if (cut && cut !== '0') { + andFilters.push({ + property: 'cut', + number: { + equals: parseInt(cut), + }, + }); + } + if (tags && tags.length > 0) { + andFilters.push({ + or: tags.map((tag) => ({ + property: 'tags', + multi_select: { + contains: tag, + }, + })), + }); + } + + try { + const response = await notionClient.databases.query({ + database_id: NOTION_DATABASE.data, + filter: { and: andFilters }, + page_size: 10, + }); + + const contents: PoseDataI[] = []; + const resultPages = response.results as PageObjectResponse[]; + + for (const page of resultPages) { + const content = refinePoseDataFromPage(page); + + if (content === null) { + continue; + } + + contents.push(content); + } + + return NextResponse.json({ + contents, + }); + } catch (error) { + console.error('[NOTION_API_ERROR]', error); + return NextResponse.json({ error: 'Failed to fetch data' }, { status: 500 }); + } +} diff --git a/src/app/api/pose/talk/route.ts b/src/app/api/pose/talk/route.ts new file mode 100644 index 0000000..99fc432 --- /dev/null +++ b/src/app/api/pose/talk/route.ts @@ -0,0 +1,40 @@ +import { PageObjectResponse } from '@notionhq/client'; +import { NextResponse } from 'next/server'; + +import { NOTION_DATABASE, notionClient } from '@/database'; +import { ApiResponse, PoseTalkResponseI } from '@/server/type'; +import { UniqueIdPropertyItemObjectResponse } from '@notionhq/client/build/src/api-endpoints'; + +// region GET +export async function GET(): Promise> { + try { + const response = await notionClient.databases.query({ + database_id: NOTION_DATABASE.talk, + }); + + const results = response.results; + if (results.length === 0) { + return NextResponse.json({ error: 'No data found' }, { status: 404 }); + } + + const randomPage = results[Math.floor(Math.random() * results.length)] as PageObjectResponse; + + const keywordProps = randomPage.properties['keyword']; + const idProps = randomPage.properties['id'] as UniqueIdPropertyItemObjectResponse; + + if (!('title' in keywordProps)) { + return NextResponse.json({ error: 'No props : keyword' }); + } + + const id = idProps.unique_id.number as number; + const keyword = keywordProps.title[0].plain_text; + + return NextResponse.json({ + id, + keyword, + }); + } catch (error) { + console.error('[NOTION_API_ERROR]', error); + return NextResponse.json({ error: 'Failed to fetch data' }, { status: 500 }); + } +} diff --git a/src/components/Feed/FeedSection.tsx b/src/components/Feed/FeedSection.tsx index c30b969..f4164bc 100644 --- a/src/components/Feed/FeedSection.tsx +++ b/src/components/Feed/FeedSection.tsx @@ -6,61 +6,61 @@ import { useInView } from 'react-intersection-observer'; import { PoseFeedContents, PoseFeedResponse } from '@/apis'; import PhotoList from '@/components/Feed/PhotoList'; +import { PoseFeedResponseI } from '@/server/type'; interface FeedSecionI extends PropsWithChildren { - query: UseSuspenseInfiniteQueryResultOnSuccess; + query?: UseSuspenseInfiniteQueryResultOnSuccess; + data: PoseFeedResponseI | null; } -export default function FeedSection({ children, query }: FeedSecionI) { +export default function FeedSection({ children, query, data }: FeedSecionI) { const { ref, inView } = useInView(); - const { data, fetchNextPage, refetch } = query; + // const { data, fetchNextPage, refetch } = query; - useEffect(() => { - refetch(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // useEffect(() => { + // refetch(); + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, []); - useEffect(() => { - if (inView) fetchNextPage(); - }, [inView, fetchNextPage]); + // useEffect(() => { + // if (inView) fetchNextPage(); + // }, [inView, fetchNextPage]); - if ('filteredContents' in data.pages[0]) - return ( -
- {data.pages[0].filteredContents.empty ? ( - children - ) : ( -
- {data.pages.map((page) => { - const poseListData = 'filteredContents' in page ? page.filteredContents : page; - return ; - })} -
- )} - {data.pages[0]?.recommendation && ( - <> -

이런 포즈는 어때요?

-
- {data.pages.map((page) => { - const poseListData = - 'recommendedContents' in page ? page.recommendedContents : page; - return ; - })} -
- - )} -
-
- ); + // if ('filteredContents' in data.pages[0]) + // return ( + //
+ // {data.pages[0].filteredContents.empty ? ( + // children + // ) : ( + //
+ // {data.pages.map((page) => { + // const poseListData = 'filteredContents' in page ? page.filteredContents : page; + // return ; + // })} + //
+ // )} + // {data.pages[0]?.recommendation && ( + // <> + //

이런 포즈는 어때요?

+ //
+ // {data.pages.map((page) => { + // const poseListData = + // 'recommendedContents' in page ? page.recommendedContents : page; + // return ; + // })} + //
+ // + // )} + //
+ //
+ // ); return (
- {data.pages[0].empty ? ( + {!data || data.contents.length === 0 ? ( children ) : (
- {data.pages.map( - (page) => 'content' in page && - )} +
)}
diff --git a/src/components/Feed/Photo.tsx b/src/components/Feed/Photo.tsx index 71da91f..725bba7 100644 --- a/src/components/Feed/Photo.tsx +++ b/src/components/Feed/Photo.tsx @@ -5,22 +5,22 @@ import Link from 'next/link'; import { useState } from 'react'; import BookmarkButton from './BookmarkButton'; -import { PoseInfo } from '@/apis'; +import { PoseDataI } from '@/server/type'; interface PhotoI { - data: PoseInfo; + data: PoseDataI; } export default function Photo({ data }: PhotoI) { - const { imageKey, source, bookmarkCheck, poseId, width, height } = data; + const { people, cut, tags, source, sourceUrl, image, id } = data; const [loaded, setLoaded] = useState(false); return (
- {imageKey && ( + {image && ( <> - + {source - {loaded && } - {loaded || ( -
- )} + {loaded && } + {loaded ||
} )}
diff --git a/src/components/Feed/PhotoList.tsx b/src/components/Feed/PhotoList.tsx index 6667310..5390494 100644 --- a/src/components/Feed/PhotoList.tsx +++ b/src/components/Feed/PhotoList.tsx @@ -1,16 +1,16 @@ +import { PoseDataI } from '@/server/type'; import Photo from './Photo'; -import { PoseFeedContents } from '@/apis'; interface PhotoList { - data?: PoseFeedContents; + datas?: PoseDataI[]; } -export default function PhotoList({ data }: PhotoList) { - if (!data) return; +export default function PhotoList({ datas }: PhotoList) { + if (!datas) return; return ( <> - {data.content.map((item) => ( - + {datas.map((data) => ( + ))} ); diff --git a/src/components/Login/LoginModal.tsx b/src/components/Login/LoginModal.tsx index 62b9fc3..97190d2 100644 --- a/src/components/Login/LoginModal.tsx +++ b/src/components/Login/LoginModal.tsx @@ -12,7 +12,8 @@ interface LoginModalProps { export default function LoginModal({ onClose }: LoginModalProps) { const router = useRouter(); const handleLogin = () => { - router.push(KAKAO_AUTHORIZE); + alert('업데이트를 기다려주세요!'); + // router.push(KAKAO_AUTHORIZE); }; return ( @@ -21,9 +22,9 @@ export default function LoginModal({ onClose }: LoginModalProps) { content={`로그인하면 북마크도 쓸 수 있어요!\n간편 로그인으로 3초만에 가입해요.`} onClose={onClose} > -
+
- window.open(URL.appstore)} /> + {/* window.open(URL.appstore)} /> */}
); diff --git a/src/database/index.ts b/src/database/index.ts new file mode 100644 index 0000000..13e1577 --- /dev/null +++ b/src/database/index.ts @@ -0,0 +1,8 @@ +import { Client } from '@notionhq/client'; + +export const notionClient = new Client({ auth: process.env.NOTION_API_KEY }); + +export const NOTION_DATABASE = { + talk: process.env.NOTION_DATABASE_KEY_POSETALK || '', + data: process.env.NOTION_DATABASE_KEY_POSEDATA || '', +} as const; diff --git a/src/server/api.ts b/src/server/api.ts new file mode 100644 index 0000000..cbc31f8 --- /dev/null +++ b/src/server/api.ts @@ -0,0 +1,18 @@ +import instance from './config'; +import { PoseDataI, PoseFeedResponseI, PosePickResponseI, PoseTalkResponseI } from './type'; + +export const getPosePick = (peopleCount: number) => + instance.get(`/pose/pick/${peopleCount}`); + +export const getPoseTalk = () => instance.get('/pose/talk'); + +export const getPoseFeed = (people: number, cut: number, tags: string) => + instance.get(`/pose`, { + params: { + people, + cut, + tags, + }, + }); + +export const getPoseDetail = (id: string) => instance.get(`/pose/${id}`); diff --git a/src/server/config.ts b/src/server/config.ts new file mode 100644 index 0000000..fc8eaf6 --- /dev/null +++ b/src/server/config.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +import { BASE_SITE_URL } from '@/constants'; + +const instance = axios.create({ + baseURL: `${BASE_SITE_URL}/api`, + withCredentials: true, +}); + +export default instance; diff --git a/src/server/type.ts b/src/server/type.ts new file mode 100644 index 0000000..db7c435 --- /dev/null +++ b/src/server/type.ts @@ -0,0 +1,32 @@ +import { NextResponse } from 'next/server'; + +// region response +type ApiErrorT = { + error: string; +}; + +export type ApiResponse = NextResponse | NextResponse; + +// region interface +export interface PoseTalkResponseI { + id: number; + keyword: string; +} + +export interface PosePickResponseI { + imageUrl: string; +} +export interface PoseDataI { + id: string; + image: string; + people: number; + cut: number; + tags: string; + source: string | null; + sourceUrl: string | null; + bookmarkCheck?: boolean; +} + +export interface PoseFeedResponseI { + contents: PoseDataI[]; +} diff --git a/src/server/utils.ts b/src/server/utils.ts new file mode 100644 index 0000000..849be5d --- /dev/null +++ b/src/server/utils.ts @@ -0,0 +1,36 @@ +import { + FilesPropertyItemObjectResponse, + MultiSelectPropertyItemObjectResponse, + NumberPropertyItemObjectResponse, + PageObjectResponse, + UniqueIdPropertyItemObjectResponse, + UrlPropertyItemObjectResponse, +} from '@notionhq/client/build/src/api-endpoints'; +import { PoseDataI } from './type'; + +export function refinePoseDataFromPage(page: PageObjectResponse): PoseDataI | null { + const idProps = page.properties['id'] as UniqueIdPropertyItemObjectResponse; + const imageProps = page.properties['image'] as FilesPropertyItemObjectResponse; + const peopleProps = page.properties['people'] as NumberPropertyItemObjectResponse; + const cutProps = page.properties['cut'] as NumberPropertyItemObjectResponse; + const tagsProps = page.properties['tags'] as MultiSelectPropertyItemObjectResponse; + const sourceProps = page.properties['source']; + const sourceUrlProps = page.properties['source_url'] as UrlPropertyItemObjectResponse; + + const imageFile = imageProps.files[0]; + + if (!(imageFile && 'file' in imageFile && 'url' in imageFile.file)) { + console.error(`No Image : ${idProps.unique_id.number}`); + return null; + } + + return { + id: idProps.unique_id.number + '', + image: imageFile.file.url, + people: peopleProps.number ?? 0, + cut: cutProps.number ?? 0, + tags: tagsProps.multi_select.map((tag) => tag.name).join(','), + source: sourceProps.id, + sourceUrl: sourceUrlProps.url, + }; +}