diff --git a/src/apis/message/apis.ts b/src/apis/message/apis.ts new file mode 100644 index 00000000..392bf9dd --- /dev/null +++ b/src/apis/message/apis.ts @@ -0,0 +1,5 @@ +import type { CreateMessageRoomReq, CreateMessageRoomRes } from './types' +import { http } from '@utils/http' + +export const createMessageRoom = (params: CreateMessageRoomReq) => + http.post('/msgrooms', params) diff --git a/src/apis/message/index.ts b/src/apis/message/index.ts index c9f6f047..d2186f10 100644 --- a/src/apis/message/index.ts +++ b/src/apis/message/index.ts @@ -1 +1,2 @@ export * from './types' +export * from './queries' diff --git a/src/apis/message/queries.ts b/src/apis/message/queries.ts new file mode 100644 index 00000000..4eb3b2ec --- /dev/null +++ b/src/apis/message/queries.ts @@ -0,0 +1,8 @@ +import { useMutation } from '@tanstack/react-query' +import { createMessageRoom } from './apis' +import type { CreateMessageRoomReq } from './types' + +export const useCreateMessageRoomMutation = () => + useMutation({ + mutationFn: (params: CreateMessageRoomReq) => createMessageRoom(params) + }) diff --git a/src/components/post/PriceOfferCard/index.tsx b/src/components/post/PriceOfferCard/index.tsx index f7b5c45e..3ede3209 100644 --- a/src/components/post/PriceOfferCard/index.tsx +++ b/src/components/post/PriceOfferCard/index.tsx @@ -1,17 +1,24 @@ -import { Divider, SelectBox, Text, Icon } from '@offer-ui/react' -import type { ReactElement } from 'react' +import { Divider, SelectBox, Text, Icon, Radio } from '@offer-ui/react' +import { useRouter } from 'next/router' +import type { ChangeEvent, ReactElement } from 'react' import { useEffect, useState } from 'react' import { Styled } from './styled' import type { PriceOfferCardProps } from './types' import { PriceOfferModal } from '../PriceOfferModal' import type { OfferForm } from '../PriceOfferModal/types' import { UserProfile } from '../UserProfile' -import { getTimeDiffText, toLocaleCurrency } from '@utils/format' +import { + getTimeDiffText, + localeCurrencyToNumber, + toLocaleCurrency, + toQueryString +} from '@utils/format' import { useUpdateLikeStatusMutation, useGetPostQuery, useGetPostOffersQuery, - useCreateOfferMutation + useCreateOfferMutation, + useCreateMessageRoomMutation } from '@apis' import { SORT_OPTIONS } from '@constants' import { useModal } from '@hooks' @@ -24,29 +31,33 @@ const PriceOfferCard = ({ const [sortOptionCode, setSortOptionCode] = useState( SORT_OPTIONS[0].code ) - const offerModal = useModal() + const [selectedOffer, setSelectedOffer] = useState(null) const [likePost, setLikePost] = useState({ status: false, count: 0 }) - const postOffersQuery = useGetPostOffersQuery({ + const router = useRouter() + const offerModal = useModal() + + const getPostOffersQuery = useGetPostOffersQuery({ postId, sort: sortOptionCode }) - const postQuery = useGetPostQuery(postId) - const likeStatusMutation = useUpdateLikeStatusMutation() - const offerMutation = useCreateOfferMutation() + const getPostQuery = useGetPostQuery(postId) + const createMessageRoomMutation = useCreateMessageRoomMutation() + const updateLikeStatusMutation = useUpdateLikeStatusMutation() + const createOfferMutation = useCreateOfferMutation() useEffect(() => { setLikePost({ - status: Boolean(postQuery.data?.liked), - count: postQuery.data?.totalLikeCount || 0 + status: Boolean(getPostQuery.data?.liked), + count: getPostQuery.data?.totalLikeCount || 0 }) - }, [postQuery]) + }, [getPostQuery.data]) const offers = - postOffersQuery.data?.offers.map(({ offerer, createdAt, ...offer }) => ({ + getPostOffersQuery.data?.offers.map(({ offerer, createdAt, ...offer }) => ({ ...offerer, level: Number(offerer.level), date: createdAt, @@ -65,7 +76,13 @@ const PriceOfferCard = ({ count: status ? count - 1 : count + 1 })) - await likeStatusMutation.mutateAsync(postId) + await updateLikeStatusMutation.mutateAsync(postId) + } + + const handleChangeOffer = (e: ChangeEvent) => { + const offerId = Number(e.target.value) + + setSelectedOffer(offerId) } const handleClickOffer = async ({ @@ -75,16 +92,29 @@ const PriceOfferCard = ({ }: OfferForm) => { const offerInfo = { postId, - // TODO: post 보내기 merge 후 number로 변환하는 유틸 적용 - price: Number(price) ?? 0, + price: localeCurrencyToNumber(price || '0'), tradeType: tradeType ?? '', location: `${tradeArea?.city} ${tradeArea?.county} ${tradeArea?.town}` } offerModal.closeModal() - await offerMutation.mutateAsync(offerInfo) - postOffersQuery.refetch() + await createOfferMutation.mutateAsync(offerInfo) + getPostOffersQuery.refetch() + } + + const handleClickStartMessage = async () => { + if (selectedOffer) { + const res = await createMessageRoomMutation.mutateAsync({ + offerId: selectedOffer + }) + + router.push( + `/messagebox${toQueryString({ + roomId: res.id + })}` + ) + } } return ( @@ -112,7 +142,10 @@ const PriceOfferCard = ({ {hasOffer ? ( - + {offers.map( ({ id, @@ -123,20 +156,33 @@ const PriceOfferCard = ({ profileImageUrl, tradeType, price - }) => ( - - - {toLocaleCurrency(price)}원 - - ) + }) => { + const isSelected = selectedOffer === id + + return ( + + + + + + {toLocaleCurrency(price)}원 + + + + ) + } )} @@ -159,21 +205,22 @@ const PriceOfferCard = ({ {isSeller ? ( + disabled={!selectedOffer} + size="large" + onClick={handleClickStartMessage}> 쪽지하기 ) : ( { offerModal.openModal() }}>{`가격 제안하기(${ - postOffersQuery.data?.offerCountOfCurrentMember || 0 + getPostOffersQuery.data?.offerCountOfCurrentMember || 0 }/2)`} )} diff --git a/src/components/post/PriceOfferCard/styled.ts b/src/components/post/PriceOfferCard/styled.ts index 8b32dbc1..8fd77e35 100644 --- a/src/components/post/PriceOfferCard/styled.ts +++ b/src/components/post/PriceOfferCard/styled.ts @@ -1,6 +1,11 @@ import { css } from '@emotion/react' import styled from '@emotion/styled' -import { Button, Divider as DividerComponent, Text } from '@offer-ui/react' +import { + Button, + Divider as DividerComponent, + Radio, + Text +} from '@offer-ui/react' const OfferPriceCardWrapper = styled.div` ${({ theme }) => { @@ -29,7 +34,7 @@ const OfferPriceCardWrapper = styled.div` }} ` -const OfferListBox = styled.div` +const OfferListBox = styled(Radio)` display: flex; flex-direction: column; gap: 8px; @@ -88,16 +93,17 @@ const BlankCard = styled.div` height: 120px; padding: 20px 0; ` -const Offer = styled.div` +const Offer = styled(Radio.Label)<{ isSelected: boolean }>` display: flex; align-items: center; - justify-content: space-between; padding: 20px; - border: ${({ theme }): string => `solid 1px ${theme.colors.grayScale10}`}; border-radius: ${({ theme }): string => theme.radius.round6}; - ${({ theme }): string => ` + ${({ theme, isSelected }) => css` + border: solid 1px + ${isSelected ? theme.colors.brandPrimary : theme.colors.grayScale10}; + ${theme.mediaQuery.tablet} { padding: 16px; border: none; @@ -110,6 +116,14 @@ const Offer = styled.div` `} ` +const OfferContent = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + + width: 100%; +` + const CardBody = styled.div` height: 564px; padding: 20px 16px; @@ -191,6 +205,7 @@ export const Styled = { Divider, BlankCard, Offer, + OfferContent, CardBody, CardFooter, MessageButton, diff --git a/src/pages/post/[postId]/index.tsx b/src/pages/post/[postId]/index.tsx index 2912bad6..902b4a9d 100644 --- a/src/pages/post/[postId]/index.tsx +++ b/src/pages/post/[postId]/index.tsx @@ -11,8 +11,8 @@ import { } from '@offer-ui/react' import type { GetServerSideProps } from 'next' import type { ReactElement } from 'react' -import { useGetPostQuery } from '@apis/post' import { getTimeDiffText, toLocaleCurrency } from '@utils/format' +import { useGetPostQuery } from '@apis' import { UserProfile, PriceOfferCard, PostFieldList } from '@components' import { TRADE_STATUS } from '@constants' import { useAuth, useModal } from '@hooks' @@ -27,84 +27,82 @@ export const getServerSideProps: GetServerSideProps = async ({ }) const PostDetailPage = ({ postId }: Props): ReactElement => { - const postQuery = useGetPostQuery(postId) + const getPostQuery = useGetPostQuery(postId) const { user } = useAuth() const imageModal = useModal() - const isSeller = user.id === postQuery.data?.seller.id - const postImages = postQuery.data?.postImages || [] + const isSeller = user.id === getPostQuery.data?.seller.id + const postImages = getPostQuery.data?.postImages || [] return ( - <> - -
-
- + +
+
+ +
+ +
+ + {isSeller ? ( + <> + { + // do something + }} + /> + + + ) : ( + + {getPostQuery.data?.tradeStatus.code} + + )} + + + {getPostQuery.data?.category.name || ''} + + + {getPostQuery.data?.title || ''} + + + {toLocaleCurrency(Number(getPostQuery.data?.price))} + +
- -
- - {isSeller ? ( - <> - { - // do something - }} - /> - - - ) : ( - - {postQuery.data?.tradeStatus.code} - - )} - - - {postQuery.data?.category.name || ''} - - - {postQuery.data?.title || ''} - - - {toLocaleCurrency(Number(postQuery.data?.price))} - - -
- -
- 상품 정보 - - - - {postQuery.data?.description} -
- - -
-
- - -
+ +
+ 상품 정보 + + + + {getPostQuery.data?.description} +
+ + + +
+ + - +
) } diff --git a/src/pages/post/index.tsx b/src/pages/post/index.tsx index 3a011ddc..25ec3d06 100644 --- a/src/pages/post/index.tsx +++ b/src/pages/post/index.tsx @@ -112,7 +112,7 @@ const PostPage = (): ReactElement => { thumbnailImageUrl }) - router.replace(`/post/${res.id}`) + router.push(`/post/${res.id}`) } return (