Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 콜렉션 상세페이지 및 폴더 이름 변경, 삭제 기능 개발 #260

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
696a116
Rename: 콜렉션 상세페이지 Ver2.0 파일 구분을 위해 이름 수정
ParkSohyunee Oct 23, 2024
81e1dde
Feat: 콜렉션 폴더 Link 태그로 변경 및 href 설정
ParkSohyunee Oct 23, 2024
c4e1774
Feat: 콜렉션 리스트조회 API 연동 및 폴더 상세페이지 레이아웃 구현
ParkSohyunee Oct 24, 2024
2d5defa
Design: 콜렉션 상세페이지 퍼블리싱
ParkSohyunee Oct 24, 2024
0c9a5a4
Design: 배경이미지 유무에 따른 스타일 variants 적용
ParkSohyunee Oct 24, 2024
4e93d94
Feat: Header 수정버튼 클릭시 공통 BottomSheet 나오도록 적용
ParkSohyunee Oct 24, 2024
a036988
Refactor: BottomSheet 컴포넌트 Compound Component 패턴으로 리팩토링
ParkSohyunee Oct 24, 2024
ba2e859
Fix: unauthorized 에러일때 로그인 모달 대신 토스트 메세지로 변경
ParkSohyunee Oct 24, 2024
293dd41
Feat: 폴더 이름 수정하기 기능 구현
ParkSohyunee Oct 24, 2024
f34e116
Feat: BottomSheet Button 이름을 children으로 받도록 수정 및 삭제일때 폰트색상 수정
ParkSohyunee Oct 24, 2024
506f30b
Feat: 폴더 삭제하기 기능 구현
ParkSohyunee Oct 24, 2024
c70188f
Feat: 폴더페이지에서 뒤로가기 클릭시 경로 수정
ParkSohyunee Oct 24, 2024
1c90379
Fix: 폴더 콜렉션 리스트 배경이미지 url 값 수정
ParkSohyunee Oct 24, 2024
fcc6738
Design: 콜렉션 페이지 스타일 수정
ParkSohyunee Oct 24, 2024
0abbd6e
Chore: 빌드 에러 수정 (기존 폴더 rename으로 인한 import 경로 오류)
ParkSohyunee Oct 25, 2024
9a135bd
Design: (코드리뷰 반영) 콜렉션 리스트 스타일 수정
ParkSohyunee Oct 25, 2024
50d4ea7
Refactor: 콜렉션 심플 리스트 공통 컴포넌트로 리팩토링
ParkSohyunee Oct 27, 2024
5e23041
Rename: BottomSheet 컴포넌트 Ver3.0 공통컴포넌트로 분리
ParkSohyunee Oct 27, 2024
d8d77fc
Refactor: BottomSheet 컴포넌트에 Content 부분 추가
ParkSohyunee Oct 27, 2024
49abc5e
Refactor: BottomSheet 컴포넌트 버튼 children prop 타입 수정
ParkSohyunee Oct 27, 2024
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
29 changes: 29 additions & 0 deletions src/app/_api/collect/getCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import axiosInstance from '@/lib/axios/axiosInstance';
import { CollectionType } from '@/lib/types/listType';

interface GetCollectionType {
folderId: string;
cursorId?: number;
}

interface ResponseType {
collectionLists: CollectionType[];
cursorId: number;
hasNext: boolean;
}

async function getCollection({ folderId, cursorId }: GetCollectionType) {
const params = new URLSearchParams({
size: '8',
});

if (cursorId) {
params.append('cursorId', cursorId.toString());
}

const response = await axiosInstance.get<ResponseType>(`/folder/${folderId}/collections?${params.toString()}`);

return response.data;
}

export default getCollection;
7 changes: 7 additions & 0 deletions src/app/_api/folder/deleteFolder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import axiosInstance from '@/lib/axios/axiosInstance';

const deleteFolder = async (folderId: string) => {
await axiosInstance.delete(`/folders/${folderId}`);
};

export default deleteFolder;
12 changes: 12 additions & 0 deletions src/app/_api/folder/updateFolder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import axiosInstance from '@/lib/axios/axiosInstance';

interface CollectionFolderProps {
folderId: string;
folderName: string;
}

