Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
6a7f51a
Style : 픽픽픽 메인 상단 설명글 UI 수정 (v2)
mandelina Oct 19, 2025
d05f235
Merge branch 'main' into DP-562
mandelina Oct 19, 2025
76df3ae
Style : 픽픽픽 메인 스켈레톤 컴포넌트 교체
mandelina Oct 19, 2025
f6b4089
Fix : 픽픽픽 메인 작성하기 버튼 UI 수정 , 컴포넌트 명 오타 수정
mandelina Oct 19, 2025
64c0360
Feat : 픽픽픽 메인 컴포넌트v2 추가
mandelina Oct 19, 2025
69d8ed9
Image : 테스트 이미지 (삭제필요)
mandelina Oct 19, 2025
8ac5a2c
Fix : 픽픽픽 메인에서 contents는 Parsing하지않고 텍스트로만 보여주도록 처리 (최대 6줄 말줄임)
mandelina Oct 26, 2025
b545cf1
Feat : 픽픽픽 메인 검색 input 생성
mandelina Oct 26, 2025
e795258
Refactor : 작성하기 , 드롭다운 위치변경 및 픽픽픽 메인 전체적인 컴포넌트 분리
mandelina Oct 26, 2025
27e56aa
Fix : 스켈레톤 컴포넌트 이름 변경에 따른 수정 (v1)
mandelina Oct 26, 2025
1f6d53c
Feat : v2 API 변경에 따른 데이터 구조 수정 및 필드 추가
mandelina Nov 2, 2025
6e6883c
Feat : 작성하기 버튼 컴포넌트 분리 , PickActionSection에 통합
mandelina Nov 2, 2025
6e5d54c
Feat : /main 페이지의 픽픽픽 컴포넌트 교체
mandelina Nov 2, 2025
38bc074
feat: VoteButtonV2 구현
minyoungg-kim Nov 2, 2025
f8573bc
feat: VoteCardV2 구현
minyoungg-kim Nov 2, 2025
73f1569
fix: pickDetailPage에 v2 적용
minyoungg-kim Nov 2, 2025
0bcf056
Remove : 사용되지 않는 테스트 이미지 파일 삭제
mandelina Nov 2, 2025
890a1ca
Fix : PickCount 컴포넌트의 불필요한 주석 제거
mandelina Nov 2, 2025
c93800b
fix: border -> outline 으로 수정
minyoungg-kim Nov 5, 2025
7c1002f
fix: VoteCardV2 layout 수정
minyoungg-kim Nov 5, 2025
13446c5
Feat : 픽픽픽 검색기능 api연동 , 데이터 뿌려주기 (스위칭)
mandelina Nov 9, 2025
9172e86
Feat : 검색 모드에 따라 드롭다운 표시 제어
mandelina Nov 9, 2025
e85ffbc
Fix : getPickSearchData 함수의 pageParam 처리 개선 및 초기 페이지 매개변수 수정
mandelina Nov 9, 2025
99ba818
fix: [wip] 픽픽픽 상세 페이지 ui 수정
minyoungg-kim Nov 12, 2025
b1cee5a
Fix : 여러 해상도에서 UI 깨지는 부분 보완 (고정 width값 제거 , 깨지지 않도록 min-w적용 )
mandelina Nov 16, 2025
f23c1a2
Merge pull request #301 from dreamyPatisiel/DP-564
mandelina Nov 16, 2025
1553119
Fix : 픽픽픽메인 box 모바일일때 패딩값 수정
mandelina Nov 16, 2025
d6776a4
Feat : 픽픽픽 메인 문구 애니메이션 개발
mandelina Nov 16, 2025
8d5da48
Feat : 픽픽픽 검색어 없을때 SearchNotFound 컴포넌트 처리 추가
mandelina Nov 16, 2025
ef19c1e
Feat : 픽픽픽 헤더를 눌렀을때 초기화 되도록 처리
mandelina Nov 16, 2025
7af2132
Merge branch 'DP-568', remote-tracking branch 'origin' into DP-562
mandelina Nov 16, 2025
8d1dcab
Fix : pending상태일때도 픽픽픽메인에서 문구 , input , 버튼이 disabled 보이도록 수정
mandelina Nov 16, 2025
bf4a041
Refactor : 애니메이션 crash 나는 부분 보완코드 추가 (마운트 되기전 controls.set() 호출을 하지 …
mandelina Nov 16, 2025
414d11e
fix: 픽픽픽 상세 api v2 적용
minyoungg-kim Nov 16, 2025
024a019
refactor : 스크롤바 등장/제거에 따른 가로 시프트 방지 기능 추가
mandelina Nov 16, 2025
ad48679
fix: dropdown border 추가
minyoungg-kim Nov 16, 2025
2ce5b52
fix: 북마크 dropdown line 추가
minyoungg-kim Nov 16, 2025
99c2d73
Merge pull request #302 from dreamyPatisiel/DP-562
minyoung22222 Nov 16, 2025
9911e7a
Merge pull request #303 from dreamyPatisiel/feature/559
minyoung22222 Nov 16, 2025
a668431
Fix: 선택지 제목의 줄 수를 5에서 3으로 변경
mandelina Nov 16, 2025
06169e5
fix: 픽픽픽 상세 제목 패딩 수정
minyoungg-kim Nov 16, 2025
b12754a
Fix : 픽픽픽 상세 페이지 - 100%인경우 UI 깨짐 현상 제거
mandelina Nov 16, 2025
b4b9c0f
Merge remote-tracking branch 'origin/feature559/qa' into feature559/qa
mandelina Nov 16, 2025
7010c59
Fix : 모바일 - 픽픽픽 문구 애니메이션 UI 깨지는 부분 수정
mandelina Nov 16, 2025
41abf1b
Fix: 픽픽픽 이미지 radius값 조정
mandelina Nov 16, 2025
258bd21
Fix: 나도고민했는데! 문구 모바일일때 20px로 수정
mandelina Nov 16, 2025
ffcdd97
Fix : 픽픽픽 메인 타이틀 마진값 잡힌부분 제거
mandelina Nov 16, 2025
bb2a420
fix: 댓글 UI 여백 (모바일) 수정
minyoungg-kim Nov 16, 2025
fe54e2d
fix: 픽픽픽 댓글 작성 시 댓글 상세 캐시 업데이트
minyoungg-kim Nov 16, 2025
6821160
Fix: 선택지 패딩 조정 및 미디어 쿼리 추가
mandelina Nov 16, 2025
d03f079
fix: 픽픽픽 메인 이미지 웹-16 모바일 10으로 radius값 수정
mandelina Nov 16, 2025
e2ba64e
Merge pull request #304 from dreamyPatisiel/feature559/qa
minyoung22222 Nov 16, 2025
d7ab368
Fix : 모바일 바텀버튼 마진값 시안과 동일하게 수정
mandelina Nov 19, 2025
1bfa1f6
fix: 메인페이지 기술블로그 제목 아래 패딩값 추가 (픽픽픽과 통일)
mandelina Nov 19, 2025
3316b09
Merge pull request #305 from dreamyPatisiel/feature559/qa
mandelina Nov 19, 2025
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 components/common/bottomContents/BottomContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function BottomContainer({
return (
<div className='fixed z-[70] inset-0'>
<div className='absolute inset-0 bg-black bg-opacity-50 ' onClick={() => onClose()}>
<div className='fixed bg-gray700 left-0 right-0 w-full bottom-0 rounded-t-[2.4rem] px-[2rem] py-[4rem] flex flex-col gap-[1.2rem]'>
<div className='fixed bg-gray700 left-0 right-0 w-full bottom-0 rounded-t-[2.4rem] py-[2.8rem] flex flex-col gap-[1.2rem]'>
{children}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/common/dropdowns/mobileDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default function MobileDropdown({

{showBottom ? (
<BottomContainer onClose={() => setShowBottom(false)}>
<b className='st2 font-bold px-[2.2rem] pt-[2.8rem] pb-[1.6rem]'>정렬</b>
<b className='st2 font-bold px-[2.2rem] pb-[1.6rem]'>정렬</b>
<ul className='flex flex-col gap-[0.4rem]'>
{dropdownOptions.map((option, index) => (
<li
Expand Down
103 changes: 90 additions & 13 deletions components/common/skeleton/pickSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { MobilePickInfo, PickInfo } from '@pages/pickpickpick/components/PickInfo';
import { MobilePickInfoV1, PickInfoV1 } from '@pages/pickpickpick/components/PickInfo';

export const PickSkeleton = () => {
import AngleRightIcon from '@components/svgs/AngleRightIcon';

// ------------------------------픽픽픽 메인 스켈레톤 v1------------------------------
export const PickSkeletonV1 = () => {
return (
<div className='px-[2.4rem] py-[3.2rem] flex flex-col gap-[3.2rem] rounded-[1.6rem] border-gray600 border-solid border'>
<div className='h-[3.7rem] w-[100%] rounded-[1.6rem] bg-gray600 relative overflow-hidden skeleton-item' />
Expand All @@ -13,56 +16,130 @@ export const PickSkeleton = () => {
);
};

interface PickSkeletonListProps {
interface PickSkeletonListV1Props {
rows: number;
itemsInRows: number;
hasInfo?: boolean;
}

export const PickSkeletonList = ({ rows, itemsInRows, hasInfo }: PickSkeletonListProps) => {
export const PickSkeletonListV1 = ({ rows, itemsInRows, hasInfo }: PickSkeletonListV1Props) => {
return (
<div className='grid grid-cols-3 gap-8'>
{hasInfo ? (
<>
<PickInfo />
<PickInfoV1 />
{Array.from({ length: rows * itemsInRows - 1 }, (_, index) => (
<PickSkeleton key={index} />
<PickSkeletonV1 key={index} />
))}
</>
) : (
<>
{Array.from({ length: rows * itemsInRows }, (_, index) => (
<PickSkeleton key={index} />
<PickSkeletonV1 key={index} />
))}
</>
)}
</div>
);
};

export const MobilePickSkeletonList = ({ rows, hasInfo }: { rows: number; hasInfo?: boolean }) => {
export const MobilePickSkeletonListV1 = ({
rows,
hasInfo,
}: {
rows: number;
hasInfo?: boolean;
}) => {
const arr = Array.from({ length: rows });

return (
<div className='grid grid-cols-1 gap-8'>
{hasInfo && <MobilePickInfo />}
{hasInfo && <MobilePickInfoV1 />}

{arr.map((_, index) => (
<PickSkeletonV1 key={index} />
))}
</div>
);
};

export const MyPickSkeletonListV1 = ({ rows, itemsInRows }: PickSkeletonListV1Props) => {
return (
<div className='grid grid-cols-2 gap-8'>
{Array.from({ length: rows * itemsInRows }, (_, index) => (
<PickSkeletonV1 key={index} />
))}
</div>
);
};
// -------------------------------------------------------------------------------

// ------------------------------픽픽픽 메인 스켈레톤 v2------------------------------

export const PickSkeletonV2 = () => {
return (
<div className='pt-[3.2rem] pb-[2.4rem] px-[3.2rem] flex flex-col gap-[3.2rem] rounded-[1.6rem] bg-gray600'>
<div className='flex flex-col gap-[2.4rem]'>
<div className='flex flex-row items-start justify-between gap-[1.6rem]'>
<div className='bg-black h-[4.3rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
<AngleRightIcon color='var(--gray300)' />
</div>
<div className='bg-black h-[1rem] w-[40%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
</div>

<div className='flex flex-row gap-[1.6rem]'>
<div className='bg-black flex flex-col gap-[1rem] p-[2.4rem] w-[50%] rounded-[1.6rem] relative overflow-hidden border border-gray500'>
<div className='bg-gray500 h-[1rem] w-[50%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
<div className='bg-gray500 h-[5rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
<div className='bg-gray500 h-[12rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
</div>
<div className='bg-black flex flex-col gap-[1rem] p-[2.4rem] w-[50%] rounded-[1.6rem] relative overflow-hidden border border-gray500'>
<div className='bg-gray500 h-[1rem] w-[50%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
<div className='bg-gray500 h-[5rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
<div className='bg-gray500 h-[12rem] w-[100%] rounded-[1.6rem] relative overflow-hidden skeleton-item' />
</div>
</div>
</div>
);
};

interface PickSkeletonListV2Props {
rows: number;
itemsInRows: number;
}

export const PickSkeletonListV2 = ({ rows, itemsInRows }: PickSkeletonListV2Props) => {
return (
<div className='grid grid-cols-2 gap-8'>
{Array.from({ length: rows * itemsInRows }, (_, index) => (
<PickSkeletonV2 key={index} />
))}
</div>
);
};

export const MobilePickSkeletonListV2 = ({ rows }: { rows: number }) => {
const arr = Array.from({ length: rows });

return (
<div className='grid grid-cols-1 gap-8'>
{arr.map((_, index) => (
<PickSkeleton key={index} />
<PickSkeletonV2 key={index} />
))}
</div>
);
};

export const MyPickSkeletonList = ({ rows, itemsInRows }: PickSkeletonListProps) => {
export const MyPickSkeletonListV2 = ({ rows, itemsInRows }: PickSkeletonListV2Props) => {
return (
<div className='grid grid-cols-2 gap-8'>
{Array.from({ length: rows * itemsInRows }, (_, index) => (
<PickSkeleton key={index} />
<PickSkeletonV2 key={index} />
))}
</div>
);
};
// -------------------------------------------------------------------------------

/** 메인페이지 픽픽픽 스켈레톤 */
export const MainPickSkeleton = () => {
Expand All @@ -86,7 +163,7 @@ export const MainPickSkeletonList = ({ itemsInRows }: MainPickSkeletonListProps)
return (
<>
{Array.from({ length: itemsInRows }, (_, index) => (
<MainPickSkeleton key={index} />
<PickSkeletonV2 key={index} />
))}
</>
);
Expand Down
10 changes: 6 additions & 4 deletions components/common/title/ArrowWithTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const ArrowWithTitleVariants = cva(ARROW_TITLE_CLASSES, {
mainTitle: ['st2', 'text-gray200'],
similarPick: ['st2', 'text-white'],
defaultPick: ['p1', 'text-gray100'],
defaultPickV2: ['st2', 'text-white'],
},
},
});
Expand All @@ -26,8 +27,8 @@ interface ArrowWithTitleProps extends VariantProps<typeof ArrowWithTitleVariants
iconText?: string;
routeURL?: string;
className?: string;
iconSize?: string;
ArrowClassName?: string;
iconSize?: { width: number; height: number };
}

const ArrowWithTitle: FC<ArrowWithTitleProps> = ({
Expand All @@ -37,9 +38,10 @@ const ArrowWithTitle: FC<ArrowWithTitleProps> = ({
routeURL,
className,
ArrowClassName,
iconSize = { width: 7, height: 14 },
}) => {
return (
<div className='grid grid-flow-col items-baseline gap-6 justify-between'>
<div className='grid grid-flow-col items-baseline gap-6 justify-between pb-[2.45rem]'>
<p className={cn(className, ArrowWithTitleVariants({ variant }))}>{title}</p>

<div className='flex items-center'>
Expand All @@ -51,8 +53,8 @@ const ArrowWithTitle: FC<ArrowWithTitleProps> = ({
<Image
src={AngleRight}
alt={'오른쪽 화살표'}
width={7}
height={14}
width={iconSize.width}
height={iconSize.height}
className={ArrowClassName}
/>
</div>
Expand Down
6 changes: 3 additions & 3 deletions components/features/main/dynamicPickComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import Link from 'next/link';

import { useInfinitePickData } from '@pages/pickpickpick/api/useInfinitePickData';
import PickContainer from '@pages/pickpickpick/components/PickContainer';
import PickContainerV2 from '@pages/pickpickpick/components/PickContainerV2';
import {
PICK_VIEW_SIZE,
MOBILE_MAIN_PICK_VIEW_SIZE,
Expand All @@ -26,7 +26,7 @@ export default function DynamicPickComponent() {
const getStatusComponent = () => {
switch (status) {
case 'pending':
return <MainPickSkeletonList itemsInRows={2} />;
return <MainPickSkeletonList itemsInRows={1} />;

default:
return (
Expand All @@ -38,7 +38,7 @@ export default function DynamicPickComponent() {
<div key={index}>
{group?.data.content.map((data: PickDataProps) => (
<Link href={`${ROUTES.PICKPICKPICK.MAIN}/${data.id}`} key={data.id}>
<PickContainer pickData={data} type='main' />
<PickContainerV2 pickData={data} />
</Link>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/features/main/dynamicTechBlogComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function DynamicTechBlogComponent({

const { techBlogData, isFetchingNextPage, hasNextPage, status, onIntersect } = data;

const SCROLL_CLASS = isMobile ? '' : 'relative overflow-y-scroll scrollbar-hide max-h-[50rem]';
const SCROLL_CLASS = isMobile ? '' : 'relative overflow-y-scroll scrollbar-hide max-h-[47rem]';

useObserver({
target: bottomDiv,
Expand Down
2 changes: 1 addition & 1 deletion components/features/techblog/BookmarkComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function BookmarkComponent() {
<MobileDropdown type='bookmark' />
</div>
) : (
<Dropdown type='bookmark' disable={!hasData} />
<Dropdown type='bookmark' disable={!hasData} line />
)}
</div>
);
Expand Down
55 changes: 55 additions & 0 deletions hooks/useVerticalStepLoop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useAnimationControls } from 'framer-motion';

import { useEffect, useRef, useState } from 'react';

type Options = {
itemCount: number;
dwellMs: number;
slideMs: number;
reduceMotion: boolean | null;
};

export function useVerticalStepLoop({ itemCount, dwellMs, slideMs, reduceMotion }: Options) {
const controls = useAnimationControls();
const firstItemRef = useRef<HTMLParagraphElement | null>(null);
const [rowHeight, setRowHeight] = useState(0);

useEffect(() => {
if (firstItemRef.current) setRowHeight(firstItemRef.current.offsetHeight);
}, []);

useEffect(() => {
if (reduceMotion === true || rowHeight === 0) return;

let i = 0;
let mounted = true;
// 한 칸씩 위로 이동 후, 마지막(복제된 첫 항목)까지 도달하면 즉시 y=0으로 스냅하여
// 시각적 깜빡임 없이 처음 상태로 되돌립니다. (복제된 첫 항목과 실제 첫 항목은 동일 내용)
const tick = async () => {
try {
i += 1;
await controls.start({
y: -(i * rowHeight),
transition: { duration: slideMs / 1000, ease: 'easeInOut' },
});
if (!mounted) return;
if (i === itemCount) {
// set()은 마운트 이후에만 안전합니다. 언마운트/미바인딩 크래시 방지를 위해
// start() + duration: 0으로 즉시 스냅 처리합니다.
await controls.start({ y: 0, transition: { duration: 0 } });
i = 0;
}
} catch (e) {
// 언마운트 타이밍 등으로 발생 가능한 경합 에러 무시
}
};

const id = setInterval(tick, dwellMs + slideMs);
return () => {
mounted = false;
clearInterval(id);
};
}, [controls, reduceMotion, rowHeight, itemCount, dwellMs, slideMs]);

return { controls, firstItemRef } as const;
}
3 changes: 1 addition & 2 deletions pages/main/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export default function Index() {
variant='mainTitle'
iconText='바로가기'
routeURL={PICK_PATH}
className='pb-[2.45rem]'
/>
<QueryErrorBoundary
fallbackRender={({ handleRetryClick }) => (
Expand Down Expand Up @@ -98,4 +97,4 @@ export function getStaticProps() {
meta: META.MAIN,
},
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { PickDataProps } from '@pages/pickpickpick/types/pick';
import { useObserver } from '@hooks/useObserver';

import {
MobilePickSkeletonList,
MyPickSkeletonList,
MobilePickSkeletonListV1,
MyPickSkeletonListV1,
} from '@components/common/skeleton/pickSkeleton';

import { ROUTES } from '@/constants/routes';
Expand Down Expand Up @@ -38,9 +38,9 @@ export default function MyPickStatusComponent() {
return (
<>
{isMobile ? (
<MobilePickSkeletonList rows={3} />
<MobilePickSkeletonListV1 rows={3} />
) : (
<MyPickSkeletonList rows={3} itemsInRows={2} />
<MyPickSkeletonListV1 rows={3} itemsInRows={2} />
)}
</>
);
Expand Down Expand Up @@ -77,9 +77,9 @@ export default function MyPickStatusComponent() {
{isFetchingNextPage && hasNextPage && (
<div className='mt-[2rem]'>
{isMobile ? (
<MobilePickSkeletonList rows={3} />
<MobilePickSkeletonListV1 rows={3} />
) : (
<MyPickSkeletonList rows={3} itemsInRows={2} />
<MyPickSkeletonListV1 rows={3} itemsInRows={2} />
)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const postPickComment = async ({ pickId, contents, isPickVotePublic }: PostPickC
return res.data;
};

export const usePostPickComment = () => {
export const usePostPickComment = ({ pickId }: { pickId: string }) => {
const queryClient = useQueryClient();
const { setToastVisible } = useToastVisibleStore();

Expand All @@ -33,6 +33,7 @@ export const usePostPickComment = () => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ['pickCommentData'] });
await queryClient.invalidateQueries({ queryKey: ['getBestComments'] });
await queryClient.invalidateQueries({ queryKey: ['getDetailPickData', pickId] });
setToastVisible({ message: '댓글을 성공적으로 작성했어요!', type: 'success' });
},
onError: (error: ErrorRespone) => {
Expand Down
2 changes: 1 addition & 1 deletion pages/pickpickpick/[id]/apiHooks/usePickDetailData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UseQueryResult, useQuery } from '@tanstack/react-query';
import { PickDetailData } from '../types/pickDetailData';

export const getPickDetailData = async (pickId: string) => {
const res = await axios.get(`/devdevdev/api/v1/picks/${pickId}`);
const res = await axios.get(`/devdevdev/api/v2/picks/${pickId}`);
return res;
};

Expand Down
Loading