Skip to content

Commit

Permalink
Merge pull request #48 from JECT-Study/feature/artwork-detail
Browse files Browse the repository at this point in the history
[TASK-84, TASK-85] feat, style: 상세 페이지 우측 버튼 그룹 및 관련 모달 컴포넌트 구현
  • Loading branch information
SangWoo9734 authored Jan 26, 2025
2 parents e0d6456 + 573035e commit 72c76f7
Show file tree
Hide file tree
Showing 28 changed files with 826 additions and 9 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@tanstack/react-query-devtools": "^5.64.1",
"@types/lodash": "^4.17.13",
"axios": "^1.7.9",
"clipboard": "^2.0.11",
"embla-carousel": "^8.5.1",
"embla-carousel-autoplay": "^8.5.1",
"embla-carousel-react": "^8.5.1",
Expand Down
34 changes: 34 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions src/apis/artwork/deleteArtworkLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isAxiosError } from 'axios';

import { ARTWORK } from '@/constants/API';
import {
COMMON_ERROR_MESSAGE,
LIKE_ERROR_MESSAGE,
} from '@/constants/errorMessage';
import { ErrorResponseType } from '@/types/errorResponse';

import { authorizedClient } from '..';

const deleteArtworkLike = async (postId: number) => {
try {
const response = await authorizedClient.delete(ARTWORK.artworkLike(postId));

if (response.status === 204) {
return true;
} else {
throw new Error('좋아요 취소 요청 실패');

Check warning on line 19 in src/apis/artwork/deleteArtworkLike.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

Exception used for local control-flow

'throw' of exception caught locally
}
} catch (error) {

Check notice on line 21 in src/apis/artwork/deleteArtworkLike.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

Duplicated code fragment

Duplicated code
if (isAxiosError<ErrorResponseType<null>>(error) && error.response) {
const { code } = error;

if (code) {
console.error(LIKE_ERROR_MESSAGE[code]);
throw new Error(LIKE_ERROR_MESSAGE[code]);
} else {
console.error(COMMON_ERROR_MESSAGE.UNKNOWN_ERROR);
throw new Error(COMMON_ERROR_MESSAGE.UNKNOWN_ERROR);
}
} else {
console.error(COMMON_ERROR_MESSAGE.NETWORK_ERROR);
throw new Error(COMMON_ERROR_MESSAGE.NETWORK_ERROR);
}
}
};

export default deleteArtworkLike;
41 changes: 41 additions & 0 deletions src/apis/artwork/postArtworkLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { isAxiosError } from 'axios';

import { ARTWORK } from '@/constants/API';
import {
COMMON_ERROR_MESSAGE,
LIKE_ERROR_MESSAGE,
} from '@/constants/errorMessage';
import { ErrorResponseType } from '@/types/errorResponse';

import { authorizedClient } from '..';

const postArtworkLike = async (postId: number) => {
try {
const response = await authorizedClient.post<null>(
ARTWORK.artworkLike(postId),
);

if (response.status === 204) {
return true;
} else {
throw new Error('좋아요 요청 실패');

Check warning on line 21 in src/apis/artwork/postArtworkLike.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

Exception used for local control-flow

'throw' of exception caught locally
}
} catch (error) {
if (isAxiosError<ErrorResponseType<null>>(error) && error.response) {
const { code } = error;

if (code) {
console.error(LIKE_ERROR_MESSAGE[code]);
throw new Error(LIKE_ERROR_MESSAGE[code]);
} else {
console.error(COMMON_ERROR_MESSAGE.UNKNOWN_ERROR);
throw new Error(COMMON_ERROR_MESSAGE.UNKNOWN_ERROR);
}
} else {
console.error(COMMON_ERROR_MESSAGE.NETWORK_ERROR);
throw new Error(COMMON_ERROR_MESSAGE.NETWORK_ERROR);
}
}
};

export default postArtworkLike;
16 changes: 16 additions & 0 deletions src/apis/collection/getAllCollectionList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { COLLECTION } from '@/constants/API';
import { CollectionType } from '@/types/collection';

import { authorizedClient } from '..';

const getAllCollectionList = async () => {
try {
const { data } = await authorizedClient.get<{
collections: CollectionType[];
}>(COLLECTION.allCollectionsList);

return data;
} catch (error) {}
};

export default getAllCollectionList;
49 changes: 49 additions & 0 deletions src/apis/collection/postCollectionAddArtwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { isAxiosError } from 'axios';

import { COLLECTION } from '@/constants/API';
import {
COLLECTION_ADD_ARTWORK_ERROR_MESSAGE,
COMMON_ERROR_MESSAGE,
} from '@/constants/errorMessage';
import { ErrorResponseType } from '@/types/errorResponse';

import { authorizedClient } from '..';

interface PostCollectionAddArtworkProps {
collectionId: number;
postId: number;
}

