From d16c37d25d69d48e303b81edca2f71fcb3a5194d Mon Sep 17 00:00:00 2001 From: j2h30728 Date: Wed, 16 Oct 2024 23:52:52 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=8A=AC=EB=9D=BC=EC=9D=B4=EB=93=9C=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20[#94]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../verification/api/useDetailVerification.ts | 16 +++ .../api/useInfiniteVerificationsForSlide.ts | 5 +- .../verification/api/verificationAPI.ts | 8 ++ src/mocks/datas/verification.ts | 15 +++ src/mocks/handlers/verification.ts | 6 ++ src/mocks/resolvers/verification.ts | 5 + .../detailVerificationPage.tsx | 40 ++++++++ src/pages/index.ts | 1 + src/pages/slide/SlidePage.tsx | 11 +-- src/router.tsx | 5 + src/shared/constants/endPoint.ts | 1 + src/shared/constants/routePath.ts | 1 + src/shared/lib/formattedDate.ts | 13 +++ .../lib/getVerificationCategoryColor.ts | 23 ++++- src/shared/lib/query/queryKey.ts | 2 + src/widgets/slide/ui/Feed.tsx | 33 +++++++ src/widgets/slide/ui/Slide.styled.ts | 99 ------------------- src/widgets/slide/ui/Slide.tsx | 55 ----------- .../ui/DetailVerificationItem.style.ts | 67 +++++++++++++ .../ui/DetailVerificationItem.tsx | 53 ++++++++++ 20 files changed, 290 insertions(+), 169 deletions(-) create mode 100644 src/entities/verification/api/useDetailVerification.ts create mode 100644 src/pages/detailVerification/detailVerificationPage.tsx create mode 100644 src/shared/lib/formattedDate.ts create mode 100644 src/widgets/slide/ui/Feed.tsx delete mode 100644 src/widgets/slide/ui/Slide.styled.ts delete mode 100644 src/widgets/slide/ui/Slide.tsx create mode 100644 src/widgets/verification/ui/DetailVerificationItem.style.ts create mode 100644 src/widgets/verification/ui/DetailVerificationItem.tsx diff --git a/src/entities/verification/api/useDetailVerification.ts b/src/entities/verification/api/useDetailVerification.ts new file mode 100644 index 0000000..e710581 --- /dev/null +++ b/src/entities/verification/api/useDetailVerification.ts @@ -0,0 +1,16 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; + +import { verificationKey } from '../../../shared/lib/query/queryKey'; +import verificationAPI from './verificationAPI'; + +const useDetailVerification = ({ + verificationId, +}: { + verificationId: string; +}) => { + return useSuspenseQuery({ + queryKey: verificationKey.verification(verificationId), + queryFn: () => verificationAPI.getDetailVerification(verificationId), + }); +}; +export default useDetailVerification; diff --git a/src/entities/verification/api/useInfiniteVerificationsForSlide.ts b/src/entities/verification/api/useInfiniteVerificationsForSlide.ts index 6223abb..461419a 100644 --- a/src/entities/verification/api/useInfiniteVerificationsForSlide.ts +++ b/src/entities/verification/api/useInfiniteVerificationsForSlide.ts @@ -16,10 +16,7 @@ const useInfiniteVerificationsForSlide = () => { const { targetItemRef } = useIntersectionObserver(fetchNextPage); - const slideData = data?.pages - .reverse() - .flatMap((page) => page.items) - .reverse(); + const slideData = data?.pages.flatMap((page) => page.items); return { data: slideData, targetItemRef, isFetchingNextPage }; }; diff --git a/src/entities/verification/api/verificationAPI.ts b/src/entities/verification/api/verificationAPI.ts index d408107..3da33ab 100644 --- a/src/entities/verification/api/verificationAPI.ts +++ b/src/entities/verification/api/verificationAPI.ts @@ -9,6 +9,7 @@ import type { VerificationsForCalendar, VerificationsFroGrid, VerificationsForGridByUploader, + VerificationResponse, } from '../../../shared/types/verification'; import apiClient from '../../../shared/api'; @@ -21,6 +22,13 @@ const verificationAPI = { ); return data; }, + /** verification detail info 조회 */ + getDetailVerification: async (verificationId: string) => { + const { data } = await apiClient.get( + END_POINT.DETAIL(verificationId) + ); + return data; + }, /** pet verification post */ postVerification: async (body: UploadVerificationContents) => { const { data } = await apiClient.post( diff --git a/src/mocks/datas/verification.ts b/src/mocks/datas/verification.ts index c60f93d..a35618a 100644 --- a/src/mocks/datas/verification.ts +++ b/src/mocks/datas/verification.ts @@ -2512,3 +2512,18 @@ export const UploaderTypeVerificationsForGridData: VerificationsForGridByUploade ], }, ]; + +export const detailVerificationData = { + id: '0', + createdAt: '2024-04-21T12:00:00Z', + category: 'walk', + imageUrl: petMockImageUrl2, + verificationOption: '1시간 10분', + comment: + '왼쪽으로 가야되는데 오른쪽으로 간다고 개찡찡댔다... 다음타자 힘들듯^^', + author: { + id: '2', + imageUrl: userMockImageUrl2, + name: '막둥이', + }, +}; diff --git a/src/mocks/handlers/verification.ts b/src/mocks/handlers/verification.ts index 53da477..2075856 100644 --- a/src/mocks/handlers/verification.ts +++ b/src/mocks/handlers/verification.ts @@ -43,4 +43,10 @@ export const verificationHandler = [ baseURL(END_POINT.UPLOADER_GRID), withAuth(verification.getVerificationForUploaderGrid) ), + + /** get detail verification by id*/ + http.get( + baseURL(END_POINT.DETAIL(':verificationId')), + withAuth(verification.getDetailVerification) + ), ]; diff --git a/src/mocks/resolvers/verification.ts b/src/mocks/resolvers/verification.ts index fa6cf68..8d6d8b0 100644 --- a/src/mocks/resolvers/verification.ts +++ b/src/mocks/resolvers/verification.ts @@ -7,12 +7,17 @@ import { verificationCountInfo, verificationForSlideData, verificationsForCalendarData, + detailVerificationData, } from '../datas/verification'; import type { UploadVerificationContents } from '../../shared/types/verification'; import { MSWResolvers } from '../../shared/lib/mswUtils'; export const verification = { + getDetailVerification: async () => { + await delay(); + return HttpResponse.json(detailVerificationData); + }, getVerificationCount: async () => { await delay(); return HttpResponse.json(verificationCountInfo); diff --git a/src/pages/detailVerification/detailVerificationPage.tsx b/src/pages/detailVerification/detailVerificationPage.tsx new file mode 100644 index 0000000..81d66a1 --- /dev/null +++ b/src/pages/detailVerification/detailVerificationPage.tsx @@ -0,0 +1,40 @@ +import { useNavigate } from 'react-router-dom'; +import { Suspense } from 'react'; + +import { Layout } from '../../shared/ui'; +import { LeftArrowIcon } from '../../shared/ui/Icons'; +import ROUTE_PATH from '../../shared/constants/routePath'; +import Spinner from '../../shared/ui/Spinner'; +import { THEME } from '../../shared/styles/theme'; +import DetailVerificationItem from '../../widgets/verification/ui/DetailVerificationItem'; +import useDetailVerification from '../../entities/verification/api/useDetailVerification'; + +const DetailVerification = () => { + const { data: detailVerification } = useDetailVerification({ + verificationId: '1', + }); + return ; +}; + +const DetailVerificationPage = () => { + const navigate = useNavigate(); + return ( + navigate(ROUTE_PATH.SLIDE)} + /> + ), + }} + > + }> + + + + ); +}; + +export default DetailVerificationPage; diff --git a/src/pages/index.ts b/src/pages/index.ts index fbde4c7..8497c37 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -5,6 +5,7 @@ export { default as UploadVerification } from './upload/UploadVerificationPage'; export { default as Intro } from './Intro'; export { default as Home } from './Home/HomePage'; +export { default as DetailVerification } from './detailVerification/detailVerificationPage'; export { default as SlidePage } from './slide/SlidePage'; export { default as Grid } from './grid/GridPage'; diff --git a/src/pages/slide/SlidePage.tsx b/src/pages/slide/SlidePage.tsx index 68d5517..06d062a 100644 --- a/src/pages/slide/SlidePage.tsx +++ b/src/pages/slide/SlidePage.tsx @@ -1,25 +1,18 @@ -import { useNavigate } from 'react-router-dom'; import { Suspense } from 'react'; import { Layout } from '../../shared/ui'; -import { GridIcon } from '../../shared/ui/Icons'; -import ROUTE_PATH from '../../shared/constants/routePath'; -import Slide from '../../widgets/slide/ui/Slide'; import Spinner from '../../shared/ui/Spinner'; +import Feed from '../../widgets/slide/ui/Feed'; const SlidePage = () => { - const navigate = useNavigate(); return ( navigate(`${ROUTE_PATH.GRID}/all`)} /> - ), }} > }> - + ); diff --git a/src/router.tsx b/src/router.tsx index f6aa49d..8779c5c 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -16,6 +16,7 @@ import { GridByUser, SendingInvitation, FamilyList, + DetailVerification, } from './pages'; import AuthProvider from './providers/AuthProvider'; @@ -45,6 +46,10 @@ const router = createBrowserRouter([ element: , path: ROUTE_PATH.ROOT, }, + { + element: , + path: ROUTE_PATH.DETAIL_VERIFICATION, + }, { element: , path: ROUTE_PATH.SLIDE, diff --git a/src/shared/constants/endPoint.ts b/src/shared/constants/endPoint.ts index da7437b..9bd5fc8 100644 --- a/src/shared/constants/endPoint.ts +++ b/src/shared/constants/endPoint.ts @@ -7,6 +7,7 @@ export const BACKEND_ENDPOINT = import.meta.env.DEV export const END_POINT = { HOME: `/api/home`, POST: '/api/post', + DETAIL: (verificationId: string) => `/api/verification/${verificationId}`, CALENDAR: '/api/calendar', SLIDE: '/api/slide', GRID: '/api/grid', diff --git a/src/shared/constants/routePath.ts b/src/shared/constants/routePath.ts index f1d7096..d57b68c 100644 --- a/src/shared/constants/routePath.ts +++ b/src/shared/constants/routePath.ts @@ -6,6 +6,7 @@ const ROUTE_PATH = { REGISTER_PEOPLE: '/register/people', VERIFICATION: '/verification', UPLOAD_VERIFICATION: '/verification/:category', + DETAIL_VERIFICATION: '/verification/:verificationId', SLIDE: '/slide', GRID: '/grid', GRID_BY_TYPE: '/grid/:type', diff --git a/src/shared/lib/formattedDate.ts b/src/shared/lib/formattedDate.ts new file mode 100644 index 0000000..820251f --- /dev/null +++ b/src/shared/lib/formattedDate.ts @@ -0,0 +1,13 @@ +const formattedDate = (dateString: string) => { + const date = new Date(dateString); + + const options: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }; + + return date.toLocaleDateString('ko-KR', options).replace(/\. /g, '.'); +}; + +export default formattedDate; diff --git a/src/shared/lib/getVerificationCategoryColor.ts b/src/shared/lib/getVerificationCategoryColor.ts index 80117f4..f679489 100644 --- a/src/shared/lib/getVerificationCategoryColor.ts +++ b/src/shared/lib/getVerificationCategoryColor.ts @@ -1,7 +1,13 @@ +/* eslint-disable @typescript-eslint/ban-types */ import { THEME } from '../styles/theme'; -import { VERIFICATION } from '../constants/verification'; +import { + VERIFICATION, + VerificationCategoryType, +} from '../constants/verification'; -export const getVerificationCategoryColor = (category: string) => { +export const getVerificationCategoryColor = ( + category: VerificationCategoryType | ({} & string) +) => { switch (category) { case VERIFICATION.WALK: return THEME.COLORS['P-BUTTON1']; @@ -15,3 +21,16 @@ export const getVerificationCategoryColor = (category: string) => { return THEME.COLORS['INACTIVE-BUTTON']; } }; + +export const getVerificationCategoryTextColor = ( + category: VerificationCategoryType | ({} & string) +) => { + switch (category) { + case VERIFICATION.WALK: + return 'white'; + case VERIFICATION.TREATS: + return 'white'; + default: + return 'black'; + } +}; diff --git a/src/shared/lib/query/queryKey.ts b/src/shared/lib/query/queryKey.ts index df1b81a..cf90e97 100644 --- a/src/shared/lib/query/queryKey.ts +++ b/src/shared/lib/query/queryKey.ts @@ -3,6 +3,8 @@ import { SortType } from '../../types/verification'; export const verificationKey = { base: ['verification'] as const, pet: (petId: string) => [...verificationKey.base, petId] as const, + verification: (verificationId: string) => + [...verificationKey.base, verificationId] as const, grid: () => [...verificationKey.base, 'grid'] as const, allGrid: (sort: SortType) => [...verificationKey.grid(), 'all', sort] as const, diff --git a/src/widgets/slide/ui/Feed.tsx b/src/widgets/slide/ui/Feed.tsx new file mode 100644 index 0000000..c166feb --- /dev/null +++ b/src/widgets/slide/ui/Feed.tsx @@ -0,0 +1,33 @@ +import styled from 'styled-components'; + +import Spinner from '../../../shared/ui/Spinner'; +import useInfiniteVerificationsForSlide from '../../../entities/verification/api/useInfiniteVerificationsForSlide'; + +import DetailVerificationItem from '../../verification/ui/DetailVerificationItem'; + +const Feed = () => { + const { data, targetItemRef, isFetchingNextPage } = + useInfiniteVerificationsForSlide(); + + return ( + + {data.map((verification) => ( + + ))} +
+ {isFetchingNextPage && } +
+ ); +}; + +export default Feed; + +export const Container = styled.div` + overflow-y: scroll; + display: flex; + flex-direction: column; + background-color: #f9f5f5; +`; diff --git a/src/widgets/slide/ui/Slide.styled.ts b/src/widgets/slide/ui/Slide.styled.ts deleted file mode 100644 index ce4da45..0000000 --- a/src/widgets/slide/ui/Slide.styled.ts +++ /dev/null @@ -1,99 +0,0 @@ -import styled from 'styled-components'; - -export const Container = styled.div` - padding: 10px 0; - overflow-y: scroll; - display: flex; - flex-direction: column; - background-color: #f9f5f5; -`; -export const Comment = styled.p` - width: 218px; - height: 44px; - top: 1843px; - left: 70px; - padding: 7px 12px; - gap: 8px; - border-radius: 10px; - font-size: ${({ theme }) => theme.FONT.XS}; - background-color: ${({ theme }) => theme.COLORS['S-BUTTON']}; - line-height: 15.36px; - display: flex; - justify-content: center; - align-items: center; -`; - -export const SlideWrapper = styled.div<{ $isLoggedInUser: boolean }>` - display: flex; - flex-direction: column; - padding: 0 15px; - margin-bottom: 20px; - overflow: hidden; - - height: fit-content; - - align-items: ${({ $isLoggedInUser }) => ($isLoggedInUser ? 'end' : 'start')}; - .verification { - margin: ${({ $isLoggedInUser }) => - $isLoggedInUser ? '0 50px 0 0' : '0 0 0 50px'}; - } - - .verification-item { - background-color: ${({ theme, $isLoggedInUser }) => - $isLoggedInUser ? theme.COLORS['SECONDARY-DASH'] : theme.COLORS.DECO2}; - } -`; - -export const Image = styled.img` - width: 150px; - height: 150px; - object-fit: cover; - object-position: center center; -`; - -export const VerificationWrapper = styled.div` - display: flex; - flex-direction: column; - width: 225px; - gap: 10px; -`; - -export const VerificationItem = styled.div` - background-color: ${({ theme }) => theme.COLORS['SECONDARY-DASH']}; - - display: flex; - flex-direction: column; - align-items: center; - - width: 218px; - padding: 14px 34px; - gap: 7px; - border-radius: 30px; - - .category { - font-size: ${({ theme }) => theme.FONT.MD}; - color: white; - line-height: 20.48px; - padding: 5px 40px; - } - .option { - width: fit-content; - height: 35px; - padding: 10px 12px; - gap: 10px; - border-radius: 20px; - background-color: ${({ theme }) => theme.COLORS['P-BUTTON2']}; - font-size: ${({ theme }) => theme.FONT.XS}; - display: flex; - justify-content: center; - align-items: center; - } -`; - -export const Author = styled.div` - display: flex; - flex-direction: column; - gap: 4px; - font-size: ${({ theme }) => theme.FONT.XS}; - margin: -40px 0 0 0; -`; diff --git a/src/widgets/slide/ui/Slide.tsx b/src/widgets/slide/ui/Slide.tsx deleted file mode 100644 index 58ff57e..0000000 --- a/src/widgets/slide/ui/Slide.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import UserImage from '../../../shared/ui/UserImage'; -import { getVerificationTitle } from '../../../shared/lib/getVerificationInfo'; - -import Spinner from '../../../shared/ui/Spinner'; -import useControlScroll from '../../../shared/hooks/useContorlScroll'; -import useInfiniteVerificationsForSlide from '../../../entities/verification/api/useInfiniteVerificationsForSlide'; - -import * as S from './Slide.styled'; - -const loggedUserId = '1'; - -const Slide = () => { - const { data, targetItemRef, isFetchingNextPage } = - useInfiniteVerificationsForSlide(); - const { containerRef } = useControlScroll(); - - return ( - - {isFetchingNextPage && } -
- {data.map((verification) => ( - - - -

- {getVerificationTitle(verification.category)} -

- {verification.verificationOption && ( -

{verification.verificationOption}

- )} - {verification.imageUrl && ( - - )} -
- {verification.comment && ( - {verification.comment} - )} -
- - - {verification.author.name} - -
- ))} -
- ); -}; - -export default Slide; diff --git a/src/widgets/verification/ui/DetailVerificationItem.style.ts b/src/widgets/verification/ui/DetailVerificationItem.style.ts new file mode 100644 index 0000000..dd09752 --- /dev/null +++ b/src/widgets/verification/ui/DetailVerificationItem.style.ts @@ -0,0 +1,67 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +import { VerificationCategoryType } from '../../../shared/constants/verification'; +import { + getVerificationCategoryColor, + getVerificationCategoryTextColor, +} from '../../../shared/lib/getVerificationCategoryColor'; + +export const FeedItemWrapper = styled(Link)` + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px; + border: 5px white solid; + text-decoration: none; + color: black; + + .image { + align-self: center; + width: 310px; + height: 310px; + border-radius: 20px; + object-fit: cover; + object-position: center center; + } + .comment { + line-height: 1.2; + } +`; + +export const FeedItemHeader = styled.div<{ + $category: VerificationCategoryType; +}>` + display: flex; + justify-content: space-between; + gap: 20px; + + & > * { + display: flex; + flex-direction: column; + gap: 10px; + } + + .info { + margin-left: auto; + } + + .category { + height: fit-content; + display: flex; + justify-content: center; + align-items: center; + padding: 4px 20px; + border-radius: 30px; + font-size: 11px; + background-color: ${({ $category }) => + getVerificationCategoryColor($category)}; + color: ${({ $category }) => getVerificationCategoryTextColor($category)}; + } + .option { + font-size: 12px; + } + .date { + font-size: 11px; + } +`; diff --git a/src/widgets/verification/ui/DetailVerificationItem.tsx b/src/widgets/verification/ui/DetailVerificationItem.tsx new file mode 100644 index 0000000..3465b87 --- /dev/null +++ b/src/widgets/verification/ui/DetailVerificationItem.tsx @@ -0,0 +1,53 @@ +import UserImage from '../../../shared/ui/UserImage'; +import { VerificationResponse } from '../../../shared/types/verification'; +import { getVerificationTitle } from '../../../shared/lib/getVerificationInfo'; +import formattedDate from '../../../shared/lib/formattedDate'; +import ROUTE_PATH from '../../../shared/constants/routePath'; + +import * as S from './DetailVerificationItem.style'; + +const DetailVerificationItem = ({ + verification, + as, +}: { + verification: VerificationResponse; + as?: string; +}) => { + return ( + + + +
+ {verification.author.name} +
+ {verification.verificationOption && ( +

{verification.verificationOption}

+ )} +
+
+
+
+ {getVerificationTitle(verification.category)} +
+

{formattedDate(verification.createdAt)}

+
+
+ {verification.comment && ( +

{verification.comment}

+ )} + {verification.imageUrl && ( + {verification.createdAt} + )} +
+ ); +}; + +export default DetailVerificationItem;