From 3e71a5b040673cb52126713aca888aecbab66a4e Mon Sep 17 00:00:00 2001 From: cjeongmin Date: Tue, 14 May 2024 20:45:16 +0900 Subject: [PATCH] feat: enhancement shared API (#75) --- src/app/pages/shared-post-page.tsx | 178 ++++++----- src/app/pages/shared-posts-page.tsx | 16 +- src/app/pages/writing-post-page.tsx | 200 +++++++----- src/app/shared/dormitory/[postId]/page.tsx | 9 + src/app/shared/{ => room}/[postId]/page.tsx | 2 +- .../writing/dormitory/[postId]/page.tsx | 9 + src/app/shared/writing/dormitory/page.tsx | 5 + src/app/shared/writing/room/[postId]/page.tsx | 9 + src/app/shared/writing/{ => room}/page.tsx | 2 +- src/components/RangeSlider.tsx | 8 +- src/components/chat/ChattingRoom.tsx | 2 +- src/components/shared-post-page/ImageGrid.tsx | 2 +- .../shared-posts/SharedPostsMenu.tsx | 16 +- .../shared-posts/filter/RoomTypeFilter.tsx | 19 +- src/entities/shared-post/shared-post.type.ts | 24 +- .../shared-posts-filter.atom.ts | 4 +- .../shared-posts-filter.type.ts | 2 +- src/features/shared/shared.api.ts | 8 +- src/features/shared/shared.atom.ts | 19 +- src/features/shared/shared.hook.ts | 301 +++++++++++------- 20 files changed, 512 insertions(+), 323 deletions(-) create mode 100644 src/app/shared/dormitory/[postId]/page.tsx rename src/app/shared/{ => room}/[postId]/page.tsx (69%) create mode 100644 src/app/shared/writing/dormitory/[postId]/page.tsx create mode 100644 src/app/shared/writing/dormitory/page.tsx create mode 100644 src/app/shared/writing/room/[postId]/page.tsx rename src/app/shared/writing/{ => room}/page.tsx (64%) diff --git a/src/app/pages/shared-post-page.tsx b/src/app/pages/shared-post-page.tsx index 31384465db..15eff7f737 100644 --- a/src/app/pages/shared-post-page.tsx +++ b/src/app/pages/shared-post-page.tsx @@ -1,7 +1,7 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { Bookmark, CircularProfileImage } from '@/components'; @@ -16,9 +16,9 @@ import { } from '@/features/profile'; import { useDeleteSharedPost, + useDormitorySharedPost, useScrapSharedPost, useSharedPost, - useSharedPostProps, } from '@/features/shared'; import { useToast } from '@/features/toast'; import { getAge } from '@/shared'; @@ -432,7 +432,13 @@ const styles = { `, }; -export function SharedPostPage({ postId }: { postId: number }) { +export function SharedPostPage({ + postId, + type, +}: { + postId: number; + type: 'hasRoom' | 'dormitory'; +}) { const auth = useAuthValue(); const [, setMap] = useState(null); @@ -440,7 +446,6 @@ export function SharedPostPage({ postId }: { postId: number }) { const router = useRouter(); const { createToast } = useToast(); - const { setStateWithPost } = useSharedPostProps(); const { mutate: deleteSharedPost } = useDeleteSharedPost(); @@ -452,11 +457,17 @@ export function SharedPostPage({ postId }: { postId: number }) { | undefined >(undefined); - const { isLoading, data: sharedPost } = useSharedPost({ + const { isLoading: isSharedPostLoading, data: sharedPost } = useSharedPost({ postId, - enabled: auth?.accessToken !== undefined, + enabled: type === 'hasRoom' && auth?.accessToken != null, }); + const { isLoading: isDormitorySharedPostLoading, data: dormitorySharedPost } = + useDormitorySharedPost({ + postId, + enabled: type === 'dormitory' && auth?.accessToken != null, + }); + const { data: userData } = useUserData(auth?.accessToken != null); const [userId, setUserId] = useState(''); @@ -513,21 +524,32 @@ export function SharedPostPage({ postId }: { postId: number }) { const members = [userId]; const { mutate: chattingMutate } = useCreateChatRoom(roomName, members); - if (isLoading || sharedPost == null) return <>; + const isLoading = useMemo( + () => + type === 'hasRoom' ? isSharedPostLoading : isDormitorySharedPostLoading, + [type, isSharedPostLoading, isDormitorySharedPostLoading], + ); + + const post = useMemo( + () => (type === 'hasRoom' ? sharedPost : dormitorySharedPost), + [type, sharedPost, dormitorySharedPost], + ); + + if (isLoading || post == null) return <>; return ( fileName)} + images={post.data.roomImages.map(({ fileName }) => fileName)} />
-

{sharedPost.data.title}

+

{post.data.title}

{ scrapPost(postId); }} @@ -535,76 +557,83 @@ export function SharedPostPage({ postId }: { postId: number }) { />
- 모집 {sharedPost.data.roomInfo.recruitmentCapacity}명 - - {sharedPost.data.roomInfo.roomType} · 방{' '} - {sharedPost.data.roomInfo.numberOfRoom} · 화장실{' '} - {sharedPost.data.roomInfo.numberOfBathRoom} - + {'roomInfo' in post.data && ( + <> + 모집 {post.data.roomInfo.recruitmentCapacity}명 + + {post.data.roomInfo.roomType} · 방{' '} + {post.data.roomInfo.numberOfRoom} · 화장실{' '} + {post.data.roomInfo.numberOfBathRoom} + + + )}
+ {'roomInfo' in post.data && ( + + 희망 월 분담금 {post.data.roomInfo.expectedPayment}만원 + + )} - 희망 월 분담금 {sharedPost.data.roomInfo.expectedPayment}만원 - - - 저장 {sharedPost.data.scrapCount} · 조회{' '} - {sharedPost.data.viewCount} + 저장 {post.data.scrapCount} · 조회 {post.data.viewCount}

상세 정보

-

{sharedPost.data.content}

+

{post.data.content}

- -

거래 정보

-
- 거래 방식 - {sharedPost.data.roomInfo.rentalType} -
-
- 희망 월 분담금 - {sharedPost.data.roomInfo.expectedPayment}만원 -
-
- -

방 정보

-
- 방 종류 - {sharedPost.data.roomInfo.roomType} -
-
- 거실 보유 - - {sharedPost.data.roomInfo.hasLivingRoom ? '유' : '무'} - -
-
- 방 개수 - {sharedPost.data.roomInfo.numberOfRoom}개 -
-
- 화장실 개수 - {sharedPost.data.roomInfo.numberOfBathRoom}개 -
-
- 평수 - {sharedPost.data.roomInfo.size}평 -
-
+ {'roomInfo' in post.data && ( + <> + +

거래 정보

+
+ 거래 방식 + {post.data.roomInfo.rentalType} +
+
+ 희망 월 분담금 + {post.data.roomInfo.expectedPayment}만원 +
+
+ +

방 정보

+
+ 방 종류 + {post.data.roomInfo.roomType} +
+
+ 거실 보유 + {post.data.roomInfo.hasLivingRoom ? '유' : '무'} +
+
+ 방 개수 + {post.data.roomInfo.numberOfRoom}개 +
+
+ 화장실 개수 + {post.data.roomInfo.numberOfBathRoom}개 +
+
+ 평수 + {post.data.roomInfo.size}평 +
+
+ + )}

위치 정보

-

{sharedPost.data.address.roadAddress}

+

{post.data.address.roadAddress}

- {sharedPost.data.publisherAccount.memberId === - auth?.user?.memberId && ( + {post.data.publisherAccount.memberId === auth?.user?.memberId && ( { - setStateWithPost(sharedPost); - router.push('/shared/writing'); + router.push( + `/shared/writing/${type === 'hasRoom' ? 'room' : 'dormitory'}/${post.data.id}`, + ); }} > 수정하기 @@ -639,15 +668,18 @@ export function SharedPostPage({ postId }: { postId: number }) { - {sharedPost.data.participants.map( - ({ memberId, profileImage }, index) => ( + {post.data.participants.map( + ({ memberId, profileImageFileName }, index) => ( { - setSelected({ memberId, profileImage }); + setSelected({ + memberId, + profileImage: profileImageFileName, + }); }} /> ), @@ -658,19 +690,17 @@ export function SharedPostPage({ postId }: { postId: number }) { -

- {sharedPost.data.publisherAccount.nickname} -

+

{post.data.publisherAccount.nickname}

- {sharedPost.data.publisherAccount.birthYear != null - ? getAge(+sharedPost.data.publisherAccount.birthYear) + {post.data.publisherAccount.birthYear != null + ? getAge(+post.data.publisherAccount.birthYear) : new Date().getFullYear()}

-

{sharedPost.data.address.roadAddress}

+

{post.data.address.roadAddress}

diff --git a/src/app/pages/shared-posts-page.tsx b/src/app/pages/shared-posts-page.tsx index d38dc2f019..f5f547ab4e 100644 --- a/src/app/pages/shared-posts-page.tsx +++ b/src/app/pages/shared-posts-page.tsx @@ -21,7 +21,6 @@ import { useRecommendationMate } from '@/features/recommendation'; import { useDormitorySharedPosts, usePaging, - useSharedPostProps, useSharedPosts, type GetDormitorySharedPostsDTO, type GetSharedPostsDTO, @@ -127,9 +126,6 @@ export function SharedPostsPage() { >(null); const { setAuthUserData } = useAuthActions(); - const { setSharedPostProps, reset: resetSharedPostProps } = - useSharedPostProps(); - const { filter, derivedFilter, reset: resetFilter } = useSharedPostsFilter(); const { data: userData } = useUserData(auth?.accessToken != null); @@ -204,9 +200,9 @@ export function SharedPostsPage() { {(selected === 'hasRoom' || selected === 'dormitory') && ( { - resetSharedPostProps(); - setSharedPostProps(prev => ({ ...prev, type: selected })); - router.push('/shared/writing'); + router.push( + `/shared/writing/${selected === 'hasRoom' ? 'room' : 'dormitory'}`, + ); }} > 작성하기 @@ -223,7 +219,6 @@ export function SharedPostsPage() { post={post} onClick={() => { router.push(`/shared/${post.id}`); - setSharedPostProps(prev => ({ ...prev, type: selected })); }} /> )) @@ -232,8 +227,9 @@ export function SharedPostsPage() { key={post.id} post={post} onClick={() => { - router.push(`/shared/${post.id}`); - setSharedPostProps(prev => ({ ...prev, type: selected })); + router.push( + `/shared/${selected === 'hasRoom' ? 'room' : 'dormitory'}/${post.id}`, + ); }} /> ))} diff --git a/src/app/pages/writing-post-page.tsx b/src/app/pages/writing-post-page.tsx index 6e86647605..81645fb392 100644 --- a/src/app/pages/writing-post-page.tsx +++ b/src/app/pages/writing-post-page.tsx @@ -25,7 +25,6 @@ import { getImageURL, putImage } from '@/features/image'; import { useCreateDormitorySharedPost, useCreateSharedPost, - usePostMateCardInputSection, useSharedPostProps, useUpdateDormitorySharedPost, useUpdateSharedPost, @@ -415,7 +414,13 @@ interface ButtonActiveProps { $isSelected: boolean; } -export function WritingPostPage() { +export function WritingPostPage({ + postId, + type, +}: { + postId?: number; + type: 'hasRoom' | 'dormitory'; +}) { const router = useRouter(); const imageInputRef = useRef(null); @@ -425,38 +430,25 @@ export function WritingPostPage() { useState(false); const { - type, - postId, title, content, images, mateLimit, houseSize, address, + mateCard, selectedOptions, selectedExtraOptions, expectedMonthlyFee, + derivedMateCardFeatures, setSharedPostProps, handleOptionClick, handleExtraOptionClick, + handleMateCardOptionalFeatureChange, + handleMateCardEssentialFeatureChange, isOptionSelected, isExtraOptionSelected, - } = useSharedPostProps(); - - const { - gender, - birthYear, - mbti, - major, - budget, - derivedFeatures, - setBirthYear, - setMbti, - setMajor, - setBudget, - handleEssentialFeatureChange, - handleOptionalFeatureChange, - } = usePostMateCardInputSection(); + } = useSharedPostProps({ postId, type }); const { mutate: createSharedPost } = useCreateSharedPost(); const { mutate: updateSharedPost } = useUpdateSharedPost(); @@ -533,24 +525,31 @@ export function WritingPostPage() { }; const handleCreatePost = (event: React.MouseEvent) => { - // if (!isPostCreatable || !isMateCardCreatable) return; + const extractFileName = (url: string): string => { + const regex = + /\/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\.\w+)/; + const match = url.match(regex); + return match != null ? match[1] : ''; + }; const dealType = selectedOptions.budget; const { roomType } = selectedOptions; const { floorType } = selectedOptions; if ( - dealType == null || - roomType == null || - floorType == null || - address == null || - selectedOptions.roomCount == null || - !(selectedOptions.roomCount in CountTypeValue) || - selectedOptions.restRoomCount == null || - !(selectedOptions.restRoomCount in CountTypeValue) + type === 'hasRoom' && + (dealType == null || + roomType == null || + floorType == null || + selectedOptions.roomCount == null || + !(selectedOptions.roomCount in CountTypeValue) || + selectedOptions.restRoomCount == null || + !(selectedOptions.restRoomCount in CountTypeValue)) ) return; + if (address == null || images.length < 2) return; + const numberOfRoomOption = selectedOptions.roomCount as | '1개' | '2개' @@ -595,10 +594,10 @@ export function WritingPostPage() { const putResults = await Promise.allSettled( urls.map(async ({ url, fileName, file, uploaded }) => { - if (uploaded) return { fileName: url }; + if (uploaded) return { uploaded, fileName: url }; if (file != null) await putImage(url, file); - return { fileName }; + return { uploaded, fileName }; }), ); @@ -607,8 +606,11 @@ export function WritingPostPage() { >((prev, result) => { if (result.status === 'rejected' || result.value.fileName == null) return prev; + const { uploaded, fileName } = result.value; return prev.concat({ - fileName: result.value.fileName, + fileName: uploaded + ? `images/${extractFileName(fileName)}` + : fileName, isThumbNail: prev.length === 0, order: prev.length + 1, }); @@ -643,12 +645,12 @@ export function WritingPostPage() { }, }, locationData: { - oldAddress: address?.jibunAddress, - roadAddress: address?.roadAddress, + oldAddress: address.jibunAddress, + roadAddress: address.roadAddress, }, roomMateCardData: { - location: address?.roadAddress, - features: derivedFeatures, + location: address.roadAddress, + features: derivedMateCardFeatures, }, participationData: { recruitmentCapacity: mateLimit, @@ -706,12 +708,12 @@ export function WritingPostPage() { }, }, locationData: { - oldAddress: address?.jibunAddress, - roadAddress: address?.roadAddress, + oldAddress: address.jibunAddress, + roadAddress: address.roadAddress, }, roomMateCardData: { - location: address?.roadAddress, - features: derivedFeatures, + location: address.roadAddress, + features: derivedMateCardFeatures, }, participationData: { recruitmentCapacity: mateLimit, @@ -743,36 +745,17 @@ export function WritingPostPage() { } } else if (type === 'dormitory') { if (postId == null) { - createDormitorySharedPost({ - imageFilesData: uploadedImages, - postData: { title, content }, - locationData: { - oldAddress: address?.jibunAddress, - roadAddress: address?.roadAddress, - }, - roomMateCardData: { - location: address?.roadAddress, - features: derivedFeatures, - }, - participationData: { - recruitmentCapacity: mateLimit, - participationMemberIds: - auth?.user != null ? [auth.user.memberId] : [], - }, - }); - } else if (postId != null) { - updateDormitorySharedPost({ - postId, - postData: { + createDormitorySharedPost( + { imageFilesData: uploadedImages, postData: { title, content }, locationData: { - oldAddress: address?.jibunAddress, - roadAddress: address?.roadAddress, + oldAddress: address.jibunAddress, + roadAddress: address.roadAddress, }, roomMateCardData: { - location: address?.roadAddress, - features: derivedFeatures, + location: address.roadAddress, + features: derivedMateCardFeatures, }, participationData: { recruitmentCapacity: mateLimit, @@ -780,7 +763,68 @@ export function WritingPostPage() { auth?.user != null ? [auth.user.memberId] : [], }, }, - }); + { + onSuccess: () => { + createToast({ + message: '게시글이 정상적으로 업로드되었습니다.', + option: { + duration: 3000, + }, + }); + router.back(); + }, + onError: () => { + createToast({ + message: '게시글 업로드에 실패했습니다.', + option: { + duration: 3000, + }, + }); + }, + }, + ); + } else if (postId != null) { + updateDormitorySharedPost( + { + postId, + postData: { + imageFilesData: uploadedImages, + postData: { title, content }, + locationData: { + oldAddress: address.jibunAddress, + roadAddress: address.roadAddress, + }, + roomMateCardData: { + location: address.roadAddress, + features: derivedMateCardFeatures, + }, + participationData: { + recruitmentCapacity: mateLimit, + participationMemberIds: + auth?.user != null ? [auth.user.memberId] : [], + }, + }, + }, + { + onSuccess: () => { + createToast({ + message: '게시글이 정상적으로 수정되었습니다.', + option: { + duration: 3000, + }, + }); + router.back(); + }, + onError: () => { + createToast({ + message: '게시글 수정에 실패했습니다.', + option: { + duration: 3000, + }, + }); + }, + }, + ); } } } catch (error) { @@ -942,22 +986,22 @@ export function WritingPostPage() { {showMateCardForm && ( {}} - onMateAgeChange={setBirthYear} - onMbtiChange={setMbti} - onMajorChange={setMajor} - onBudgetChange={setBudget} + onMateAgeChange={() => {}} + onMbtiChange={() => {}} + onMajorChange={() => {}} + onBudgetChange={() => {}} /> )} diff --git a/src/app/shared/dormitory/[postId]/page.tsx b/src/app/shared/dormitory/[postId]/page.tsx new file mode 100644 index 0000000000..f3c65f83df --- /dev/null +++ b/src/app/shared/dormitory/[postId]/page.tsx @@ -0,0 +1,9 @@ +import { SharedPostPage } from '@/app/pages'; + +export default function Page({ + params: { postId }, +}: { + params: { postId: string }; +}) { + return ; +} diff --git a/src/app/shared/[postId]/page.tsx b/src/app/shared/room/[postId]/page.tsx similarity index 69% rename from src/app/shared/[postId]/page.tsx rename to src/app/shared/room/[postId]/page.tsx index 5c1f9dc90b..0997478567 100644 --- a/src/app/shared/[postId]/page.tsx +++ b/src/app/shared/room/[postId]/page.tsx @@ -5,5 +5,5 @@ export default function Page({ }: { params: { postId: string }; }) { - return ; + return ; } diff --git a/src/app/shared/writing/dormitory/[postId]/page.tsx b/src/app/shared/writing/dormitory/[postId]/page.tsx new file mode 100644 index 0000000000..0227db26b3 --- /dev/null +++ b/src/app/shared/writing/dormitory/[postId]/page.tsx @@ -0,0 +1,9 @@ +import { WritingPostPage } from '@/app/pages'; + +export default function Page({ + params: { postId }, +}: { + params: { postId: string }; +}) { + return ; +} diff --git a/src/app/shared/writing/dormitory/page.tsx b/src/app/shared/writing/dormitory/page.tsx new file mode 100644 index 0000000000..63c8058a56 --- /dev/null +++ b/src/app/shared/writing/dormitory/page.tsx @@ -0,0 +1,5 @@ +import { WritingPostPage } from '@/app/pages'; + +export default function Page() { + return ; +} diff --git a/src/app/shared/writing/room/[postId]/page.tsx b/src/app/shared/writing/room/[postId]/page.tsx new file mode 100644 index 0000000000..e40d73887d --- /dev/null +++ b/src/app/shared/writing/room/[postId]/page.tsx @@ -0,0 +1,9 @@ +import { WritingPostPage } from '@/app/pages'; + +export default function Page({ + params: { postId }, +}: { + params: { postId: string }; +}) { + return ; +} diff --git a/src/app/shared/writing/page.tsx b/src/app/shared/writing/room/page.tsx similarity index 64% rename from src/app/shared/writing/page.tsx rename to src/app/shared/writing/room/page.tsx index 84ba7e9f1e..2f8cf87c76 100644 --- a/src/app/shared/writing/page.tsx +++ b/src/app/shared/writing/room/page.tsx @@ -1,5 +1,5 @@ import { WritingPostPage } from '@/app/pages'; export default function Page() { - return ; + return ; } diff --git a/src/components/RangeSlider.tsx b/src/components/RangeSlider.tsx index a7cff45397..ae5c7982c1 100644 --- a/src/components/RangeSlider.tsx +++ b/src/components/RangeSlider.tsx @@ -107,11 +107,9 @@ export function RangeSlider({ min, max, step, onChange }: Props) { }, [containerRef, state, max, min]); useEffect(() => { - setState({ - low: (min + max - 1) * 0.25, - high: (min + max - 1) * 0.75, - }); - }, []); + // width 값을 초기에 계산하기 위해 추가된 effect. + setState({ low: min, high: max }); + }, [setState, min, max]); return ( diff --git a/src/components/chat/ChattingRoom.tsx b/src/components/chat/ChattingRoom.tsx index acf3d036f4..56e28276c2 100644 --- a/src/components/chat/ChattingRoom.tsx +++ b/src/components/chat/ChattingRoom.tsx @@ -247,7 +247,7 @@ export function ChattingRoom({ stompClient.publish({ destination, body: JSON.stringify({ - roomId: roomId, + roomId, sender: user, message: inputMessage, nickname: userName, diff --git a/src/components/shared-post-page/ImageGrid.tsx b/src/components/shared-post-page/ImageGrid.tsx index aaa100e4c7..116632a19a 100644 --- a/src/components/shared-post-page/ImageGrid.tsx +++ b/src/components/shared-post-page/ImageGrid.tsx @@ -103,7 +103,7 @@ export function ImageGrid({ images: imagesParam, className }: Props) { image !== 'none' ? ( {image} diff --git a/src/components/shared-posts/SharedPostsMenu.tsx b/src/components/shared-posts/SharedPostsMenu.tsx index 34ad5cec80..60471bb2b1 100644 --- a/src/components/shared-posts/SharedPostsMenu.tsx +++ b/src/components/shared-posts/SharedPostsMenu.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; import { type SharedPostsType } from '@/entities/shared-posts-filter'; +import { useAuthValue, useUserData } from '@/features/auth'; const styles = { container: styled.div` @@ -55,7 +56,8 @@ export function SharedPostsMenu({ handleSelect, className, }: Props & React.ComponentProps<'div'>) { - // const auth = useAuthValue(); + const auth = useAuthValue(); + const { data: user } = useUserData(auth?.accessToken != null); return ( @@ -75,8 +77,16 @@ export function SharedPostsMenu({ > 방 없는 메이트 - {/* {auth?.user?.univCertified === true ?? ( - )} */} + {user?.univCertified ?? ( + { + handleSelect('dormitory'); + }} + className={selected === 'dormitory' ? 'selected' : ''} + > + 기숙사 메이트 + + )} { handleSelect('dormitory'); diff --git a/src/components/shared-posts/filter/RoomTypeFilter.tsx b/src/components/shared-posts/filter/RoomTypeFilter.tsx index 2efa88c5ae..227cf24ab7 100644 --- a/src/components/shared-posts/filter/RoomTypeFilter.tsx +++ b/src/components/shared-posts/filter/RoomTypeFilter.tsx @@ -224,15 +224,18 @@ export function RoomTypeFilter() {

거실 유무

{ - setFilter(prev => ({ - ...prev, - roomInfo: { - ...prev.roomInfo, - hasLivingRoom: !prev.roomInfo.hasLivingRoom, - }, - })); + setFilter(prev => { + const value = prev.roomInfo.hasLivingRoom ?? false; + return { + ...prev, + roomInfo: { + ...prev.roomInfo, + hasLivingRoom: !value, + }, + }; + }); }} />
diff --git a/src/entities/shared-post/shared-post.type.ts b/src/entities/shared-post/shared-post.type.ts index 523864c9be..be3f35dc2b 100644 --- a/src/entities/shared-post/shared-post.type.ts +++ b/src/entities/shared-post/shared-post.type.ts @@ -47,15 +47,12 @@ export interface SharedPost { title: string; content: string; roomMateFeatures: { - location: string; - features: { - smoking: string; - roomSharingOption: string; - mateAge: string; - options: string; // 프런트에서 파싱 필요. - }; + smoking?: string; + roomSharingOption?: string; + mateAge?: string; + options: string; // 프런트에서 파싱 필요. }; - participants: Array<{ memberId: string; profileImage: string }>; + participants: Array<{ memberId: string; profileImageFileName: string }>; roomImages: Array<{ fileName: string; isThumbnail: boolean; @@ -145,13 +142,10 @@ export interface DormitorySharedPost { title: string; content: string; roomMateFeatures: { - location: string; - features: { - smoking: string; - roomSharingOption: string; - mateAge: string; - options: string; - }; + smoking?: string; + roomSharingOption?: string; + mateAge?: string; + options: string; }; participants: Array<{ memberId: string; diff --git a/src/entities/shared-posts-filter/shared-posts-filter.atom.ts b/src/entities/shared-posts-filter/shared-posts-filter.atom.ts index 415767c3eb..ac0849eed5 100644 --- a/src/entities/shared-posts-filter/shared-posts-filter.atom.ts +++ b/src/entities/shared-posts-filter/shared-posts-filter.atom.ts @@ -5,9 +5,7 @@ import { type SharedPostsFilter } from './shared-posts-filter.type'; export const sharedPostsFilterState = atom({ key: 'sharedPostsFilterState', default: { - roomInfo: { - hasLivingRoom: false, - }, + roomInfo: {}, dealInfo: {}, extraInfo: {}, }, diff --git a/src/entities/shared-posts-filter/shared-posts-filter.type.ts b/src/entities/shared-posts-filter/shared-posts-filter.type.ts index 1ec83e8775..09d1de79c9 100644 --- a/src/entities/shared-posts-filter/shared-posts-filter.type.ts +++ b/src/entities/shared-posts-filter/shared-posts-filter.type.ts @@ -57,7 +57,7 @@ export interface SharedPostsFilter { cardType?: CardType; roomInfo: { roomType?: Partial>; - hasLivingRoom: boolean; + hasLivingRoom?: boolean; roomCount?: CountType; restRoomCount?: CountType; size?: { low: number; high: number }; diff --git a/src/features/shared/shared.api.ts b/src/features/shared/shared.api.ts index 006b750724..8e6d5dd26d 100644 --- a/src/features/shared/shared.api.ts +++ b/src/features/shared/shared.api.ts @@ -6,7 +6,7 @@ import { type GetSharedPostDTO, type GetSharedPostsDTO, } from './shared.dto'; -import { type SharedPostProps, type GetSharedPostsProps } from './shared.type'; +import { type GetSharedPostsProps, type SharedPostProps } from './shared.type'; import { type SuccessBaseDTO } from '@/shared/types'; @@ -19,7 +19,7 @@ export const getSharedPosts = async ({ const baseURL = '/maru-api/shared/posts/studio'; let query = ''; - if (filter != null) { + if (filter != null && Object.keys(filter).length > 0) { query += `filter=${JSON.stringify(filter)}`; } @@ -70,6 +70,10 @@ export const getDormitorySharedPosts = async ({ const baseURL = '/maru-api/shared/posts/dormitory'; let query = ''; + if (filter != null && Object.keys(filter).length > 0) { + query += `filter=${JSON.stringify(filter)}`; + } + if (search != null) { query += `&search=${search}`; } diff --git a/src/features/shared/shared.atom.ts b/src/features/shared/shared.atom.ts index 3956a6579c..60f4d93a72 100644 --- a/src/features/shared/shared.atom.ts +++ b/src/features/shared/shared.atom.ts @@ -9,8 +9,6 @@ import { import { type NaverAddress } from '@/features/geocoding'; export const sharedPostPropState = atom<{ - type: 'hasRoom' | 'dormitory'; - postId?: number; title: string; content: string; images: ImageFile[]; @@ -20,11 +18,23 @@ export const sharedPostPropState = atom<{ houseSize: number; selectedExtraOptions: SelectedExtraOptions; selectedOptions: SelectedOptions; + mateCard: { + gender?: string; + birthYear?: number; + location?: string; + mbti?: string; + major?: string; + budget?: string; + features: { + smoking?: string; + roomSharingOption?: string; + mateAge?: number; + options: Set; + }; + }; }>({ key: 'sharedPostPropState', default: { - type: 'hasRoom', - postId: undefined, title: '', content: '', images: [], @@ -34,5 +44,6 @@ export const sharedPostPropState = atom<{ houseSize: 0, selectedExtraOptions: {}, selectedOptions: {}, + mateCard: { features: { options: new Set() } }, }, }); diff --git a/src/features/shared/shared.hook.ts b/src/features/shared/shared.hook.ts index 18bea894a6..7fbba296f8 100644 --- a/src/features/shared/shared.hook.ts +++ b/src/features/shared/shared.hook.ts @@ -17,7 +17,10 @@ import { updateSharedPost, } from './shared.api'; import { sharedPostPropState } from './shared.atom'; -import { type GetSharedPostDTO } from './shared.dto'; +import { + type GetDormitorySharedPostDTO, + type GetSharedPostDTO, +} from './shared.dto'; import { type GetSharedPostsProps, type SelectedExtraOptions, @@ -26,7 +29,7 @@ import { } from './shared.type'; import { fromAddrToCoord } from '../geocoding'; -import { useAuthValue } from '@/features/auth'; +import { useAuthValue, useUserData } from '@/features/auth'; import { useDebounce } from '@/shared/debounce'; import { type FailureDTO, type SuccessBaseDTO } from '@/shared/types'; @@ -97,57 +100,128 @@ export const usePaging = ({ ); }; -export const useSharedPostProps = () => { +export const useSharedPostProps = ({ + postId, + type, +}: { + postId?: number; + type?: 'hasRoom' | 'dormitory'; +}) => { const [state, setState] = useRecoilState(sharedPostPropState); const reset = useResetRecoilState(sharedPostPropState); - const setStateWithPost = ({ data }: GetSharedPostDTO) => { - fromAddrToCoord({ query: data.address.roadAddress }) - .then(res => { - const address = res.data.addresses.shift(); - if (address != null) setState(prev => ({ ...prev, address })); - }) - .catch(err => { - console.error(err); - }); - - let roomCount = '1개'; - if (data.roomInfo.numberOfRoom === 2) roomCount = '2개'; - else if (data.roomInfo.numberOfRoom === 3) roomCount = '3개 이상'; - - let restRoomCount = '1개'; - if (data.roomInfo.numberOfBathRoom === 2) restRoomCount = '2개'; - else if (data.roomInfo.numberOfBathRoom === 3) restRoomCount = '3개'; - - setState({ - type: state.type, - postId: data.id, - title: data.title, - content: data.content, - images: data.roomImages.map(({ fileName }) => ({ - url: fileName, - uploaded: true, - })), - mateLimit: data.roomInfo.recruitmentCapacity, - expectedMonthlyFee: data.roomInfo.expectedPayment, - houseSize: data.roomInfo.size, - selectedOptions: { - roomType: data.roomInfo.roomType, - roomCount, - budget: data.roomInfo.rentalType, - floorType: data.roomInfo.floorType, - livingRoom: data.roomInfo.hasLivingRoom ? '유' : '무', - restRoomCount, - }, - selectedExtraOptions: { - canPark: data.roomInfo.extraOption.canPark, - hasAirConditioner: data.roomInfo.extraOption.hasAirConditioner, - hasRefrigerator: data.roomInfo.extraOption.hasRefrigerator, - hasWasher: data.roomInfo.extraOption.hasWasher, - hasTerrace: data.roomInfo.extraOption.hasTerrace, - }, - }); - }; + useEffect(() => { + reset(); + + if (postId == null) return; + + const setStateWithPost = ({ + data, + }: GetSharedPostDTO | GetDormitorySharedPostDTO) => { + fromAddrToCoord({ query: data.address.roadAddress }) + .then(res => { + const address = res.data.addresses.shift(); + if (address != null) setState(prev => ({ ...prev, address })); + }) + .catch(err => { + console.error(err); + }); + + let roomCount = '1개'; + let restRoomCount = '1개'; + if ('roomInfo' in data) { + if (data.roomInfo.numberOfRoom === 2) roomCount = '2개'; + else if (data.roomInfo.numberOfRoom === 3) roomCount = '3개 이상'; + + if (data.roomInfo.numberOfBathRoom === 2) restRoomCount = '2개'; + else if (data.roomInfo.numberOfBathRoom === 3) restRoomCount = '3개'; + } + + if ('roomInfo' in data) { + setState({ + ...state, + title: data.title, + content: data.content, + images: data.roomImages.map(({ fileName }) => ({ + url: fileName, + uploaded: true, + })), + mateLimit: data.roomInfo.recruitmentCapacity, + expectedMonthlyFee: data.roomInfo.expectedPayment, + houseSize: data.roomInfo.size, + selectedOptions: { + roomType: data.roomInfo.roomType, + roomCount, + budget: data.roomInfo.rentalType, + floorType: data.roomInfo.floorType, + livingRoom: data.roomInfo.hasLivingRoom ? '유' : '무', + restRoomCount, + }, + selectedExtraOptions: { + canPark: data.roomInfo.extraOption.canPark, + hasAirConditioner: data.roomInfo.extraOption.hasAirConditioner, + hasRefrigerator: data.roomInfo.extraOption.hasRefrigerator, + hasWasher: data.roomInfo.extraOption.hasWasher, + hasTerrace: data.roomInfo.extraOption.hasTerrace, + }, + mateCard: { + ...state.mateCard, + location: data.address.roadAddress, + features: { + smoking: data.roomMateFeatures.smoking, + roomSharingOption: data.roomMateFeatures.roomSharingOption, + mateAge: + data.roomMateFeatures.mateAge != null + ? +data.roomMateFeatures.mateAge + : undefined, + options: new Set( + JSON.parse(data.roomMateFeatures.options) as string[], + ), + }, + }, + }); + } else { + setState({ + ...state, + title: data.title, + content: data.content, + images: data.roomImages.map(({ fileName }) => ({ + url: fileName, + uploaded: true, + })), + mateLimit: data.recruitmentCapacity, + expectedMonthlyFee: 0, + houseSize: 0, + selectedOptions: {}, + selectedExtraOptions: {}, + mateCard: { + ...state.mateCard, + location: data.address.roadAddress, + features: { + smoking: data.roomMateFeatures.smoking, + roomSharingOption: data.roomMateFeatures.roomSharingOption, + mateAge: + data.roomMateFeatures.mateAge != null + ? +data.roomMateFeatures.mateAge + : undefined, + options: new Set( + JSON.parse(data.roomMateFeatures.options) as string[], + ), + }, + }, + }); + } + }; + + (async () => { + const post = + type === 'hasRoom' + ? await getSharedPost(postId) + : await getDormitorySharedPost(postId); + + setStateWithPost(post.data); + })(); + }, []); const handleOptionClick = ( optionName: keyof SelectedOptions, @@ -181,94 +255,86 @@ export const useSharedPostProps = () => { const isExtraOptionSelected = (item: keyof SelectedExtraOptions) => state.selectedExtraOptions[item] === true; - return { - ...state, - setSharedPostProps: setState, - setStateWithPost, - reset, - handleOptionClick, - handleExtraOptionClick, - isOptionSelected, - isExtraOptionSelected, - }; -}; - -export const usePostMateCardInputSection = () => { - const [gender, setGender] = useState(undefined); - const [birthYear, setBirthYear] = useState(undefined); - const [location, setLocation] = useState(undefined); - const [mbti, setMbti] = useState(undefined); - const [major, setMajor] = useState(undefined); - const [budget, setBudget] = useState(undefined); - - const [features, setFeatures] = useState<{ - smoking?: string; - roomSharingOption?: string; - mateAge?: number; - options: Set; - }>({ options: new Set() }); - - const handleEssentialFeatureChange = ( + const handleMateCardEssentialFeatureChange = ( key: 'smoking' | 'roomSharingOption' | 'mateAge', value: string | number | undefined, ) => { - setFeatures(prev => { - if (prev[key] === value) { - const newFeatures = { ...prev }; + setState(prev => { + if (prev.mateCard.features[key] === value) { + const newFeatures = { ...prev.mateCard.features }; newFeatures[key] = undefined; - return newFeatures; + return { + ...prev, + mateCard: { ...prev.mateCard, features: newFeatures }, + }; } - return { ...prev, [key]: value }; + return { + ...prev, + mateCard: { + ...prev.mateCard, + features: { ...prev.mateCard.features, [key]: value }, + }, + }; }); }; - const handleOptionalFeatureChange = (option: string) => { - setFeatures(prev => { - const { options } = prev; + const handleMateCardOptionalFeatureChange = (option: string) => { + setState(prev => { + const { options } = prev.mateCard.features; const newOptions = new Set(options); if (options.has(option)) newOptions.delete(option); else newOptions.add(option); - return { ...prev, options: newOptions }; + return { + ...prev, + mateCard: { + ...prev.mateCard, + features: { ...prev.mateCard.features, options: newOptions }, + }, + }; }); }; - const derivedFeatures = useMemo(() => { + const derivedMateCardFeatures = useMemo(() => { const options: string[] = []; + const { features } = state.mateCard; features.options.forEach(option => options.push(option)); return { smoking: features?.smoking ?? '상관없어요', roomSharingOption: features?.roomSharingOption ?? '상관없어요', - mateAge: birthYear, + mateAge: state.mateCard.birthYear, options: JSON.stringify(options), }; - }, [features, birthYear]); + }, [state.mateCard]); const auth = useAuthValue(); + const { data: user } = useUserData(auth?.accessToken != null); + useEffect(() => { - if (auth?.user != null) { - setGender(auth.user.gender); + if (user?.gender != null) { + setState(prev => ({ + ...prev, + mateCard: { + ...prev.mateCard, + gender: user.gender, + }, + })); } - }, [auth?.user]); + }, [user, setState]); return { - gender, - setGender, - birthYear, - setBirthYear, - location, - setLocation, - mbti, - setMbti, - major, - setMajor, - budget, - setBudget, - derivedFeatures, - handleEssentialFeatureChange, - handleOptionalFeatureChange, + ...state, + derivedMateCardFeatures, + setSharedPostProps: setState, + reset, + handleOptionClick, + handleExtraOptionClick, + isOptionSelected, + isExtraOptionSelected, + handleMateCardOptionalFeatureChange, + handleMateCardEssentialFeatureChange, }; }; @@ -291,7 +357,6 @@ export const useSharedPosts = ({ await getSharedPosts({ filter: debounceFilter, search, page }).then( response => response.data, ), - staleTime: 60000, enabled, }); }; @@ -339,16 +404,20 @@ export const useDormitorySharedPosts = ({ search, page, enabled, -}: GetSharedPostsProps & { enabled: boolean }) => - useQuery({ - queryKey: ['/api/shared/posts/dormitory', { filter, search, page }], +}: GetSharedPostsProps & { enabled: boolean }) => { + const debounceFilter = useDebounce(filter, 1000); + + return useQuery({ + queryKey: ['/api/shared/posts/dormitory', { debounceFilter, search, page }], queryFn: async () => - await getDormitorySharedPosts({ filter, search, page }).then( - response => response.data, - ), - staleTime: 60000, + await getDormitorySharedPosts({ + filter: debounceFilter, + search, + page, + }).then(response => response.data), enabled, }); +}; export const useDormitorySharedPost = ({ postId,