From ec0c16862680842e24fd69168fcdd8acd60a84e2 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Tue, 8 Oct 2024 11:23:43 +0900 Subject: [PATCH 01/36] =?UTF-8?q?[KAN-112]=20refactor:=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EA=B3=B5=ED=86=B5=ED=99=94=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/data.type.ts | 45 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/src/types/data.type.ts b/src/types/data.type.ts index a518ed30..a4ba478d 100644 --- a/src/types/data.type.ts +++ b/src/types/data.type.ts @@ -1,8 +1,7 @@ import { GatheringsType } from './client.type'; -// GET : /{teamId}/gatherings/joined -// 로그인된 사용자가 참석한 모임 목록 조회 시 Response Data -export interface UserJoinedGatheringsData { +// GET : /{teamId}/gatherings +export interface GatheringType { teamId: number | string; id: number; type: string; @@ -15,44 +14,20 @@ export interface UserJoinedGatheringsData { image: string; createdBy: number; canceledAt?: string | null; +} + +// GET : /{teamId}/gatherings/joined +export interface UserJoinedGatheringsData extends GatheringType { joinedAt?: string; isCompleted?: boolean; isReviewed?: boolean; } -// GET : /{teamId}/gatherings -// 모임 목록 조회 시 Response Data -export interface GatheringsListData { - teamId: number | string; - id: number; - type: string; - name: string; - dateTime: string; - registrationEnd: string; - location: string; - participantCount: number; - capacity: number; - image: string; - createdBy: number; - canceledAt?: string | null; -} - // GET : /{teamId}/gatherings/{gatheringId} // POST: /{teamId}/gatherings -export interface GatheringInfoType { - teamId: number | string; - id: number; - type: string; +export type GatheringInfoType = Omit & { name: string | null; - dateTime: string; - registrationEnd: string; - location: string; - participantCount: number; - capacity: number; - image: string; - createdBy: number; - canceledAt?: string | null; -} +}; // GET : /{teamId}/gatherings/{id}/participants export interface GatheringParticipantsType { @@ -70,12 +45,12 @@ export interface GatheringParticipantsType { } // GET : /{teamId}/gatherings/joined -// 참석한 모임 목록 조회 시 Response Data -export interface myGatheringData extends GatheringsListData { +export interface myGatheringData extends GatheringType { joinedAt: string; isCompleted: boolean; isReviewed: boolean; } + // GET : mockdata // 무한스크롤 구현을 위한 mock data의 인터페이스입니다. 수정 및 삭제될 수 있음 export interface FetchGatheringsResponse { From c205676e4907a7aa6da173d61555e85366301129 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Tue, 8 Oct 2024 11:24:11 +0900 Subject: [PATCH 02/36] =?UTF-8?q?[KAN-112]=20fix:=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=ED=99=94=20=EC=9E=91=EC=97=85=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=94=EA=BE=BC=20=ED=83=80=EC=9E=85=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/gatherings/_component/ClientSideGatherings.tsx | 4 ++-- .../(main)/gatherings/_component/GatheringCardList.tsx | 4 ++-- .../mypage/created/_component/ClientSideGatherings.tsx | 4 ++-- .../(main)/mypage/created/_component/GatheringList.tsx | 4 ++-- src/app/(main)/saved/_component/SavedList.tsx | 4 ++-- src/app/api/actions/gatherings/getGatherings.ts | 6 +++--- src/app/components/CardList/CardList.test.tsx | 4 ++-- src/app/components/CardList/CardList.tsx | 4 ++-- src/hooks/useGatherings.ts | 8 ++++---- src/hooks/useLoadMore.ts | 6 +++--- src/hooks/useSavedGatherings.ts | 8 ++++---- src/hooks/useUserCreated.ts | 6 +++--- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/app/(main)/gatherings/_component/ClientSideGatherings.tsx b/src/app/(main)/gatherings/_component/ClientSideGatherings.tsx index f4b8c373..487cc234 100644 --- a/src/app/(main)/gatherings/_component/ClientSideGatherings.tsx +++ b/src/app/(main)/gatherings/_component/ClientSideGatherings.tsx @@ -8,7 +8,7 @@ import MakeGatheringModal from '@/app/components/Modal/MakeGatheringModal'; import Tabs from '@/app/components/Tabs/Tabs'; import useGatherings from '@/hooks/useGatherings'; import usePreventScroll from '@/hooks/usePreventScroll'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; import CreateGatheringButton from './CreateGatheringButton'; import Filters from '@/app/components/Filters/Filters'; import GatheringCardList from './GatheringCardList'; @@ -20,7 +20,7 @@ import { useInView } from 'react-intersection-observer'; import { SORT_OPTIONS } from '@/constants/common'; interface ClientSideGatheringsProps { - gatherings: GatheringsListData[]; + gatherings: GatheringType[]; user: UserData | null; } diff --git a/src/app/(main)/gatherings/_component/GatheringCardList.tsx b/src/app/(main)/gatherings/_component/GatheringCardList.tsx index cc37fc18..8961490d 100644 --- a/src/app/(main)/gatherings/_component/GatheringCardList.tsx +++ b/src/app/(main)/gatherings/_component/GatheringCardList.tsx @@ -2,13 +2,13 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; import CardList from '@/app/components/CardList/CardList'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; import { useSavedGatheringList } from '@/context/SavedGatheringContext'; import { useInView } from 'react-intersection-observer'; interface GatheringCardListProps { - gatherings: GatheringsListData[]; + gatherings: GatheringType[]; } const GatheringCardList = ({ gatherings }: GatheringCardListProps) => { diff --git a/src/app/(main)/mypage/created/_component/ClientSideGatherings.tsx b/src/app/(main)/mypage/created/_component/ClientSideGatherings.tsx index 60f0f29b..f0de4a64 100644 --- a/src/app/(main)/mypage/created/_component/ClientSideGatherings.tsx +++ b/src/app/(main)/mypage/created/_component/ClientSideGatherings.tsx @@ -4,11 +4,11 @@ import { useUserCreated } from '@/hooks/useUserCreated'; import GatheringList from './GatheringList'; import { useInView } from 'react-intersection-observer'; import { useEffect } from 'react'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; import Loader from '@/app/components/Loader/Loader'; interface ClientSideGatheringsProps { - gatherings: GatheringsListData[]; + gatherings: GatheringType[]; createdBy: string; } diff --git a/src/app/(main)/mypage/created/_component/GatheringList.tsx b/src/app/(main)/mypage/created/_component/GatheringList.tsx index 7c857ff2..951a3ada 100644 --- a/src/app/(main)/mypage/created/_component/GatheringList.tsx +++ b/src/app/(main)/mypage/created/_component/GatheringList.tsx @@ -1,11 +1,11 @@ 'use client'; import Card from '@/app/components/Card/Card'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; import Link from 'next/link'; interface GatheringListProps { - dataList: GatheringsListData[]; + dataList: GatheringType[]; } const GatheringList = ({ dataList }: GatheringListProps) => { diff --git a/src/app/(main)/saved/_component/SavedList.tsx b/src/app/(main)/saved/_component/SavedList.tsx index 8b8a91df..7abcffcf 100644 --- a/src/app/(main)/saved/_component/SavedList.tsx +++ b/src/app/(main)/saved/_component/SavedList.tsx @@ -2,11 +2,11 @@ import CardList from '@/app/components/CardList/CardList'; import { useSavedGatheringList } from '@/context/SavedGatheringContext'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; import Link from 'next/link'; interface SavedListProps { - dataList: GatheringsListData[]; + dataList: GatheringType[]; } const SavedList = ({ dataList }: SavedListProps) => { diff --git a/src/app/api/actions/gatherings/getGatherings.ts b/src/app/api/actions/gatherings/getGatherings.ts index 45b721f1..0b54a23b 100644 --- a/src/app/api/actions/gatherings/getGatherings.ts +++ b/src/app/api/actions/gatherings/getGatherings.ts @@ -2,7 +2,7 @@ import qs from 'qs'; import { GatheringsType } from '@/types/client.type'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; interface GetGatheringsParams { id?: string; @@ -18,7 +18,7 @@ interface GetGatheringsParams { const getGatherings = async ( params: GetGatheringsParams = {}, -): Promise => { +): Promise => { try { const { limit = 10, offset = 0, ...rest } = params; @@ -48,7 +48,7 @@ const getGatherings = async ( throw new Error('모임을 불러오지 못했습니다.'); } - const data: GatheringsListData[] = await res.json(); + const data: GatheringType[] = await res.json(); return data; } catch (error) { diff --git a/src/app/components/CardList/CardList.test.tsx b/src/app/components/CardList/CardList.test.tsx index dd5f7acb..feec2f9c 100644 --- a/src/app/components/CardList/CardList.test.tsx +++ b/src/app/components/CardList/CardList.test.tsx @@ -2,12 +2,12 @@ import React, { MouseEventHandler } from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import CardList from './CardList'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; const date = new Date(); date.setDate(date.getDate() + 3); -const MOCK_DATA_BASE: GatheringsListData = { +const MOCK_DATA_BASE: GatheringType = { teamId: 1, id: 1, type: 'test', diff --git a/src/app/components/CardList/CardList.tsx b/src/app/components/CardList/CardList.tsx index 17f9acee..1f28d208 100644 --- a/src/app/components/CardList/CardList.tsx +++ b/src/app/components/CardList/CardList.tsx @@ -13,7 +13,7 @@ import { formatTimeHours, isSameDate, } from '@/utils/formatDate'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; import Tag from '@/app/components/Tag/Tag'; import InfoChip from '@/app/components/Chip/InfoChip'; import ProgressBar from '@/app/components/ProgressBar/ProgressBar'; @@ -21,7 +21,7 @@ import { MouseEvent, useState } from 'react'; // TODO : optional props를 필수로 변경 interface CardProps { - data: GatheringsListData; + data: GatheringType; isSaved?: boolean; handleButtonClick?: (id: number) => void; } diff --git a/src/hooks/useGatherings.ts b/src/hooks/useGatherings.ts index 880355c8..27950566 100644 --- a/src/hooks/useGatherings.ts +++ b/src/hooks/useGatherings.ts @@ -3,7 +3,7 @@ * 이 훅은 초기 모임 데이터를 상태로 관리하며, 다양한 필터와 정렬 옵션을 설정할 수 있는 핸들러 함수를 제공합니다. * 또한, 필터 변경 시 쿼리 파라미터를 업데이트하고, 필터링된 데이터를 서버에서 가져오는 기능을 포함합니다. * - * @param {GatheringsListData[]} initialGatherings - 초기 모임 데이터 배열 + * @param {GatheringType[]} initialGatherings - 초기 모임 데이터 배열 */ import { useEffect, useState } from 'react'; @@ -18,11 +18,11 @@ import { GatheringFilters, GatheringTabsType, } from '@/types/client.type'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; -const useGatherings = (initialGatherings: GatheringsListData[]) => { +const useGatherings = (initialGatherings: GatheringType[]) => { const [filteredData, setFilteredData] = - useState(initialGatherings); + useState(initialGatherings); const [activeTab, setActiveTab] = useState('DALLAEMFIT'); const [selectedLocation, setSelectedLocation] = useState( diff --git a/src/hooks/useLoadMore.ts b/src/hooks/useLoadMore.ts index b5ba95c3..c6f6025c 100644 --- a/src/hooks/useLoadMore.ts +++ b/src/hooks/useLoadMore.ts @@ -1,16 +1,16 @@ import { useState } from 'react'; import { LIMIT_PER_REQUEST } from '@/constants/common'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; const useLoadMore = ( fetchFilteredGatherings: (filters: { offset: number; - }) => Promise, + }) => Promise, offset: number, setOffset: (offset: number) => void, setFilteredData: ( - data: (prevData: GatheringsListData[]) => GatheringsListData[], + data: (prevData: GatheringType[]) => GatheringType[], ) => void, ) => { const [isLoading, setIsLoading] = useState(false); diff --git a/src/hooks/useSavedGatherings.ts b/src/hooks/useSavedGatherings.ts index aa857464..1ccd1134 100644 --- a/src/hooks/useSavedGatherings.ts +++ b/src/hooks/useSavedGatherings.ts @@ -1,14 +1,14 @@ import getGatherings from '@/app/api/actions/gatherings/getGatherings'; import { SORT_OPTIONS_MAP } from '@/constants/common'; import { GatheringChipsType, GatheringTabsType } from '@/types/client.type'; -import { GatheringsListData, GetGatheringsParams } from '@/types/data.type'; +import { GatheringType, GetGatheringsParams } from '@/types/data.type'; import { formatCalendarDate } from '@/utils/formatDate'; import { useEffect, useState } from 'react'; const useSavedGatherings = (savedGatherings: number[]) => { - const [gatheringListData, setGatheringListData] = useState< - GatheringsListData[] - >([]); + const [gatheringListData, setGatheringListData] = useState( + [], + ); const [activeTab, setActiveTab] = useState('DALLAEMFIT'); const [activeChip, setActiveChip] = useState('ALL'); diff --git a/src/hooks/useUserCreated.ts b/src/hooks/useUserCreated.ts index cf30a645..5167757e 100644 --- a/src/hooks/useUserCreated.ts +++ b/src/hooks/useUserCreated.ts @@ -1,14 +1,14 @@ import { useState } from 'react'; import getGatherings from '@/app/api/actions/gatherings/getGatherings'; -import { GatheringsListData } from '@/types/data.type'; +import { GatheringType } from '@/types/data.type'; import { LIMIT_PER_REQUEST, SORT_OPTIONS_MAP } from '@/constants/common'; export const useUserCreated = ( - initialGatheringList: GatheringsListData[], + initialGatheringList: GatheringType[], createdBy: string, ) => { const [gatheringsList, setGatheringsList] = - useState(initialGatheringList); + useState(initialGatheringList); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState( initialGatheringList.length >= LIMIT_PER_REQUEST, From a28075d7cf2218b7d802d4be7560a52b0f98c8ca Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Tue, 8 Oct 2024 13:19:21 +0900 Subject: [PATCH 03/36] =?UTF-8?q?[KAN-114]=20feat:=20name=EC=97=90=20?= =?UTF-8?q?=EA=B4=80=ED=95=9C=20=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/actions/gatherings/postGatherings.ts | 4 +++- src/app/components/Modal/MakeGatheringModal.tsx | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/api/actions/gatherings/postGatherings.ts b/src/app/api/actions/gatherings/postGatherings.ts index ba52efa9..f2027837 100644 --- a/src/app/api/actions/gatherings/postGatherings.ts +++ b/src/app/api/actions/gatherings/postGatherings.ts @@ -1,6 +1,7 @@ import { getCookie } from '@/actions/auth/cookie/cookie'; interface PostGatheringsParams { + name: string; location: string; type: string; dateTime: string; @@ -10,7 +11,7 @@ interface PostGatheringsParams { } const postGatherings = async (params: PostGatheringsParams) => { - const { location, type, dateTime, capacity, image } = params; + const { name, location, type, dateTime, capacity, image } = params; try { const token = await getCookie('token'); @@ -19,6 +20,7 @@ const postGatherings = async (params: PostGatheringsParams) => { } const formData = new FormData(); + formData.append('name', name); formData.append('location', location); formData.append('type', type); formData.append('dateTime', dateTime); diff --git a/src/app/components/Modal/MakeGatheringModal.tsx b/src/app/components/Modal/MakeGatheringModal.tsx index 3cf01251..8f6a5327 100644 --- a/src/app/components/Modal/MakeGatheringModal.tsx +++ b/src/app/components/Modal/MakeGatheringModal.tsx @@ -19,6 +19,7 @@ interface MakeGatheringModalProps { } const MakeGatheringModal = ({ onClose }: MakeGatheringModalProps) => { + const [name, setName] = useState(''); const [location, setLocation] = useState(null); const [image, setImage] = useState(null); const [gatheringType, setGatheringType] = useState>({ @@ -52,6 +53,7 @@ const MakeGatheringModal = ({ onClose }: MakeGatheringModalProps) => { } const isFormValid = () => + name && location && image && !isAllGatheringTypeFalse() && @@ -68,6 +70,7 @@ const MakeGatheringModal = ({ onClose }: MakeGatheringModalProps) => { } const { success, message } = await postGatherings({ + name, location: location as string, type: getSelectedGatheringType(), dateTime: (combinedDateTime as Date).toISOString(), @@ -96,6 +99,8 @@ const MakeGatheringModal = ({ onClose }: MakeGatheringModalProps) => { {/* 헤더 */} + {/* 모임 이름 */} + {/* 장소 */} Date: Tue, 8 Oct 2024 13:31:01 +0900 Subject: [PATCH 04/36] =?UTF-8?q?[KAN-114]=20feat:=20name=EC=9D=84=20?= =?UTF-8?q?=EB=B0=9B=EB=8A=94=20=EB=B6=80=EB=B6=84=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Modal/MakeGatheringModal.tsx | 2 ++ .../Modal/MakeGatheringModal/NameInput.tsx | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/app/components/Modal/MakeGatheringModal/NameInput.tsx diff --git a/src/app/components/Modal/MakeGatheringModal.tsx b/src/app/components/Modal/MakeGatheringModal.tsx index 8f6a5327..8d10acff 100644 --- a/src/app/components/Modal/MakeGatheringModal.tsx +++ b/src/app/components/Modal/MakeGatheringModal.tsx @@ -13,6 +13,7 @@ import RecruitmentNumber from './MakeGatheringModal/RecruitmentNumber'; import SelectTimeChip from './MakeGatheringModal/SelectTimeChip'; import ModalFrame from './ModalFrame'; import ModalHeader from './ModalHeader'; +import NameInput from './MakeGatheringModal/NameInput'; interface MakeGatheringModalProps { onClose: () => void; @@ -100,6 +101,7 @@ const MakeGatheringModal = ({ onClose }: MakeGatheringModalProps) => { {/* 모임 이름 */} + {/* 장소 */} >; +} +const NameInput = ({ setName }: NameInputProps) => { + return ( +
+

