-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from dnd-side-project/OZ-71-F-detail-page
Feature : 포즈 상세 페이지 구현
- Loading branch information
Showing
23 changed files
with
363 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
declare global { | ||
interface Window { | ||
Kakao; | ||
} | ||
} | ||
|
||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,10 @@ | ||
import { UseQueryOptions, useQuery } from '@tanstack/react-query'; | ||
|
||
import { PosePickResponse, PoseTalkResponse } from '.'; | ||
import { PoseDetailResponse, PosePickResponse, PoseTalkResponse } from '.'; | ||
import publicApi from './config/publicApi'; | ||
|
||
export const getPosePick = (peopleCount: number) => | ||
publicApi.get<PosePickResponse>(`/pose/pick/${peopleCount}`); | ||
|
||
export const getPoseDetail = (poseId: number) => publicApi.get(`/pose/${poseId}`); | ||
export const getPoseDetail = (poseId: number) => | ||
publicApi.get<PoseDetailResponse>(`/pose/${poseId}`); | ||
|
||
export const getPoseTalk = () => publicApi.get<PoseTalkResponse>('/pose/talk'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import Image from 'next/image'; | ||
|
||
import IconButton from '@/components/Button/IconButton'; | ||
import { Header } from '@/components/Header'; | ||
|
||
export default function DetailHeader() { | ||
return ( | ||
<Header | ||
leftNode={ | ||
<IconButton size="large"> | ||
<Image src="/icons/close.svg" width={24} height={24} alt="back" /> | ||
</IconButton> | ||
} | ||
rightNode={ | ||
<IconButton size="large"> | ||
<Image src="/icons/bookmark.svg" width={24} height={24} alt="bookmark" /> | ||
</IconButton> | ||
} | ||
className="px-4" | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
'use client'; | ||
|
||
import Image from 'next/image'; | ||
import Link from 'next/link'; | ||
import { usePathname } from 'next/navigation'; | ||
|
||
import LinkShareModal from './LinkShareModal'; | ||
import { usePoseDetailQuery } from '@/apis'; | ||
import BottomFixedDiv from '@/components/BottomFixedDiv'; | ||
import { Button } from '@/components/Button'; | ||
import { useOverlay } from '@/components/Overlay/useOverlay'; | ||
import { BASE_SITE_URL } from '@/constants'; | ||
import useKakaoShare from '@/hooks/useKakaoShare'; | ||
import { copy } from '@/utils/copy'; | ||
|
||
interface DetailSectionProps { | ||
poseId: number; | ||
} | ||
|
||
export default function DetailSection({ poseId }: DetailSectionProps) { | ||
const { data } = usePoseDetailQuery(poseId); | ||
const { shareKakao } = useKakaoShare(); | ||
const { open } = useOverlay(); | ||
const pathname = usePathname(); | ||
|
||
if (!data) return null; | ||
const { imageKey, tagAttributes, sourceUrl } = data.poseInfo; | ||
|
||
const handleShareLink = async () => { | ||
await copy(BASE_SITE_URL + pathname); | ||
|
||
open(({ exit }) => <LinkShareModal onClose={exit} />); | ||
}; | ||
|
||
return ( | ||
<div> | ||
{sourceUrl && ( | ||
<Link | ||
href={'https://' + sourceUrl} | ||
className="text-subtitle-2 flex h-26 justify-center text-tertiary" | ||
> | ||
↗ 이미지 출처 | ||
</Link> | ||
)} | ||
<div className="relative h-520"> | ||
<Image src={imageKey} fill alt="detailImage" /> | ||
</div> | ||
<div className="flex gap-10 px-20 py-12"> | ||
{tagAttributes?.split(',').map((tag, index) => <Tag key={index} name={tag} />)} | ||
</div> | ||
|
||
<BottomFixedDiv className="flex gap-8"> | ||
<Button className="w-160 bg-sub-white" type="button" onClick={handleShareLink}> | ||
링크 공유 | ||
</Button> | ||
<Button | ||
className="grow bg-main-violet text-white" | ||
onClick={() => shareKakao(BASE_SITE_URL + pathname)} | ||
> | ||
카카오 공유 | ||
</Button> | ||
</BottomFixedDiv> | ||
</div> | ||
); | ||
} | ||
interface TagProps { | ||
name: string; | ||
} | ||
|
||
function Tag({ name }: TagProps) { | ||
return ( | ||
<button | ||
type="button" | ||
className="text-subtitle-2 rounded-30 bg-sub-white px-12 py-5 text-secondary" | ||
> | ||
{name} | ||
</button> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Button } from '@/components/Button'; | ||
import { Popup } from '@/components/Modal'; | ||
|
||
interface LinkShareModalProps { | ||
onClose: () => void; | ||
} | ||
export default function LinkShareModal({ onClose }: LinkShareModalProps) { | ||
return ( | ||
<Popup> | ||
<p className="py-32">링크가 복사되었습니다.</p> | ||
<Button className="bg-main-violet text-white" onClick={onClose}> | ||
확인 | ||
</Button> | ||
</Popup> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import DetailHeader from './components/DetailHeader'; | ||
import DetailSection from './components/DetailSection'; | ||
import { getPoseDetail } from '@/apis'; | ||
import { HydrationProvider } from '@/components/Provider/HydrationProvider'; | ||
|
||
export default function DetailPage({ params }: { params: { id: number } }) { | ||
const { id } = params; | ||
|
||
return ( | ||
<div> | ||
<DetailHeader /> | ||
<HydrationProvider queryKey={['poseId', id]} queryFn={() => getPoseDetail(id)}> | ||
<DetailSection poseId={id} /> | ||
</HydrationProvider> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import clsx from 'clsx'; | ||
|
||
import type { StrictPropsWithChildren } from '@/types'; | ||
|
||
interface IconButtonProps { | ||
/** | ||
* 버튼의 크기를 설정합니다. small: 24px, medium: 40px, large: 48px (default: small) | ||
*/ | ||
size?: 'small' | 'medium' | 'large'; | ||
/** | ||
* 버튼의 클릭 이벤트를 설정합니다. | ||
*/ | ||
onClick?: () => void; | ||
} | ||
export default function IconButton({ | ||
onClick, | ||
children, | ||
size = 'small', | ||
}: StrictPropsWithChildren<IconButtonProps>) { | ||
return ( | ||
<button | ||
className={clsx('flex items-center justify-center', { | ||
'h-24 w-24': size === 'small', | ||
'h-40 w-40': size === 'medium', | ||
'h-48 w-48': size === 'large', | ||
})} | ||
onClick={onClick} | ||
> | ||
{children} | ||
</button> | ||
); | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
export { default as Popup } from './Popup'; | ||
export { default as Popup } from './Modal'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Hydrate, QueryClient, dehydrate } from '@tanstack/react-query'; | ||
import { cache } from 'react'; | ||
|
||
import type { StrictPropsWithChildren } from '@/types'; | ||
import type { QueryFunction, QueryKey } from '@tanstack/react-query'; | ||
|
||
type HydrationProviderProps = { | ||
queryKey: QueryKey; | ||
queryFn: QueryFunction; | ||
isInfiniteQuery?: boolean; | ||
}; | ||
|
||
export const HydrationProvider = async ({ | ||
children, | ||
queryKey, | ||
queryFn, | ||
isInfiniteQuery = false, | ||
}: StrictPropsWithChildren<HydrationProviderProps>) => { | ||
const getQueryClient = cache(() => new QueryClient()); | ||
|
||
const queryClient = getQueryClient(); | ||
|
||
if (isInfiniteQuery) await queryClient.prefetchInfiniteQuery(queryKey, queryFn); | ||
else await queryClient.prefetchQuery(queryKey, queryFn); | ||
|
||
const dehydratedState = dehydrate(queryClient); | ||
|
||
return <Hydrate state={dehydratedState}>{children}</Hydrate>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export const BASE_API_URL = process.env.NEXT_PUBLIC_API_URL; | ||
export const BASE_SITE_URL = process.env.NEXT_PUBLIC_SITE_URL; | ||
export const KAKAO_KEY = process.env.NEXT_PUBLIC_KAKAO_KEY; |
Oops, something went wrong.