diff --git a/src/app/pages/main-page.tsx b/src/app/pages/main-page.tsx index 6fa40ae..c9df3f3 100644 --- a/src/app/pages/main-page.tsx +++ b/src/app/pages/main-page.tsx @@ -9,7 +9,7 @@ import { CircularButton } from '@/components'; import { UserCard } from '@/components/main-page'; import { useAuthActions, useAuthValue, useUserData } from '@/features/auth'; import { getGeolocation } from '@/features/geocoding'; -import { useRecommendationMate } from '@/features/recommendation'; +import { useDummyUsers } from '@/features/shared'; const styles = { container: styled.div` @@ -94,11 +94,11 @@ export function MainPage() { const { data: userData } = useUserData(auth?.accessToken !== undefined); - const { data: recommendationMates } = useRecommendationMate({ - memberId: auth?.user?.memberId ?? 'undefined', - cardType: 'mate', - enabled: auth?.accessToken != null, - }); + // const { data: recommendationMates } = useRecommendationMate({ + // memberId: auth?.user?.memberId ?? 'undefined', + // cardType: 'mate', + // enabled: auth?.accessToken != null, + // }); const [map, setMap] = useState(null); @@ -147,6 +147,8 @@ export function MainPage() { } }, [userData, router, setAuthUserData]); + const users = useDummyUsers(); + return ( @@ -168,14 +170,26 @@ export function MainPage() { onClick={handleScrollLeft} /> - {recommendationMates?.map(({ name, similarity, userId }) => ( + {/* {recommendationMates?.map(({ name, similarity, userId }) => ( - ))} + ))} */} + {users?.map( + ({ + userId, + data: { + authResponse: { name }, + }, + }) => ( + + + + ), + )} (null); - const { data: sharedPost } = useSharedPost({ + const [selected, setSelected] = useState< + | { + memberId: string; + profileImage: string; + } + | undefined + >(undefined); + + const { isLoading, data: sharedPost } = useSharedPost({ postId, enabled: auth?.accessToken !== undefined, }); @@ -438,17 +437,21 @@ export function SharedPostPage({ postId }: { postId: number }) { const members = [userId]; const { mutate: chattingMutate } = useCreateChatRoom(roomName, members); + if (isLoading || sharedPost == null) return <>; + return ( - + fileName)} + />
-

{sharedPost?.data.title}

+

{sharedPost.data.title}

{ scrapPost(postId); }} @@ -456,79 +459,85 @@ export function SharedPostPage({ postId }: { postId: number }) { />
- 모집 1명 / 총원 2명 - 원룸 / 방 1 + 모집 {sharedPost.data.roomInfo.recruitmentCapacity}명 + + {sharedPost.data.roomInfo.roomType} · 방{' '} + {sharedPost.data.roomInfo.numberOfRoom} · 화장실{' '} + {sharedPost.data.roomInfo.numberOfBathRoom} +
- 희망 월 분담금 65만원 - 저장 4 · 조회 22 + + 희망 월 분담금 {sharedPost.data.roomInfo.expectedPayment}만원 + + + 저장 {sharedPost.data.scrapCount} · 조회{' '} + {sharedPost.data.viewCount} +

상세 정보

-

- 안녕하세요! 저는 현재 룸메이트를 찾고 있는 정연수입니다. 서울시 - 정릉동에서 함께 살아갈 룸메이트를 구하고 있습니다. 주로 밤에 - 작업을 하며 새벽 2시~3시쯤에 취침합니다. 관심있으신 분들 연락 - 주세요! -

+

{sharedPost.data.content}

거래 정보

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

방 정보

방 종류 - 원룸 + {sharedPost.data.roomInfo.roomType}
거실 보유 - 있음 + + {sharedPost.data.roomInfo.hasLivingRoom ? '유' : '무'} +
방 개수 - 2개 + {sharedPost.data.roomInfo.numberOfRoom}개
화장실 개수 - 1개 + {sharedPost.data.roomInfo.numberOfBathRoom}개
평수 - 14평 + {sharedPost.data.roomInfo.size}평

위치 정보

-

{sharedPost?.data.roomInfo.address.roadAddress}

+

{sharedPost.data.address.roadAddress}

- - - + {sharedPost.data.participants.map( + ({ memberId, profileImage }, index) => ( + { + setSelected({ memberId, profileImage }); + }} + /> + ), + )} @@ -539,15 +548,15 @@ export function SharedPostPage({ postId }: { postId: number }) { />

- {sharedPost?.data.publisherAccount.nickname} + {sharedPost.data.publisherAccount.nickname}

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

-

서웉특별시 성북구

+

{sharedPost.data.address.roadAddress}

diff --git a/src/app/pages/shared-posts-page.tsx b/src/app/pages/shared-posts-page.tsx index 2c9f3c2..a314707 100644 --- a/src/app/pages/shared-posts-page.tsx +++ b/src/app/pages/shared-posts-page.tsx @@ -17,8 +17,7 @@ import { type SharedPostsType, } from '@/entities/shared-posts-filter'; import { useAuthActions, useAuthValue, useUserData } from '@/features/auth'; -import { useRecommendationMate } from '@/features/recommendation'; -import { usePaging, useSharedPosts } from '@/features/shared'; +import { useDummyUsers, usePaging, useSharedPosts } from '@/features/shared'; import { type GetSharedPostsDTO } from '@/features/shared/'; const styles = { @@ -120,7 +119,7 @@ export function SharedPostsPage() { useState(null); const { setAuthUserData } = useAuthActions(); - const { filter, derivedFilter, reset: resetFilter } = useSharedPostsFilter(); + const { derivedFilter, reset: resetFilter } = useSharedPostsFilter(); const { data: userData } = useUserData(auth?.accessToken != null); const { @@ -143,11 +142,11 @@ export function SharedPostsPage() { page: page - 1, }); - const { data: recommendationMates } = useRecommendationMate({ - memberId: auth?.user?.memberId ?? 'undefined', - cardType: filter.cardType ?? 'mate', - enabled: auth?.accessToken != null && selected === 'homeless', - }); + // const { data: recommendationMates } = useRecommendationMate({ + // memberId: auth?.user?.memberId ?? 'undefined', + // cardType: filter.cardType ?? 'mate', + // enabled: auth?.accessToken != null && selected === 'homeless', + // }); useEffect(() => { resetFilter(); @@ -172,6 +171,8 @@ export function SharedPostsPage() { } }, [userData, router, setAuthUserData]); + const users = useDummyUsers(); + return ( @@ -254,11 +255,23 @@ export function SharedPostsPage() { ) : ( - {recommendationMates?.map(({ userId, name, similarity }) => ( + {/* {recommendationMates?.map(({ userId, name, similarity }) => ( - ))} + ))} */} + {users?.map( + ({ + userId, + data: { + authResponse: { name }, + }, + }) => ( + + + + ), + )} )} diff --git a/src/components/card/VitalSection.tsx b/src/components/card/VitalSection.tsx index e33d93a..364da6a 100644 --- a/src/components/card/VitalSection.tsx +++ b/src/components/card/VitalSection.tsx @@ -271,7 +271,7 @@ export function VitalSection({ }); onFeatureChange(key, value); }, - [], + [onFeatureChange], ); const [initialLocation, setInitialLocation] = useState(''); @@ -279,7 +279,7 @@ export function VitalSection({ if (location !== undefined && type === 'myCard') { setInitialLocation(location); } - }, [location]); + }, [location, type]); const [locationInput, setLocation] = useState(''); useEffect(() => { @@ -289,9 +289,10 @@ export function VitalSection({ const handleLocationChange = (event: React.ChangeEvent) => { setLocation(event.target.value); }; + useEffect(() => { onLocationChange(locationInput); - }, [locationInput]); + }, [onLocationChange, locationInput]); const [initialAge, setInitialAge] = useState(0); const [ageValue, setAgeValue] = useState(0); @@ -305,8 +306,11 @@ export function VitalSection({ }, [initialAge]); const handleAgeChange = (e: React.ChangeEvent) => { - setAgeValue(Number(e.target.value)); - onMateAgeChange(Number(e.target.value)); + if (!Number.isNaN(e.target.value)) { + const n = Number(e.target.value); + setAgeValue(n); + onMateAgeChange(n === 11 ? undefined : n); + } }; let ageValueString; diff --git a/src/components/shared-posts/PostCard.tsx b/src/components/shared-posts/PostCard.tsx index 33c5e8f..548ae82 100644 --- a/src/components/shared-posts/PostCard.tsx +++ b/src/components/shared-posts/PostCard.tsx @@ -124,26 +124,23 @@ export function PostCard({ post }: { post: SharedPostListItem }) { return (
- +

{post.title}

-

{post.roomInfo.address.roadAddress}

+

{post.address.roadAddress}

-

모집 1명 / 총원 2명

-

원룸 · 방1

-

500 / 50 / 5

+

모집 {post.roomInfo.recruitmentCapacity}명

+

+ {post.roomInfo.roomType} · 방 {post.roomInfo.numberOfRoom} · + 화장실 {post.roomInfo.numberOfBathRoom} +

+

희망 월 분담금 {post.roomInfo.expectedPayment}

- +

50%

diff --git a/src/entities/shared-post/shared-post.type.ts b/src/entities/shared-post/shared-post.type.ts index 76e9683..523864c 100644 --- a/src/entities/shared-post/shared-post.type.ts +++ b/src/entities/shared-post/shared-post.type.ts @@ -20,12 +20,12 @@ export interface SharedPostListItem { modifiedAt: Date; modifiedBy: string; }; + address: { + oldAddress: string; + roadAddress: string; + }; roomInfo: { id: number; - address: { - oldAddress: string; - roadAddress: string; - }; roomType: string; floorType: string; size: number; @@ -33,6 +33,7 @@ export interface SharedPostListItem { numberOfBathRoom: number; rentalType: string; expectedPayment: number; + recruitmentCapacity: number; }; isScrapped: boolean; createdAt: Date; @@ -45,9 +46,17 @@ export interface SharedPost { id: number; title: string; content: string; - roomMateFeatures: string[]; + roomMateFeatures: { + location: string; + features: { + smoking: string; + roomSharingOption: string; + mateAge: string; + options: string; // 프런트에서 파싱 필요. + }; + }; participants: Array<{ memberId: string; profileImage: string }>; - roomImages: Set<{ + roomImages: Array<{ fileName: string; isThumbnail: boolean; order: number; @@ -59,21 +68,18 @@ export interface SharedPost { birthYear: string; gender: string; phoneNumber: string; - myCardFeatures: string[]; - mateCardFeatures: string[]; + profileImageFileName: string; createdAt: Date; createdBy: string; modifiedAt: Date; modifiedBy: string; }; + address: { + oldAddress: string; + roadAddress: string; + }; roomInfo: { id: number; - address: { - city: string; - oldAddress: string; - roadAddress: string; - detailAddress?: string; - }; roomType: string; floorType: string; size: number; @@ -99,3 +105,86 @@ export interface SharedPost { modifiedAt: Date; modifiedBy: string; } + +export interface DormitorySharedPostListItem { + id: number; + title: string; + content: string; + thumbnail: { + fileName: string; + isThumbNail: boolean; + order: number; + }; + publisherAccount: { + memberId: string; + email: string; + nickname: string; + birthYear: string; + gender: string; + phoneNumber: string; + profileImageFileName: string; + createdAt: Date; + createdBy: string; + modifiedAt: Date; + modifiedBy: string; + }; + address: { + oldAddress: string; + roadAddress: string; + }; + recruitmentCapacity: number; + isScrapped: boolean; + createdAt: Date; + createdBy: string; + modifiedAt: Date; + modifiedBy: string; +} + +export interface DormitorySharedPost { + id: number; + title: string; + content: string; + roomMateFeatures: { + location: string; + features: { + smoking: string; + roomSharingOption: string; + mateAge: string; + options: string; + }; + }; + participants: Array<{ + memberId: string; + profileImageFileName: string; + }>; + roomImages: Array<{ + fileName: string; + isThumbNail: boolean; + order: number; + }>; + publisherAccount: { + memberId: string; + email: string; + nickname: string; + birthYear: string; + gender: string; + phoneNumber: string; + profileImageFileName: string; + createdAt: Date; + createdBy: string; + modifiedAt: Date; + modifiedBy: string; + }; + address: { + oldAddress: string; + roadAddress: string; + }; + recruitmentCapacity: number; + isScrapped: boolean; + scrapCount: number; + viewCount: number; + createdAt: Date; + createdBy: string; + modifiedAt: Date; + modifiedBy: string; +} diff --git a/src/features/profile/profile.api.ts b/src/features/profile/profile.api.ts index 3c5a523..404ea00 100644 --- a/src/features/profile/profile.api.ts +++ b/src/features/profile/profile.api.ts @@ -10,7 +10,7 @@ import { export const postUserProfile = async (memberId: string) => { const res = await axios.post(`/maru-api/profile`, { - memberId: memberId, + memberId, }); return res.data; @@ -49,7 +49,7 @@ export const getFollowingListData = async () => export const postFollowUser = async (memberId: string) => { await axios .post(`/maru-api/profile/follow`, { - memberId: memberId, + memberId, }) .then(res => res.data); }; @@ -57,14 +57,14 @@ export const postFollowUser = async (memberId: string) => { export const postUnfollowUser = async (memberId: string) => { await axios .post(`/maru-api/profile/unfollow`, { - memberId: memberId, + memberId, }) .then(res => res.data); }; export const postSearchUser = async (email: string) => { const res = await axios.post(`/maru-api/profile/search`, { - email: email, + email, }); return res.data; diff --git a/src/features/shared/shared.api.ts b/src/features/shared/shared.api.ts index 976b72c..e232014 100644 --- a/src/features/shared/shared.api.ts +++ b/src/features/shared/shared.api.ts @@ -1,6 +1,11 @@ import axios from 'axios'; -import { type GetSharedPostDTO, type GetSharedPostsDTO } from './shared.dto'; +import { + type GetDormitorySharedPostDTO, + type GetDormitorySharedPostsDTO, + type GetSharedPostDTO, + type GetSharedPostsDTO, +} from './shared.dto'; import { type CreateSharedPostProps, type GetSharedPostsProps, @@ -43,6 +48,42 @@ export const deleteSharedPost = async (postId: number) => await axios.delete(`/maru-api/shared/posts/studio/${postId}`); export const scrapPost = async (postId: number) => - await axios.get( + await axios.post( `/maru-api/shared/posts/studio/${postId}/scrap`, ); + +export const getDormitorySharedPosts = async ({ + filter, + search, + page, +}: GetSharedPostsProps) => { + const getURI = () => { + const baseURL = '/maru-api/shared/posts/dormitory'; + let query = ''; + + if (search != null) { + query += `&search=${search}`; + } + + query += `&page=${page}`; + + return `${baseURL}?${encodeURI(query)}`; + }; + + return await axios.get(getURI()); +}; + +export const getDormitorySharedPost = async (postId: number) => + await axios.get( + `/maru-api/shared/posts/dormitory/${postId}`, + ); + +export const deleteDormitorySharedPost = async (postId: number) => + await axios.delete( + `/maru-api/shared/posts/dormitory/${postId}`, + ); + +export const scrapDormitoryPost = async (postId: number) => + await axios.post( + `/maru-api/shared/posts/dormitory/${postId}/scrap`, + ); diff --git a/src/features/shared/shared.dto.ts b/src/features/shared/shared.dto.ts index 65997e9..ff0a5f0 100644 --- a/src/features/shared/shared.dto.ts +++ b/src/features/shared/shared.dto.ts @@ -1,4 +1,6 @@ import { + type DormitorySharedPost, + type DormitorySharedPostListItem, type SharedPost, type SharedPostListItem, } from '@/entities/shared-post'; @@ -38,3 +40,38 @@ export interface GetSharedPostsDTO extends SuccessBaseDTO { export interface GetSharedPostDTO extends SuccessBaseDTO { data: SharedPost; } + +export interface GetDormitorySharedPostsDTO extends SuccessBaseDTO { + data: { + content: DormitorySharedPostListItem[]; + pageable: { + pageNumber: number; + pageSize: number; + sort: { + empty: boolean; + unsorted: boolean; + sorted: boolean; + }; + offset: number; + paged: boolean; + unpaged: boolean; + }; + last: boolean; + totalPages: number; + totalElements: number; + first: boolean; + size: number; + number: number; + sort: { + empty: boolean; + unsorted: boolean; + sorted: boolean; + }; + numberOfElements: number; + empty: boolean; + }; +} + +export interface GetDormitorySharedPostDTO extends SuccessBaseDTO { + data: DormitorySharedPost; +} diff --git a/src/features/shared/shared.hook.ts b/src/features/shared/shared.hook.ts index 328ca25..6a0621b 100644 --- a/src/features/shared/shared.hook.ts +++ b/src/features/shared/shared.hook.ts @@ -4,18 +4,24 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { createSharedPost, + deleteDormitorySharedPost, deleteSharedPost, + getDormitorySharedPost, + getDormitorySharedPosts, getSharedPost, getSharedPosts, + scrapDormitoryPost, scrapPost, } from './shared.api'; import { - type ImageFile, type CreateSharedPostProps, type GetSharedPostsProps, + type ImageFile, type SelectedExtraOptions, type SelectedOptions, } from './shared.type'; +import { postUserProfile } from '../profile/profile.api'; +import { type PostUserProfileDTO } from '../profile/profile.dto'; import { useAuthValue } from '@/features/auth'; import { type NaverAddress } from '@/features/geocoding'; @@ -229,7 +235,7 @@ export const usePostMateCardInputSection = () => { const handleEssentialFeatureChange = useCallback( ( key: 'smoking' | 'roomSharingOption' | 'mateAge', - value: string | number, + value: string | number | undefined, ) => { setFeatures(prev => { if (prev[key] === value) { @@ -262,10 +268,10 @@ export const usePostMateCardInputSection = () => { return { smoking: features?.smoking ?? '상관없어요', roomSharingOption: features?.roomSharingOption ?? '상관없어요', - mateAge: features?.mateAge ?? 0, + mateAge: birthYear, options: JSON.stringify(options), }; - }, [features]); + }, [features, birthYear]); const auth = useAuthValue(); useEffect(() => { @@ -380,3 +386,107 @@ export const useScrapSharedPost = () => useMutation, FailureDTO, number>({ mutationFn: scrapPost, }); + +export const useDormitorySharedPosts = ({ + filter, + search, + page, + enabled, +}: GetSharedPostsProps & { enabled: boolean }) => + useQuery({ + queryKey: ['/api/shared/posts/dormitory', { filter, search, page }], + queryFn: async () => + await getDormitorySharedPosts({ filter, search, page }).then( + response => response.data, + ), + staleTime: 60000, + enabled, + }); + +export const useDormitorySharedPost = ({ + postId, + enabled, +}: { + postId: number; + enabled: boolean; +}) => + useQuery({ + queryKey: [`/api/shared/posts/dormitory/${postId}`], + queryFn: async () => + await getDormitorySharedPost(postId).then(response => response.data), + enabled, + }); + +export const useDeleteDormitorySharedPost = ({ + postId, + onSuccess, + onError, +}: { + postId: number; + onSuccess: (data: SuccessBaseDTO) => void; + onError: (error: Error) => void; +}) => + useMutation({ + mutationFn: async () => + await deleteDormitorySharedPost(postId).then(response => response.data), + onSuccess, + onError, + }); + +export const useScrapDormitorySharedPost = () => + useMutation, FailureDTO, number>({ + mutationFn: scrapDormitoryPost, + }); + +const userIds = [ + 'naver_0', + 'kakao_1', + 'kakao_2', + 'naver_3', + 'kakao_4', + 'naver_5', + 'kakao_6', + 'kakao_7', + 'kakao_8', + 'naver_9', + 'naver_10', + 'naver_11', + 'naver_12', + 'naver_13', + 'kakao_14', + 'naver_15', + 'kakao_16', + 'naver_17', + 'naver_18', + 'kakao_19', +]; + +export const useDummyUsers = () => { + const [users, setUsers] = + useState>(); + + useEffect(() => { + (async () => { + const userData = await Promise.allSettled( + userIds.map(async userId => { + const result = await postUserProfile(userId); + return { ...result, userId }; + }), + ); + + setUsers( + userData.reduce>( + (prev, curr) => { + if (curr.status === 'fulfilled') { + prev.push({ ...curr.value, userId: curr.value.userId }); + } + return prev; + }, + [], + ), + ); + })(); + }, []); + + return users; +}; diff --git a/src/features/shared/shared.type.ts b/src/features/shared/shared.type.ts index e0ba8eb..e809f5b 100644 --- a/src/features/shared/shared.type.ts +++ b/src/features/shared/shared.type.ts @@ -76,7 +76,7 @@ export interface CreateSharedPostProps { features: { smoking: string; roomSharingOption: string; - mateAge: number | null; // 0 ~ 10: +- 범위 값, null: 상관 없어요. + mateAge: number | undefined; // 0 ~ 10: +- 범위 값, null: 상관 없어요. options: string; }; };