Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pages/pickpickpick/[id]/components/PickCommentSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function PickCommentSection({

return (
<>
<div className='flex gap-[1.6rem] flex-col'>
<div className='flex gap-[1.6rem] flex-col w-full'>
<CommentUserInfo />
<WritableComment
type='pickpickpick'
Expand Down
115 changes: 115 additions & 0 deletions pages/pickpickpick/[id]/components/VoteButtonV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { motion } from 'framer-motion';

import Image from 'next/image';
import { useRouter } from 'next/router';

import { cn } from '@utils/mergeStyle';

import { useToastVisibleStore } from '@stores/toastVisibleStore';

import PickCheck from '@public/image/pickpickpick/pick-check.svg';
import PickNope from '@public/image/pickpickpick/pick-nope.svg';
import PointUp from '@public/image/pickpickpick/point-up.svg';

import { useMediaQueryContext } from '@/contexts/MediaQueryContext';

import { usePostVote } from '../apiHooks/usePostVote';
import { PickOptionData } from '../types/pickDetailData';

interface VoteButtonProps {
pickOptionData?: PickOptionData;
dataIsVoted?: boolean;
pickOrder: 'first' | 'second';
}

export default function VoteButtonV2({ pickOptionData, dataIsVoted, pickOrder }: VoteButtonProps) {
const { id: optionId, isPicked, percent, voteTotalCount } = pickOptionData ?? {};

const { mutate: postVoteMutate } = usePostVote();

const router = useRouter();
const { id } = router.query;

const { setToastVisible } = useToastVisibleStore();

const { isMobile } = useMediaQueryContext();

const handleVote = () => {
if (!isPicked) {
return postVoteMutate({ pickId: id as string, pickOptionId: optionId });
}

return setToastVisible({
message: '동일한 픽픽픽 선택지에 투표할 수 없습니다.',
type: 'error',
});
};

const renderVoteResult = () => {
if (!dataIsVoted) {
return (
<>
<div className='flex items-center gap-[1rem]'>
<span className='st2 font-bold text-gray200'>?? %</span>
<div className='w-[23.7rem] h-[1.6rem] rounded-[1rem] bg-gray200'></div>
</div>

<span className='st2 font-bold text-white flex gap-[1rem]'>
<Image src={PointUp} alt='위를 가리키는 손가락 아이콘' />
PICK {pickOrder === 'first' ? 'A' : 'B'}
</span>
</>
);
}

const isNotVotedOrPicked = !isPicked;

const percentageColor = isNotVotedOrPicked ? 'text-gray300' : 'text-white';
const voteCountColor = isNotVotedOrPicked ? 'text-gray300' : 'text-primary200';
const percentageBarColor = isNotVotedOrPicked ? 'bg-gray300' : 'bg-primary200';

return (
<>
<div className='flex items-center gap-[1rem]'>
<span className={cn(`st2 font-bold text-gray200 ${percentageColor}`)}>{percent} %</span>
<div
className={cn(`h-[1.6rem] rounded-[1rem] ${percentageBarColor}`)}
style={{ width: `${23.7 * (percent ?? 1) * 0.01}rem` }}
></div>
<span className={cn(`c1 font-bold ${voteCountColor}`)}>{voteTotalCount}표</span>
</div>

{isPicked ? (
<span className='st2 font-bold text-white flex gap-[1rem]'>
<Image src={PickCheck} alt='체크된 아이콘' />
PICK!
</span>
) : (
<span className='st2 font-bold text-gray300 flex gap-[1rem]'>
<Image src={PickNope} alt='엑스 아이콘' />
NOPE
</span>
)}
</>
);
};

const VOTE_BUTTON_STYLE = `rounded-[1.6rem] border border-gray300 flex flex-col justify-center gap-[2rem] p-[2.4rem] w-full
${isMobile ? '' : 'min-w-[16rem] max-h-[28.7rem]'}`;

const votebuttonClass = cn(VOTE_BUTTON_STYLE, {
'bg-primary400 border-primary400': isPicked && dataIsVoted,
'bg-gray500 border-gray400': !isPicked && dataIsVoted,
});

return (
<motion.button
whileHover={isMobile ? '' : { scale: 0.95 }}
whileTap={{ scale: 0.9 }}
onClick={handleVote}
className={votebuttonClass}
>
{renderVoteResult()}
</motion.button>
);
}
110 changes: 110 additions & 0 deletions pages/pickpickpick/[id]/components/VoteCardV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useState } from 'react';

import Image from 'next/image';

import AngleDownPoint from '@public/image/pickpickpick/angle-down-point.svg';
import AngleUpPoint from '@public/image/pickpickpick/angle-up-point.svg';

import { useMediaQueryContext } from '@/contexts/MediaQueryContext';

import { PickOptionData } from '../types/pickDetailData';
import MarkdownViewer from './MarkdownViewer';
import VoteButtonV2 from './VoteButtonV2';

export default function VoteCardV2({
dataIsVoted,
pickDetailOptionData,
pickOrder,
}: {
dataIsVoted?: boolean;
pickDetailOptionData?: PickOptionData;
pickOrder: 'first' | 'second';
}) {
const [isFullContents, setFullContents] = useState(false);
const { isMobile } = useMediaQueryContext();

const handleFullContents = () => {
setFullContents(!isFullContents);
};

return (
<div className={`flex-1 flex flex-col gap-[2.4rem]`}>
<VoteButtonV2
pickOptionData={pickDetailOptionData}
dataIsVoted={dataIsVoted}
pickOrder={pickOrder}
/>
<div
className={`py-[1.6rem] rounded-[1.6rem] outline flex flex-col w-full bg-black
${isMobile ? 'px-[2.4rem]' : 'px-[4rem] '}
${pickDetailOptionData?.isPicked ? 'outline-primary400 outline-[0.2rem]' : 'outline-gray500 outline-[0.1rem]'}
${isFullContents ? '' : 'max-h-[58.8rem]'}
`}
>
<div className='relative overflow-hidden'>
<div
className={`transition-[max-height] duration-300 ease-in-out ${
isFullContents ? 'max-h-none' : `${isMobile ? 'max-h-[32rem]' : ''} `
}`}
>
<p className='py-[2.4rem] pb-[3.2rem] text-st1 leading-[2.8rem] font-semibold'>
{pickDetailOptionData?.title}
</p>

{(pickDetailOptionData?.content ||
pickDetailOptionData?.pickDetailOptionImages?.length !== 0) && (
<div className='border-t-[0.1rem] border-t-gray600 pt-[2.4rem] pb-[4rem] flex flex-col gap-[2.4rem]'>
{pickDetailOptionData?.content && (
<MarkdownViewer pickDetailOptionContents={pickDetailOptionData?.content} />
)}

{pickDetailOptionData?.pickDetailOptionImages?.length !== 0 && (
<>
<p className='p2 font-light text-gray200 py-[2.4rem]'>첨부 이미지</p>
<div className='flex flex-col gap-[2.4rem]'>
{pickDetailOptionData?.pickDetailOptionImages?.map((optionImage) => (
<img
src={optionImage.imageUrl}
alt={`픽픽픽 옵션 이미지-${optionImage.id}`}
key={optionImage.id}
className='rounded-[1.2rem]'
/>
))}
</div>
</>
)}
</div>
)}
</div>

{/* gradient overlay */}
{!isFullContents &&
(pickDetailOptionData?.content ||
pickDetailOptionData?.pickDetailOptionImages?.length !== 0) && (
<div className='absolute bottom-0 left-0 right-0 h-[17rem] bg-gradient-to-t from-black to-transparent z-20 pointer-events-none'></div>
)}
</div>

{(pickDetailOptionData?.content ||
pickDetailOptionData?.pickDetailOptionImages?.length !== 0) && (
<button
className='p2 font-bold text-secondary400 flex items-center gap-[0.8rem] justify-center mt-[4rem] mb-[1.6rem]'
onClick={handleFullContents}
>
{isFullContents ? (
<>
<span>내용 접기</span>
<Image src={AngleUpPoint} alt='위 방향 화살표' />
</>
) : (
<>
<span>내용 전체보기</span>
<Image src={AngleDownPoint} alt='아래 방향 화살표' />
</>
)}
</button>
)}
</div>
</div>
);
}
121 changes: 80 additions & 41 deletions pages/pickpickpick/[id]/index.page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from 'next/link';
import type { NextPage } from 'next';
import Link from 'next/link';
import { useRouter } from 'next/router';

import DevLoadingComponent from '@pages/loading/index.page';
Expand All @@ -17,8 +17,12 @@ import {
PICK_VOTE_MODIFIED_MODAL,
} from '@components/common/modals/modalConfig/pickVote';
import MoreButton from '@components/common/moreButton';
import StatisticsItem from '@components/features/pickpickpick/StatisticsItem';
import type { MetaHeadProps } from '@components/meta/MetaHead';

import GrayComment from '@public/image/comment-dots.svg';
import GrayFire from '@public/image/fire-alt.svg';

import { META } from '@/constants/metaData';
import { ROUTES } from '@/constants/routes';
import { useMediaQueryContext } from '@/contexts/MediaQueryContext';
Expand All @@ -27,7 +31,7 @@ import { useGetSimilarPick } from './apiHooks/useGetSimilarPick';
import { useGetPickDetailData } from './apiHooks/usePickDetailData';
import PickCommentSection from './components/PickCommentSection';
import SimilarPick from './components/SimilarPick';
import VoteCard from './components/VoteCard';
import VoteCardV2 from './components/VoteCardV2';
import usePickDetailHandlers from './handlers/usePickDetailHandlers';

type NextPageWithMeta = NextPage & { meta?: MetaHeadProps };
Expand Down Expand Up @@ -83,55 +87,90 @@ const PickDetailPage: NextPageWithMeta = () => {
return (
<>
<div
className={`flex flex-col gap-[4rem]
className={`flex flex-col gap-[4rem]
${isMobile ? 'px-[1.6rem]' : 'px-[20.4rem] pt-[6.4rem] pb-[12.2rem]'}
`}
>
<div className='border-b-[0.1rem] border-b-gray400 flex justify-between items-baseline pb-[1.6rem] pl-[1rem]'>
<div>
<h3 className='h3 font-bold mb-[0.8rem]'>{pickDetailData?.pickTitle}</h3>

<div>
<span className='p2 text-gray100 font-bold'>
<NicknameWithMaskedEmail
author={pickDetailData?.nickname ?? ''}
maskedEmail={pickDetailData?.userId ?? ''}
textSize='p2'
/>
</span>
<span className='p2 text-gray200 ml-[2rem] mr-[1rem]'>{formatPickDate}</span>
{loginStatus === 'login' && !pickDetailData?.isAuthor && (
<span
className='p2 text-gray200 cursor-pointer'
onClick={() => handleBlameButtonClick()}
>
신고
<div className='flex flex-col items-center'>
<div
className={`w-full border-b-[0.1rem] border-b-gray400 flex justify-between items-start ${isMobile ? 'pb-[1.6rem] mb-[2rem]' : 'min-w-[77.5rem] py-[1.6rem] px-[3.2rem] bg-gray800 rounded-t-[1.2rem]'}`}
>
<div className='w-full'>
<div className={`flex items-baseline gap-[1.2rem] `}>
<h3 className={`font-bold mb-[0.8rem] flex w-full ${isMobile ? 'st2' : 'h3'}`}>
{pickDetailData?.pickTitle}
</h3>
{pickDetailData?.isAuthor && (
<div className='mr-0'>
<MoreButton
moreButtonList={PICK_DETAIL_MORE_BUTTON_TYPE.map((type) => ({
buttonType: type,
moreButtonOnclick: handleVoteMoreButtonClick(type),
}))}
/>
</div>
)}
</div>

<div className={`${isMobile ? 'flex flex-col gap-[0.4rem]' : ''}`}>
<span className='p2 text-gray100 font-bold mr-[2rem] '>
<NicknameWithMaskedEmail
author={pickDetailData?.nickname ?? ''}
maskedEmail={pickDetailData?.userId ?? ''}
textSize='p2'
/>
</span>
<span>
<span className='p2 text-gray200 mr-[1rem]'>{formatPickDate}</span>
{loginStatus === 'login' && !pickDetailData?.isAuthor && (
<span
className='p2 text-gray200 cursor-pointer'
onClick={() => handleBlameButtonClick()}
>
신고
</span>
)}
</span>
</div>
{!isMobile && (
<div className='flex mt-[1rem] gap-[2rem]'>
<StatisticsItem
icon={GrayFire}
alt='투표 이미지'
text='투표'
count={1345}
// count={pickData.voteTotalCount}
textColor='text-gray100'
/>
<StatisticsItem
icon={GrayComment}
alt='댓글 이미지'
text='댓글'
count={123}
// count={pickData.commentTotalCount}
textColor='text-gray100'
/>
</div>
)}
</div>
</div>

{pickDetailData?.isAuthor && (
<div className='flex-shrink-0 ml-[2.1rem]'>
<MoreButton
moreButtonList={PICK_DETAIL_MORE_BUTTON_TYPE.map((type) => ({
buttonType: type,
moreButtonOnclick: handleVoteMoreButtonClick(type),
}))}
/>
</div>
)}
<div
className={`w-full flex ${isMobile ? 'flex-col gap-[4rem]' : 'min-w-[77.5rem] gap-[2.4rem] p-[4rem] bg-gray800 rounded-b-[1.2rem]'}`}
>
<VoteCardV2
pickDetailOptionData={pickDetailData?.pickOptions.firstPickOption}
dataIsVoted={pickDetailData?.isVoted}
pickOrder='first'
/>
<VoteCardV2
pickDetailOptionData={pickDetailData?.pickOptions.secondPickOption}
dataIsVoted={pickDetailData?.isVoted}
pickOrder='second'
/>
</div>
</div>

<VoteCard
pickDetailOptionData={pickDetailData?.pickOptions.firstPickOption}
dataIsVoted={pickDetailData?.isVoted}
/>
<VoteCard
pickDetailOptionData={pickDetailData?.pickOptions.secondPickOption}
dataIsVoted={pickDetailData?.isVoted}
/>

<div className='py-[4.8rem]'>
<h3 className='h3 mb-[2.4rem] font-bold'>나도 고민했는데! 다른 픽픽픽 💘</h3>
<div className={`flex gap-[2rem] ${isMobile && 'flex-col'}`}>
Expand Down
3 changes: 3 additions & 0 deletions public/image/pickpickpick/pick-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading