From 91e8074739fcd5f7b8e3fe27116f56936b30156e Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Sat, 28 Sep 2024 10:58:32 +0900 Subject: [PATCH 1/6] =?UTF-8?q?deploy:=20eslint=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflow/ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflow/ci.yml diff --git a/.github/workflow/ci.yml b/.github/workflow/ci.yml new file mode 100644 index 0000000..5cc3024 --- /dev/null +++ b/.github/workflow/ci.yml @@ -0,0 +1,26 @@ +name: ESLint test + +on: + pull_request: + branches: + - main + - develop + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Install Pnpm package manager + run: | + npm install -g pnpm + + - name: Install Dependencies + run: pnpm install + + - name: Lint Code + run: pnpm lint From 5c4565a37ea7fbcfaea10325b9a309e73159da5b Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Sat, 28 Sep 2024 16:57:57 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Feat(challenge-list):=20api=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20response=20type=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenge-list/getChallengeList.api.ts | 27 +++++++++++++++++++ .../getChallengeList.response.ts | 17 ++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/apis/challenge-list/getChallengeList.api.ts create mode 100644 src/apis/challenge-list/getChallengeList.response.ts diff --git a/src/apis/challenge-list/getChallengeList.api.ts b/src/apis/challenge-list/getChallengeList.api.ts new file mode 100644 index 0000000..84ec38f --- /dev/null +++ b/src/apis/challenge-list/getChallengeList.api.ts @@ -0,0 +1,27 @@ +import { ChallengeListResponse } from './getChallengeList.response'; +import { axiosClient } from '@/apis/AxiosClient'; +import { useQuery } from '@tanstack/react-query'; + +const getChallengeListPath = () => '/api/challengeGroups/shorts'; + +const challengeListQueryKey = [getChallengeListPath()]; + +const getChallengeList = async ( + page: number, + size: number +): Promise => { + const response = await axiosClient.get(getChallengeListPath(), { + params: { + page, + size, + }, + }); + return response.data; +}; + +export const useGetChallengeList = (page: number, size: number) => { + return useQuery({ + queryKey: [challengeListQueryKey, page, size], + queryFn: () => getChallengeList(page, size), + }); +}; diff --git a/src/apis/challenge-list/getChallengeList.response.ts b/src/apis/challenge-list/getChallengeList.response.ts new file mode 100644 index 0000000..7e04e68 --- /dev/null +++ b/src/apis/challenge-list/getChallengeList.response.ts @@ -0,0 +1,17 @@ +import ApiResponse from '@/apis/ApiResponse'; + +export type ChallengeListData = { + totalPage: number; + hasNext: boolean; + data: { + id: number; + title: string; + content: string; + participantCount: number; + startDate: string; + endDate: string; + category: 'HEALTH' | 'ECHO' | 'SHARE' | 'VOLUNTEER' | 'ETC'; + }[]; +}; + +export type ChallengeListResponse = ApiResponse; From 1e3e9d1f37a315a94758d2fe545881d71b36960e Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Sat, 28 Sep 2024 18:46:14 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Feat(router):=20challenge-list=20path=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/path.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/path.ts b/src/routes/path.ts index fd20627..b55e80a 100644 --- a/src/routes/path.ts +++ b/src/routes/path.ts @@ -15,4 +15,5 @@ export const RouterPath = { register: 'register', review: 'review', write: 'write', + challengeList: 'list', }; From d92352433475a9f585b58cb36ee19c7db0403774 Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Sat, 28 Sep 2024 18:47:59 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Feat(router):=20challenge-list=20path=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/index.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/routes/index.tsx b/src/routes/index.tsx index cc6e2e7..d25bf30 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -4,6 +4,7 @@ import { ProtectedRoute } from './protected-route'; import NavBar from '@/components/features/layout/nav-bar'; import ErrorPage from '@/pages/ErrorPage'; import ChallengeDetailPage from '@/pages/challenge-detail'; +import ChallengeList from '@/pages/challenge-list'; import ChallengeRecord from '@/pages/challenge-record'; import DashBoardPage from '@/pages/dashboard'; import LoginPage from '@/pages/login'; @@ -58,6 +59,14 @@ const router = createBrowserRouter([ ), }, + { + path: RouterPath.challengeList, + element: ( + + + + ), + }, { path: `:id/${RouterPath.detail}`, element: ( From 5cddeb4646fc09c6734a2bd02e2a79e79a4684ad Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Sat, 28 Sep 2024 21:09:04 +0900 Subject: [PATCH 5/6] =?UTF-8?q?Feat(challenge-list):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=B1=8C?= =?UTF-8?q?=EB=A6=B0=EC=A7=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/challenge-list/index.tsx | 116 +++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/pages/challenge-list/index.tsx diff --git a/src/pages/challenge-list/index.tsx b/src/pages/challenge-list/index.tsx new file mode 100644 index 0000000..5452826 --- /dev/null +++ b/src/pages/challenge-list/index.tsx @@ -0,0 +1,116 @@ +import { useCallback, useEffect, useState } from 'react'; + +import Contents from './components/contents'; +import { useGetChallengeList } from '@/apis/challenge-list/getChallengeList.api'; +import { Tab, Tabs } from '@/components/common/tabs'; +import { TabPanel } from '@/components/common/tabs/tap-panels'; +import TopBar from '@/components/features/layout/top-bar'; +import { Box, Spinner } from '@chakra-ui/react'; +import styled from '@emotion/styled'; + +type Challenge = { + id: number; + title: string; + content: string; + participantCount: number; + category: 'HEALTH' | 'ECHO' | 'SHARE' | 'VOLUNTEER' | 'ETC'; + startDate: string; + endDate: string; +}; + +const ChallengeList = () => { + const [activeTab, setActiveTab] = useState(0); + const [allData, setAllData] = useState([]); + const [page, setPage] = useState(0); + + const categoryList = [ + { label: '건강', data: 'HEALTH' }, + { label: '에코', data: 'ECHO' }, + { label: '나눔', data: 'SHARE' }, + { label: '봉사', data: 'VOLUNTEER' }, + ]; + + const { data, isLoading } = useGetChallengeList(page, 20); + + useEffect(() => { + if (data) { + setAllData((prevData) => [...prevData, ...data.data.data]); + } + }, [data]); + + const handleSelectedTab = (value: number) => { + setActiveTab(value); + sessionStorage.setItem('activeTab', String(value)); + }; + + const filteredData = allData.filter( + (item) => item.category === categoryList[activeTab].data + ); + + const loadNextPage = useCallback(() => { + if (data?.data.hasNext && !isLoading) { + setPage((prevPage) => prevPage + 1); + } + }, [data, isLoading]); + + useEffect(() => { + const handleScroll = () => { + if ( + window.innerHeight + document.documentElement.scrollTop !== + document.documentElement.offsetHeight + ) + return; + loadNextPage(); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, [loadNextPage]); + + return ( + <> + + + + {categoryList.map((category, index) => ( + + ))} + + + {categoryList.map((_, index) => ( + + <> + {filteredData.map((challenge) => ( + + ))} + + + ))} + + {isLoading && } + + + ); +}; + +export default ChallengeList; + +const ChallengeListLayout = styled.div` + display: block; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + margin: 0 1.5rem; + padding-bottom: 6rem; +`; + +const TabPanelsLayout = styled(Box)` + margin: 1rem 0; +`; From 9516b38b283c179f9ed022efe3938ea8e85632f4 Mon Sep 17 00:00:00 2001 From: Dobbymin Date: Sat, 28 Sep 2024 21:09:12 +0900 Subject: [PATCH 6/6] =?UTF-8?q?Feat(challenge-list):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=B1=8C?= =?UTF-8?q?=EB=A6=B0=EC=A7=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/contents/index.tsx | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/pages/challenge-list/components/contents/index.tsx diff --git a/src/pages/challenge-list/components/contents/index.tsx b/src/pages/challenge-list/components/contents/index.tsx new file mode 100644 index 0000000..2aa0b64 --- /dev/null +++ b/src/pages/challenge-list/components/contents/index.tsx @@ -0,0 +1,83 @@ +import { useState } from 'react'; + +import { Box, Text } from '@chakra-ui/react'; +import styled from '@emotion/styled'; + +type Props = { + title: string; + content: string; + startDate: string; + endDate: string; + participantCount: number; +}; + +const Contents = ({ + title, + content, + startDate, + endDate, + participantCount, +}: Props) => { + const [isClicked, setIsClicked] = useState(false); + + const handleBoxClick = () => { + setIsClicked(!isClicked); + }; + + const date = `${startDate} ~ ${endDate}`; + + return ( + + + + {title} + + + + {content} + + 누적 참여자 수 : {participantCount}명 + + + + + 참여 가능 기간 + + {date} + + + ); +}; + +export default Contents; + +const ContentsBox = styled(Box)<{ isClicked: boolean }>` + width: 100%; + height: 100%; + background-color: ${({ isClicked }) => + isClicked ? 'var(--color-green-06)' : 'var(--color-green-01)'}; + border-radius: 1.2rem; + padding: 1rem; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + text-align: left; + display: flex; + flex-direction: column; + align-items: flex-start; + margin-bottom: 1.5rem; + cursor: pointer; +`; + +const FlexBox = styled(Box)` + display: flex; + flex-direction: column; + gap: 1rem; +`; + +const TextItem = styled(Text)<{ isClicked: boolean }>` + color: ${({ isClicked }) => (isClicked ? '#000' : '#fff')}; + font-size: 1rem; +`;