diff --git a/apps/jurumarble/public/images/index.ts b/apps/jurumarble/public/images/index.ts index 54780de5..8aa56547 100644 --- a/apps/jurumarble/public/images/index.ts +++ b/apps/jurumarble/public/images/index.ts @@ -9,3 +9,4 @@ export { default as DrinkCapacityLow } from './DrinkCapacityLow.png'; export { default as DrinkCapacityMedium } from './DrinkCapacityMedium.png'; export { default as DrinkCapacityHigh } from './DrinkCapacityHigh.png'; export { default as ImgScroll } from './ImgScroll.png'; +export { default as restaurantImg } from './restaurantImg.png'; diff --git a/apps/jurumarble/public/images/restaurantImg.png b/apps/jurumarble/public/images/restaurantImg.png new file mode 100644 index 00000000..5007c49c Binary files /dev/null and b/apps/jurumarble/public/images/restaurantImg.png differ diff --git a/apps/jurumarble/src/app/drink-info/[id]/components/DrinkCommentContainer.tsx b/apps/jurumarble/src/app/drink-info/[id]/components/DrinkCommentContainer.tsx index 8d735df4..806c3a55 100644 --- a/apps/jurumarble/src/app/drink-info/[id]/components/DrinkCommentContainer.tsx +++ b/apps/jurumarble/src/app/drink-info/[id]/components/DrinkCommentContainer.tsx @@ -111,6 +111,7 @@ function DrinkCommentContainer() { mutateLike={() => mutateLike(id)} mutateHate={() => mutateHate(id)} key={`comment_id_${index}`} + region="" /> ), )} diff --git a/apps/jurumarble/src/app/drink-info/[id]/services/useDrinkLoadService.ts b/apps/jurumarble/src/app/drink-info/[id]/services/useDrinkLoadService.ts index 225df315..941b296b 100644 --- a/apps/jurumarble/src/app/drink-info/[id]/services/useDrinkLoadService.ts +++ b/apps/jurumarble/src/app/drink-info/[id]/services/useDrinkLoadService.ts @@ -1,10 +1,17 @@ import { useQuery } from '@tanstack/react-query'; import { getDrinkInfo } from 'lib/apis/drink'; -import { reactQueryKeys } from 'lib/queryKeys'; +import { queryKeys } from 'lib/queryKeys'; -export default function useDrinkLoadService(id: number) { +type GetDrinkInfoProps = Exclude[0], undefined>; + +const getQueryKey = (params: GetDrinkInfoProps) => [ + queryKeys.DRINK_INFO, + params, +]; + +export default function useDrinkLoadService(id: GetDrinkInfoProps) { const { data, isLoading, isError } = useQuery( - reactQueryKeys.drinksInfo(id), + getQueryKey(id), () => getDrinkInfo(id), { enabled: !!id, diff --git a/apps/jurumarble/src/app/my/components/ChipContainer.tsx b/apps/jurumarble/src/app/my/components/ChipContainer.tsx index c542281d..18e83516 100644 --- a/apps/jurumarble/src/app/my/components/ChipContainer.tsx +++ b/apps/jurumarble/src/app/my/components/ChipContainer.tsx @@ -1,5 +1,6 @@ import { UseMutateFunction } from '@tanstack/react-query'; import Chip from 'components/Chip'; +import { formatDate } from 'lib/utils/formatDate'; import SvgIcBookmark from 'src/assets/icons/components/IcBookmark'; import SvgIcBookmarkActive from 'src/assets/icons/components/IcBookmarkActive'; import styled from 'styled-components'; @@ -10,21 +11,24 @@ interface Props { region: string; mutateBookMark: UseMutateFunction; isBookmark: boolean; + votedCount: number; + createdAt: string; } const ChipContainer = ({ - date, title, region, mutateBookMark, isBookmark, + votedCount, + createdAt, }: Props) => { return ( <> {region && {region}} - 122명이 즐겼어요 + {votedCount}명이 참여중 {isBookmark ? ( @@ -48,11 +52,8 @@ const ChipContainer = ({ )} - - {title} - {/* {date.slice(0, 10)} */} - - {date} + {title} + {formatDate(createdAt)} ); }; diff --git a/apps/jurumarble/src/app/my/components/VoteItem.tsx b/apps/jurumarble/src/app/my/components/VoteItem.tsx index d33c0074..3e976484 100644 --- a/apps/jurumarble/src/app/my/components/VoteItem.tsx +++ b/apps/jurumarble/src/app/my/components/VoteItem.tsx @@ -7,9 +7,26 @@ import styled, { css } from 'styled-components'; import ChipContainer from './ChipContainer'; import VoteDescription from './VoteDescription'; -type Props = Pick; +type Props = Pick< + Content, + | 'voteId' + | 'region' + | 'title' + | 'imageA' + | 'imageB' + | 'votedCount' + | 'createdAt' +>; -function VoteItem({ voteId, region, title, imageA, imageB }: Props) { +function VoteItem({ + voteId, + region, + title, + imageA, + imageB, + votedCount, + createdAt, +}: Props) { const { isBookmark, mutateBookMark } = useBookmarkService(voteId); const router = useRouter(); @@ -25,6 +42,8 @@ function VoteItem({ voteId, region, title, imageA, imageB }: Props) { region={region} mutateBookMark={mutateBookMark} isBookmark={isBookmark} + votedCount={votedCount} + createdAt={createdAt} /> diff --git a/apps/jurumarble/src/app/my/components/VoteListContainer.tsx b/apps/jurumarble/src/app/my/components/VoteListContainer.tsx index 36c5087d..d8683ca3 100644 --- a/apps/jurumarble/src/app/my/components/VoteListContainer.tsx +++ b/apps/jurumarble/src/app/my/components/VoteListContainer.tsx @@ -47,16 +47,28 @@ function VoteListContainer() { /> - {myVoteList.map(({ voteId, region, title, imageA, imageB }) => ( - - ))} + {myVoteList.map( + ({ + voteId, + region, + title, + imageA, + imageB, + votedCount, + createdAt, + }) => ( + + ), + )}
diff --git a/apps/jurumarble/src/app/notification/page.tsx b/apps/jurumarble/src/app/notification/page.tsx index df2ff3ca..be4c649e 100644 --- a/apps/jurumarble/src/app/notification/page.tsx +++ b/apps/jurumarble/src/app/notification/page.tsx @@ -2,6 +2,7 @@ import VoteHeader from 'components/VoteHeader'; import { Button } from 'components/button'; +import Path from 'lib/Path'; import { NotificationType } from 'lib/apis/notification'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; @@ -49,24 +50,29 @@ function NotificationPage() { ) : ( - {notificationList.map(({ content, createdAt, id, type, isRead }) => ( - readNotification(id)} - > - - - {content} - {NOTIFICATION_TYPE[type]} - {createdAt} - - - ))} + {notificationList.map( + ({ content, createdAt, id, type, isRead, url }) => ( + { + router.push(`${Path.VOTE_DETAIL_PAGE}/${url}`); + readNotification(id); + }} + > + + + {content} + {NOTIFICATION_TYPE[type]} + {createdAt} + + + ), + )} )} @@ -100,6 +106,7 @@ const NotificationItem = styled.li<{ isRead: boolean }>` display: flex; align-items: center; padding: 16px 20px; + cursor: pointer; `} `; diff --git a/apps/jurumarble/src/app/notification/services/useNotificationService.ts b/apps/jurumarble/src/app/notification/services/useNotificationService.ts index fd952bac..553dd5cd 100644 --- a/apps/jurumarble/src/app/notification/services/useNotificationService.ts +++ b/apps/jurumarble/src/app/notification/services/useNotificationService.ts @@ -5,6 +5,7 @@ import { readNotificationAPI, } from 'lib/apis/notification'; import { queryKeys } from 'lib/queryKeys'; +import { formatDate } from 'lib/utils/formatDate'; type ReadNotificationProps = Exclude< Parameters[0], @@ -19,15 +20,10 @@ export default function useNotificationService() { getNotificationListAPI, { select: (data) => - data.map((notification) => { - const createdAtDate = new Date(notification.createdAt); - return { - ...notification, - createdAt: `${createdAtDate.getFullYear() - 2000}. ${ - createdAtDate.getMonth() + 1 - }. ${createdAtDate.getDate()}`, - }; - }), + data.map((notification) => ({ + ...notification, + createdAt: formatDate(notification.createdAt), + })), }, ); diff --git a/apps/jurumarble/src/app/search/components/SearchContainer.tsx b/apps/jurumarble/src/app/search/components/SearchContainer.tsx index cc96fba6..c9494b70 100644 --- a/apps/jurumarble/src/app/search/components/SearchContainer.tsx +++ b/apps/jurumarble/src/app/search/components/SearchContainer.tsx @@ -10,7 +10,7 @@ import { Button } from 'components/button'; import { useCreateQueryString } from 'hooks/useCreateQueryString'; import { DRINK_INFO_SORT_LIST, DRINK_VOTE_SORT_LIST } from 'lib/constants'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import SvgIcPrev from 'src/assets/icons/components/IcPrev'; +import { SvgIcPrevious } from 'src/assets/icons/components'; import { DrinkInfoSortType, RegionType, VoteSortType } from 'src/types/common'; import styled, { css, DefaultTheme } from 'styled-components'; @@ -202,8 +202,14 @@ function SearchContainer() { <> - - + + {TAB_LIST.map(({ id, name }) => ( @@ -241,6 +247,7 @@ const SearchBox = styled.div` display: flex; align-items: center; gap: 14px; + margin-top: 8px; `; const TabBox = styled.ul` diff --git a/apps/jurumarble/src/app/stamp/components/DrinkStampContainer.tsx b/apps/jurumarble/src/app/stamp/components/DrinkStampContainer.tsx index 2c8f47d3..6d81cc39 100644 --- a/apps/jurumarble/src/app/stamp/components/DrinkStampContainer.tsx +++ b/apps/jurumarble/src/app/stamp/components/DrinkStampContainer.tsx @@ -15,7 +15,7 @@ function DrinkStampContainer() { setRegionOption(value); }; - const { drinkList, subscribe, numberOfStampedDrinks } = useDrinkStampService({ + const { drinkList, subscribe, enjoyedDrinkCount } = useDrinkStampService({ page: 0, size: 10, region: regionOption, @@ -23,9 +23,7 @@ function DrinkStampContainer() { return ( <> - +

우리술 도장을
- {numberOfStampedDrinks}개 모았어요. + {enjoyedDrinkCount}개 모았어요.

diff --git a/apps/jurumarble/src/app/stamp/service/useDrinkStampListService.ts b/apps/jurumarble/src/app/stamp/service/useDrinkStampListService.ts index 11018a63..972c107f 100644 --- a/apps/jurumarble/src/app/stamp/service/useDrinkStampListService.ts +++ b/apps/jurumarble/src/app/stamp/service/useDrinkStampListService.ts @@ -22,7 +22,8 @@ export default function useDrinkStampListService(params: DrinkStampListProps) { page: pageParam?.page || params.page, }), { - getNextPageParam: ({ last, number }) => { + getNextPageParam: ({ drinkData }) => { + const { last, number } = drinkData; if (last) { return undefined; } @@ -34,11 +35,11 @@ export default function useDrinkStampListService(params: DrinkStampListProps) { }, ); - const numberOfStampedDrinks = data?.pages[0].numberOfElements ?? 0; + const enjoyedDrinkCount = data?.pages[0].enjoyedDrinkCount ?? 0; - const drinkList = data?.pages.flatMap((page) => page.content) ?? []; + const drinkList = data?.pages.flatMap((page) => page.drinkData.content) ?? []; const [subscribe] = useInfiniteScroll(fetchNextPage); - return { drinkList, fetchNextPage, subscribe, numberOfStampedDrinks }; + return { drinkList, fetchNextPage, subscribe, enjoyedDrinkCount }; } diff --git a/apps/jurumarble/src/app/vote/[id]/components/ChipContainer.tsx b/apps/jurumarble/src/app/vote/[id]/components/ChipContainer.tsx index ac42c899..66f9cd85 100644 --- a/apps/jurumarble/src/app/vote/[id]/components/ChipContainer.tsx +++ b/apps/jurumarble/src/app/vote/[id]/components/ChipContainer.tsx @@ -4,7 +4,6 @@ import ModifyDeleteButtonBox from 'app/vote/components/MenuBox'; import NonWriterBox from 'app/vote/components/NonWriterBox'; import Chip from 'components/Chip'; import Path from 'lib/Path'; -import { AorB } from 'lib/apis/vote'; import { isLogin } from 'lib/utils/auth'; import { formatDate } from 'lib/utils/formatDate'; import { useRouter } from 'next/navigation'; @@ -27,7 +26,7 @@ interface Props { isBookmark: boolean; postedUserId: number; voteId: number; - select: AorB | null; + votedCount: number; onToggleReplaceLoginPageModal: () => void; } @@ -40,7 +39,7 @@ const ChipContainer = ({ mutateBookMark, isBookmark, postedUserId, - select, + votedCount, onToggleReplaceLoginPageModal, }: Props) => { const { userInfo } = useGetUserInfo(); @@ -62,7 +61,7 @@ const ChipContainer = ({ {region && {region}} - {select && 참여중} + {votedCount ?? 0}명이 참여중 {isBookmark ? ( diff --git a/apps/jurumarble/src/app/vote/[id]/components/Comment.tsx b/apps/jurumarble/src/app/vote/[id]/components/Comment.tsx index 12dc8361..45ffeeb8 100644 --- a/apps/jurumarble/src/app/vote/[id]/components/Comment.tsx +++ b/apps/jurumarble/src/app/vote/[id]/components/Comment.tsx @@ -26,6 +26,7 @@ import useCommentReportService from '../services/useCommentReportService'; interface Props { voteType: 'drinks' | 'votes'; postId: number; + region: string; comment: { id: number; content: string; @@ -48,7 +49,14 @@ interface Props { mutateHate?(): void; } -function Comment({ comment, mutateLike, mutateHate, voteType, postId }: Props) { +function Comment({ + comment, + mutateLike, + mutateHate, + voteType, + postId, + region, +}: Props) { const { userInfo } = useGetUserInfo(); const { mutate } = useCommentReportService(); const { @@ -254,6 +262,7 @@ function Comment({ comment, mutateLike, mutateHate, voteType, postId }: Props) { )} diff --git a/apps/jurumarble/src/app/vote/[id]/components/CommentContainer.tsx b/apps/jurumarble/src/app/vote/[id]/components/CommentContainer.tsx index f2631982..916b68ba 100644 --- a/apps/jurumarble/src/app/vote/[id]/components/CommentContainer.tsx +++ b/apps/jurumarble/src/app/vote/[id]/components/CommentContainer.tsx @@ -12,9 +12,10 @@ import useCommentServices from '../services/useCommentServices'; interface Props { postId: number; + region: string; } -function CommentContainer({ postId }: Props) { +function CommentContainer({ postId, region }: Props) { const queryClient = useQueryClient(); const [sortBy, setSortBy] = useState<'ByTime' | 'ByPopularity'>('ByTime'); const onChangeFilter = (sort: 'ByTime' | 'ByPopularity') => { @@ -114,6 +115,7 @@ function CommentContainer({ postId }: Props) { mutateLike={() => mutateLike(id)} mutateHate={() => mutateHate(id)} key={`comment_id_${index}`} + region={region} /> ), )} diff --git a/apps/jurumarble/src/app/vote/[id]/components/DetailChipContainer.tsx b/apps/jurumarble/src/app/vote/[id]/components/DetailChipContainer.tsx index aef39ef6..4464f581 100644 --- a/apps/jurumarble/src/app/vote/[id]/components/DetailChipContainer.tsx +++ b/apps/jurumarble/src/app/vote/[id]/components/DetailChipContainer.tsx @@ -4,7 +4,6 @@ import ModifyDeleteButtonBox from 'app/vote/components/MenuBox'; import NonWriterBox from 'app/vote/components/NonWriterBox'; import Chip from 'components/Chip'; import Path from 'lib/Path'; -import { AorB } from 'lib/apis/vote'; import { isLogin } from 'lib/utils/auth'; import { formatDate } from 'lib/utils/formatDate'; import { useRouter } from 'next/navigation'; @@ -27,8 +26,8 @@ interface Props { isBookmark: boolean; postedUserId: number; voteId: number; - select: AorB | null; onToggleReplaceLoginPageModal: () => void; + votedCount: number; } const ChipContainer = ({ @@ -40,7 +39,7 @@ const ChipContainer = ({ mutateBookMark, isBookmark, postedUserId, - select, + votedCount, onToggleReplaceLoginPageModal, }: Props) => { const { userInfo } = useGetUserInfo(); @@ -62,7 +61,7 @@ const ChipContainer = ({ {region && {region}} - {select && 참여중} + {votedCount}명이 참여중 {isBookmark ? ( diff --git a/apps/jurumarble/src/app/vote/[id]/components/RestaurantItem.tsx b/apps/jurumarble/src/app/vote/[id]/components/RestaurantItem.tsx index 72b925cc..632c629d 100644 --- a/apps/jurumarble/src/app/vote/[id]/components/RestaurantItem.tsx +++ b/apps/jurumarble/src/app/vote/[id]/components/RestaurantItem.tsx @@ -1,5 +1,6 @@ import { RestaurantInfo } from 'lib/apis/restaurant'; import Image from 'next/image'; +import { restaurantImg } from 'public/images'; import styled, { css } from 'styled-components'; interface Props { @@ -11,7 +12,16 @@ function RestaurantItem({ restaurantInfo, onClickSelectedRestaurant }: Props) { const { restaurantName, treatMenu, restaurantImage } = restaurantInfo; return ( onClickSelectedRestaurant(restaurantInfo)}> - 음식점 이미지 + {restaurantImage ? ( + 음식점 이미지 + ) : ( + 음식점 이미지 + )} {restaurantName} diff --git a/apps/jurumarble/src/app/vote/[id]/components/SearchRestaurantModal.tsx b/apps/jurumarble/src/app/vote/[id]/components/SearchRestaurantModal.tsx index 571683cb..7dce7a6b 100644 --- a/apps/jurumarble/src/app/vote/[id]/components/SearchRestaurantModal.tsx +++ b/apps/jurumarble/src/app/vote/[id]/components/SearchRestaurantModal.tsx @@ -6,6 +6,7 @@ import VoteHeader from 'components/VoteHeader'; import { Button, ModalTemplate } from 'components/index'; import useInput from 'hooks/useInput'; import { RestaurantInfo } from 'lib/apis/restaurant'; +import { REGION_LIST } from 'lib/constants'; import { transitions } from 'lib/styles'; import Image from 'next/image'; import SvgIcX from 'src/assets/icons/components/IcX'; @@ -19,12 +20,14 @@ import useRestaurantService from '../services/useRestaurantService'; interface Props { commentId: number; postId: number; + region: string; onToggleSearchRestaurantModal: () => void; } function SearchRestaurantModal({ commentId, postId, + region, onToggleSearchRestaurantModal, }: Props) { const [selectedRestaurant, setSelectedRestaurant] = @@ -43,12 +46,13 @@ function SearchRestaurantModal({ }, ); + const regionValue = REGION_LIST.find((item) => item.label === region)?.value; const { restaurantList, subscribe } = useRestaurantService({ commentId, commentType: 'votes', keyword: searchText, page: 1, - region: 'ALL', + region: regionValue, typeId: postId, }); diff --git a/apps/jurumarble/src/app/vote/[id]/page.tsx b/apps/jurumarble/src/app/vote/[id]/page.tsx index afbab3fe..20a40d56 100644 --- a/apps/jurumarble/src/app/vote/[id]/page.tsx +++ b/apps/jurumarble/src/app/vote/[id]/page.tsx @@ -124,6 +124,7 @@ function Detail() { postedUserNickname, postedUserAlcoholLimit, createdAt, + votedCount, } = data; const { percentageA, percentageB, totalCountA, totalCountB } = statistics; @@ -155,7 +156,7 @@ function Detail() { mutateBookMark={mutateBookMark} isBookmark={isBookmark} postedUserId={data.postedUserId} - select={select.choice} + votedCount={votedCount} onToggleReplaceLoginPageModal={onToggleReplaceLoginPageModal} /> )} - + {isReplaceLoginPageModal && ( ({ choice: null, @@ -16,6 +18,7 @@ export default function useExecuteVoteService(voteId: number) { queryClient.invalidateQueries([queryKeys.VOTE_DETAIL]); queryClient.invalidateQueries([queryKeys.VOTING_CHECK]); queryClient.invalidateQueries([queryKeys.DETAIL_FILTERED_ANALYSIS]); + queryClient.invalidateQueries(getMyParticipateVotedQueryKey); }, }, ); diff --git a/apps/jurumarble/src/app/vote/[id]/update/components/UpdateVoteContainer.tsx b/apps/jurumarble/src/app/vote/[id]/update/components/UpdateVoteContainer.tsx index 5937e21f..7e522420 100644 --- a/apps/jurumarble/src/app/vote/[id]/update/components/UpdateVoteContainer.tsx +++ b/apps/jurumarble/src/app/vote/[id]/update/components/UpdateVoteContainer.tsx @@ -1,171 +1,111 @@ 'use client'; -import { useMemo, useState } from 'react'; +import { useMemo } from 'react'; -import { useToggle } from '@monorepo/hooks'; -import DrinkSearchModal from 'app/vote/post/components/DrinkSearchModal'; -import PostBottomSheet from 'app/vote/post/components/PostBottomSheet'; +import { media } from '@monorepo/ui/styles/media'; import TitleAndDescriptionSection from 'app/vote/post/components/TitleAndDescriptionSection'; import AorBMark from 'components/AorBMark'; -import ImageUploadButton from 'components/ImageUploadButton'; -import VoteHeader from 'components/VoteHeader'; -import { Button, Input } from 'components/index'; +import { Input } from 'components/index'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; -import SvgIcPrevious from 'src/assets/icons/components/IcPrevious'; -import { DrinkInfoType } from 'src/types/drink'; +import { DrinkImage } from 'public/images'; import styled, { css } from 'styled-components'; import useUpdateVoteForm from '../hook/useUpdataVoteForm'; -const STEP_ONE = 1; -const STEP_TWO = 2; - function UpdateVoteContainer() { - const [isDrinkSearchModal, onToggleDrinkSearchModal] = useToggle(); - const router = useRouter(); - - const [postStep, setPostStep] = useState(STEP_ONE); - const onChangePostStep = () => { - setPostStep((prev) => prev + 1); - }; - - const { - onChangeVoteText, - postVoteInfo, - onUploadImage, - onClickPostVoteComplete, - updatePostVoteInfo, - } = useUpdateVoteForm(); + const { onChangeVoteText, postVoteInfo, onClickPostVoteComplete } = + useUpdateVoteForm(); const isCompleted = useMemo(() => { return !postVoteInfo.titleA || !postVoteInfo.titleB; }, [postVoteInfo]); - const onClickSearchDrinkComplete = (selectedDrinkList: DrinkInfoType[]) => { - onToggleDrinkSearchModal(); - updatePostVoteInfo(selectedDrinkList); - onChangePostStep(); - }; - const { title, detail, titleA, titleB, imageA, imageB } = postVoteInfo; return ( - router.back()}> - - - } - > - 수정하기 - - 고민되는 술을 선택해주세요 - 안내문구 안내문구 영역입니다. 안내문구 영역 - + +
+ + 수정하실 제목과
내용을 등록해주세요. +
+ 등록된 후보는 수정할 수 없어요. +
+
+ + + + A이미지 + A + + + B이미지 + B + + + - {postStep === STEP_TWO && ( - - )} - {postStep === STEP_ONE && ( - - )} - {isDrinkSearchModal && ( - - )} +
); } const Container = styled.div` - padding: 0 20px; + padding: 0 20px 20px 20px; `; -const PreviousButton = styled(Button)` - ${({ theme }) => css` - background-color: ${theme.colors.white}; - `} +const FlexBetween = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 24px; `; const GuideText = styled.div` @@ -173,23 +113,27 @@ const GuideText = styled.div` css` ${theme.typography.body01} color: ${theme.colors.black_02}; - margin-top: 24px; `} `; +const Br = styled.br` + ${media.medium} { + display: none; + } +`; + const SubText = styled.div` ${({ theme }) => css` ${theme.typography.body03} color: ${theme.colors.black_03}; - margin-top: 8px; + margin-top: 11px; + display: flex; + align-items: center; + gap: 4px; `} `; -const ImageUploadInput = styled.input` - display: none; -`; - const ImageSection = styled.section` margin-top: 24px; cursor: pointer; @@ -210,14 +154,6 @@ const ImageWrapper = styled.div` `} `; -const ImageBox = styled.div` - ${({ theme }) => css` - background-color: ${theme.colors.black_05}; - border-radius: 10px; - height: 100%; - `} -`; - const VoteOptionText = styled.div` margin-top: 12px; display: flex; @@ -226,12 +162,16 @@ const VoteOptionText = styled.div` gap: 16px; `; -const ABInput = styled(Input)` - ${({ theme }) => +const ABInput = styled(Input)<{ AorB: 'A' | 'B' }>` + ${({ theme, AorB }) => css` ${theme.typography.body_long03} color: ${theme.colors.black_04}; border-bottom: 1px solid ${theme.colors.line_01}; + :focus { + border-bottom: 1px solid + ${AorB === 'A' ? theme.colors.sub_01 : theme.colors.sub_02}; + } `} `; diff --git a/apps/jurumarble/src/app/vote/[id]/update/hook/useUpdataVoteForm.ts b/apps/jurumarble/src/app/vote/[id]/update/hook/useUpdataVoteForm.ts index 0e6ad01e..ea01c055 100644 --- a/apps/jurumarble/src/app/vote/[id]/update/hook/useUpdataVoteForm.ts +++ b/apps/jurumarble/src/app/vote/[id]/update/hook/useUpdataVoteForm.ts @@ -1,19 +1,34 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import Path from 'lib/Path'; -import { uploadImageAPI } from 'lib/apis/common'; -import { postDrinkVoteAPI, postNormalVoteAPI } from 'lib/apis/vote'; +import { modifyDrinkVoteAPI, modifyNormalVoteAPI } from 'lib/apis/vote'; +import { queryKeys, reactQueryKeys } from 'lib/queryKeys'; import { useParams, useRouter } from 'next/navigation'; import { DrinkInfoType } from 'src/types/drink'; import { PostVoteType } from 'src/types/vote'; import useVoteLoadService from '../../services/useVoteLoadService'; +type modifyNormalVoteProps = Exclude< + Parameters[0], + undefined +>; + +type modifyDrinkVoteProps = Exclude< + Parameters[0], + undefined +>; + +const getMyCreatedVoteQueryKey = [queryKeys.MY_CREATED_VOTE]; + export default function useUpdateVoteForm() { const router = useRouter(); + const queryClient = useQueryClient(); + const params = useParams(); - const { data } = useVoteLoadService(Number(params.id)); + const voteId = Number(params.id); + const { data } = useVoteLoadService(voteId); const [postVoteInfo, setPostVoteInfo] = useState({ detail: '', @@ -30,7 +45,6 @@ export default function useUpdateVoteForm() { if (!data) { return; } - console.log(data); setPostVoteInfo({ detail: data.detail, drinkAId: data.drinkAId, @@ -43,8 +57,7 @@ export default function useUpdateVoteForm() { }); }, [data]); - const { title, detail, titleA, titleB, imageA, imageB, drinkAId, drinkBId } = - postVoteInfo; + const { title, detail, titleA, titleB, drinkAId, drinkBId } = postVoteInfo; const onChangeVoteText = ( e: React.ChangeEvent, @@ -71,112 +84,49 @@ export default function useUpdateVoteForm() { })); }; - const onUploadImage = useCallback( - async (e: React.ChangeEvent) => { - if (e.target.files === null) { - return; - } - - if (e.target.files.length === 2) { - if ( - e.target.files[0].size > 10485760 || - e.target.files[1].size > 10485760 - ) { - alert('파일 용량이 10MB를 초과하였습니다.'); - return; - } - const formDataA = new FormData(); - const formDataB = new FormData(); - formDataA.append('images', e.target.files[0]); - formDataB.append('images', e.target.files[1]); - try { - const dataA = await uploadImageAPI(formDataA); - const dataB = await uploadImageAPI(formDataB); - - setPostVoteInfo({ - ...postVoteInfo, - imageA: dataA.imageUrl, - imageB: dataB.imageUrl, - }); - } catch (error) { - alert(`이미지 업로드에 실패했습니다.${error}`); - } - return; - } - - if (e.target.files.length === 1) { - if (e.target.files[0].size > 10485760) { - alert('파일 용량이 10MB를 초과하였습니다.'); - return; - } - const formDataA = new FormData(); - formDataA.append('images', e.target.files[0]); - try { - const dataA = await uploadImageAPI(formDataA); - - setPostVoteInfo({ - ...postVoteInfo, - imageA: dataA.imageUrl, - imageB: '', - }); - } catch (error) { - alert(`이미지 업로드에 실패했습니다.${error}`); - } - return; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, - [], - ); - - const { mutate: mutateNomalVote } = useMutation( - (voteInfo: Omit) => - postNormalVoteAPI(voteInfo), + const { mutate: mutateDrinkVote } = useMutation( + (voteInfo: modifyDrinkVoteProps) => modifyDrinkVoteAPI(voteInfo), { onSuccess: () => { - router.push(`${Path.VOTE_HOME}/?isSuccess=true`); + queryClient.invalidateQueries([queryKeys.VOTE_LIST]); + queryClient.invalidateQueries(getMyCreatedVoteQueryKey); + queryClient.invalidateQueries(reactQueryKeys.voteDetail(voteId)); + router.push(Path.VOTE_HOME); }, }, ); - const { mutate: mutateDrinkVote } = useMutation( - (voteInfo: Omit) => - postDrinkVoteAPI(voteInfo), + const { mutate: mutateNomalVote } = useMutation( + (voteInfo: modifyNormalVoteProps) => modifyNormalVoteAPI(voteInfo), { onSuccess: () => { - router.push(`${Path.VOTE_HOME}/?isSuccess=true`); + queryClient.invalidateQueries([queryKeys.VOTE_LIST]); + queryClient.invalidateQueries(getMyCreatedVoteQueryKey); + router.push(Path.VOTE_HOME); }, }, ); - const guidePostVote = () => { - if (title === '') { - alert('제목을 입력해주세요.'); - return; - } - if (detail === '') { - alert('설명을 입력해주세요.'); - return; - } - if (titleA === '') { - alert('선택지 A를 입력해주세요.'); - return; - } - if (titleB === '') { - alert('선택지 B를 입력해주세요.'); - return; - } - }; const onClickPostVoteComplete = () => { - guidePostVote(); - postVoteInfo.drinkAId === 0 - ? mutateNomalVote({ detail, imageA, imageB, title, titleA, titleB }) - : mutateDrinkVote({ title, detail, drinkAId, drinkBId }); + !postVoteInfo.drinkAId + ? mutateNomalVote({ + detail, + title, + titleA, + titleB, + voteId: voteId, + }) + : mutateDrinkVote({ + title, + detail, + drinkAId, + drinkBId, + voteId: voteId, + }); }; return { postVoteInfo, onChangeVoteText, - onUploadImage, onClickPostVoteComplete, updatePostVoteInfo, }; diff --git a/apps/jurumarble/src/app/vote/layout.tsx b/apps/jurumarble/src/app/vote/layout.tsx index 94e96495..669dd645 100644 --- a/apps/jurumarble/src/app/vote/layout.tsx +++ b/apps/jurumarble/src/app/vote/layout.tsx @@ -26,7 +26,11 @@ export default function Layout({ children }: PropsWithChildren) { } > - {pathname === Path.POST_PAGE ? '투표 등록' : '상세페이지'} + {pathname === Path.POST_PAGE + ? '투표 등록' + : pathname.includes('update') + ? '투표 수정' + : '상세페이지'} )} {children} diff --git a/apps/jurumarble/src/app/vote/page.tsx b/apps/jurumarble/src/app/vote/page.tsx index 294f1e13..7b143bdf 100644 --- a/apps/jurumarble/src/app/vote/page.tsx +++ b/apps/jurumarble/src/app/vote/page.tsx @@ -63,6 +63,7 @@ function VoteHomePage() { voteType, drinkAId, drinkBId, + votedCount, } = mainVoteList[nowShowing] || {}; const { isBookmark, mutateBookMark } = useBookmarkService(voteId); @@ -166,7 +167,7 @@ function VoteHomePage() { description={detail} mutateBookMark={mutateBookMark} isBookmark={isBookmark} - select={select.choice} + votedCount={votedCount} onToggleReplaceLoginPageModal={onToggleReplaceLoginPageModal} /> selectedDrink.id === drinkInfo.id; @@ -31,8 +31,8 @@ function DrinkItem({ drinkInfo, onClickAddDrink, selectedDrinkList }: Props) { {manufacturer} - 서울 - 213명이 즐겼어요 + {region} + {enjoyCount}명이 즐겼어요 diff --git a/apps/jurumarble/src/app/vote/post/components/DrinkSearchModal.tsx b/apps/jurumarble/src/app/vote/post/components/DrinkSearchModal.tsx index 5c074472..bd27f4c9 100644 --- a/apps/jurumarble/src/app/vote/post/components/DrinkSearchModal.tsx +++ b/apps/jurumarble/src/app/vote/post/components/DrinkSearchModal.tsx @@ -123,6 +123,9 @@ const CloseButton = styled(Button)` const SearchSection = styled.section` padding: 24px 20px; box-shadow: 0px 0px 32px 0px rgba(0, 0, 0, 0.08); + display: flex; + flex-direction: column; + gap: 8px; `; const ResultSection = styled.section` diff --git a/apps/jurumarble/src/app/vote/post/components/PostVoteContainer.tsx b/apps/jurumarble/src/app/vote/post/components/PostVoteContainer.tsx index 60f913b6..b344ea61 100644 --- a/apps/jurumarble/src/app/vote/post/components/PostVoteContainer.tsx +++ b/apps/jurumarble/src/app/vote/post/components/PostVoteContainer.tsx @@ -183,7 +183,7 @@ function PostVoteContainer() { } const Container = styled.div` - padding: 0 20px; + padding: 0 20px 20px 20px; `; const FlexBetween = styled.div` diff --git a/apps/jurumarble/src/app/vote/post/services/usePostVoteService.ts b/apps/jurumarble/src/app/vote/post/services/usePostVoteService.ts index 79891cd3..e54edc03 100644 --- a/apps/jurumarble/src/app/vote/post/services/usePostVoteService.ts +++ b/apps/jurumarble/src/app/vote/post/services/usePostVoteService.ts @@ -9,6 +9,8 @@ import { useRouter } from 'next/navigation'; import { DrinkInfoType } from 'src/types/drink'; import { PostVoteType } from 'src/types/vote'; +const getMyCreatedVoteQueryKey = [queryKeys.MY_CREATED_VOTE]; + export default function usePostVoteService() { const router = useRouter(); const queryClient = useQueryClient(); @@ -131,6 +133,7 @@ export default function usePostVoteService() { { onSuccess: () => { queryClient.invalidateQueries([queryKeys.VOTE_LIST]); + queryClient.invalidateQueries(getMyCreatedVoteQueryKey); router.push(`${Path.VOTE_HOME}/?isSuccess=true`); }, }, @@ -141,6 +144,7 @@ export default function usePostVoteService() { { onSuccess: () => { queryClient.invalidateQueries([queryKeys.VOTE_LIST]); + queryClient.invalidateQueries(getMyCreatedVoteQueryKey); router.push(`${Path.VOTE_HOME}/?isSuccess=true`); }, }, diff --git a/apps/jurumarble/src/components/SearchInput.tsx b/apps/jurumarble/src/components/SearchInput.tsx index 771489dd..831c2208 100644 --- a/apps/jurumarble/src/components/SearchInput.tsx +++ b/apps/jurumarble/src/components/SearchInput.tsx @@ -63,7 +63,6 @@ const SearchInput = forwardRef( const Search = styled.form` display: flex; - margin-top: 8px; width: 100%; `; diff --git a/apps/jurumarble/src/lib/apis/drink.ts b/apps/jurumarble/src/lib/apis/drink.ts index 328c6af1..45b751e8 100644 --- a/apps/jurumarble/src/lib/apis/drink.ts +++ b/apps/jurumarble/src/lib/apis/drink.ts @@ -42,7 +42,12 @@ interface GetEnjoyedDrinkListRequest { region?: string; } -export interface GetEnjoyedDrinkListResponse extends DrinkListResponse {} +interface DrinkData extends DrinkListResponse {} + +interface GetEnjoyedDrinkListResponse { + drinkData: DrinkData; + enjoyedDrinkCount: number; +} export const getEnjoyedDrinkList = async ( params: GetEnjoyedDrinkListRequest, diff --git a/apps/jurumarble/src/lib/apis/restaurant.ts b/apps/jurumarble/src/lib/apis/restaurant.ts index 4d580e03..f8163146 100644 --- a/apps/jurumarble/src/lib/apis/restaurant.ts +++ b/apps/jurumarble/src/lib/apis/restaurant.ts @@ -9,7 +9,10 @@ interface GetRestaurantRequest { typeId: number; commentId: number; keyword: string; - region: string; + /** + * @TODO 이거 타입 괜찮나? + */ + region?: string; page: number; } diff --git a/apps/jurumarble/src/lib/apis/vote.ts b/apps/jurumarble/src/lib/apis/vote.ts index 6cc18b73..0d0366cf 100644 --- a/apps/jurumarble/src/lib/apis/vote.ts +++ b/apps/jurumarble/src/lib/apis/vote.ts @@ -1,4 +1,4 @@ -import { SERVER_URL } from 'lib/constants'; +import { commonHttpFetch } from 'src/modules/httpFetch'; import { VoteSortType } from 'src/types/common'; import { CommonVoteListResponse, Content } from 'src/types/vote'; @@ -41,6 +41,21 @@ export const getVoteListAPI = async ({ return response.data; }; +export const getVoteListV2API = async ({ + page, + size, + sortBy, + keyword, +}: GetVoteListRequest) => { + const response = await commonHttpFetch( + `api/votes/v2?page=${page}&size=${size}&sortBy=${sortBy}&keyword=${ + keyword ?? '' + }`, + ); + const data = await response.json(); + return data as GetVoteListResponse; +}; + export interface GetVoteByIdResponse { createdAt: string; voteId: number; @@ -80,26 +95,69 @@ export const getVoteByVoteIdAPI = async (voteId: number) => { // return voteInfo.data; // }; -interface ModifyVoteRequest { +interface ModifyNormalVoteRequest { title: string; detail: string; titleA: string; titleB: string; + voteId: number; } -export const modifyVoteAPI = async ( - newVoteInfo: ModifyVoteRequest, - voteId: number, -) => { - const response = await fetch(`${SERVER_URL}api/votes/${voteId}`, { - method: 'PUT', - body: JSON.stringify({ - newVoteInfo, - }), +export const modifyNormalVoteAPI = async (params: ModifyNormalVoteRequest) => { + const response = await http.put(`api/votes/${params.voteId}/normal`, { + title: params.title, + detail: params.detail, + titleA: params.titleA, + titleB: params.titleB, }); - const voteInfo = await response.json(); - return voteInfo.data; + + return response.data; }; +// export const modifyNormalVoteAPI = async (params: ModifyNormalVoteRequest) => { +// const response = await authHttpFetch(`api/votes/${params.voteId}/normal`, { +// method: 'PUT', +// body: JSON.stringify({ +// title: params.title, +// detail: params.detail, +// titleA: params.titleA, +// titleB: params.titleB, +// }), +// }); +// const res = await response.json(); +// return res.data; +// }; + +interface ModifyDrinkVoteRequest { + title: string; + detail: string; + drinkAId: number; + drinkBId: number; + voteId: number; +} + +export const modifyDrinkVoteAPI = async (params: ModifyDrinkVoteRequest) => { + const response = await http.put(`api/votes/${params.voteId}/drink`, { + title: params.title, + detail: params.detail, + drinkAId: params.drinkAId, + drinkBId: params.drinkBId, + }); + + return response.data; +}; +// export const modifyDrinkVoteAPI = async (params: ModifyDrinkVoteRequest) => { +// const response = await authHttpFetch(`api/votes/${params.voteId}/drink`, { +// method: 'PUT', +// body: JSON.stringify({ +// title: params.title, +// detail: params.detail, +// drinkAId: params.drinkAId, +// drinkBId: params.drinkBId, +// }), +// }); +// const res = await response.json(); +// return res.data; +// }; export interface PostNormalVoteRequest { title: string; diff --git a/apps/jurumarble/src/lib/queryKeys.ts b/apps/jurumarble/src/lib/queryKeys.ts index 46fc457e..5219766c 100644 --- a/apps/jurumarble/src/lib/queryKeys.ts +++ b/apps/jurumarble/src/lib/queryKeys.ts @@ -3,9 +3,9 @@ export const queryKeys = { DETAIL_COMMENT_LIST: 'commentByVoteId' as const, DETAIL_FILTERED_ANALYSIS: 'filteredAnalysisByVoteId' as const, DETAIL_VOTE_COUNT: 'voteCountByVoteId' as const, + DRINK_INFO: 'drinkInfo' as const, DRINK_STAMP: 'drinkStamp' as const, DRINK_STAMP_LIST: 'drinkStampList' as const, - DRINKS_INFO: 'drinksInfo' as const, DRINKS_MAP: 'drinksMap' as const, HOT_DRINK_LIST: 'hotDrinkList' as const, HOT_DRINK_VOTE: 'hotDrinkVote' as const, @@ -59,7 +59,6 @@ export const reactQueryKeys = { alcoholLimit, ] as const, detailVoteCount: (id: number) => [queryKeys.DETAIL_VOTE_COUNT, id] as const, - drinksInfo: (id: number) => [queryKeys.DRINKS_INFO, id] as const, drinksMap: ( startX: number, startY: number, diff --git a/apps/jurumarble/src/lib/utils/CalculateDifferenceTime.ts b/apps/jurumarble/src/lib/utils/CalculateDifferenceTime.ts new file mode 100644 index 00000000..93b7e9ef --- /dev/null +++ b/apps/jurumarble/src/lib/utils/CalculateDifferenceTime.ts @@ -0,0 +1,19 @@ +// @note 현재 시간과 이전 시간의 차이를 계산하여 단위별로 출력 +export function CalculateDifferenceTime(previous: string) { + const currentDate = new Date(); + const modifiedDate = new Date(previous); + + const diffMSec = currentDate.getTime() - modifiedDate.getTime(); + // 1시간 이내 분 단위로 표시 + if (diffMSec < 60 * 60 * 1000) { + const diffMin = diffMSec / (60 * 1000); + return `${Math.round(diffMin)}m`; + } + // 24시간 이내 시간 단위로 표시 + else if (diffMSec < 24 * 60 * 60 * 1000) { + const diffHour = diffMSec / (60 * 60 * 1000); + return `${Math.round(diffHour)}h`; + } + // 24시간 이상 일로 표시 + return modifiedDate.toLocaleDateString(); +} diff --git a/apps/jurumarble/src/lib/utils/formatDate.ts b/apps/jurumarble/src/lib/utils/formatDate.ts index dcdc2fc6..7f077b46 100644 --- a/apps/jurumarble/src/lib/utils/formatDate.ts +++ b/apps/jurumarble/src/lib/utils/formatDate.ts @@ -1,6 +1,6 @@ export function formatDate(inputDate: string) { const date = new Date(inputDate); - const year = date.getFullYear(); + const year = date.getFullYear() - 2000; const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}. ${month}. ${day}`; diff --git a/apps/jurumarble/src/modules/httpFetch.ts b/apps/jurumarble/src/modules/httpFetch.ts index 7ed1be32..869d60b8 100644 --- a/apps/jurumarble/src/modules/httpFetch.ts +++ b/apps/jurumarble/src/modules/httpFetch.ts @@ -7,6 +7,29 @@ export const httpFetch = createHttpFetch({ baseUrl: SERVER_URL, }); +export const commonHttpFetch = createHttpFetch({ + baseUrl: SERVER_URL, + interceptors: { + request: async (args) => { + const tokens = userStorage.get(); + if (tokens) { + const { accessToken } = tokens; + if (accessToken) { + args[1] = { + ...args[1], + headers: { + ...args[1]?.headers, + Authorization: `Bearer ${accessToken}`, + }, + }; + } + } + + return args; + }, + }, +}); + export const authHttpFetch = createHttpFetch({ baseUrl: SERVER_URL, interceptors: { diff --git a/apps/jurumarble/src/services/useBookmarkService.ts b/apps/jurumarble/src/services/useBookmarkService.ts index 640101ce..3accad45 100644 --- a/apps/jurumarble/src/services/useBookmarkService.ts +++ b/apps/jurumarble/src/services/useBookmarkService.ts @@ -13,6 +13,11 @@ const getBookmarkQueryKey = (params: PostBookmarkProps) => [ params, ]; +const getTheNumberOfMyVoteQueryKey = [queryKeys.THE_NUMBER_OF_MY_VOTE]; +const getMyCreatedVoteQueryKey = [queryKeys.MY_CREATED_VOTE]; +const getMyBookmarkedVoteQueryKey = [queryKeys.MY_BOOKMARKED_VOTE]; +const getMyParticipateVotedQueryKey = [queryKeys.MY_PARTICIPATED_VOTE]; + export default function useBookmarkService(voteId: PostBookmarkProps) { const queryClient = useQueryClient(); @@ -52,6 +57,10 @@ export default function useBookmarkService(voteId: PostBookmarkProps) { toastId: 'bookmark', }, ); + queryClient.invalidateQueries(getTheNumberOfMyVoteQueryKey); + queryClient.invalidateQueries(getMyCreatedVoteQueryKey); + queryClient.invalidateQueries(getMyBookmarkedVoteQueryKey); + queryClient.invalidateQueries(getMyParticipateVotedQueryKey); }, onError(err, drinkId, context) { queryClient.setQueryData( diff --git a/apps/jurumarble/src/services/useDrinkStampService.ts b/apps/jurumarble/src/services/useDrinkStampService.ts index c83cfb25..78b5ac59 100644 --- a/apps/jurumarble/src/services/useDrinkStampService.ts +++ b/apps/jurumarble/src/services/useDrinkStampService.ts @@ -1,17 +1,32 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { getIsEnjoyedDrinkAPI, postDrinkEnjoyAPI } from 'lib/apis/drink'; +import { + getDrinkInfo, + getIsEnjoyedDrinkAPI, + postDrinkEnjoyAPI, +} from 'lib/apis/drink'; import { queryKeys } from 'lib/queryKeys'; type PostDrinkStampProps = Exclude< Parameters[0], undefined >; +type GetDrinkInfoProps = Exclude[0], undefined>; + const getDrinkStampQueryKey = (params: PostDrinkStampProps) => [ queryKeys.DRINK_STAMP, params, ]; +const getDrinkInfoQueryKey = (params: GetDrinkInfoProps) => [ + queryKeys.DRINK_INFO, + params, +]; + +const getDrinkStampListQueryKey = [queryKeys.DRINK_STAMP_LIST]; + +const getDrinkListQueryKey = [queryKeys.SEARCH_DRINK_LIST]; + export default function useDrinkStampService(drinkId: PostDrinkStampProps) { const { data: isStampedDrink } = useQuery( getDrinkStampQueryKey(drinkId), @@ -33,6 +48,11 @@ export default function useDrinkStampService(drinkId: PostDrinkStampProps) { ]); return { previousData }; }, + onSuccess() { + queryClient.invalidateQueries(getDrinkListQueryKey); + queryClient.invalidateQueries(getDrinkInfoQueryKey(drinkId)); + queryClient.invalidateQueries(getDrinkStampListQueryKey); + }, onError(err, drinkId, context) { queryClient.setQueryData( getDrinkStampQueryKey(drinkId), diff --git a/apps/jurumarble/src/types/vote.ts b/apps/jurumarble/src/types/vote.ts index 80daa664..ef460e12 100644 --- a/apps/jurumarble/src/types/vote.ts +++ b/apps/jurumarble/src/types/vote.ts @@ -29,7 +29,7 @@ export interface Content { titleA: string; titleB: string; region: string; - createdAt: Date; + createdAt: string; drinkAId: number; drinkBId: number; }