Skip to content

Commit

Permalink
Merge pull request #75 from Prography-9th-3team/feat/bookmark-modify
Browse files Browse the repository at this point in the history
Feat/북마크 삭제 & 복원 추가
  • Loading branch information
bbung95 authored Jul 26, 2024
2 parents f7a7e4b + fd32046 commit e43989f
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 33 deletions.
2 changes: 2 additions & 0 deletions src/apis/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const apis = {
bookmark_save: '/api/v1/book-mark',
bookmark_like: '/api/v1/book-mark/like',
bookmark_read: '/api/v1/book-mark/read/count',
bookmark_delete: '/api/v1/book-mark',
bookmark_restore: '/api/v1/book-mark/restore',
},
category: {
category_list: '/api/v1/category/list',
Expand Down
40 changes: 40 additions & 0 deletions src/apis/bookmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,43 @@ export const fetchBookmarkReadCount = (bookMarkId: number) => {

return fetchData.put(url, { bookMarkId });
};

/**
* 북마크 삭제
*/
export const useBookmarkDelete = () => {
const queryClient = useQueryClient();
const url = apis.bookmark.bookmark_delete;

return useMutation<AxiosResponse, AxiosError, Array<number>>({
mutationFn: (data) => fetchData.delete(url, data),
onSuccess: () => {
queryClient.refetchQueries({
queryKey: [apis.bookmark.bookmark_list],
});
queryClient.refetchQueries({
queryKey: [apis.category.category_list],
});
},
});
};

/**
* 북마크 삭제
*/
export const useBookmarkRestore = () => {
const queryClient = useQueryClient();
const url = apis.bookmark.bookmark_restore;

return useMutation<AxiosResponse, AxiosError, Array<number>>({
mutationFn: (data) => fetchData.post(url, data),
onSuccess: () => {
queryClient.refetchQueries({
queryKey: [apis.bookmark.bookmark_list],
});
queryClient.refetchQueries({
queryKey: [apis.category.category_list],
});
},
});
};
2 changes: 1 addition & 1 deletion src/apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const fetchData = {
return res;
},

delete: async <T>(url: string, data: unknown) => {
delete: async <T>(url: string, data?: unknown) => {
const res = await axiosInstance<T>({
method: 'delete',
url: url,
Expand Down
40 changes: 34 additions & 6 deletions src/components/BookmarkCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use client';

import { useBookmarkLike } from '@/apis/bookmark';
import useOnClickOutside from '@/hooks/useOnClickOutside';
import { bookmarkValidateSiteName } from '@/constants/data';
import { isValidUrl } from '@/lib/url';
import { cn } from '@/lib/utils';
import useToastStore from '@/stores/toastStore';
import { MouseEvent, useState } from 'react';
import { MouseEvent, useRef, useState } from 'react';
import Icon from '../common/Icon';
import { Option } from '../common/Option';
export interface IBookmarkCard {
bookMarkId: number;
categoryNames: Array<number>;
Expand All @@ -19,6 +21,8 @@ export interface IBookmarkCard {
imageUUID?: string;
isFavorite: boolean;
onClick: () => void;
onDelete?: () => void;
onModify?: () => void;
isRecommendCard?: boolean;
}

Expand All @@ -34,14 +38,21 @@ const BookmarkCard = ({
imageUUID,
isFavorite,
onClick,
onDelete,
isRecommendCard = false,
}: IBookmarkCard) => {
const { addToast } = useToastStore();

const SEVER_URL = process.env.NEXT_PUBLIC_SERVER_URL;

const optionRef = useRef<HTMLButtonElement>(null);

const { mutateAsync: mutateBookmarkLike } = useBookmarkLike();

const [isLike, setIsLike] = useState(isFavorite);
const [isShowOption, setIsShowOption] = useState<boolean>(false);

useOnClickOutside([optionRef], () => setIsShowOption(false));

const bookmarkTitle = title !== '' ? title : url;
const bookmarkSiteName = siteName !== '' ? siteName : url.split('/')[2];
Expand All @@ -60,10 +71,17 @@ const BookmarkCard = ({
e.stopPropagation();

navigator.clipboard.writeText(url).then(() => {
addToast('링크가 복사되었습니다.', 'success');
addToast({ message: '링크가 복사되었습니다.', type: 'success' });
});
};

// 북마크 옵션 오픈
const handleOpenOption = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();

setIsShowOption((prev) => !prev);
};

// empty 이미지 랜덤
const getRandomNumber = () => {
return Math.random() < 0.5 ? 1 : 2;
Expand Down Expand Up @@ -133,7 +151,7 @@ const BookmarkCard = ({
<div
className={cn([
'hidden items-center gap-12 *:text-icon-minimal group-hover:flex',
isLike && 'flex',
(isLike || isShowOption) && 'flex',
])}
>
<button onClick={handleToggleLike}>
Expand All @@ -146,9 +164,19 @@ const BookmarkCard = ({
<button onClick={handleCopyUrl}>
<Icon name='link_03' className='w-20 h-20' />
</button>
<button onClick={(e) => e.stopPropagation()}>
<Icon name='dotsVertical' className='w-20 h-20' />
</button>
<button className='relative' onClick={handleOpenOption} ref={optionRef}>
<Icon name='dotsVertical' className='w-20 h-20' />
{isShowOption && (
<div className='absolute top-[calc(100%+8px)] w-[165px] flex flex-col gap-4 p-8 bg-surface rounded-lg shadow-layer z-10'>
{/* <Option onClick={onModify}>
<Option.Label>수정</Option.Label>
</Option> */}
<Option onClick={onDelete}>
<Option.Label className='text-critical'>삭제</Option.Label>
</Option>
</div>
)}
</button>
</div>
)}
</div>
Expand Down
36 changes: 32 additions & 4 deletions src/components/BookmarkItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client';

import { useBookmarkLike } from '@/apis/bookmark';
import useOnClickOutside from '@/hooks/useOnClickOutside';
import useToastStore from '@/stores/toastStore';
import { MouseEvent, useState } from 'react';
import { MouseEvent, useRef, useState } from 'react';
import Icon from '../common/Icon';
import { Option } from '../common/Option';
export interface IBookmarkCard {
bookMarkId: number;
categoryNames: Array<number>;
Expand All @@ -15,6 +17,8 @@ export interface IBookmarkCard {
imageUUID?: string;
isFavorite: boolean;
onClick: () => void;
onModify?: () => void;
onDelete?: () => void;
}

/**
Expand All @@ -30,15 +34,21 @@ const BookmarkItem = ({
url,
isFavorite,
onClick,
onDelete,
}: IBookmarkCard) => {
const { addToast } = useToastStore();

const optionRef = useRef<HTMLButtonElement>(null);

const { mutateAsync: mutateBookmarkLike } = useBookmarkLike();
const [isLike, setIsLike] = useState(isFavorite);
const [isShowOption, setIsShowOption] = useState<boolean>(false);

const bookmarkTitle = title !== '' ? title : url;
const bookmarkSiteName = siteName !== '' ? siteName : url.split('/')[2];

useOnClickOutside([optionRef], () => setIsShowOption(false));

// 북마크 좋아요
const handleToggleLike = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
Expand All @@ -52,10 +62,17 @@ const BookmarkItem = ({
e.stopPropagation();

navigator.clipboard.writeText(url).then(() => {
addToast('링크가 복사되었습니다.', 'success');
addToast({ message: '링크가 복사되었습니다.', type: 'success' });
});
};

// 북마크 옵션 오픈
const handleOpenOption = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();

setIsShowOption((prev) => !prev);
};

return (
<div
className='cursor-pointer h-[66px] px-40 grid grid-cols-[1fr,160px,140px,152px] even:bg-surface-sub hover:bg-action-primary-tonal'
Expand Down Expand Up @@ -91,10 +108,21 @@ const BookmarkItem = ({
<Icon name='link_03' className='w-16 h-16' />
</button>
<button
className='w-40 h-40 flex items-center justify-center'
onClick={(e) => e.stopPropagation()}
className='relative w-40 h-40 flex items-center justify-center'
onClick={handleOpenOption}
ref={optionRef}
>
<Icon name='dotsVertical' className='w-20 h-20' />
{isShowOption && (
<div className='absolute top-[calc(100%+8px)] right-0 w-[165px] flex flex-col gap-4 p-8 bg-surface rounded-lg shadow-layer z-10'>
{/* <Option onClick={onModify}>
<Option.Label>수정</Option.Label>
</Option> */}
<Option onClick={onDelete}>
<Option.Label className='text-critical'>삭제</Option.Label>
</Option>
</div>
)}
</button>
</div>
</div>
Expand Down
36 changes: 35 additions & 1 deletion src/components/ContentBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
'use client';

import { fetchBookmarkReadCount, useBookmarkInfinityAPI } from '@/apis/bookmark';
import {
fetchBookmarkReadCount,
useBookmarkDelete,
useBookmarkInfinityAPI,
useBookmarkRestore,
} from '@/apis/bookmark';
import useQueryString from '@/hooks/useQueyString';
import { cn } from '@/lib/utils';
import useModalStore from '@/stores/modalStore';
import useToastStore from '@/stores/toastStore';
import { useEffect } from 'react';
import { useInView } from 'react-intersection-observer';
import BookmarkCard from '../BookmarkCard';
Expand All @@ -18,6 +24,10 @@ const ContentBox = () => {

const { queryParam } = useQueryString();
const { openModal } = useModalStore();
const { addToast } = useToastStore();

const { mutateAsync: mutateBookmarkDelete } = useBookmarkDelete();
const { mutateAsync: mutateBookmarkRestore } = useBookmarkRestore();

// 카테고리
const categoryId =
Expand Down Expand Up @@ -47,6 +57,27 @@ const ContentBox = () => {
window.open(url, '_blank');
};

// 북마크 수정
const handleRestoreBookmark = async (bookMarkId: number) => {
await mutateBookmarkRestore([bookMarkId]);
};

// 북마크 삭제
const handleDeleteBookmark = async (bookMarkId: number) => {
const res = await mutateBookmarkDelete([bookMarkId]);

if (res.data.code === '200') {
addToast({
message: '북마크가 삭제되었어요',
type: 'default',
clickText: '복구하기 ',
onClick: () => {
handleRestoreBookmark(bookMarkId);
},
});
}
};

useEffect(() => {
if (hasNextPage) {
fetchNextPage();
Expand All @@ -65,6 +96,8 @@ const ContentBox = () => {
{...item}
imageUUID={item.userInsertRepresentImage?.uuid}
onClick={() => handleOpenBlank({ url: item.url, bookMarkId: item.bookMarkId })}
onDelete={() => handleDeleteBookmark(item.bookMarkId)}
// onModify={() => handleDeleteBookmark(item.bookMarkId)}
/>
))}
</div>
Expand All @@ -83,6 +116,7 @@ const ContentBox = () => {
{...item}
imageUUID={item.userInsertRepresentImage?.uuid}
onClick={() => handleOpenBlank({ url: item.url, bookMarkId: item.bookMarkId })}
onDelete={() => handleDeleteBookmark(item.bookMarkId)}
/>
))}
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/components/FilterBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,22 @@ const FilterBox = () => {

const handleAddCategory = () => {
if (!category) {
addToast('카테고리를 입력해주세요', 'error');
addToast({ message: '카테고리를 입력해주세요', type: 'error' });

return;
}
if (category === '전체') {
setIsError(true);
addToast('카테고리가 이미 존재해요', 'error');
addToast({ message: '카테고리가 이미 존재해요', type: 'error' });
return;
}

mutateSaveCategory(category).then((res) => {
if (res.data.code === 'C002') {
setIsError(true);
addToast(res.data.message, 'error');
addToast({ message: res.data.message, type: 'error' });
} else {
addToast('카테고리가 추가되었어요', 'success');
addToast({ message: '카테고리가 추가되었어요', type: 'success' });
setIsOpenCategory(false);
setCategory('');
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/TabList/CategoryEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const CategoryEditModal = ({
const handleEditCategory = async () => {
await editCategory({ categoryId, categoryName: editCategoryName }).then((res) => {
if (res.status === 200) {
addToast('카테고리 이름이 수정되었어요', 'default');
addToast({ message: '카테고리 이름이 수정되었어요', type: 'default' });
handleCloseModal();
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/TabList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const TabList = ({ tabs = [] }: ITabList) => {

const handleDeleteCategory = async (categoryId: number) => {
await deleteCategory([categoryId]).then((res) => {
if (res.status === 200) addToast('카테고리가 삭제되었어요', 'default');
if (res.status === 200) addToast({ message: '카테고리가 삭제되었어요', type: 'default' });
});
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Modal/ui/BookmarkModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ const BookmarkModal = () => {
values.categoryIds = selectCategory.map((item) => item.value);

mutateSaveBookmark(values).then(() => {
addToast('북마크가 추가되었어요.', 'success');
addToast({ message: '북마크가 추가되었어요.', type: 'success' });

closeModal(MODAL_NAME.BOOKMARK_MODAL);
});
Expand Down
9 changes: 6 additions & 3 deletions src/components/common/Option/ui/OptionLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { cn } from '@/lib/utils';
import { PropsWithChildren } from 'react';

export interface IOptionLabel extends PropsWithChildren {}
export interface IOptionLabel extends PropsWithChildren {
className?: string;
}

const OptionLabel = ({ children }: PropsWithChildren) => {
return <div className='flex-1 text-left text-text label-md '>{children}</div>;
const OptionLabel = ({ children, className }: IOptionLabel) => {
return <div className={cn(['flex-1 text-left text-text label-md', className])}>{children}</div>;
};

export default OptionLabel;
Loading

0 comments on commit e43989f

Please sign in to comment.