From 3e2db47ce7b0dcfca0de2487b1b81c8cee414124 Mon Sep 17 00:00:00 2001 From: chsua <113416448+chsua@users.noreply.github.com> Date: Fri, 11 Aug 2023 12:04:09 +0900 Subject: [PATCH] =?UTF-8?q?=ED=88=AC=ED=91=9C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=EC=97=90=EC=84=9C=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=EC=9D=B4=20=EC=A6=89?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=81=EC=9A=A9=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=8C=20(#306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: (#275) 게시물 상세 컴포넌트에서 게시글 정보 불러오기 리액트 쿼리 도입 * feat: (#275) 마감완료 여부 색상 동그라미로 보여주기 구현 * feat: (#275) 작성시간/마감시간 날짜형식에서 텍스트 형태로 수정 - 마감되었을 경우 마감완료로 표시 * fix: (#275) 게시물 목록 필터링/솔팅 셀렉터가 글에 가려지는 오류 수정 - z-index를 1로 설정 * feat: (#275) 전역 로그인 정보에 회원id 필드 추가 * feat: (#275) 게스트용 게시글 api 제작 및 연결 - 상세페이지에서 로그인 정보가 false라면 게스트용 게시글 상세정보 api로 fetch * refactor: (#275) 작성된 선택지 목록 컴포넌트에 작성자 여부 정의 방법 수정 * fix: 댓글창에 게스트여부 잘못 전달되는 오류 수정 * feat: 사용하지 않는 유저 포인트 정보 UI 삭제 * feat: (#275) 조기마감/게시글 삭제도 리액트 쿼리로 전환 * refactor: (#275) 가독성을 위한 개행 * feat: (#275) 게시물 삭제 시 유저 정보 캐시 삭제하도록 설정 * 회원 닉네임 수정, 회원 탈퇴 react-query를 이용한 커스텀훅 구현 (#192) * feat: (#187) 유저가 닉네임을 변경하는 커스텀 훅 구현 * feat: (#187) 유저 회원 탈퇴 기능 커스텀 훅 구현 * feat: (#187) 닉네임 변경 부분 낙관적 업데이트 적용 * refactor: (#187) 닉네임 변경에 실패했을 경우 console.error 코드 추가 * refactor: (#187) 회원 탈퇴를 cancel에서 withdrawal로 변경 * refactor: (#187) dev 브런치 머지 후 파일 경로 재설정 * 토스트/스낵바 컴포넌트 제작 (#259) * feat: (#258) 토스트 컴포넌트 제작 * feat: (#258) 토스트 컴포넌트 생성/삭제 훅 생성 * feat: (#258) 토스트 노출 시간 상수화 * feat: (#258) 토스트 fadeIn/Out 애니메이션 추가 * feat: (#258) 토스트 위치 props 추가 - top/bottom 설정 가능 - 맨 위 화면에 노출 - vw/vh를 사용하여 화면의 가로 중앙에 위치 * test: (#258) 토스트 컴포넌트 사이즈별/위치별 테스트 생성 * feat: (#258) 스낵바 컴포넌트 제작 * test: (#258) 스낵바 컴포넌트 사이즈별/위치별 테스트 생성 * refactor: (#258) 토스트 열림상태 변경 코드 리팩토링 * refactor: 스타일 관련 상수 표기법 변경 및 공통 상수 파일 분리 * 게시글 옵션을 ContextApi를 이용해 전역으로 관리 (#288) * feat: (#282) 게시글 옵션을 ContextApi를 이용해 전역으로 관리 상세 게시글을 보다가 뒤로가기를 하여도 원래 보던 옵션 게시글 목록이 나오도록 하기 위해서 * refactor: (#282) Provider를 RouterProvider만 감싸도록 수정 contextApi를 사용하며 setPostOption의 타입을 선언하는 곳에서 Dispatch, SetStateAction를 사용하도록 수정 * 게시글 목록이 비었을 때 사용자에게 해당 조건에 맞는 게시글이 존재하지 않는다는 UI 컴포넌트 구현 (#199) * feat: (#188) 빈 게시글 목록 리스트일 경우 사용자에게 보여주는 컴포넌트 UI 구현 및 적용 * chore: (#188) husky 권한 부여 * refactor: (#188) usePostList에서 데이터가 비었는지 여부를 반환하는 기능 추가 * refactor: (#188) 스타일 속성 개행, MSW 코드 중 검색 키워드 문자열로 수정 * refactor: (#188) 검색했을 때 전체 옵션이 아닌 경우 '현재 조건에는'이라는 접두사를 붙혀줌 --------- Co-authored-by: chsua <113416448+chsua@users.noreply.github.com> * (비회원) 게시글 목록 조회 (#291) * feat: (#285) querydsl 설정 * feat: (#285) 전체, 진행중, 마감완료, 최신순, 인기순 필터에 따른 게시글 목록 동적 쿼리 구현 * feat: (#285) 쿼리 성능 최적화를 위한 default batch fetch size 설정 * feat: (#285) 비회원 게시글 목록 조회 기능 구현 * feat: (#285) 비회원 게시글 목록 조회 API 구현 * refactor: (#285) 코드 컨벤션 수정 * refactor: (#285) 불필요한 출력문 제거 * refactor: (#285) 중복 어노테이션 제거 * refactor: (#285) 테스트 메서드 가독성 향상 * refactor: (#285) API Swagger 문서화 * chore: (#285) 코드 컨벤션 수정 * refactor: (#285) 시간 저장 범위 수정 * feat: (#285) 카테고리 필터링 조회 구현 * fix: (#285) 불필요한 클래스 제거 * 투표글, 댓글, 닉네임 신고 기능 구현 (#262) * feat: (#164) 신고 엔티티, 레포지토리 구현 * feat: (#164) 게시글 신고 기능 구현 * refactor: (#164) 도메인으로 로직 이동, 및 예외 추가, 함수 분리 * test: (#164) Report의 repository, service 테스트 추가 * feat: (#164) 닉네임 신고 기능 구현 * refactor: (#164) 닉네임 getter 추가 * test: (#164) 닉네임 신고 기능에 대한 검증 추가 * chore: (#164) swagger 어노테이션 추가 * test: (#164) 컨트롤러 신고 검증 추가 * refactor: (#164) nested로 신고 기능 묶기 * refactor: (#164) Report 엔티티 클래스에 컬럼 복합 인덱스 설정 * refactor: (#164) 신고에 대한 비즈니스 로직을 어플리케이션 계층으로 이동 * refactor: (#164) enum타입으로 request받기, ReportService 클래스의 메서드 분리, 중복제거 * refactor: (#164) Report 클래스 reason 필드 추가 * refactor: (#164) NumberGenerator, NicknameNumberGenerator 삭제 * refactor: (#164) 신고 횟수에 대한 비즈니스 로직을 어플리케이션 계층으로 이동 * chore: (#164) conflict 해결 완료 * chore: (#164) conflict 해결 완료 * Revert "투표글, 댓글, 닉네임 신고 기능 구현 (#262)" (#315) This reverts commit 3495a235055fc56b23034e8545adfa6d0057ace8. * feat: (#164) 신고 기능 구현 (#316) * 게시글, 댓글, 닉네임 신고 API fetch 함수 구현 HTML5 해결 (#296) * feat: (#251) html5으로 인식되도록 코드 추가 * feat: (#167) 신고하는 api 생성 * feat: (#167) 신고용 모달 생성 및 기존 코멘트모달 리팩토링 - 기존 commentModal을 twoButtonModal로 수정 * refactor: 공통으로 사용되는 메뉴 컴포넌트 이름 수정 및 common으로 이동 - CommentMenu > PostMenu * refactor: 신고모달 생성에 따른 기존 댓글신고, 댓글 작성자 신고 모달 대체 * refactor: postMenu를 처음 commentMenu으로 되돌리기 * feat: 공용으로 사용할 수 있는 메뉴 컴포넌트 제작 * test: 공용으로 사용할 수 있는 메뉴 컴포넌트 테스트 * feat: (#167) 게시물 신고 ui 수정 및 api 연동 - 신고 버튼 > 모바일에서는 버튼을 누르면 (게시글/닉네임) menu 가 등장 > 데스크탑에서는 버튼을 게시물신고와 작성자 닉네임 신고로 분리 - 삭제 버튼 클릭시 확인하는 모달 등장 - 신고 버튼 클릭시 사유를 선택하는 모들 등장 - 다만, 아직 api 오류 발생 * test: (#167) 신고 기능 추가에 따라 컴포넌트가 수정되어 테스트 코드도 수정 * fix: then이 catch보다 늦게 있어 오류 발생해도 then이 실행되는 오류 수정 * fix: (#167) 모달창에서 버튼을 클릭해도 모달이 닫히지 않는 오류 수정 * feat: 게시물 삭제 조건 변경 및 예외처리 시 api 통신하지 않도록 수정 - 삭제조건: 마감되지 않으면 삭제불가 > 20인 이상 투표시 삭제 불가 * test: (#167) 게시물 삭제/신고 msw 생성 및 테스트 * refactor: alert창 오타 수정 * test: 댓글 가지고 오는 api msw 중지 * fix: (#167) 모달창에서 버튼을 클릭해도 모달이 닫히지 않는 오류 수정 * feat: (#167) 댓글 신고/댓글 작성자 닉네임 신고기능 생성 * feat: (#167) 게시글 액션 타입 제한 적용 * test: (#167) 게시글 메뉴 컴포넌트 타입 제한에 따른 테스트 변경 * test: (#167) api 테스트 통과를 위해 댓글 msw 복구 * refactor: DeleteModal과 동일한 CommentDeleteModal 삭제하기 * refactor: (#167) 불필요한 타입 정의 정리 * refactor: (#167) 스타일 컨벤션 지키기 * refactor: (#167) 삭제 모달의 삭제 대상 타입 정의 및 상수화 * fix: (#167) 댓글 신고 시 request 데이터 타입 오지정 수정 * feat: (#308) 유저 정보, 카테고리 목록에서 캐싱, 스테일 타임을 1시간으로 수정 (#309) * 게시글 작성, 게시글 투표 시 유저의 정보가 변하도록 수정, 비회원 유저 정보 요청하지 않도록 수정 (#307) * feat: (#299) 게시글 작성, 게시글 투표 시 유저의 정보가 변하도록 수정 비회원일 때 members/me로 통신하지 않도록 수정 * test: (#299) 비회원일때 유저 정보를 불러오는 테스트 케이스 추가 * refactor: (#299) boolean 값을 상수화하여 코드 가독성 개선 * refactor: (#299) api 함수에서 isLogged를 isLoggedIn으로 변수명 변경 * Member 스키마 수정에 따른 코드 변경 (#330) * refactor: (#323) Member 엔티티 필드 수정으로 인한 컴파일 에러 처리 * refactor: (#323) 테스트 오류 수정 --------- Co-authored-by: jeomxon * 비회원 게시글 목록 조회 추가 기능 구현 (#304) * test: (#292) 비회원 게시글 목록 조회 테스트 케이스 추가 및 비회원 게시글 목록 목킹 데이터 추가 * feat: (#292) 게시글 목록을 불러오는 api 함수에서 회원, 비회원을 구별하여 요청하는 것으로 변경 * test: (#292) 게시글 목록 조회 회원, 비회원에 대한 테스트 케이스 추가 * feat: (#292) 포스트 리스트에 적용 * fix: (#292) 댓글, 투표 MSW 코드에서 .env의 환경 변수를 설정한 것을 제거하고 테스트가 동작하도록 수정 * feat: (#292) 액세스 토큰을 가지고 있을 때 처음 렌더링 시 게스트 게시글 목록에서 회원용 게시글 목록으로 리패치 되도록 구현 * feat: (#292) 카테고리별 게시글일 때 서버에 요청하는 URL 변경 잘못 설정되어 있던 카테고리별 게시글 라우터 변경 내가 투표한 게시글과 내가 작성한 게시글을 번갈아 누를 때 요청하지 않는 문제 수정 * test: (#292) 투표 테스트에서 given을 구체화 * 현재 유저가 어떤 게시글 종류를 보고 있는지 정보를 반환하는 함수 구현 및 적용 (#318) * feat: (#310) 유저에게 어떤 게시글에 대한 종류를 보고 있는지 정보를 반환하는 함수 구현 * feat: (#310) 레이아웃, 포스트 리스트 페이지에 코드 적용 * feat: (#310) 검색어 글자가 10글자가 넘어간다면 ...으로 축약해서 보여주도록 구현 test의 given을 구체화 --------- Co-authored-by: 김영길/KIM YOUNG GIL <80146176+Gilpop8663@users.noreply.github.com> Co-authored-by: 최우창 Co-authored-by: JeongHun Yu --- frontend/src/api/post.ts | 7 +++ .../components/comment/CommentList/index.tsx | 2 +- .../common/Dashboard/UserProfile/index.tsx | 6 +-- .../common/Dashboard/UserProfile/style.ts | 2 +- frontend/src/components/common/Post/index.tsx | 14 ++++-- frontend/src/components/common/Post/style.ts | 14 ++++++ .../src/components/common/Select/style.ts | 2 + frontend/src/hooks/context/auth.tsx | 8 +++- .../src/hooks/query/post/useDeletePost.ts | 21 +++++++++ .../src/hooks/query/post/useEarlyClosePost.ts | 21 +++++++++ .../src/hooks/query/post/usePostDetail.ts | 12 +++-- .../BottomButtonPart.stories.tsx | 2 +- .../PostDetail/BottomButtonPart/index.tsx | 8 ++-- .../InnerHeaderPart.stories.tsx | 2 +- .../post/PostDetail/InnerHeaderPart/index.tsx | 6 +-- frontend/src/pages/post/PostDetail/index.tsx | 46 +++++++++---------- frontend/src/styles/globalStyle.ts | 1 + frontend/src/styles/theme.ts | 1 + frontend/src/types/user.ts | 1 + frontend/src/utils/time.ts | 28 +++++++++++ 20 files changed, 155 insertions(+), 49 deletions(-) create mode 100644 frontend/src/hooks/query/post/useDeletePost.ts create mode 100644 frontend/src/hooks/query/post/useEarlyClosePost.ts diff --git a/frontend/src/api/post.ts b/frontend/src/api/post.ts index 4e2705da7..4c30386d6 100644 --- a/frontend/src/api/post.ts +++ b/frontend/src/api/post.ts @@ -60,6 +60,12 @@ export const getPost = async (postId: number): Promise => { return transformPostResponse(post); }; +export const getPostForGuest = async (postId: number): Promise => { + const post = await getFetch(`${BASE_URL}/posts/${postId}/guest`); + + return transformPostResponse(post); +}; + export const createPost = async (newPost: FormData) => { return await multiPostFetch(`${BASE_URL}/posts`, newPost); }; @@ -68,6 +74,7 @@ export const editPost = async (postId: number, updatedPost: FormData) => { return await multiPutFetch(`${BASE_URL}/posts/${postId}`, updatedPost); }; + export const removePost = async (postId: number) => { return await deleteFetch(`${MOCK_URL}/posts/${postId}`); }; diff --git a/frontend/src/components/comment/CommentList/index.tsx b/frontend/src/components/comment/CommentList/index.tsx index 5a90df2f1..0d42cfb2c 100644 --- a/frontend/src/components/comment/CommentList/index.tsx +++ b/frontend/src/components/comment/CommentList/index.tsx @@ -14,7 +14,7 @@ import * as S from './style'; interface CommentListProps { commentList: Comment[]; - memberId: number; + memberId?: number; isGuest: boolean; postWriterName: string; } diff --git a/frontend/src/components/common/Dashboard/UserProfile/index.tsx b/frontend/src/components/common/Dashboard/UserProfile/index.tsx index f0b41a227..26f01449c 100644 --- a/frontend/src/components/common/Dashboard/UserProfile/index.tsx +++ b/frontend/src/components/common/Dashboard/UserProfile/index.tsx @@ -13,17 +13,13 @@ interface UserProfileProps { } export default function UserProfile({ userInfo }: UserProfileProps) { - const { nickname, userPoint, postCount, voteCount, badge } = userInfo; + const { nickname, postCount, voteCount, badge } = userInfo; return ( {badge && [{badge}]} {nickname} - - 포인트 - {userPoint} - 작성글 {postCount} diff --git a/frontend/src/components/common/Dashboard/UserProfile/style.ts b/frontend/src/components/common/Dashboard/UserProfile/style.ts index 3d42424a4..967f19dfd 100644 --- a/frontend/src/components/common/Dashboard/UserProfile/style.ts +++ b/frontend/src/components/common/Dashboard/UserProfile/style.ts @@ -16,7 +16,7 @@ export const NickName = styled.span` export const UserInfoContainer = styled.div` display: flex; - justify-content: space-between; + justify-content: space-around; `; export const TextCardContainer = styled.div` diff --git a/frontend/src/components/common/Post/index.tsx b/frontend/src/components/common/Post/index.tsx index 62b6351a3..5a2b22ee7 100644 --- a/frontend/src/components/common/Post/index.tsx +++ b/frontend/src/components/common/Post/index.tsx @@ -11,6 +11,8 @@ import WrittenVoteOptionList from '@components/optionList/WrittenVoteOptionList' import { PATH } from '@constants/path'; import { POST } from '@constants/vote'; +import { checkClosedPost, convertTimeToWord } from '@utils/time'; + import * as S from './style'; interface PostProps { @@ -24,6 +26,8 @@ export default function Post({ postInfo, isPreview }: PostProps) { const { mutate: createVote } = useCreateVote({ isPreview, postId }); const { mutate: editVote } = useEditVote({ isPreview, postId }); + const isActive = !checkClosedPost(deadline); + const handleVoteClick = (newOptionId: number) => { if (writer.nickname === loggedInfo.userInfo?.nickname) return; @@ -58,14 +62,17 @@ export default function Post({ postInfo, isPreview }: PostProps) { {category.map(category => category.name).join(' | ')} + {title} {writer.nickname} - {createTime} - {deadline} + {convertTimeToWord(createTime)} + + {isActive ? convertTimeToWord(deadline) : '마감 완료'} + @@ -73,8 +80,7 @@ export default function Post({ postInfo, isPreview }: PostProps) { ` + width: 15px; + height: 15px; + border-radius: 50%; + + position: absolute; + right: 0; + top: 0; + + background-color: ${({ $isActive }) => ($isActive ? 'var(--active-post)' : 'var(--dark-gray)')}; +`; + export const Title = styled.p<{ $isPreview: boolean }>` display: -webkit-box; diff --git a/frontend/src/components/common/Select/style.ts b/frontend/src/components/common/Select/style.ts index f27065999..83c98faa7 100644 --- a/frontend/src/components/common/Select/style.ts +++ b/frontend/src/components/common/Select/style.ts @@ -51,6 +51,8 @@ export const SelectedContainer = styled.button<{ $status: Status }>` export const OptionListParent = styled.div` position: relative; + + z-index: ${theme.zIndex.select}; `; export const OptionListContainer = styled.div` diff --git a/frontend/src/hooks/context/auth.tsx b/frontend/src/hooks/context/auth.tsx index b9f3bfb48..9f1b3f54b 100644 --- a/frontend/src/hooks/context/auth.tsx +++ b/frontend/src/hooks/context/auth.tsx @@ -4,7 +4,7 @@ import { LoggedInfo } from '@type/user'; import { useUserInfo } from '@hooks/query/user/useUserInfo'; -import { getCookieToken } from '@utils/cookie'; +import { getCookieToken, getMemberId } from '@utils/cookie'; interface Auth { loggedInfo: LoggedInfo; @@ -35,7 +35,11 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { useEffect(() => { const accessToken = getCookieToken().accessToken; - if (accessToken) setLoggedInfo(origin => ({ ...origin, accessToken, isLoggedIn: true })); + if (accessToken) { + const decodedPayload = getMemberId(accessToken); + const id = decodedPayload.memberId; + setLoggedInfo(origin => ({ ...origin, accessToken, id, isLoggedIn: true })); + } }, []); return ( diff --git a/frontend/src/hooks/query/post/useDeletePost.ts b/frontend/src/hooks/query/post/useDeletePost.ts new file mode 100644 index 000000000..491167359 --- /dev/null +++ b/frontend/src/hooks/query/post/useDeletePost.ts @@ -0,0 +1,21 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { deletePost } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useDeletePost = (postId: number, isLogged: boolean) => { + const queryClient = useQueryClient(); + + const { mutate, isError, error } = useMutation({ + mutationFn: () => deletePost(postId), + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.USER_INFO, isLogged]); + }, + onError: error => { + window.console.log('게시물 삭제에 실패했습니다.', error); + }, + }); + + return { mutate, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/useEarlyClosePost.ts b/frontend/src/hooks/query/post/useEarlyClosePost.ts new file mode 100644 index 000000000..2d84352ac --- /dev/null +++ b/frontend/src/hooks/query/post/useEarlyClosePost.ts @@ -0,0 +1,21 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { setEarlyClosePost } from '@api/post'; + +import { QUERY_KEY } from '@constants/queryKey'; + +export const useEarlyClosePost = (postId: number) => { + const queryClient = useQueryClient(); + + const { mutate, isError, error } = useMutation({ + mutationFn: () => setEarlyClosePost(postId), + onSuccess: () => { + queryClient.invalidateQueries([QUERY_KEY.POST_DETAIL, postId]); + }, + onError: error => { + window.console.log('조기마감에 실패했습니다.', error); + }, + }); + + return { mutate, isError, error }; +}; diff --git a/frontend/src/hooks/query/post/usePostDetail.ts b/frontend/src/hooks/query/post/usePostDetail.ts index b523cd275..39420effa 100644 --- a/frontend/src/hooks/query/post/usePostDetail.ts +++ b/frontend/src/hooks/query/post/usePostDetail.ts @@ -2,14 +2,16 @@ import { useQuery } from '@tanstack/react-query'; import { PostInfo } from '@type/post'; -import { getPost } from '@api/post'; +import { getPost, getPostForGuest } from '@api/post'; import { QUERY_KEY } from '@constants/queryKey'; -export const usePostDetail = (postId: number) => { - const { data, error, isLoading } = useQuery( +export const usePostDetail = (isGuest: boolean, postId: number) => { + const fetchApi = isGuest ? getPostForGuest : getPost; + + const { data, isError, isLoading, error } = useQuery( [QUERY_KEY.POST_DETAIL, postId], - () => getPost(postId), + () => fetchApi(postId), { onSuccess: data => { return data; @@ -20,5 +22,5 @@ export const usePostDetail = (postId: number) => { } ); - return { data, error, isLoading }; + return { data, isError, isLoading, error }; }; diff --git a/frontend/src/pages/post/PostDetail/BottomButtonPart/BottomButtonPart.stories.tsx b/frontend/src/pages/post/PostDetail/BottomButtonPart/BottomButtonPart.stories.tsx index b41e945fb..9e02a0cf7 100644 --- a/frontend/src/pages/post/PostDetail/BottomButtonPart/BottomButtonPart.stories.tsx +++ b/frontend/src/pages/post/PostDetail/BottomButtonPart/BottomButtonPart.stories.tsx @@ -14,7 +14,7 @@ const handleEvent = { }, controlPost: { setEarlyClosePost: () => {}, - removePost: () => {}, + deletePost: () => {}, reportPost: (reason: string) => {}, reportNickname: (reason: string) => {}, }, diff --git a/frontend/src/pages/post/PostDetail/BottomButtonPart/index.tsx b/frontend/src/pages/post/PostDetail/BottomButtonPart/index.tsx index 2a6f52312..fa2882e82 100644 --- a/frontend/src/pages/post/PostDetail/BottomButtonPart/index.tsx +++ b/frontend/src/pages/post/PostDetail/BottomButtonPart/index.tsx @@ -15,7 +15,7 @@ interface PostDetailPageChildProps { movePage: Record void>; controlPost: { setEarlyClosePost: () => void; - removePost: () => void; + deletePost: () => void; reportPost: (reason: string) => void; reportNickname: (reason: string) => void; }; @@ -28,7 +28,8 @@ export default function BottomButtonPart({ handleEvent: { movePage, controlPost }, }: PostDetailPageChildProps) { const { moveWritePostPage, moveVoteStatisticsPage } = movePage; - const { setEarlyClosePost, removePost, reportPost, reportNickname } = controlPost; + const { setEarlyClosePost, deletePost, reportPost, reportNickname } = controlPost; + const [action, setAction] = useState(null); const handleActionButtonClick = (action: string) => { @@ -58,6 +59,7 @@ export default function BottomButtonPart({ 수 정 + handleActionButtonClick('DELETE')}> 삭 제 @@ -76,7 +78,7 @@ export default function BottomButtonPart({ )} {action === 'POST_REPORT' && ( diff --git a/frontend/src/pages/post/PostDetail/InnerHeaderPart/InnerHeaderPart.stories.tsx b/frontend/src/pages/post/PostDetail/InnerHeaderPart/InnerHeaderPart.stories.tsx index 3d1c8b043..9040e39bf 100644 --- a/frontend/src/pages/post/PostDetail/InnerHeaderPart/InnerHeaderPart.stories.tsx +++ b/frontend/src/pages/post/PostDetail/InnerHeaderPart/InnerHeaderPart.stories.tsx @@ -17,7 +17,7 @@ const handleEvent = { }, controlPost: { setEarlyClosePost: () => {}, - removePost: () => {}, + deletePost: () => {}, reportPost: (reason: string) => {}, reportNickname: (reason: string) => {}, }, diff --git a/frontend/src/pages/post/PostDetail/InnerHeaderPart/index.tsx b/frontend/src/pages/post/PostDetail/InnerHeaderPart/index.tsx index fad4c50dc..192f79c5c 100644 --- a/frontend/src/pages/post/PostDetail/InnerHeaderPart/index.tsx +++ b/frontend/src/pages/post/PostDetail/InnerHeaderPart/index.tsx @@ -22,7 +22,7 @@ interface PostDetailPageChildProps { movePage: Record void>; controlPost: { setEarlyClosePost: () => void; - removePost: () => void; + deletePost: () => void; reportPost: (reason: string) => void; reportNickname: (reason: string) => void; }; @@ -40,7 +40,7 @@ export default function InnerHeaderPart({ handleEvent: { movePage, controlPost }, }: PostDetailPageChildProps) { const { moveWritePostPage, moveVoteStatisticsPage, movePostListPage } = movePage; - const { setEarlyClosePost, removePost, reportPost, reportNickname } = controlPost; + const { setEarlyClosePost, deletePost, reportPost, reportNickname } = controlPost; const { isOpen, toggleComponent, closeComponent } = useToggle(); const [action, setAction] = useState(null); @@ -90,7 +90,7 @@ export default function InnerHeaderPart({ )} {action === 'POST_REPORT' && ( diff --git a/frontend/src/pages/post/PostDetail/index.tsx b/frontend/src/pages/post/PostDetail/index.tsx index e4dd2d7aa..954f7c6a3 100644 --- a/frontend/src/pages/post/PostDetail/index.tsx +++ b/frontend/src/pages/post/PostDetail/index.tsx @@ -1,11 +1,15 @@ +import { useContext } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { ReportRequest } from '@type/report'; +import { AuthContext } from '@hooks/context/auth'; import { useCommentList } from '@hooks/query/comment/useCommentList'; -import { useFetch } from '@hooks/useFetch'; +import { useDeletePost } from '@hooks/query/post/useDeletePost'; +import { useEarlyClosePost } from '@hooks/query/post/useEarlyClosePost'; +import { usePostDetail } from '@hooks/query/post/usePostDetail'; + -import { getPost, removePost, setEarlyClosePost } from '@api/post'; import { reportContent } from '@api/report'; import CommentList from '@components/comment/CommentList'; @@ -13,7 +17,6 @@ import Layout from '@components/common/Layout'; import NarrowTemplateHeader from '@components/common/NarrowTemplateHeader'; import Post from '@components/common/Post'; -import { getCookieToken, getMemberId } from '@utils/cookie'; import { checkClosedPost } from '@utils/time'; import BottomButtonPart from './BottomButtonPart'; @@ -26,13 +29,18 @@ export default function PostDetailPage() { const params = useParams() as { postId: string }; const postId = Number(params.postId); - const token = getCookieToken().accessToken; - const decodedPayload = getMemberId(token); - const memberId = decodedPayload.memberId; - - const { data: postData, errorMessage, isLoading, refetch } = useFetch(() => getPost(postId)); + const { loggedInfo } = useContext(AuthContext); + const memberId = loggedInfo.id; + + const { + data: postData, + isLoading, + isError: isPostError, + error: postError, + } = usePostDetail(!loggedInfo.isLoggedIn, postId); + const { mutate: deletePost } = useDeletePost(postId, loggedInfo.isLoggedIn); + const { mutate: earlyClosePost } = useEarlyClosePost(postId); const { data: commentData, isLoading: isCommentLoading } = useCommentList(postId); - // const { data: userInfo, isLoading: isUserInfoLoading, error } = useUserInfo(isLoggedIn); if (!postData) { return ( @@ -44,7 +52,7 @@ export default function PostDetailPage() { {isLoading && 'loading'} - {errorMessage && errorMessage} + {isPostError && postError instanceof Error && postError.message} ); @@ -72,21 +80,13 @@ export default function PostDetailPage() { }; const controlPost = { - setEarlyClosePost: async () => { - await setEarlyClosePost(postId) - .then(res => { - alert('게시물을 즉시마감했습니다.'); - refetch(); - }) - .catch(error => alert(error.message)); - }, - removePost: async () => { + setEarlyClosePost: earlyClosePost, + deletePost: () => { if (postData.voteInfo.allPeopleCount >= 20) return alert('20인 이상 투표한 게시물은 삭제할 수 없습니다.'); - await removePost(postId) - .then(res => alert('게시물을 삭제했습니다.')) - .catch(error => alert(error.message)); + deletePost(); + //추후 삭제가 되었을 때 nav로 홈으로 이동하도록 하기 }, reportPost: async (reason: string) => { const reportData: ReportRequest = { type: 'POST', id: postId, reason }; @@ -127,7 +127,7 @@ export default function PostDetailPage() { )} diff --git a/frontend/src/styles/globalStyle.ts b/frontend/src/styles/globalStyle.ts index 42c069ad8..646feae1b 100644 --- a/frontend/src/styles/globalStyle.ts +++ b/frontend/src/styles/globalStyle.ts @@ -34,6 +34,7 @@ export const GlobalStyle = createGlobalStyle` --header: #1f1f1f; --graph-color-purple:#853DE1; --graph-color-green:#5AEAA5; + --active-post: #00DFA2; /* Fonts *****************************************/ --text-title: 600 2rem/2.4rem san-serif; diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts index ffc90c2b6..03e1728e7 100644 --- a/frontend/src/styles/theme.ts +++ b/frontend/src/styles/theme.ts @@ -9,6 +9,7 @@ const breakpoint = { lg: '1440px', }; const zIndex = { + select: 1, header: 100, modal: 200, }; diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts index eb1bc0d56..3c6a400f1 100644 --- a/frontend/src/types/user.ts +++ b/frontend/src/types/user.ts @@ -21,5 +21,6 @@ export interface ModifyNicknameRequest { export interface LoggedInfo { accessToken: string; isLoggedIn: boolean; + id?: number; userInfo?: User; } diff --git a/frontend/src/utils/time.ts b/frontend/src/utils/time.ts index b33ba3728..b0681b25d 100644 --- a/frontend/src/utils/time.ts +++ b/frontend/src/utils/time.ts @@ -22,3 +22,31 @@ export const checkClosedPost = (deadline: string) => { const endTimeNumber = convertTimeFromStringToNumber(deadline); return nowTimeNumber >= endTimeNumber; }; + +const time = { + day: 3, + hour: 24, + minute: 60, +}; + +export const convertTimeToWord = (date: string) => { + const targetDate = new Date(date); + const currentDate = new Date(); + + //분 단위로 산출됨 + const timeDifference = Math.floor((targetDate.getTime() - currentDate.getTime()) / 60000); + + if (timeDifference === 0) return '지금'; + + const afterBefore = timeDifference > 0 ? '후 마감' : '전 작성 |'; + + const positiveTimeDifference = Math.abs(timeDifference); + + if (Math.round(positiveTimeDifference / (time.hour * time.minute)) > 0) + return `${Math.round(positiveTimeDifference / (time.hour * time.minute))}일 ${afterBefore}`; + + if (Math.round(positiveTimeDifference / time.minute) > 0) + return `${Math.round(positiveTimeDifference / time.minute)}시간 ${afterBefore}`; + + return `${positiveTimeDifference}분 ${afterBefore}`; +};