이름

+ setName(e.target.value)} + /> +
+ ); +}; + +export default NameInput; From 6181a3333abd9a5541eba7f9c8ec57957888fe76 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Tue, 8 Oct 2024 11:18:23 +0900 Subject: [PATCH 05/36] =?UTF-8?q?[KAN-48]=20test:=20UserInfo=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserProfileLayout/UserInfo.test.tsx | 38 +++++++++++++++++++ .../UserProfileLayout/useProfileState.test.ts | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/app/components/UserProfileLayout/UserInfo.test.tsx diff --git a/src/app/components/UserProfileLayout/UserInfo.test.tsx b/src/app/components/UserProfileLayout/UserInfo.test.tsx new file mode 100644 index 00000000..e206844c --- /dev/null +++ b/src/app/components/UserProfileLayout/UserInfo.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from '@testing-library/react'; +import UserInfo from './UserInfo'; +import '@testing-library/jest-dom'; +import { UserData } from '@/types/client.type'; + +describe('UserInfo', () => { + const mockUser: UserData = { + id: '1', + email: 'test@example.com', + name: 'Test User', + companyName: 'Test Company', + image: 'test-image.jpg', + createdAt: '2024-10-01T00:00:00Z', + updatedAt: '2024-10-03T00:00:00Z', + }; + + // 유저 정보가 정상적으로 렌더링 + it('renders user information correctly', () => { + // 컴포넌트를 렌더링할 때 mockUser를 prop으로 전달 + render(); + + // 이름, 회사명, 이메일이 올바르게 화면에 표시되는지 확인 + expect(screen.getByText('Test User')).toBeInTheDocument(); + expect(screen.getByText('Test Company')).toBeInTheDocument(); + expect(screen.getByText('test@example.com')).toBeInTheDocument(); + }); + + // user prop이 null일 때 아무것도 렌더링 안됨 + it('renders nothing when user prop is null', () => { + // user가 null인 경우로 렌더링 + render(); + + // 유저 정보가 존재하지 않으므로 해당 요소들이 화면에 없는지 확인 + expect(screen.queryByText('Test User')).not.toBeInTheDocument(); + expect(screen.queryByText('Test Company')).not.toBeInTheDocument(); + expect(screen.queryByText('test@example.com')).not.toBeInTheDocument(); + }); +}); diff --git a/src/app/components/UserProfileLayout/useProfileState.test.ts b/src/app/components/UserProfileLayout/useProfileState.test.ts index 9b42aec7..9a3b4c85 100644 --- a/src/app/components/UserProfileLayout/useProfileState.test.ts +++ b/src/app/components/UserProfileLayout/useProfileState.test.ts @@ -3,7 +3,7 @@ import { renderHook, act } from '@testing-library/react'; import { useProfileState } from './useProfileState'; import { UserData } from '@/types/client.type'; -describe('useProfileState 훅 테스트', () => { +describe('useProfileState', () => { // 테스트 전에 사용할 기본 유저 데이터 let user: UserData; From 9c09446d167a89350c18e744ca2cc159e1a0084d Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Tue, 8 Oct 2024 13:42:58 +0900 Subject: [PATCH 06/36] =?UTF-8?q?[KAN-48]=20test:=20UserProfileHeader=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserProfileHeader.test.tsx | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/app/components/UserProfileLayout/UserProfileHeader.test.tsx diff --git a/src/app/components/UserProfileLayout/UserProfileHeader.test.tsx b/src/app/components/UserProfileLayout/UserProfileHeader.test.tsx new file mode 100644 index 00000000..e0110564 --- /dev/null +++ b/src/app/components/UserProfileLayout/UserProfileHeader.test.tsx @@ -0,0 +1,44 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import UserProfileHeader from './UserProfileHeader'; +import '@testing-library/jest-dom'; + +// 이미지 모킹 +jest.mock('@/public/images', () => ({ + BtnEdit: () =>
, + ImageProfile: () =>
, +})); + +describe('UserProfileHeader', () => { + const mockToggleModal = jest.fn(); + + beforeEach(() => { + render(); + }); + + // 기본 렌더링 + it('renders correctly', () => { + // 헤더 이미지가 렌더링 되었는지 확인 + const profileImage = screen.getByTestId('ImageProfile'); + expect(profileImage).toBeInTheDocument(); + + // 프로필수정 버튼이 렌더링 되었는지 확인 + const button = screen.getByRole('button'); + expect(button).toBeInTheDocument(); + + // 버튼아이콘이 렌더링 되었는지 확인 + const buttonIcon = screen.getByTestId('BtnEdit'); + expect(buttonIcon).toBeInTheDocument(); + }); + + // 버튼 클릭 시 toggleModal 함수가 호출되는지 확인 + it('calls toggleModal when button is clicked', () => { + // 버튼 찾기 + const button = screen.getByRole('button'); + + // 버튼 클릭 + fireEvent.click(button); + + // toggleModal 함수가 한 번 호출되었는지 확인 + expect(mockToggleModal).toHaveBeenCalledTimes(1); + }); +}); From a92c50a9dd81e161735ef894b0223690d33c4646 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Tue, 8 Oct 2024 15:42:04 +0900 Subject: [PATCH 07/36] =?UTF-8?q?[KAN-48]=20test:=20UserProfileLayout=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserProfileLayout.test.tsx | 94 +++++++++++++++++++ .../UserProfileLayout/useProfileState.test.ts | 1 - 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/app/components/UserProfileLayout/UserProfileLayout.test.tsx diff --git a/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx new file mode 100644 index 00000000..2abb26c9 --- /dev/null +++ b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx @@ -0,0 +1,94 @@ +// src/app/components/UserProfileLayout/UserProfileLayout.test.tsx +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import UserProfileLayout from './UserProfileLayout'; +import { UserData } from '@/types/client.type'; +import '@testing-library/jest-dom'; +import { Dispatch, SetStateAction } from 'react'; + +// 프로필수정 모달 모킹 +jest.mock('../Modal/ProfileEditModal', () => { + return function MockProfileEditModal({ + onClose, + onUploadProfileImage, + onSubmit, + profileInput, + setProfileInput, + }: { + user: UserData | null; + onClose: () => void; + onUploadProfileImage?: () => void; + onSubmit?: () => void; + imagePreview?: string; + profileInput: string; + setProfileInput: Dispatch>; + }) { + return ( +
+

프로필 수정 모달

+ + + + setProfileInput(e.target.value)} + /> +
+ ); + }; +}); + +// UserProfileHeader 모킹 +jest.mock('./UserProfileHeader', () => { + return ({ toggleModal }: { toggleModal: () => void }) => ( +
+
UserProfileHeader Mock
+ +
+ ); +}); + +const mockUser: UserData = { + id: '1', + email: 'test@example.com', + name: 'Test User', + companyName: 'Test Company', + image: '/test-image.jpg', + createdAt: '2024-10-01T00:00:00Z', + updatedAt: '2024-10-03T00:00:00Z', +}; + +describe('UserProfileLayout', () => { + beforeEach(() => { + const modalRoot = document.createElement('div'); + modalRoot.setAttribute('id', 'modal-root'); + document.body.appendChild(modalRoot); + }); + + beforeEach(() => { + render(); + }); + + // 기본 렌더링 + it('render correctly', () => { + expect(screen.getByText('Test User')).toBeInTheDocument(); + expect(screen.getByText('Test Company')).toBeInTheDocument(); + expect(screen.getByText('test@example.com')).toBeInTheDocument(); + expect(screen.getByAltText('Profile')).toBeInTheDocument(); + }); + + // 모달 열기 및 닫기 테스트 + it('opens and closes the ProfileEditModal', async () => { + // 모달 열기 버튼 클릭 + fireEvent.click(screen.getByRole('button', { name: 'Edit' })); + + // 모달이 열렸는지 확인 + expect(await screen.findByText('프로필 수정 모달')).toBeInTheDocument(); + + // 모달 닫기 버튼 클릭 + fireEvent.click(screen.getByRole('button', { name: '닫기' })); + + // 모달이 닫혔는지 확인 + expect(screen.queryByText('프로필 수정 모달')).not.toBeInTheDocument(); + }); +}); diff --git a/src/app/components/UserProfileLayout/useProfileState.test.ts b/src/app/components/UserProfileLayout/useProfileState.test.ts index 9a3b4c85..6f0b8d6d 100644 --- a/src/app/components/UserProfileLayout/useProfileState.test.ts +++ b/src/app/components/UserProfileLayout/useProfileState.test.ts @@ -1,4 +1,3 @@ -// useProfileState.test.ts import { renderHook, act } from '@testing-library/react'; import { useProfileState } from './useProfileState'; import { UserData } from '@/types/client.type'; From 41679983fc501a0095af4baa423d5cf556cd1b3a Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Tue, 8 Oct 2024 15:44:54 +0900 Subject: [PATCH 08/36] =?UTF-8?q?[KAN-48]=20chore:=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/UserProfileLayout/UserProfileLayout.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx index 2abb26c9..29b4552f 100644 --- a/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx +++ b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx @@ -1,4 +1,3 @@ -// src/app/components/UserProfileLayout/UserProfileLayout.test.tsx import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import UserProfileLayout from './UserProfileLayout'; import { UserData } from '@/types/client.type'; From 1eedbbc4a00bccb363d9c60c69a368494106d469 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Tue, 8 Oct 2024 15:48:47 +0900 Subject: [PATCH 09/36] =?UTF-8?q?[KAN-48]=20chore:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/UserProfileLayout/UserProfileLayout.test.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx index 29b4552f..4b6f5771 100644 --- a/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx +++ b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx @@ -58,12 +58,6 @@ const mockUser: UserData = { }; describe('UserProfileLayout', () => { - beforeEach(() => { - const modalRoot = document.createElement('div'); - modalRoot.setAttribute('id', 'modal-root'); - document.body.appendChild(modalRoot); - }); - beforeEach(() => { render(); }); From ec631d4a5c5035eb6aaa70f96d7d4cf11db908b9 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Tue, 8 Oct 2024 16:11:04 +0900 Subject: [PATCH 10/36] =?UTF-8?q?[KAN-48]=20fix:=20mock=20component=20?= =?UTF-8?q?=EB=94=94=EC=8A=A4=ED=94=8C=EB=A0=88=EC=9D=B4=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=ED=95=B4=EC=84=9C=20=EB=A6=B0=ED=8A=B8?= =?UTF-8?q?=EC=98=A4=EB=A5=98=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserProfileLayout/UserProfileLayout.test.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx index 4b6f5771..80eb0e95 100644 --- a/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx +++ b/src/app/components/UserProfileLayout/UserProfileLayout.test.tsx @@ -6,7 +6,7 @@ import { Dispatch, SetStateAction } from 'react'; // 프로필수정 모달 모킹 jest.mock('../Modal/ProfileEditModal', () => { - return function MockProfileEditModal({ + function MockProfileEditModal({ onClose, onUploadProfileImage, onSubmit, @@ -34,17 +34,27 @@ jest.mock('../Modal/ProfileEditModal', () => { />
); - }; + } + + MockProfileEditModal.displayName = 'MockProfileEditModal'; + return MockProfileEditModal; }); // UserProfileHeader 모킹 jest.mock('./UserProfileHeader', () => { - return ({ toggleModal }: { toggleModal: () => void }) => ( + const MockUserProfileHeader = ({ + toggleModal, + }: { + toggleModal: () => void; + }) => (
UserProfileHeader Mock
); + + MockUserProfileHeader.displayName = 'MockUserProfileHeader'; + return MockUserProfileHeader; }); const mockUser: UserData = { From 6d4694978eeb9cb3dab2d33e5ca873dea8083edf Mon Sep 17 00:00:00 2001 From: chaeney Date: Tue, 8 Oct 2024 18:32:08 +0900 Subject: [PATCH 11/36] =?UTF-8?q?[KAN-115]=20fix=20:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20layoutshift=20?= =?UTF-8?q?=ED=98=84=EC=83=81=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(auth)/loading.tsx | 2 +- src/app/(main)/gatherings/loading.tsx | 2 +- src/app/loading.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/(auth)/loading.tsx b/src/app/(auth)/loading.tsx index 50e35ca6..3bb70580 100644 --- a/src/app/(auth)/loading.tsx +++ b/src/app/(auth)/loading.tsx @@ -4,7 +4,7 @@ import Loader from '@/app/components/Loader/Loader'; const Loading = () => { return ( -
+
;
); diff --git a/src/app/(main)/gatherings/loading.tsx b/src/app/(main)/gatherings/loading.tsx index 50e35ca6..8a1d4420 100644 --- a/src/app/(main)/gatherings/loading.tsx +++ b/src/app/(main)/gatherings/loading.tsx @@ -5,7 +5,7 @@ import Loader from '@/app/components/Loader/Loader'; const Loading = () => { return (
- ; +
); }; diff --git a/src/app/loading.tsx b/src/app/loading.tsx index 50e35ca6..8a1d4420 100644 --- a/src/app/loading.tsx +++ b/src/app/loading.tsx @@ -5,7 +5,7 @@ import Loader from '@/app/components/Loader/Loader'; const Loading = () => { return (
- ; +
); }; From b9b79d219c426097849a2642ab56c6b487772caa Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 13:48:17 +0900 Subject: [PATCH 12/36] =?UTF-8?q?[KAN-116]=20fix:=20=EC=A7=80=EB=82=9C=20?= =?UTF-8?q?=EB=AA=A8=EC=9E=84=EB=8F=84=20=EC=B7=A8=EC=86=8C=EA=B0=80=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/BottomFloatingBar/ParticipationButton.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/components/BottomFloatingBar/ParticipationButton.tsx b/src/app/components/BottomFloatingBar/ParticipationButton.tsx index 379187a8..3bb0ae9c 100644 --- a/src/app/components/BottomFloatingBar/ParticipationButton.tsx +++ b/src/app/components/BottomFloatingBar/ParticipationButton.tsx @@ -87,10 +87,11 @@ const ParticipationButton = ({ // 주최자일 경우 if (isHost) { - const disabled = isRegistrationEnded || isCancelled; // 마감일이 지났거나 취소되었을 경우 button 비활성화 + const disabled = isRegistrationEnded; // 마감일이 지난 경우 버튼 비활성화 + return (
- {renderButton('취소하기', 'white', cancelGathering, disabled)} + {renderButton('취소하기', 'white', cancelGathering)} {renderButton('공유하기', 'default', copyUrlToClipboard, disabled)}
); From 4cc6a2dd2d07f3de47995c49b8242a40267dc2a6 Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 14:47:15 +0900 Subject: [PATCH 13/36] =?UTF-8?q?[KAN-116]=20fix:=20pretendard=20=ED=8F=B0?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=9B=EC=95=84=EC=98=A4=EB=8A=94=20url=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/globals.css | 73 +----------------------------------------- 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/src/styles/globals.css b/src/styles/globals.css index 73ac91d3..1014a17c 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -2,78 +2,7 @@ @tailwind components; @tailwind utilities; -@font-face { - font-family: 'Pretendard'; - font-weight: 100; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-Thin.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 200; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-ExtraLight.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 300; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-Light.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 400; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-Regular.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 500; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-Medium.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 600; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-SemiBold.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 700; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-Bold.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 800; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-ExtraBold.woff') - format('woff'); - font-display: swap; -} -@font-face { - font-family: 'Pretendard'; - font-weight: 900; - font-style: normal; - src: url('https://cdn.jsdelivr.net/gh/webfontworld/pretendard/Pretendard-Black.woff') - format('woff'); - font-display: swap; -} +@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-dynamic-subset.min.css'); /* 사용자 정의 스크롤 바 숨기기 클래스 */ .scrollbar-hide { From 9b07321d0d83f2da5e849e3a851b43bb3690e42c Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 15:09:35 +0900 Subject: [PATCH 14/36] =?UTF-8?q?[KAN-116]=20chore:=20=EB=AA=A8=EC=A7=91?= =?UTF-8?q?=20=EC=A0=95=EC=9B=90=20=3D>=20=ED=98=84=EC=9E=AC=20=EC=9D=B8?= =?UTF-8?q?=EC=9B=90=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EC=B5=9C=EC=86=8C=EC=9D=B8=EC=9B=90,=20=EC=B5=9C=EB=8C=80?= =?UTF-8?q?=EC=9D=B8=EC=9B=90=20=EB=9D=84=EC=96=B4=EC=93=B0=EA=B8=B0=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/app/components/InformationCard/InformationCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/components/InformationCard/InformationCard.tsx b/src/app/components/InformationCard/InformationCard.tsx index 94106fad..8fe5ae36 100644 --- a/src/app/components/InformationCard/InformationCard.tsx +++ b/src/app/components/InformationCard/InformationCard.tsx @@ -158,7 +158,7 @@ const InformationCard = ({
- 모집 정원 {participantCount}명 + 현재 인원 {participantCount}명
{renderAvatars()}
@@ -184,9 +184,9 @@ const InformationCard = ({ />
-
최소인원 {MIN_PARTICIPANTS}명
+
최소 인원 {MIN_PARTICIPANTS}명
- 최대인원 {maxParticipants}명 + 최대 인원 {maxParticipants}명
From e431da45901c8bd1e9e19b6d9b20b0ee967427f1 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 15:24:56 +0900 Subject: [PATCH 15/36] =?UTF-8?q?[KAN-113]=20refactor:=20ReviewFilterButto?= =?UTF-8?q?ns=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EC=B6=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mypage/_component/ReviewFilterButtons.tsx | 48 ++++++++++++------- .../components/Modal/MakeGatheringModal.tsx | 1 - 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/app/(main)/mypage/_component/ReviewFilterButtons.tsx b/src/app/(main)/mypage/_component/ReviewFilterButtons.tsx index 9600dc70..a78ff47f 100644 --- a/src/app/(main)/mypage/_component/ReviewFilterButtons.tsx +++ b/src/app/(main)/mypage/_component/ReviewFilterButtons.tsx @@ -1,32 +1,48 @@ 'use client'; import Chip from '@/app/components/Chip/Chip'; +import { useRouter, usePathname } from 'next/navigation'; +import { useEffect, useState } from 'react'; -interface ReviewFilterButtonsProps { - filterType: string; - setFilterType: (type: string) => void; -} +type filterType = 'writable' | 'written'; -const ReviewFilterButtons = ({ - filterType, - setFilterType, -}: ReviewFilterButtonsProps) => { - const filterTypeList = ['작성 가능한 리뷰', '작성한 리뷰'] as const; +const ReviewFilterButtons = () => { + const filterTypeButtons = [ + { name: '작성 가능한 리뷰', type: 'writable' }, + { name: '작성한 리뷰', type: 'written' }, + ]; - const handleChangeFilterType = (type: (typeof filterTypeList)[number]) => { - setFilterType(type); + const [currentFilterType, setCurrentFilterType] = useState( + null, + ); + + const router = useRouter(); + const pathname = usePathname(); + + useEffect(() => { + if (typeof window !== 'undefined') { + const path = pathname.replace('/mypage/review/', '') as filterType; + setCurrentFilterType(path); + } + }, [pathname]); + + const handleChangeFilterType = (filterType: filterType) => { + router.push(`/mypage/review/${filterType}`); }; return (
- {filterTypeList.map((type) => ( + {filterTypeButtons.map((filterType) => ( ))}
diff --git a/src/app/components/Modal/MakeGatheringModal.tsx b/src/app/components/Modal/MakeGatheringModal.tsx index 6c5268d8..ba1e6ba1 100644 --- a/src/app/components/Modal/MakeGatheringModal.tsx +++ b/src/app/components/Modal/MakeGatheringModal.tsx @@ -93,7 +93,6 @@ const MakeGatheringModal = ({ onClose }: MakeGatheringModalProps) => { router.push(`/gatherings/${data.id}`); toast.success(message); - // TODO : 모임 생성 후 페이지 리로드 }; return ( From a1fbf455e256ea87cd8968d6bf4f3433e0ab7152 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 15:25:17 +0900 Subject: [PATCH 16/36] =?UTF-8?q?[KAN-113]=20feat:=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?=EA=B0=80=EB=8A=A5=ED=95=9C=20=EB=A6=AC=EB=B7=B0=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/mypage/review/writable/page.tsx | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/app/(main)/mypage/review/writable/page.tsx diff --git a/src/app/(main)/mypage/review/writable/page.tsx b/src/app/(main)/mypage/review/writable/page.tsx new file mode 100644 index 00000000..f02bf935 --- /dev/null +++ b/src/app/(main)/mypage/review/writable/page.tsx @@ -0,0 +1,76 @@ +'use client'; + +import getJoinedGatherings from '@/app/api/actions/gatherings/getJoinedGathering'; +import Card from '@/app/components/Card/Card'; +import ReviewModal from '@/app/components/Modal/ReviewModal'; +import usePreventScroll from '@/hooks/usePreventScroll'; +import { useQuery } from '@tanstack/react-query'; +import { useState } from 'react'; +import ReviewFilterButtons from '../../_component/ReviewFilterButtons'; + +const Page = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [cardId, setCardId] = useState(0); + + const { data } = useQuery({ + queryKey: ['writableReviews'], + queryFn: () => getJoinedGatherings(), + }); + + const writableReviews = data?.filter((data) => { + return data?.isCompleted && !data?.isReviewed; + }); + + const handleOpenModal = (id: number) => { + setCardId(id); + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + }; + + usePreventScroll(isModalOpen); + + return ( + <> +
+ {/* chips */} + + + {/* cards */} + {writableReviews?.length ? ( + writableReviews.map((data) => ( + + + + + data.isCompleted && handleOpenModal(data.id) + } + /> + + )) + ) : ( + + )} +
+ + {isModalOpen && ( + + )} + + ); +}; + +export default Page; + +const EmptyPage = () => { + return ( +
+

+ 아직 작성한 리뷰가 없어요 +

+
+ ); +}; From e0f0d34bdec601eb60ca40b41c48c9f061e55508 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 15:41:12 +0900 Subject: [PATCH 17/36] =?UTF-8?q?[KAN-113]=20feat:=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=9C=20=EB=A6=AC=EB=B7=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=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/app/(main)/mypage/review/written/page.tsx | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/app/(main)/mypage/review/written/page.tsx diff --git a/src/app/(main)/mypage/review/written/page.tsx b/src/app/(main)/mypage/review/written/page.tsx new file mode 100644 index 00000000..52753e97 --- /dev/null +++ b/src/app/(main)/mypage/review/written/page.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { useUser } from '@/app/(auth)/context/UserContext'; +import getReviewList from '@/app/api/actions/reviews/getReviewList'; +import Review from '@/app/components/Review/Review'; +import { useQuery } from '@tanstack/react-query'; +import Link from 'next/link'; +import ReviewFilterButtons from '../../_component/ReviewFilterButtons'; + +const Page = () => { + const { user } = useUser(); + + const { data: writtenReviews } = useQuery({ + queryKey: ['myreviews'], + queryFn: () => + getReviewList({ + userId: Number(user?.id), + sortBy: 'createdAt', + sortOrder: 'desc', + }), + }); + + return ( + <> +
+ {/* chips */} + + + {/* cards */} + {writtenReviews?.length ? ( +
+ {writtenReviews.map((review) => ( + + + + ))} +
+ ) : ( + + )} +
+ + ); +}; + +export default Page; + +const EmptyPage = () => { + return ( +
+

+ 아직 작성한 리뷰가 없어요 +

+
+ ); +}; From f6c061dcb19c9a2265aae5158cb88a0f87e9781c Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 15:53:20 +0900 Subject: [PATCH 18/36] =?UTF-8?q?[KAN-113]=20fix:=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=83=AD=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/_component/Tab.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/(main)/mypage/_component/Tab.tsx b/src/app/(main)/mypage/_component/Tab.tsx index 16f63334..1ba85a77 100644 --- a/src/app/(main)/mypage/_component/Tab.tsx +++ b/src/app/(main)/mypage/_component/Tab.tsx @@ -6,15 +6,15 @@ import { usePathname } from 'next/navigation'; const tabList = [ { name: '나의 모임', - link: '/mypage', + link: ['/mypage'], }, { name: '나의 리뷰', - link: '/mypage/review', + link: ['/mypage/review/writable', '/mypage/review/written'], }, { name: '내가 만든 모임', - link: '/mypage/created', + link: ['/mypage/created'], }, ]; @@ -26,8 +26,11 @@ const Tab = () => { return (
    {tabList.map((item, index) => ( -
  • - {item.name} +
  • + {item.name}
  • ))}
From 085c3724026bc1ba3d3d193b9e3854ba22f7d6ac Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 15:56:22 +0900 Subject: [PATCH 19/36] =?UTF-8?q?[KAN-113]=20fix:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=83=AD=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...wFilterButtons.tsx => ReviewFilterTab.tsx} | 4 +- src/app/(main)/mypage/review/page.tsx | 144 ------------------ .../(main)/mypage/review/writable/page.tsx | 6 +- src/app/(main)/mypage/review/written/page.tsx | 4 +- 4 files changed, 7 insertions(+), 151 deletions(-) rename src/app/(main)/mypage/_component/{ReviewFilterButtons.tsx => ReviewFilterTab.tsx} (94%) delete mode 100644 src/app/(main)/mypage/review/page.tsx diff --git a/src/app/(main)/mypage/_component/ReviewFilterButtons.tsx b/src/app/(main)/mypage/_component/ReviewFilterTab.tsx similarity index 94% rename from src/app/(main)/mypage/_component/ReviewFilterButtons.tsx rename to src/app/(main)/mypage/_component/ReviewFilterTab.tsx index a78ff47f..08ec2bc2 100644 --- a/src/app/(main)/mypage/_component/ReviewFilterButtons.tsx +++ b/src/app/(main)/mypage/_component/ReviewFilterTab.tsx @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react'; type filterType = 'writable' | 'written'; -const ReviewFilterButtons = () => { +const ReviewFilterTab = () => { const filterTypeButtons = [ { name: '작성 가능한 리뷰', type: 'writable' }, { name: '작성한 리뷰', type: 'written' }, @@ -49,4 +49,4 @@ const ReviewFilterButtons = () => { ); }; -export default ReviewFilterButtons; +export default ReviewFilterTab; diff --git a/src/app/(main)/mypage/review/page.tsx b/src/app/(main)/mypage/review/page.tsx deleted file mode 100644 index 8cd75d7c..00000000 --- a/src/app/(main)/mypage/review/page.tsx +++ /dev/null @@ -1,144 +0,0 @@ -'use client'; - -import { useUser } from '@/app/(auth)/context/UserContext'; -import getJoinedGatherings from '@/app/api/actions/gatherings/getJoinedGathering'; -import getReviewList from '@/app/api/actions/reviews/getReviewList'; -import Card from '@/app/components/Card/Card'; -import ReviewModal from '@/app/components/Modal/ReviewModal'; -import Review from '@/app/components/Review/Review'; -import { MYPAGE_REVIEW_TABS } from '@/constants/common'; -import usePreventScroll from '@/hooks/usePreventScroll'; -import { useQueries } from '@tanstack/react-query'; -import Link from 'next/link'; -import { useState } from 'react'; -import ReviewFilterButtons from '../_component/ReviewFilterButtons'; - -const Page = () => { - const [isModalOpen, setIsModalOpen] = useState(false); - const [filterType, setFilterType] = useState( - MYPAGE_REVIEW_TABS.WRITABLE, - ); - const [cardId, setCardId] = useState(0); - - const { user } = useUser(); - - const results = useQueries({ - queries: [ - { - queryKey: ['writableReviews'], - queryFn: () => getJoinedGatherings(), - }, - { - queryKey: ['myreviews'], - queryFn: () => - getReviewList({ - userId: Number(user?.id), - sortBy: 'createdAt', - sortOrder: 'desc', - }), - }, - ], - }); - - const writableReviewData = results[0].data; - const reviewData = results[1].data; - - const filteredData = Array.isArray(writableReviewData) - ? writableReviewData.filter((data) => { - switch (filterType) { - case MYPAGE_REVIEW_TABS.WRITABLE: // 작성 가능한 리뷰 - return data?.isCompleted && !data?.isReviewed; - - case MYPAGE_REVIEW_TABS.WRITTEN: // 작성한 리뷰 - return data?.isCompleted && data?.isReviewed; - - default: - return true; - } - }) - : []; - - const handleOpenModal = (id: number) => { - setCardId(id); - setIsModalOpen(true); - }; - - const handleCloseModal = () => { - setIsModalOpen(false); - }; - - usePreventScroll(isModalOpen); - - return ( - <> -
- {/* chips */} - - {/* cards */} - {(() => { - switch (filterType) { - // 전체 혹은 작성 가능한 리뷰 - case MYPAGE_REVIEW_TABS.WRITABLE: - return filteredData?.length ? ( - filteredData.map((data) => ( - - - - - data.isCompleted && handleOpenModal(data.id) - } - /> - - )) - ) : ( - - ); - // 작성한 리뷰 - case MYPAGE_REVIEW_TABS.WRITTEN: - return reviewData?.length ? ( -
- {reviewData.map((review) => ( - - - - ))} -
- ) : ( - - ); - } - })()} -
- - {isModalOpen && ( - - )} - - ); -}; - -export default Page; - -const EmptyPage = () => { - return ( -
-

- 아직 작성한 리뷰가 없어요 -

-
- ); -}; diff --git a/src/app/(main)/mypage/review/writable/page.tsx b/src/app/(main)/mypage/review/writable/page.tsx index f02bf935..1ae7bcc4 100644 --- a/src/app/(main)/mypage/review/writable/page.tsx +++ b/src/app/(main)/mypage/review/writable/page.tsx @@ -6,7 +6,7 @@ import ReviewModal from '@/app/components/Modal/ReviewModal'; import usePreventScroll from '@/hooks/usePreventScroll'; import { useQuery } from '@tanstack/react-query'; import { useState } from 'react'; -import ReviewFilterButtons from '../../_component/ReviewFilterButtons'; +import ReviewFilterTab from '../../_component/ReviewFilterTab'; const Page = () => { const [isModalOpen, setIsModalOpen] = useState(false); @@ -35,8 +35,8 @@ const Page = () => { return ( <>
- {/* chips */} - + {/* tab */} + {/* cards */} {writableReviews?.length ? ( diff --git a/src/app/(main)/mypage/review/written/page.tsx b/src/app/(main)/mypage/review/written/page.tsx index 52753e97..da3335e8 100644 --- a/src/app/(main)/mypage/review/written/page.tsx +++ b/src/app/(main)/mypage/review/written/page.tsx @@ -5,7 +5,7 @@ import getReviewList from '@/app/api/actions/reviews/getReviewList'; import Review from '@/app/components/Review/Review'; import { useQuery } from '@tanstack/react-query'; import Link from 'next/link'; -import ReviewFilterButtons from '../../_component/ReviewFilterButtons'; +import ReviewFilterTab from '../../_component/ReviewFilterTab'; const Page = () => { const { user } = useUser(); @@ -24,7 +24,7 @@ const Page = () => { <>
{/* chips */} - + {/* cards */} {writtenReviews?.length ? ( From f566e62218024c8861ca5e9121cd8bafb7051c0b Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 15:58:18 +0900 Subject: [PATCH 20/36] =?UTF-8?q?[KAN-113]=20refactor:=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EA=B4=80=EB=A0=A8=20=EB=B9=88=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(main)/mypage/_component/EmptyReviewPage.tsx | 11 +++++++++++ src/app/(main)/mypage/review/writable/page.tsx | 13 ++----------- src/app/(main)/mypage/review/written/page.tsx | 15 +++------------ 3 files changed, 16 insertions(+), 23 deletions(-) create mode 100644 src/app/(main)/mypage/_component/EmptyReviewPage.tsx diff --git a/src/app/(main)/mypage/_component/EmptyReviewPage.tsx b/src/app/(main)/mypage/_component/EmptyReviewPage.tsx new file mode 100644 index 00000000..c999a1c7 --- /dev/null +++ b/src/app/(main)/mypage/_component/EmptyReviewPage.tsx @@ -0,0 +1,11 @@ +const EmptyReviewPage = () => { + return ( +
+

+ 아직 작성한 리뷰가 없어요 +

+
+ ); +}; + +export default EmptyReviewPage; diff --git a/src/app/(main)/mypage/review/writable/page.tsx b/src/app/(main)/mypage/review/writable/page.tsx index 1ae7bcc4..9b6d02e5 100644 --- a/src/app/(main)/mypage/review/writable/page.tsx +++ b/src/app/(main)/mypage/review/writable/page.tsx @@ -6,6 +6,7 @@ import ReviewModal from '@/app/components/Modal/ReviewModal'; import usePreventScroll from '@/hooks/usePreventScroll'; import { useQuery } from '@tanstack/react-query'; import { useState } from 'react'; +import EmptyReviewPage from '../../_component/EmptyReviewPage'; import ReviewFilterTab from '../../_component/ReviewFilterTab'; const Page = () => { @@ -52,7 +53,7 @@ const Page = () => { )) ) : ( - + )}
@@ -64,13 +65,3 @@ const Page = () => { }; export default Page; - -const EmptyPage = () => { - return ( -
-

- 아직 작성한 리뷰가 없어요 -

-
- ); -}; diff --git a/src/app/(main)/mypage/review/written/page.tsx b/src/app/(main)/mypage/review/written/page.tsx index da3335e8..be4aec70 100644 --- a/src/app/(main)/mypage/review/written/page.tsx +++ b/src/app/(main)/mypage/review/written/page.tsx @@ -5,6 +5,7 @@ import getReviewList from '@/app/api/actions/reviews/getReviewList'; import Review from '@/app/components/Review/Review'; import { useQuery } from '@tanstack/react-query'; import Link from 'next/link'; +import EmptyReviewPage from '../../_component/EmptyReviewPage'; import ReviewFilterTab from '../../_component/ReviewFilterTab'; const Page = () => { @@ -23,7 +24,7 @@ const Page = () => { return ( <>
- {/* chips */} + {/* tab */} {/* cards */} @@ -43,7 +44,7 @@ const Page = () => { ))}
) : ( - + )}
@@ -51,13 +52,3 @@ const Page = () => { }; export default Page; - -const EmptyPage = () => { - return ( -
-

- 아직 작성한 리뷰가 없어요 -

-
- ); -}; From 0a18a6ab51c67e9e37eb1e2ef43597e02d0aea32 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Thu, 10 Oct 2024 16:01:49 +0900 Subject: [PATCH 21/36] =?UTF-8?q?[KAN-47]=20test:=20ParticipationButton=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/BottomFloatingBar/Mock.ts | 50 --- .../ParticipationButton.test.tsx | 310 ++++++++++++++++++ 2 files changed, 310 insertions(+), 50 deletions(-) delete mode 100644 src/app/components/BottomFloatingBar/Mock.ts create mode 100644 src/app/components/BottomFloatingBar/ParticipationButton.test.tsx diff --git a/src/app/components/BottomFloatingBar/Mock.ts b/src/app/components/BottomFloatingBar/Mock.ts deleted file mode 100644 index 007acc77..00000000 --- a/src/app/components/BottomFloatingBar/Mock.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* user mock data */ -export const userData = { - name: 'test name', - id: 1234, -}; - -/* 모임 상세 조회 mock data */ -export const groupData = { - id: 0, - registrationEnd: '2024-09-22T23:59:59Z', - participantCount: 5, - capacity: 10, - createdBy: 1232, - canceledAt: null, -}; - -/* 참가자 mock data */ -export const participantsData = [ - { - User: { - id: 1232, - }, - }, - { - User: { - id: 1534, - }, - }, - { - User: { - id: 1344, - }, - }, - { - User: { - id: 1654, - }, - }, - { - User: { - id: 1634, - }, - }, -]; - -/* 예비로 추가한 이벤트핸들링함수 */ -export const onCancel = () => console.log('모임이 취소되었습니다.'); -export const onShare = () => console.log('모임을 공유했습니다.'); -export const onJoin = () => console.log('모임에 참여했습니다.'); -export const onWithdraw = () => console.log('참여를 취소했습니다.'); diff --git a/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx b/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx new file mode 100644 index 00000000..ada3c4b2 --- /dev/null +++ b/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx @@ -0,0 +1,310 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import ParticipationButton from './ParticipationButton'; +import { useRouter } from 'next/navigation'; +import useCopyUrlToClipboard from '@/hooks/useCopyUrlToClipboard'; +import useCancelGathering from '@/hooks/useCancelGathering'; +import useParticipation from '@/hooks/useParticipation'; +import { UserData } from '@/types/client.type'; +import Button from '../Button/Button'; +import '@testing-library/jest-dom'; + +// 유저데이터 모킹 +const mockUser: UserData = { + id: 1, + email: 'test@example.com', + name: 'Test User', + companyName: 'Test Company', + image: 'test-image.jpg', + createdAt: '2024-10-01T00:00:00Z', + updatedAt: '2024-10-03T00:00:00Z', +}; + +// Popup 컴포넌트 모킹 +jest.mock('../Popup/Popup', () => ({ + default: ({ onClickConfirm }: { onClickConfirm: () => void }) => ( +
+

로그인이 필요해요.

+
+ ), +})); + +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + useParams: jest.fn(() => ({ id: '1' })), +})); + +// hook 컴포넌트 모킹 +jest.mock('@/hooks/useCopyUrlToClipboard', () => jest.fn()); +jest.mock('@/hooks/useCancelGathering', () => jest.fn()); +jest.mock('@/hooks/useParticipation', () => jest.fn()); +let isShowPopup = false; + +describe('ParticipationButton', () => { + const mockRouter = { push: jest.fn() }; + const mockCopyUrlToClipboard = jest.fn(); + const mockCancelGathering = jest.fn(); + const mockSetHasParticipated = jest.fn(); + const mockSetIsShowPopup = jest.fn(); + + beforeEach(() => { + (useRouter as jest.Mock).mockReturnValue(mockRouter); + + // useCopyUrlToClipboard의 함수 모킹 + (useCopyUrlToClipboard as jest.Mock).mockReturnValue({ + copyUrlToClipboard: mockCopyUrlToClipboard, + }); + + // useCancelGathering의 함수 모킹 + (useCancelGathering as jest.Mock).mockReturnValue({ + cancelGathering: mockCancelGathering, + }); + + // useParticipation 훅의 반환값 모킹 + (useParticipation as jest.Mock).mockImplementation((user) => ({ + hasParticipated: false, + setHasParticipated: mockSetHasParticipated, + isShowPopup, + setIsShowPopup: mockSetIsShowPopup, // 모킹된 함수 사용 + handleJoinClick: jest.fn(async (value) => { + if (!user) { + isShowPopup = value; + mockSetIsShowPopup(true); + } + }), + })); + }); + + afterEach(() => { + jest.clearAllMocks(); // 모킹된 함수 초기화 + // isShowPopup = false; // 팝업 초기화 + }); + + // 주최자일 때 "취소하기"와 "공유하기" 버튼이 렌더링되는지 확인 + it('checks if "Cancel" and "Share" buttons are rendered when the user is a host', () => { + render( + , + ); + + expect( + screen.getByRole('button', { name: '취소하기' }), + ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: '공유하기' }), + ).toBeInTheDocument(); + }); + + // 주최자일 때 "공유하기" 버튼을 클릭하면 copyUrlToClipboard 함수가 호출되는지 확인 + it('checks if the copyUrlToClipboard function is called when "Share" button is clicked by the host', () => { + render( + , + ); + + const shareButton = screen.getByRole('button', { name: '공유하기' }); + fireEvent.click(shareButton); + + expect(mockCopyUrlToClipboard).toHaveBeenCalled(); + }); + + // 주최자일 때 "취소하기" 버튼을 클릭하면 copyUrlToClipboard 함수가 호출되는지 확인 + it('checks if the copyUrlToClipboard function is called when "Cancel" button is clicked by the host', () => { + render( + , + ); + + const cancelButton = screen.getByRole('button', { name: '취소하기' }); + fireEvent.click(cancelButton); + + expect(mockCancelGathering).toHaveBeenCalled(); + }); + + // 주최자일 때 마감일이 지났거나 모임이 취소된 경우 버튼이 비활성화되는지 확인 + it('checks if buttons are disabled when the host has passed the deadline or the gathering is canceled', () => { + render( + , + ); + + const cancelButton = screen.getByRole('button', { name: '취소하기' }); + const shareButton = screen.getByRole('button', { name: '공유하기' }); + + expect(cancelButton).toBeDisabled(); + expect(shareButton).toBeDisabled(); + }); + + // 참여자일 때 참여 상태에 따라 버튼 텍스트가 변경되는지 확인 + it('checks if button text changes based on participation status when the user is a participant', () => { + (useParticipation as jest.Mock).mockReturnValue({ + hasParticipated: true, + setHasParticipated: mockSetHasParticipated, + isShowPopup: false, + setIsShowPopup: mockSetIsShowPopup, + handleJoinClick: jest.fn(), + handleWithdrawClick: jest.fn(), + }); + + render( + , + ); + + expect( + screen.getByRole('button', { name: '참여 취소하기' }), + ).toBeInTheDocument(); + }); + + // 참여 인원이 가득 찼을 때 버튼이 비활성화되는지 확인 + it('checks if the button is disabled when the participant count is full', () => { + render( + , + ); + + const button = screen.getByRole('button', { name: '참여하기' }); + expect(button).toBeDisabled(); + }); + + // 마감일이 지났을 때 버튼이 비활성화되는지 확인 + it('checks if the button is disabled when the deadline has passed', () => { + render( + , + ); + + const button = screen.getByRole('button', { name: '참여하기' }); + expect(button).toBeDisabled(); + }); + + // 모임이 취소되었을 때 버튼이 비활성화되는지 확인 + it('checks if the button is disabled when the gathering is canceled', () => { + render( + , + ); + + const button = screen.getByRole('button', { name: '참여하기' }); + expect(button).toBeDisabled(); + }); + + // 참여 인원이 가득 찼고 마감일이 지난 경우 버튼이 비활성화되는지 확인 + it('checks if the button is disabled when participant count is full and registration end date has passed.', () => { + render( + , + ); + + const button = screen.getByRole('button', { name: '참여하기' }); + expect(button).toBeDisabled(); + }); + + // 모임이 취소되고 마감일이 지난 경우 버튼이 비활성화되는지 확인 + it('checks if the button is disabled when the meeting is canceled and registration end date has passed.', () => { + render( + , + ); + + const button = screen.getByRole('button', { name: '참여하기' }); + expect(button).toBeDisabled(); + }); + + // 모든 조건이 충족될 때 버튼이 비활성화되는지 확인 + it('checks if the button is disabled when all conditions are met.', () => { + render( + , + ); + + const button = screen.getByRole('button', { name: '참여하기' }); + expect(button).toBeDisabled(); + }); +}); From 39ec96c2d9f13dfc34123c040c66279f3b4b24db Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Thu, 10 Oct 2024 16:30:22 +0900 Subject: [PATCH 22/36] =?UTF-8?q?[KAN-47]=20test:=20BottomFloatingBar=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BottomFloatingBar.test.tsx | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/app/components/BottomFloatingBar/BottomFloatingBar.test.tsx diff --git a/src/app/components/BottomFloatingBar/BottomFloatingBar.test.tsx b/src/app/components/BottomFloatingBar/BottomFloatingBar.test.tsx new file mode 100644 index 00000000..a3386b56 --- /dev/null +++ b/src/app/components/BottomFloatingBar/BottomFloatingBar.test.tsx @@ -0,0 +1,97 @@ +'use client'; + +import { render, screen } from '@testing-library/react'; +import { useRouter } from 'next/navigation'; +import BottomFloatingBar from './BottomFloatingBar'; +import { UserData } from '@/types/client.type'; +import { GatheringParticipantsType } from '@/types/data.type'; +import '@testing-library/jest-dom'; + +// 유저데이터 모킹 +const mockUser: UserData = { + id: 1, + email: 'test@example.com', + name: 'Test User', + companyName: 'Test Company', + image: 'test-image.jpg', + createdAt: '2024-10-01T00:00:00Z', + updatedAt: '2024-10-03T00:00:00Z', +}; +const mockParticipantsData: GatheringParticipantsType[] = []; // 참가자 데이터 모킹 + +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + useParams: jest.fn(() => ({ id: '1' })), +})); + +describe('BottomFloatingBar 컴포넌트 테스트', () => { + const mockRouter = { push: jest.fn() }; + + beforeEach(() => { + (useRouter as jest.Mock).mockReturnValue(mockRouter); + }); + + // 참가자일 때 기본 렌더링 확인 + it('renders correctly when the user is a participant', () => { + render( + , + ); + + // 컴포넌트의 텍스트가 올바르게 렌더링되는지 확인 + expect( + screen.getByText('더 건강한 나와 팀을 위한 프로그램 🏃‍️️'), + ).toBeInTheDocument(); + expect( + screen.getByText( + '국내 최고 웰니스 전문가와 프로그램을 통해 지친 몸과 마음을 회복해봐요', + ), + ).toBeInTheDocument(); + + // 참여하기 버튼이 존재하는지 확인 + expect( + screen.getByRole('button', { name: '참여하기' }), + ).toBeInTheDocument(); + }); + + // 주최자일 때 기본 렌더링 확인 + it('renders correctly when the user is the organizer', () => { + render( + , + ); + + // 컴포넌트의 텍스트가 올바르게 렌더링되는지 확인 + expect( + screen.getByText('더 건강한 나와 팀을 위한 프로그램 🏃‍️️'), + ).toBeInTheDocument(); + expect( + screen.getByText( + '국내 최고 웰니스 전문가와 프로그램을 통해 지친 몸과 마음을 회복해봐요', + ), + ).toBeInTheDocument(); + + // 공유하기 버튼이 존재하는지 확인 + expect( + screen.getByRole('button', { name: '공유하기' }), + ).toBeInTheDocument(); + // 취소하기 버튼이 존재하는지 확인 + expect( + screen.getByRole('button', { name: '취소하기' }), + ).toBeInTheDocument(); + }); +}); From 4b28ef74928e24ab9118abf05d86c519154ea9b0 Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Thu, 10 Oct 2024 16:38:19 +0900 Subject: [PATCH 23/36] =?UTF-8?q?[KAN-47]=20chore:=20afterEach=EC=97=90?= =?UTF-8?q?=EC=84=9C=20isShowPopup=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/BottomFloatingBar/ParticipationButton.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx b/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx index ada3c4b2..4da25883 100644 --- a/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx +++ b/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx @@ -77,7 +77,6 @@ describe('ParticipationButton', () => { afterEach(() => { jest.clearAllMocks(); // 모킹된 함수 초기화 - // isShowPopup = false; // 팝업 초기화 }); // 주최자일 때 "취소하기"와 "공유하기" 버튼이 렌더링되는지 확인 From 371ae2d9d1899f386a549d9f970445d045344d50 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 16:43:30 +0900 Subject: [PATCH 24/36] =?UTF-8?q?[KAN-113]=20feat:=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20Info,=20Image=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=EC=9D=84=20Link=20=ED=83=9C=EA=B7=B8?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=90=EC=8B=B8=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/review/written/page.tsx | 48 +++++++++---------- src/app/components/Card/Card.tsx | 27 ++++++----- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/app/(main)/mypage/review/written/page.tsx b/src/app/(main)/mypage/review/written/page.tsx index be4aec70..b49c32f2 100644 --- a/src/app/(main)/mypage/review/written/page.tsx +++ b/src/app/(main)/mypage/review/written/page.tsx @@ -22,32 +22,30 @@ const Page = () => { }); return ( - <> -
- {/* tab */} - +
+ {/* tab */} + - {/* cards */} - {writtenReviews?.length ? ( -
- {writtenReviews.map((review) => ( - - - - ))} -
- ) : ( - - )} -
- + {/* cards */} + {writtenReviews?.length ? ( +
+ {writtenReviews.map((review) => ( + + + + ))} +
+ ) : ( + + )} +
); }; diff --git a/src/app/components/Card/Card.tsx b/src/app/components/Card/Card.tsx index d7f75610..95a54f08 100644 --- a/src/app/components/Card/Card.tsx +++ b/src/app/components/Card/Card.tsx @@ -12,6 +12,7 @@ import { formatDate, formatTime } from '@/utils/formatDate'; import { UserJoinedGatheringsData } from '@/types/data.type'; import { createContext, PropsWithChildren, useContext } from 'react'; import { MIN_PARTICIPANTS } from '@/constants/common'; +import Link from 'next/link'; interface CardProps { data: UserJoinedGatheringsData; @@ -32,16 +33,18 @@ const Card = ({
{/* 이미지 */} -
- 모임 이미지 -
+ +
+ 모임 이미지 +
+ {/* content - chip, info, button */} @@ -131,7 +134,7 @@ const CardInfo = (): JSX.Element => { const { name, location, dateTime, participantCount, capacity } = data; return ( - <> +

{name}

| @@ -146,7 +149,7 @@ const CardInfo = (): JSX.Element => { {participantCount}/{capacity}

- + ); }; From 7425c0e25457fabaf93e2cb7f2286e1e7af8911a Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 17:12:16 +0900 Subject: [PATCH 25/36] =?UTF-8?q?[KAN-113]=20fix:=20queryKey=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/review/writable/page.tsx | 2 +- src/app/(main)/mypage/review/written/page.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/mypage/review/writable/page.tsx b/src/app/(main)/mypage/review/writable/page.tsx index 9b6d02e5..a577c11e 100644 --- a/src/app/(main)/mypage/review/writable/page.tsx +++ b/src/app/(main)/mypage/review/writable/page.tsx @@ -14,7 +14,7 @@ const Page = () => { const [cardId, setCardId] = useState(0); const { data } = useQuery({ - queryKey: ['writableReviews'], + queryKey: ['reviews', 'writable'], queryFn: () => getJoinedGatherings(), }); diff --git a/src/app/(main)/mypage/review/written/page.tsx b/src/app/(main)/mypage/review/written/page.tsx index b49c32f2..63f8227d 100644 --- a/src/app/(main)/mypage/review/written/page.tsx +++ b/src/app/(main)/mypage/review/written/page.tsx @@ -12,7 +12,7 @@ const Page = () => { const { user } = useUser(); const { data: writtenReviews } = useQuery({ - queryKey: ['myreviews'], + queryKey: ['reviews', 'written'], queryFn: () => getReviewList({ userId: Number(user?.id), From 589958409b432e1146881c4646a11adc19f8bd50 Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 17:15:48 +0900 Subject: [PATCH 26/36] =?UTF-8?q?[KAN-118]=20refactor:=20renderAvatars=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EB=A5=BC=20=ED=9B=85=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InformationCard/InformationCard.tsx | 62 +---------------- src/hooks/useAvatars.tsx | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+), 60 deletions(-) create mode 100644 src/hooks/useAvatars.tsx diff --git a/src/app/components/InformationCard/InformationCard.tsx b/src/app/components/InformationCard/InformationCard.tsx index 94106fad..606a12d9 100644 --- a/src/app/components/InformationCard/InformationCard.tsx +++ b/src/app/components/InformationCard/InformationCard.tsx @@ -8,13 +8,13 @@ import { IconSaveActive, IconSaveInactive, } from '@/public/icons'; -import Avatar from './Avatar'; import InfoChip from '../Chip/InfoChip'; import ProgressBar from '../ProgressBar/ProgressBar'; import { GatheringParticipantsType } from '@/types/data.type'; import { MIN_PARTICIPANTS } from '@/constants/common'; import { formatDate, formatTimeColon } from '@/utils/formatDate'; import { useSavedGatheringList } from '@/context/SavedGatheringContext'; +import useAvatars from '@/hooks/useAvatars'; interface InformationCardProps { title: string; @@ -47,7 +47,6 @@ const InformationCard = ({ const [isSaved, setIsSaved] = useState(() => checkGatheringSaved(gatheringId, savedGatherings), ); - const [isHovered, setIsHovered] = useState(false); const handleToggleSave = () => { setIsSaved((prev) => !prev); @@ -56,64 +55,7 @@ const InformationCard = ({ } }; - const handleMouseEnter = () => { - setIsHovered(true); - }; - - const handleMouseLeave = () => { - setIsHovered(false); - }; - - // function of setting Avatars with remaining - const renderAvatars = () => { - const maxVisible = 4; - const visibleAvatars = participants - .slice(0, maxVisible) - .map(({ User }) => ( - - )); - - if (participantCount > maxVisible) { - visibleAvatars.push( -
-
- +{participantCount - maxVisible} -
- -
- {participants.slice(maxVisible).map(({ User }) => ( - - ))} -
-
, - ); - } - - return visibleAvatars; - }; + const { renderAvatars } = useAvatars({ participants, participantCount }); return (
diff --git a/src/hooks/useAvatars.tsx b/src/hooks/useAvatars.tsx new file mode 100644 index 00000000..b129f7b5 --- /dev/null +++ b/src/hooks/useAvatars.tsx @@ -0,0 +1,69 @@ +import { useState } from 'react'; + +import { GatheringParticipantsType } from '@/types/data.type'; +import Avatar from '@/app/components/InformationCard/Avatar'; + +interface UseAvatarsProps { + participants: GatheringParticipantsType[]; + participantCount: number; +} + +const useAvatars = ({ participants, participantCount }: UseAvatarsProps) => { + const [isHovered, setIsHovered] = useState(false); + + const renderAvatars = () => { + const maxVisible = 4; + + const visibleAvatars = participants + .slice(0, maxVisible) + .map(({ User }) => ( + + )); + + if (participantCount > maxVisible) { + visibleAvatars.push( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > +
+ +{participantCount - maxVisible} +
+
+ {participants.slice(maxVisible).map(({ User }) => ( + + ))} +
+
, + ); + } + + return visibleAvatars; + }; + + return { renderAvatars }; +}; + +export default useAvatars; From 23534d58ec94c5648999d488bb15c5798ebba857 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Thu, 10 Oct 2024 17:25:40 +0900 Subject: [PATCH 27/36] =?UTF-8?q?[KAN-113]=20fix:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A1=B0=EA=B1=B4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/_component/ReviewFilterTab.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/(main)/mypage/_component/ReviewFilterTab.tsx b/src/app/(main)/mypage/_component/ReviewFilterTab.tsx index 08ec2bc2..2477fc79 100644 --- a/src/app/(main)/mypage/_component/ReviewFilterTab.tsx +++ b/src/app/(main)/mypage/_component/ReviewFilterTab.tsx @@ -20,10 +20,8 @@ const ReviewFilterTab = () => { const pathname = usePathname(); useEffect(() => { - if (typeof window !== 'undefined') { - const path = pathname.replace('/mypage/review/', '') as filterType; - setCurrentFilterType(path); - } + const path = pathname.replace('/mypage/review/', '') as filterType; + setCurrentFilterType(path); }, [pathname]); const handleChangeFilterType = (filterType: filterType) => { From 4801e82d31f82a3e0766820874e768db0ca5f9b9 Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 17:45:14 +0900 Subject: [PATCH 28/36] =?UTF-8?q?[KAN-118]=20refactor:=20InformationCard?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/InformationCard/Avatars.tsx | 61 +++++++++ .../InformationCard/CapacityInfo.tsx | 31 +++++ .../InformationCard/DateTimeChips.tsx | 22 ++++ .../InformationCard/InformationCard.tsx | 116 +++--------------- .../InformationCard/InformationCardHeader.tsx | 60 +++++++++ .../InformationCard/ParticipantsInfo.tsx | 42 +++++++ 6 files changed, 233 insertions(+), 99 deletions(-) create mode 100644 src/app/components/InformationCard/Avatars.tsx create mode 100644 src/app/components/InformationCard/CapacityInfo.tsx create mode 100644 src/app/components/InformationCard/DateTimeChips.tsx create mode 100644 src/app/components/InformationCard/InformationCardHeader.tsx create mode 100644 src/app/components/InformationCard/ParticipantsInfo.tsx diff --git a/src/app/components/InformationCard/Avatars.tsx b/src/app/components/InformationCard/Avatars.tsx new file mode 100644 index 00000000..7772dfda --- /dev/null +++ b/src/app/components/InformationCard/Avatars.tsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; +import Avatar from './Avatar'; +import { GatheringParticipantsType } from '@/types/data.type'; + +interface AvatarsProps { + participants: GatheringParticipantsType[]; + participantCount: number; +} + +const Avatars = ({ participants, participantCount }: AvatarsProps) => { + const [isHovered, setIsHovered] = useState(false); + + const maxVisible = 4; + const visibleAvatars = participants + .slice(0, maxVisible) + .map(({ User }) => ( + + )); + + if (participantCount > maxVisible) { + visibleAvatars.push( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > +
+ +{participantCount - maxVisible} +
+
+ {participants.slice(maxVisible).map(({ User }) => ( + + ))} +
+
, + ); + } + + return <>{visibleAvatars}; +}; + +export default Avatars; diff --git a/src/app/components/InformationCard/CapacityInfo.tsx b/src/app/components/InformationCard/CapacityInfo.tsx new file mode 100644 index 00000000..bccb904e --- /dev/null +++ b/src/app/components/InformationCard/CapacityInfo.tsx @@ -0,0 +1,31 @@ +import ProgressBar from '../ProgressBar/ProgressBar'; +import { MIN_PARTICIPANTS } from '@/constants/common'; + +interface CapacityInfoProps { + participantCount: number; + maxParticipants: number; +} + +const CapacityInfo = ({ + participantCount, + maxParticipants, +}: CapacityInfoProps) => { + return ( +
+ + +
+
최소 인원 {MIN_PARTICIPANTS}명
+
최대 인원 {maxParticipants}명
+
+
+ ); +}; + +export default CapacityInfo; diff --git a/src/app/components/InformationCard/DateTimeChips.tsx b/src/app/components/InformationCard/DateTimeChips.tsx new file mode 100644 index 00000000..b15066ea --- /dev/null +++ b/src/app/components/InformationCard/DateTimeChips.tsx @@ -0,0 +1,22 @@ +import InfoChip from '../Chip/InfoChip'; +import { formatDate, formatTimeColon } from '@/utils/formatDate'; + +interface DateTimeChipsProps { + date: string; + time: string; +} + +const DateTimeChips = ({ date, time }: DateTimeChipsProps) => { + return ( +
+ + {formatDate(date)} + + + {formatTimeColon(time)} + +
+ ); +}; + +export default DateTimeChips; diff --git a/src/app/components/InformationCard/InformationCard.tsx b/src/app/components/InformationCard/InformationCard.tsx index 606a12d9..17da522c 100644 --- a/src/app/components/InformationCard/InformationCard.tsx +++ b/src/app/components/InformationCard/InformationCard.tsx @@ -1,20 +1,12 @@ 'use client'; -import { useState } from 'react'; import { useParams } from 'next/navigation'; -import { - IconCheckCircle, - IconSaveActive, - IconSaveInactive, -} from '@/public/icons'; -import InfoChip from '../Chip/InfoChip'; -import ProgressBar from '../ProgressBar/ProgressBar'; import { GatheringParticipantsType } from '@/types/data.type'; -import { MIN_PARTICIPANTS } from '@/constants/common'; -import { formatDate, formatTimeColon } from '@/utils/formatDate'; -import { useSavedGatheringList } from '@/context/SavedGatheringContext'; -import useAvatars from '@/hooks/useAvatars'; +import InformationCardHeader from './InformationCardHeader'; +import DateTimeChips from './DateTimeChips'; +import ParticipantsInfo from './ParticipantsInfo'; +import CapacityInfo from './CapacityInfo'; interface InformationCardProps { title: string; @@ -38,99 +30,25 @@ const InformationCard = ({ const params = useParams(); const gatheringId = Number(params.id); - const { savedGatherings, updateGathering } = useSavedGatheringList(); - - const checkGatheringSaved = (id: number | undefined, savedList: number[]) => { - return id ? savedList.includes(id) : false; - }; - - const [isSaved, setIsSaved] = useState(() => - checkGatheringSaved(gatheringId, savedGatherings), - ); - - const handleToggleSave = () => { - setIsSaved((prev) => !prev); - if (gatheringId) { - updateGathering(gatheringId); - } - }; - - const { renderAvatars } = useAvatars({ participants, participantCount }); - return (
-
-
-
- {title} -
-
- {address} -
-
- - {/* 찜 */} - {isSaved ? ( - - ) : ( - - )} -
- - {/* 날짜, 시간 chip */} -
- - {formatDate(date)} - - - {formatTimeColon(time)} - -
+ +
- - {/* 모집 정원 */}
-
-
-
- 모집 정원 {participantCount}명 -
-
{renderAvatars()}
-
-
- {participantCount >= MIN_PARTICIPANTS && ( - <> - -
- 개설확정 -
- - )} -
-
- - {/* progress bar */} - + - -
-
최소인원 {MIN_PARTICIPANTS}명
-
- 최대인원 {maxParticipants}명 -
-
); diff --git a/src/app/components/InformationCard/InformationCardHeader.tsx b/src/app/components/InformationCard/InformationCardHeader.tsx new file mode 100644 index 00000000..6e7553d7 --- /dev/null +++ b/src/app/components/InformationCard/InformationCardHeader.tsx @@ -0,0 +1,60 @@ +import { useState } from 'react'; + +import { IconSaveActive, IconSaveInactive } from '@/public/icons'; +import { useSavedGatheringList } from '@/context/SavedGatheringContext'; + +interface InformationCardHeaderProps { + title: string; + address: string; + gatheringId: number; +} + +const InformationCardHeader = ({ + title, + address, + gatheringId, +}: InformationCardHeaderProps) => { + const { savedGatherings, updateGathering } = useSavedGatheringList(); + + const checkGatheringSaved = (id: number | undefined, savedList: number[]) => { + return id ? savedList.includes(id) : false; + }; + + const [isSaved, setIsSaved] = useState(() => + checkGatheringSaved(gatheringId, savedGatherings), + ); + + const handleToggleSave = () => { + setIsSaved((prev) => !prev); + if (gatheringId) { + updateGathering(gatheringId); + } + }; + + return ( +
+
+
+ {title} +
+
+ {address} +
+
+ + {isSaved ? ( + + ) : ( + + )} +
+ ); +}; + +export default InformationCardHeader; diff --git a/src/app/components/InformationCard/ParticipantsInfo.tsx b/src/app/components/InformationCard/ParticipantsInfo.tsx new file mode 100644 index 00000000..71ab27e2 --- /dev/null +++ b/src/app/components/InformationCard/ParticipantsInfo.tsx @@ -0,0 +1,42 @@ +import Avatars from './Avatars'; +import { IconCheckCircle } from '@/public/icons'; +import { GatheringParticipantsType } from '@/types/data.type'; +import { MIN_PARTICIPANTS } from '@/constants/common'; + +interface ParticipantsInfoProps { + participants: GatheringParticipantsType[]; + participantCount: number; +} + +const ParticipantsInfo = ({ + participants, + participantCount, +}: ParticipantsInfoProps) => { + return ( +
+
+
+ 현재 인원 {participantCount}명 +
+
+ +
+
+ + {/* 개설 확정 여부 */} + {participantCount >= MIN_PARTICIPANTS && ( +
+ +
+ 개설확정 +
+
+ )} +
+ ); +}; + +export default ParticipantsInfo; From 911827f92cead1cd74e60b266c02c5f87579098f Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 17:52:58 +0900 Subject: [PATCH 29/36] =?UTF-8?q?[KAN-118]=20remove:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20useAvatars=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAvatars.tsx | 69 ---------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 src/hooks/useAvatars.tsx diff --git a/src/hooks/useAvatars.tsx b/src/hooks/useAvatars.tsx deleted file mode 100644 index b129f7b5..00000000 --- a/src/hooks/useAvatars.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useState } from 'react'; - -import { GatheringParticipantsType } from '@/types/data.type'; -import Avatar from '@/app/components/InformationCard/Avatar'; - -interface UseAvatarsProps { - participants: GatheringParticipantsType[]; - participantCount: number; -} - -const useAvatars = ({ participants, participantCount }: UseAvatarsProps) => { - const [isHovered, setIsHovered] = useState(false); - - const renderAvatars = () => { - const maxVisible = 4; - - const visibleAvatars = participants - .slice(0, maxVisible) - .map(({ User }) => ( - - )); - - if (participantCount > maxVisible) { - visibleAvatars.push( -
setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > -
- +{participantCount - maxVisible} -
-
- {participants.slice(maxVisible).map(({ User }) => ( - - ))} -
-
, - ); - } - - return visibleAvatars; - }; - - return { renderAvatars }; -}; - -export default useAvatars; From ed0ecaf09338e45682a39f2e67e58fc8a1ad84e9 Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 20:27:58 +0900 Subject: [PATCH 30/36] =?UTF-8?q?[KAN-118]=20chore:=20MAX=5FVISIBLE=5FAVAT?= =?UTF-8?q?AR=20=EC=83=81=EC=88=98=EB=A1=9C=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/InformationCard/Avatars.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/components/InformationCard/Avatars.tsx b/src/app/components/InformationCard/Avatars.tsx index 7772dfda..d02df87c 100644 --- a/src/app/components/InformationCard/Avatars.tsx +++ b/src/app/components/InformationCard/Avatars.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; + import Avatar from './Avatar'; import { GatheringParticipantsType } from '@/types/data.type'; @@ -7,10 +8,12 @@ interface AvatarsProps { participantCount: number; } +const MAX_VISIBLE_AVATAR = 4; + const Avatars = ({ participants, participantCount }: AvatarsProps) => { const [isHovered, setIsHovered] = useState(false); - const maxVisible = 4; + const maxVisible = MAX_VISIBLE_AVATAR; const visibleAvatars = participants .slice(0, maxVisible) .map(({ User }) => ( From 50f1dbcfec0929bc11f09d863949deb78db893fa Mon Sep 17 00:00:00 2001 From: Homin Date: Thu, 10 Oct 2024 20:29:57 +0900 Subject: [PATCH 31/36] =?UTF-8?q?[KAN-118]=20chore:=20fetchFilteredGatheri?= =?UTF-8?q?ngs=20=EC=9D=98=20sortOrder=20=EC=A1=B0=EA=B1=B4=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useFetchFilteredGatherings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useFetchFilteredGatherings.ts b/src/hooks/useFetchFilteredGatherings.ts index 87645b77..8f3713fc 100644 --- a/src/hooks/useFetchFilteredGatherings.ts +++ b/src/hooks/useFetchFilteredGatherings.ts @@ -24,7 +24,7 @@ const useFetchFilteredGatherings = ( ? formatCalendarDate(selectedDate) : undefined; const sortBy = overrides.sortBy || sortOption; - const sortOrder = sortBy ? 'desc' : undefined; + const sortOrder = sortBy === 'registrationEnd' ? 'asc' : 'desc'; const newData = await getGatherings({ type, From db08b124849344335ce040182d0fa5586cc7dd46 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Fri, 11 Oct 2024 09:39:43 +0900 Subject: [PATCH 32/36] =?UTF-8?q?[KAN-113]=20fix:=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=9C=20=EB=A6=AC=EB=B7=B0=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=82=B4=20useUser=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/review/written/page.tsx | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/app/(main)/mypage/review/written/page.tsx b/src/app/(main)/mypage/review/written/page.tsx index 63f8227d..4d090f00 100644 --- a/src/app/(main)/mypage/review/written/page.tsx +++ b/src/app/(main)/mypage/review/written/page.tsx @@ -1,24 +1,22 @@ -'use client'; - -import { useUser } from '@/app/(auth)/context/UserContext'; +import { getUserData } from '@/app/api/actions/mypage/getUserData'; import getReviewList from '@/app/api/actions/reviews/getReviewList'; import Review from '@/app/components/Review/Review'; -import { useQuery } from '@tanstack/react-query'; import Link from 'next/link'; +import { redirect } from 'next/navigation'; import EmptyReviewPage from '../../_component/EmptyReviewPage'; import ReviewFilterTab from '../../_component/ReviewFilterTab'; -const Page = () => { - const { user } = useUser(); +const Page = async () => { + const user = await getUserData(); + + if (!user) { + redirect('/signin'); + } - const { data: writtenReviews } = useQuery({ - queryKey: ['reviews', 'written'], - queryFn: () => - getReviewList({ - userId: Number(user?.id), - sortBy: 'createdAt', - sortOrder: 'desc', - }), + const writtenReviews = await getReviewList({ + userId: user?.id as number, + sortBy: 'createdAt', + sortOrder: 'desc', }); return ( From adc84c0fb6277fa63a9f0d4ed7101b0f1f58d3f2 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Fri, 11 Oct 2024 09:48:16 +0900 Subject: [PATCH 33/36] =?UTF-8?q?[KAN-113]=20fix:=20=EB=AA=A8=EC=9E=84?= =?UTF-8?q?=EB=A7=8C=EB=93=A4=EA=B8=B0=20=EC=9D=B4=EB=A6=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EB=8A=A5=EC=97=90=EC=84=9C=20=EB=B9=A0?= =?UTF-8?q?=EC=A7=84=20=EB=B6=80=EB=B6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/components/Modal/MakeGatheringModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/components/Modal/MakeGatheringModal.tsx b/src/app/components/Modal/MakeGatheringModal.tsx index ba1e6ba1..3229df24 100644 --- a/src/app/components/Modal/MakeGatheringModal.tsx +++ b/src/app/components/Modal/MakeGatheringModal.tsx @@ -74,6 +74,7 @@ const MakeGatheringModal = ({ onClose }: MakeGatheringModalProps) => { } const formData = new FormData(); + formData.append('name', name); formData.append('location', location!); formData.append('type', getSelectedGatheringType()); formData.append('dateTime', (combinedDateTime as Date).toISOString()); From 2897d4b17ed8064f375ca690e71b731a07cd4f68 Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Fri, 11 Oct 2024 09:52:10 +0900 Subject: [PATCH 34/36] =?UTF-8?q?[KAN-113]=20fix:=20=EB=82=B4=EA=B0=80=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=ED=95=9C=20=EB=AA=A8=EC=9E=84=20API=20?= =?UTF-8?q?=EA=B3=BC=20=EA=B4=80=EB=A0=A8=ED=95=9C=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/review/writable/page.tsx | 8 ++------ src/app/api/actions/gatherings/getJoinedGathering.ts | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/app/(main)/mypage/review/writable/page.tsx b/src/app/(main)/mypage/review/writable/page.tsx index a577c11e..69d1f417 100644 --- a/src/app/(main)/mypage/review/writable/page.tsx +++ b/src/app/(main)/mypage/review/writable/page.tsx @@ -13,13 +13,9 @@ const Page = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [cardId, setCardId] = useState(0); - const { data } = useQuery({ + const { data: writableReviews } = useQuery({ queryKey: ['reviews', 'writable'], - queryFn: () => getJoinedGatherings(), - }); - - const writableReviews = data?.filter((data) => { - return data?.isCompleted && !data?.isReviewed; + queryFn: () => getJoinedGatherings({ completed: true, reviewed: false }), }); const handleOpenModal = (id: number) => { diff --git a/src/app/api/actions/gatherings/getJoinedGathering.ts b/src/app/api/actions/gatherings/getJoinedGathering.ts index e05b7055..480376fa 100644 --- a/src/app/api/actions/gatherings/getJoinedGathering.ts +++ b/src/app/api/actions/gatherings/getJoinedGathering.ts @@ -18,8 +18,8 @@ const getJoinedGatherings = async ( ): Promise => { try { const { - completed = true, - reviewed = false, + completed, + reviewed, limit = 10, offset = 0, sortBy = 'dateTime', From ea95a2eadf739a2e2f014419907d0bd73f0d146a Mon Sep 17 00:00:00 2001 From: hakyoung12 Date: Fri, 11 Oct 2024 10:05:15 +0900 Subject: [PATCH 35/36] =?UTF-8?q?[KAN-47]=20test:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=A3=BC=EC=84=9D=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParticipationButton.test.tsx | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx b/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx index 4da25883..309a2a66 100644 --- a/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx +++ b/src/app/components/BottomFloatingBar/ParticipationButton.test.tsx @@ -140,26 +140,27 @@ describe('ParticipationButton', () => { expect(mockCancelGathering).toHaveBeenCalled(); }); - // 주최자일 때 마감일이 지났거나 모임이 취소된 경우 버튼이 비활성화되는지 확인 - it('checks if buttons are disabled when the host has passed the deadline or the gathering is canceled', () => { - render( - , - ); - - const cancelButton = screen.getByRole('button', { name: '취소하기' }); - const shareButton = screen.getByRole('button', { name: '공유하기' }); - - expect(cancelButton).toBeDisabled(); - expect(shareButton).toBeDisabled(); - }); + // @todo 테스트코드 수정하기 + // // 주최자일 때 마감일이 지났거나 모임이 취소된 경우 버튼이 비활성화되는지 확인 + // it('checks if buttons are disabled when the host has passed the deadline or the gathering is canceled', () => { + // render( + // , + // ); + + // const cancelButton = screen.getByRole('button', { name: '취소하기' }); + // const shareButton = screen.getByRole('button', { name: '공유하기' }); + + // expect(cancelButton).toBeDisabled(); + // expect(shareButton).toBeDisabled(); + // }); // 참여자일 때 참여 상태에 따라 버튼 텍스트가 변경되는지 확인 it('checks if button text changes based on participation status when the user is a participant', () => { From 0e7971c6400bc4d9d1b4a7808a5e646efca1e23e Mon Sep 17 00:00:00 2001 From: Matthew Song Date: Fri, 11 Oct 2024 10:13:50 +0900 Subject: [PATCH 36/36] =?UTF-8?q?[KAN-113]=20fix:=20Page=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(main)/mypage/review/writable/page.tsx | 4 ++-- src/app/(main)/mypage/review/written/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/(main)/mypage/review/writable/page.tsx b/src/app/(main)/mypage/review/writable/page.tsx index 69d1f417..edae1e8b 100644 --- a/src/app/(main)/mypage/review/writable/page.tsx +++ b/src/app/(main)/mypage/review/writable/page.tsx @@ -9,7 +9,7 @@ import { useState } from 'react'; import EmptyReviewPage from '../../_component/EmptyReviewPage'; import ReviewFilterTab from '../../_component/ReviewFilterTab'; -const Page = () => { +const WritableReviewsPage = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [cardId, setCardId] = useState(0); @@ -60,4 +60,4 @@ const Page = () => { ); }; -export default Page; +export default WritableReviewsPage; diff --git a/src/app/(main)/mypage/review/written/page.tsx b/src/app/(main)/mypage/review/written/page.tsx index 4d090f00..414cd09d 100644 --- a/src/app/(main)/mypage/review/written/page.tsx +++ b/src/app/(main)/mypage/review/written/page.tsx @@ -6,7 +6,7 @@ import { redirect } from 'next/navigation'; import EmptyReviewPage from '../../_component/EmptyReviewPage'; import ReviewFilterTab from '../../_component/ReviewFilterTab'; -const Page = async () => { +const WrittenReviewsPage = async () => { const user = await getUserData(); if (!user) { @@ -47,4 +47,4 @@ const Page = async () => { ); }; -export default Page; +export default WrittenReviewsPage;