const updateCollectionFolder = async ({ folderId, folderName }: CollectionFolderProps) => {
await axiosInstance.put(`/folders/${folderId}`, { folderName });
};

export default updateCollectionFolder;
13 changes: 13 additions & 0 deletions src/app/collection/[folderId]/_components/Collections.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';

export const container = style({
padding: '2.4rem 1.6rem',
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gridTemplateRows: 'max-content',
gridGap: 12,
alignContent: 'flex-start',
justifyItems: 'center',
backgroundColor: vars.color.bggray,
});
31 changes: 31 additions & 0 deletions src/app/collection/[folderId]/_components/Collections.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client';

import { useQuery } from '@tanstack/react-query';

import * as styles from './Collections.css';

import ShapeSimpleList from '@/components/ShapeSimpleList/ShapeSimpleList';
import getCollection from '@/app/_api/collect/getCollection';
import { QUERY_KEYS } from '@/lib/constants/queryKeys';

interface CollectionsProps {
folderId: string;
}

// TODO 무한스크롤

export default function Collections({ folderId }: CollectionsProps) {
const { data } = useQuery({
queryKey: [QUERY_KEYS.getCollection],
queryFn: () => getCollection({ folderId }),
});

return (
<ul className={styles.container}>
{data?.collectionLists.map(({ list, id }) => {
const hasImage = !!list.representativeImageUrl;
return <ShapeSimpleList list={list} hasImage={hasImage} key={id} />;
})}
</ul>
);
}
42 changes: 42 additions & 0 deletions src/app/collection/[folderId]/_components/HeaderContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import BottomSheet from '@/components/BottomSheet/BottomSheet';
import Header from '@/components/Header/Header';

import useBooleanOutput from '@/hooks/useBooleanOutput';

interface HeaderContainerProps {
handleSetOnBottomSheet: () => void;
handleSetOnDeleteOption: () => void;
}

export default function HeaderContainer({ handleSetOnBottomSheet, handleSetOnDeleteOption }: HeaderContainerProps) {
const { isOn, handleSetOn, handleSetOff } = useBooleanOutput();

const bottomSheetOptionList = [
{
key: 'editFolder',
title: '폴더 이름 바꾸기', // TODO locale 적용
onClick: handleSetOnBottomSheet,
},
{
key: 'deleteFolder',
title: '폴더 삭제하기',
onClick: handleSetOnDeleteOption,
},
];

const RightButton = () => {
const handleClickOption = () => {
handleSetOn();
};
return <button onClick={handleClickOption}>수정</button>;
};

return (
<>
<Header title="콜렉션" left="back" right={<RightButton />} leftClick={() => history.back()} />
{isOn && <BottomSheet onClose={handleSetOff} optionList={bottomSheetOptionList} isActive />}
</>
);
}
34 changes: 34 additions & 0 deletions src/app/collection/[folderId]/page.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@/styles/theme.css';
import { Subtitle } from '@/styles/font.css';

export const container = style({
height: '100vh',
backgroundColor: vars.color.bggray,
});

// BottomSheet Input
export const contentInput = style({
padding: '2rem 2.4rem',
backgroundColor: '#F5F6FA',
borderRadius: 18,

color: vars.color.black,
fontSize: '1.6rem',
fontWeight: 400,
});

// BottomSheet Description
export const content = style([
Subtitle,
{
paddingTop: 30,
fontWeight: 600,
color: vars.color.black,

display: 'flex',
flexDirection: 'column',
gap: 8,
alignItems: 'center',
},
]);
130 changes: 130 additions & 0 deletions src/app/collection/[folderId]/page.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seoyoung-min 서영님, ver3.0 BottomSheet 컴포넌트를 공통 컴포넌트로 만들어 두었습니다. Contents, Button 등 필요에따라 사용하시면 될 것 같습니다~!

Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
'use client';

