diff --git a/src/apis/my-challenge-record/getReview.api.ts b/src/apis/my-challenge-record/getReview.api.ts new file mode 100644 index 0000000..cb8744a --- /dev/null +++ b/src/apis/my-challenge-record/getReview.api.ts @@ -0,0 +1,27 @@ +import { ChallengeResponse } from './getReview.response'; +import { axiosClient } from '@/apis/AxiosClient'; +import { useQuery } from '@tanstack/react-query'; + +const getReviewPath = () => '/api/challengeGroups/reviews'; + +const ReviewQueryKey = [getReviewPath()]; + +const getReview = async ( + page: number, + size: number +): Promise => { + const response = await axiosClient.get(getReviewPath(), { + params: { + page, + size, + }, + }); + return response.data; +}; + +export const useGetReview = (page: number, size: number) => { + return useQuery({ + queryKey: [ReviewQueryKey, page, size], + queryFn: () => getReview(page, size), + }); +}; diff --git a/src/apis/my-challenge-record/getReview.response.ts b/src/apis/my-challenge-record/getReview.response.ts new file mode 100644 index 0000000..2d6660d --- /dev/null +++ b/src/apis/my-challenge-record/getReview.response.ts @@ -0,0 +1,28 @@ +import ApiResponse from '@/apis/ApiResponse'; + +export type ChallengeUser = { + id: number; + nickname: string; + profileImageUrl: string; + tierInfo: { + tier: string; + totalExp: number; + currentExp: number; + }; +}; + +export type ChallengeData = { + challengeId: number; + challengeTitle: string; + user: ChallengeUser; + content: string; + rating: number; +}; + +export type ChallengeResponseData = { + totalPage: number; + hasNext: boolean; + data: ChallengeData[]; +}; + +export type ChallengeResponse = ApiResponse; diff --git a/src/pages/my-challenge-record/components/list-item.tsx b/src/pages/my-challenge-record/components/list-item.tsx index b60f23f..db2dd3e 100644 --- a/src/pages/my-challenge-record/components/list-item.tsx +++ b/src/pages/my-challenge-record/components/list-item.tsx @@ -2,18 +2,31 @@ import ProfileImg from '@/assets/challenge/ZZAN-Green.png'; import { Image, Text } from '@chakra-ui/react'; import styled from '@emotion/styled'; -const ListItem = () => { +type Props = { + challengeTitle: string; + userNickname: string; + profileImageUrl?: string | null; + id: number; + onClick: (id: number) => void; +}; + +const ListItem = ({ challengeTitle, profileImageUrl, onClick, id }: Props) => { + const handleClick = () => { + onClick(id); + }; + return ( - + - profile + profile - - 쓰레기 줍기 챌린지 + + {challengeTitle} - - 2024.08.05 - ); }; @@ -25,10 +38,12 @@ const ListItemLayout = styled.div` flex-direction: row; width: 100%; gap: 20px; - justify-content: space-between; align-items: center; border-bottom: 1px solid #e0e0e0; margin: 0.2rem 1rem; + &:last-child { + border-bottom: none; + } `; const ProfileContainer = styled.div` @@ -47,5 +62,4 @@ const ListText = styled(Text)` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - /* max-width: 150px; */ `; diff --git a/src/pages/my-challenge-record/index.tsx b/src/pages/my-challenge-record/index.tsx index dfe69af..661cd83 100644 --- a/src/pages/my-challenge-record/index.tsx +++ b/src/pages/my-challenge-record/index.tsx @@ -1,9 +1,52 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + import ListItem from './components/list-item'; +import { useGetReview } from '@/apis/my-challenge-record/getReview.api'; +import { ChallengeData } from '@/apis/my-challenge-record/getReview.response'; import TopBar from '@/components/features/layout/top-bar'; -import { Box } from '@chakra-ui/react'; +import { Box, Spinner, Text } from '@chakra-ui/react'; import styled from '@emotion/styled'; const MyChallengeRecord = () => { + const [page, setPage] = useState(0); + const [allChallenges, setAllChallenges] = useState([]); + const { data, isLoading } = useGetReview(page, 20); + const navigate = useNavigate(); + + const loadMoreChallenges = useCallback(() => { + if (data?.data.hasNext && !isLoading) { + setPage((prevPage) => prevPage + 1); + } + }, [data, isLoading]); + + useEffect(() => { + if (data?.data.data) { + setAllChallenges((prevChallenges) => [ + ...prevChallenges, + ...data.data.data, + ]); + } + }, [data]); + + useEffect(() => { + const handleScroll = () => { + if ( + window.innerHeight + document.documentElement.scrollTop >= + document.documentElement.offsetHeight - 200 + ) { + loadMoreChallenges(); + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, [loadMoreChallenges]); + + const handleNavigate = (id: number) => { + navigate(`/challenge/${id}/review`); + }; + return ( <> { backgroundColor='var(--color-green-06)' /> + + 챌린지 기록을 눌러 리뷰를 작성해보세요! + - - - + {allChallenges.length > 0 ? ( + allChallenges.map((challenge, index) => ( + handleNavigate(challenge.challengeId)} + /> + )) + ) : ( + 챌린지 기록이 존재하지 않습니다. + )} + {isLoading && ( + + + + )} ); @@ -27,8 +89,8 @@ export default MyChallengeRecord; const MyChallengeRecordLayout = styled.div` display: flex; flex-direction: column; - height: 100vh; background-color: var(--color-green-06); + padding-bottom: 5rem; `; const ChallengeList = styled(Box)` @@ -37,7 +99,13 @@ const ChallengeList = styled(Box)` padding: 0.625rem 1.5rem; flex-direction: column; align-items: center; - border-radius: 1.25rem; background-color: #fff; `; + +const SpinnerContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +`;