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

[TASK-84, TASK-85] feat, style: 상세 페이지 우측 버튼 그룹 및 관련 모달 컴포넌트 구현 #48

Merged
merged 16 commits into from
Jan 26, 2025
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
"@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",
"embla-carousel-wheel-gestures": "^8.0.1",
"framer-motion": "^11.14.4",
"lodash": "^4.17.21",
"next": "15.0.4",

Check notice on line 31 in package.json

View workflow job for this annotation

GitHub Actions / Qodana for JS

Vulnerable declared dependency

Dependency npm:next:15.0.4 is vulnerable , safe version 15.1.2 * [GHSA-7m27-7ghc-44w9](https://osv.dev/vulnerability/GHSA-7m27-7ghc-44w9) 5.3 Next.js Allows a Denial of Service (DoS) with Server Actions Results powered by [Mend.io](https://www.mend.io/?utm_source=JetBrains)
"next-themes": "^0.4.4",
"react": "19.0.0",
"react-dom": "19.0.0",
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';

Check failure on line 1 in src/apis/artwork/deleteArtworkLike.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package

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

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

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';

Check failure on line 1 in src/apis/artwork/postArtworkLike.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package

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

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';

Check failure on line 1 in src/apis/collection/getAllCollectionList.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
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';

Check failure on line 1 in src/apis/collection/postCollectionAddArtwork.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package

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

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';

Check failure on line 1 in src/apis/collection/postCreateCollection.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package

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';

Check failure on line 1 in src/components/ArtworkDetailPage/ButtonGroup.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package

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
Loading