import { useRouter } from 'next/navigation';
import { ChangeEvent, useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { isAxiosError } from 'axios';

import HeaderContainer from './_components/HeaderContainer';
import Collections from './_components/Collections';
import BottomSheet from '@/components/BottomSheet/ver3.0/BottomSheet';

import * as styles from './page.css';

import useBooleanOutput from '@/hooks/useBooleanOutput';
import { useLanguage } from '@/store/useLanguage';
import toasting from '@/lib/utils/toasting';
import toastMessage from '@/lib/constants/toastMessage';
import { QUERY_KEYS } from '@/lib/constants/queryKeys';
import updateCollectionFolder from '@/app/_api/folder/updateFolder';
import deleteFolder from '@/app/_api/folder/deleteFolder';

interface ParamType {
params: { folderId: string };
}

// TODO API에 FolderName 필드 추가 요청 => input value에 보여주기 & 헤더 타이틀
export default function CollectionDetailPage({ params }: ParamType) {
const folderId = params.folderId;
const { isOn, handleSetOn, handleSetOff } = useBooleanOutput();
const {
isOn: isDeleteOption,
handleSetOn: handleSetOnDeleteOption,
handleSetOff: handleSetOffDeleteOption,
} = useBooleanOutput();
const queryClient = useQueryClient();
const { language } = useLanguage();
const router = useRouter();

const [value, setValue] = useState('');

// 폴더 수정하기 mutation
const editFolderMutation = useMutation({
mutationFn: updateCollectionFolder,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getFolders] });
setValue('');
handleSetOff();
},
onError: (error) => {
if (isAxiosError(error)) {
const errorData = error.response?.data;
if (errorData.error === 'UNAUTHORIZED') {
toasting({ type: 'error', txt: toastMessage[language].requiredLogin });
return;
}
if (errorData.code.split('_')[0] === 'DUPLICATE') {
toasting({ type: 'error', txt: toastMessage[language].duplicatedFolderName });
return;
}
}
toasting({ type: 'error', txt: toastMessage[language].failedFolder });
},
});

// 폴더 삭제하기 mutation
const deleteFolderMutation = useMutation({
mutationFn: deleteFolder,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getFolders] });
handleSetOffDeleteOption();
router.push('/collection');
},
onError: (error) => {
if (isAxiosError(error)) {
const errorData = error.response?.data;
if (errorData.error === 'UNAUTHORIZED') {
toasting({ type: 'error', txt: toastMessage[language].requiredLogin });
return;
}
}
},
});

const handleChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};

const handleEditFolder = () => {
if (!value.trim()) {
toasting({ type: 'warning', txt: toastMessage[language].emptyFolderName });
return;
}
editFolderMutation.mutate({ folderId, folderName: value });
};

const handleDeleteFolder = () => {
deleteFolderMutation.mutate(folderId);
};

return (
<section className={styles.container}>
<HeaderContainer handleSetOnBottomSheet={handleSetOn} handleSetOnDeleteOption={handleSetOnDeleteOption} />
<Collections folderId={folderId} />
<BottomSheet isOn={isOn}>
<BottomSheet.Title>폴더 이름 바꾸기</BottomSheet.Title>
<input
autoFocus
placeholder="폴더명을 작성해 주세요"
value={value}
onChange={handleChangeInput}
className={styles.contentInput}
/>
<BottomSheet.Button onClose={handleSetOff} onClick={handleEditFolder}>
{['취소', '만들기']}
</BottomSheet.Button>
</BottomSheet>
<BottomSheet isOn={isDeleteOption}>
<BottomSheet.Content
contents={{
text: '정말 삭제하시나요?',
subText: '폴더와 안에 있었던 리스트가 모두 삭제돼요',
}}
/>
<BottomSheet.Button onClose={handleSetOffDeleteOption} isDelete={true} onClick={handleDeleteFolder}>
{['취소', '삭제']}
</BottomSheet.Button>
</BottomSheet>
</section>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { QUERY_KEYS } from '@/lib/constants/queryKeys';
import { useEffect, useMemo } from 'react';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import getCollection from '@/app/_api/collect/__getCollection';
import Top3CardSkeleton from '@/app/collection/[category]/_components/Top3CardSkeleton';
import NoData from '@/app/collection/[category]/_components/NoData';
import Top3CardSkeleton from '@/app/collection/_[category]/_components/Top3CardSkeleton';
import NoData from '@/app/collection/_[category]/_components/NoData';
import { CollectionType } from '@/lib/types/listType';
import Top3Card from '@/app/collection/[category]/_components/Top3Card';
import Top3Card from '@/app/collection/_[category]/_components/Top3Card';
import { categoriesLocale } from '@/app/collection/locale';
import { useLanguage } from '@/store/useLanguage';

Expand Down
Loading