const postCollectionAddArtwork = async ({
collectionId,
postId,
}: PostCollectionAddArtworkProps) => {
try {
const response = await authorizedClient.post<null>(
COLLECTION.collectionAddArtwork(collectionId, postId),
);

if (response.status === 201) {
return true;
} else {
throw new Error('컬랙션 내 작품 추가 요청 실패');

Check warning on line 29 in src/apis/collection/postCollectionAddArtwork.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

Exception used for local control-flow

'throw' of exception caught locally
}
} catch (error) {
if (isAxiosError<ErrorResponseType<null>>(error) && error.response) {
const { code } = error;

if (code) {
console.error(COLLECTION_ADD_ARTWORK_ERROR_MESSAGE[code]);
throw new Error(COLLECTION_ADD_ARTWORK_ERROR_MESSAGE[code]);
} else {
console.error(COMMON_ERROR_MESSAGE.UNKNOWN_ERROR);
throw new Error(COMMON_ERROR_MESSAGE.UNKNOWN_ERROR);
}
} else {
console.error(COMMON_ERROR_MESSAGE.NETWORK_ERROR);
throw new Error(COMMON_ERROR_MESSAGE.NETWORK_ERROR);
}
}
};

export default postCollectionAddArtwork;
22 changes: 22 additions & 0 deletions src/apis/collection/postCreateCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { COLLECTION } from '@/constants/API';

import { authorizedClient } from '..';

export interface PostCreateColleactionProps {
name: string;
isPrivate: boolean;
}

const postCreateCollection = async ({
name,
isPrivate,
}: PostCreateColleactionProps) => {
const response = await authorizedClient.post(COLLECTION.collection, {
name,
status: isPrivate ? 'PRIVATE' : 'PUBLIC',
});

return response.status === 201;
};

export default postCreateCollection;
108 changes: 108 additions & 0 deletions src/components/ArtworkDetailPage/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
'use client';

import { useEffect, useState } from 'react';
import { useStore } from 'zustand';

import Icon from '@/components/Icon/Icon';
import CollectionModal from '@/components/Modal/CollectionModal';
import useToggleLike from '@/hooks/serverStateHooks/useToggleLike';
import modalStore from '@/stores/modalStore';
import { ArtworkPostSocialInfoType } from '@/types';

import ShareModal from '../Modal/ShareModal';

interface ButtonGroupProps {
socialInfo: ArtworkPostSocialInfoType;
}

const ButtonGroup = ({
socialInfo: {
postId,
nickname,
title,
likeCount: initialLikeCount,
isLiked: initialIsLiked,
},
}: ButtonGroupProps) => {
const { openModal } = useStore(modalStore);
const [blockButton, setBlockButton] = useState(false);

const {
mutate: toggleLike,
setLikeStatus,
isLiked,
likeCount,
} = useToggleLike({
isLiked: initialIsLiked,
likeCount: initialLikeCount,
});

const openCollectionModal = () => {
openModal({
modalSize: 'lg',
contents: <CollectionModal />,
});
};

const openShareModal = () => {
openModal({
modalSize: 'md',
contents: <ShareModal nickname={nickname} title={title} />,
});
};

const handleClickLikeToggle = () => {
setBlockButton(true);
toggleLike(postId);
setBlockButton(false);
};

useEffect(() => {
setLikeStatus({
isLiked: initialIsLiked,
likeCount: initialLikeCount,
});
}, [initialLikeCount, initialIsLiked]);

return (
<div className='tablet:absolute top-0 left-full tablet:w-[77px] w-content tablet:h-full tablet:ml-[30px] button-s text-center'>
<div className='sticky flex tablet:flex-col gap-[40px] top-[100px]'>
<div className='flex flex-col items-center gap-[10px]'>
<button
className='flex flex-col justify-center items-center tablet:gap-[3px] tablet:w-full w-[57px] rounded-full bg-gray-800 aspect-square active:bg-[#3E3B43]'
onClick={handleClickLikeToggle}
disabled={blockButton}
>
<Icon
name={isLiked ? 'HeartFilled' : 'Heart'}
size='l'
className={isLiked ? 'text-[#FF4548]' : 'text-white'}
/>
<p>{likeCount}</p>
</button>
<p>좋아요</p>
</div>
<div className='flex flex-col items-center gap-[10px]'>
<button
className='flex flex-col justify-center items-center tablet:gap-[3px] tablet:w-full w-[57px] rounded-full bg-gray-800 aspect-square active:bg-[#3E3B43]'
onClick={openShareModal}
>
<Icon name='AlternateShare' size='l' />
</button>
<p>공유하기</p>
</div>
<div className='flex flex-col items-center gap-[10px]'>
<button
className='flex flex-col justify-center items-center tablet:gap-[3px] tablet:w-full w-[57px] rounded-full bg-gray-800 aspect-square active:bg-[#3E3B43]'
onClick={openCollectionModal}
>
<Icon name='Bookmark' size='l' />
</button>
<p>저장하기</p>
</div>
</div>
</div>
);
};

export default ButtonGroup;
Loading

0 comments on commit 72c76f7

Please sign in to comment.