From dfcf56fbd8fa80b3168095c5e6508b843e6ca662 Mon Sep 17 00:00:00 2001 From: Shim Jeong ah <35457850+joanShim@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:49:03 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20#96=20=ED=81=AC=EB=A3=A8=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C,=20=ED=81=AC=EB=A3=A8?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=ED=81=AC=EB=A3=A8=20=EC=83=9D=EC=84=B1=20(#132)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor: Tabs 컴포넌트 공통화 * Feat: 크루 헤더 및 탭 추가 * Fix: 수정된 Tabs 컴포넌트에 맞게 props 및 경로 수정 * Chore : 이미지 호스트 변경 * Feat: 크루 리스트 아이템 구현 * Feat : 정렬 옵션 구현 * Feat : 크루 페이지 구현 * Feat: 크루 api 수정 * Feat : 크루 상세페이지 조회 * Move : BottomSheet 공용 컴포넌트로 이동 및 수정 내부에 사용하는 Form을 prop으로 받아 사용합니다 * Feat : 크루 가입신청 기능 * Feat : 가입 질문 렌더링 * Feat : 전체/내크루 리스트 아이템 컴포넌트 분리 * Feat : 가입요청 후 바텀시트 닫기 * Fix : 무한스크롤 페이징 수정 * Feat : 채팅 경로 및 UI * Feat : 크루 생성 멀티스텝 폼 * Fix : 인풋 값 입력시 다음버튼 활성화 * Chore : 콘솔 제거 * Feat : 지역 선택 체크박스 * Fix : 타입수정 * Feat : 크루 생성 컴포넌트 구조 수정 * Refactor : 중복코드 수정 * Feat : 단계별 컴포넌트 정의 * Feat : number input 디폴트 ui 삭제 * Feat : 크루 상세정보 데이터 추가 * Feat : 크루생성 폼 컴포넌트화 * Feat : 크루 이름, 소개, 태그 입력 * Feat : 크루 기반 지역 선택 * Feat : 가입형식 지정 * Feat : 인원, 연령대, 성별 선택 * Feat : 크루생성 api 설정 * Fix : 크루리스트 무한스크롤 로직 수정 * Feat : 크루 대표 이미지 추가 컴포넌트 * Fix : api 전송 형식 변경 * Feat : 렌더되는 정보 추가 * Feat : 옵션 배열 추가 * Feat : 태그선택 필수 및 최대 3개 제한 * Chore : 라벨 스타일 수정 * Feat : number 커스텀 입력 UX 개선 * Feat : 크루리스트 옵션 선택 추가 * Feat : 크루 생성 성공 후 리스트로 이동 * Fix : api 스키마에 일치 * Feat : 지역 배열 렌더링 수정 * Feat : 가입질문 UI 수정 * Feat : 엔드포인트 조건 수정 * Update .eslintrc.json * Chore : EsLint Rule 재설정 * Fix: 빌드에러 수정 * Remove : 미사용 컴포넌트 삭제 * Feat : 가입정보 UI 수정 * Design : 간격 및 이미지 높이 수정 * Feat : 크루 생성 후 리스트 데이터 mutate * Design : 크루목록 아이템 간격 및 렌더방식 수정 * Feat : 일정 옵션 버튼 컴포넌트 생성 * Feat : 멤버 목록 구현 * Fix : RESTful 규칙에 맞게 경로 수정 * Fix : 배열 map key값 수정 * Fix : RESTful 규칙에 맞게 경로 수정 * Chore : 상수 컨벤션 수정 * Feat : 크루멤버목록 api * Feat : 크루 생성 후 크루목록 갱신 (mutate) * Feat : 크루 수정 페이지 * Feat : 선택된 태그 유지 * Chore : 임포트 수정 * Chore : 상수 컨벤션으로 변경 * Fix : 컴포넌트명 파일명과 일치 * Feat : 챕터 변경 시 입력값 유지 * Chore : 임포트 수정 * Chore : 미완성 기능 비활성화 --- .eslintrc.json | 32 +-- assets/images/crewLeader.png | Bin 0 -> 2106 bytes package-lock.json | 31 +++ package.json | 1 + .../common}/BottomSheetContainer.tsx | 14 +- src/app/_components/common/Header.tsx | 36 ++- .../common}/Tabs.tsx | 15 +- src/app/_components/ui/checkbox.tsx | 30 +++ src/app/service/community/page.tsx | 11 +- .../crew/[id]/_components/MemberList.tsx | 89 +++++++ .../crew/[id]/_components/Schedules.tsx | 64 +++++ .../service/crew/[id]/api/getCrewDetail.ts | 22 ++ .../service/crew/[id]/api/getCrewMembers.ts | 22 ++ .../service/crew/[id]/api/postCrewApply.ts | 18 ++ src/app/service/crew/[id]/edit/EditCrew.tsx | 13 + src/app/service/crew/[id]/edit/page.tsx | 9 + src/app/service/crew/[id]/page.tsx | 241 ++++++++++++++++++ .../crew/_components/ApplicantForm.tsx | 53 ++++ .../crew/_components/ApplicantPage.tsx | 26 ++ src/app/service/crew/_components/ChatList.tsx | 12 + .../service/crew/_components/ChatListFAB.tsx | 17 ++ .../service/crew/_components/ChatListItem.tsx | 36 +++ src/app/service/crew/_components/CrewList.tsx | 77 ++++++ .../service/crew/_components/CrewListItem.tsx | 96 +++++++ .../crew/_components/MyCrewListItem.tsx | 89 +++++++ .../service/crew/_components/SortOptions.tsx | 81 ++++++ .../service/crew/_components/SortStatus.tsx | 39 +++ .../crew/_components/UI/commonClass.ts | 10 + .../create/_components/AgeRangeSelection.tsx | 182 +++++++++++++ .../create/_components/CapacitySelection.tsx | 117 +++++++++ .../create/_components/CreateCrewForm.tsx | 162 ++++++++++++ .../create/_components/GenderSelection.tsx | 62 +++++ .../create/_components/SingleImageSubmit.tsx | 83 ++++++ .../service/crew/create/_components/Step0.tsx | 172 +++++++++++++ .../service/crew/create/_components/Step1.tsx | 71 ++++++ .../service/crew/create/_components/Step2.tsx | 108 ++++++++ .../service/crew/create/_components/Step3.tsx | 40 +++ .../service/crew/create/_components/Step4.tsx | 24 ++ .../crew/create/_components/UI/Label.tsx | 22 ++ .../crew/create/_components/UI/commonClass.ts | 14 + src/app/service/crew/create/api/createCrew.ts | 28 ++ src/app/service/crew/create/page.tsx | 15 ++ src/app/service/crew/create/schema.ts | 23 ++ src/app/service/crew/my-chat/[id]/page.tsx | 11 + src/app/service/crew/my-chat/page.tsx | 14 + src/app/service/crew/page.tsx | 24 +- src/constants/api/end-point.ts | 17 ++ src/constants/community/tabs.ts | 4 +- src/constants/crew/crewOptions.ts | 23 ++ src/constants/crew/formSteps.ts | 19 ++ src/constants/crew/regions.ts | 27 ++ src/constants/crew/sortBy.ts | 28 ++ src/constants/crew/tabs.ts | 12 + src/constants/ui/common/header.ts | 2 + src/hooks/crew/useCrewList.ts | 23 ++ src/store/crew/bottomSheetStore.ts | 13 + src/store/crew/crewListStore.ts | 7 + src/store/crew/sortsStore.ts | 12 + src/styles/globals.css | 8 + src/types/common/tabs.ts | 12 + src/types/community/bottomSheet.ts | 4 +- src/types/crew/createFormSteps.ts | 11 + src/types/crew/crew.ts | 26 ++ src/types/crew/crewList.ts | 38 +++ src/types/crew/sort.ts | 4 + src/types/ui/common/header.ts | 1 + src/utils/getCrewCreateData.ts | 32 +++ src/utils/getKeys.ts | 77 +++++- 68 files changed, 2684 insertions(+), 72 deletions(-) create mode 100644 assets/images/crewLeader.png rename src/app/{service/community/_components => _components/common}/BottomSheetContainer.tsx (78%) rename src/app/{service/community/_components => _components/common}/Tabs.tsx (64%) create mode 100644 src/app/_components/ui/checkbox.tsx create mode 100644 src/app/service/crew/[id]/_components/MemberList.tsx create mode 100644 src/app/service/crew/[id]/_components/Schedules.tsx create mode 100644 src/app/service/crew/[id]/api/getCrewDetail.ts create mode 100644 src/app/service/crew/[id]/api/getCrewMembers.ts create mode 100644 src/app/service/crew/[id]/api/postCrewApply.ts create mode 100644 src/app/service/crew/[id]/edit/EditCrew.tsx create mode 100644 src/app/service/crew/[id]/edit/page.tsx create mode 100644 src/app/service/crew/[id]/page.tsx create mode 100644 src/app/service/crew/_components/ApplicantForm.tsx create mode 100644 src/app/service/crew/_components/ApplicantPage.tsx create mode 100644 src/app/service/crew/_components/ChatList.tsx create mode 100644 src/app/service/crew/_components/ChatListFAB.tsx create mode 100644 src/app/service/crew/_components/ChatListItem.tsx create mode 100644 src/app/service/crew/_components/CrewList.tsx create mode 100644 src/app/service/crew/_components/CrewListItem.tsx create mode 100644 src/app/service/crew/_components/MyCrewListItem.tsx create mode 100644 src/app/service/crew/_components/SortOptions.tsx create mode 100644 src/app/service/crew/_components/SortStatus.tsx create mode 100644 src/app/service/crew/_components/UI/commonClass.ts create mode 100644 src/app/service/crew/create/_components/AgeRangeSelection.tsx create mode 100644 src/app/service/crew/create/_components/CapacitySelection.tsx create mode 100644 src/app/service/crew/create/_components/CreateCrewForm.tsx create mode 100644 src/app/service/crew/create/_components/GenderSelection.tsx create mode 100644 src/app/service/crew/create/_components/SingleImageSubmit.tsx create mode 100644 src/app/service/crew/create/_components/Step0.tsx create mode 100644 src/app/service/crew/create/_components/Step1.tsx create mode 100644 src/app/service/crew/create/_components/Step2.tsx create mode 100644 src/app/service/crew/create/_components/Step3.tsx create mode 100644 src/app/service/crew/create/_components/Step4.tsx create mode 100644 src/app/service/crew/create/_components/UI/Label.tsx create mode 100644 src/app/service/crew/create/_components/UI/commonClass.ts create mode 100644 src/app/service/crew/create/api/createCrew.ts create mode 100644 src/app/service/crew/create/page.tsx create mode 100644 src/app/service/crew/create/schema.ts create mode 100644 src/app/service/crew/my-chat/[id]/page.tsx create mode 100644 src/app/service/crew/my-chat/page.tsx create mode 100644 src/constants/crew/crewOptions.ts create mode 100644 src/constants/crew/formSteps.ts create mode 100644 src/constants/crew/regions.ts create mode 100644 src/constants/crew/sortBy.ts create mode 100644 src/constants/crew/tabs.ts create mode 100644 src/hooks/crew/useCrewList.ts create mode 100644 src/store/crew/bottomSheetStore.ts create mode 100644 src/store/crew/crewListStore.ts create mode 100644 src/store/crew/sortsStore.ts create mode 100644 src/types/common/tabs.ts create mode 100644 src/types/crew/createFormSteps.ts create mode 100644 src/types/crew/crew.ts create mode 100644 src/types/crew/crewList.ts create mode 100644 src/types/crew/sort.ts create mode 100644 src/utils/getCrewCreateData.ts diff --git a/.eslintrc.json b/.eslintrc.json index 60552981..eff4c942 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,7 @@ "es2021": true, "jest": true }, - "extends": ["next/core-web-vitals", "airbnb", "airbnb-typescript"], + "extends": ["next/core-web-vitals"], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { @@ -17,36 +17,8 @@ "plugins": ["react", "@typescript-eslint"], "rules": { "react-hooks/exhaustive-deps": "off", - "react/jsx-props-no-spreading": "off", - "linebreak-style": 0, - "import/no-extraneous-dependencies": "off", "import/prefer-default-export": ["off", { "target": "single" }], - "object-curly-newline": "off", - "arrow-parens": "off", - "arrow-body-style": "off", - "@typescript-eslint/no-shadow": "off", - "operator-linebreak": "off", - "react/require-default-props": "off", - "implicit-arrow-linebreak": "off", - "@typescript-eslint/naming-convention": "off", - "consistent-return": "off", - "@typescript-eslint/indent": "off", - "jsx-a11y/control-has-associated-label": "off", - "react/jsx-wrap-multilines": "off", - "react/jsx-indent": "off", - "react/no-invalid-html-attribute": "off", - "eslint-plugin-import/no-cycle": "off", - "import/no-cycle":"off", - "no-param-reassign":"off", - "@typescript-eslint/no-unused-vars": "warn", //사용안한 변수는 경고처리 - "react/jsx-curly-newline": "off", // jsx안에 }를 새로운 라인에 사용할 수 있다. - "@typescript-eslint/no-use-before-define": ["warn"], // 선언하기 전에 사용 한다면 경고 - "jsx-a11y/label-has-associated-control": [ - 2, - { - "labelAttributes": ["htmlFor"] - } - ] + "react/display-name": "off" }, "globals": { "React": "writable" diff --git a/assets/images/crewLeader.png b/assets/images/crewLeader.png new file mode 100644 index 0000000000000000000000000000000000000000..fb777fbaaa1d612cd18179caba18ae3a0cf4bf2a GIT binary patch literal 2106 zcmV-A2*vk_P)_j}*}_pL)S zj04T?I>)8e{Hk|}R`V<85Y@>s{M|6$A9frnxDH+5*HFhX=RD0I9GU@uOlF#P@&dQ; zs&kYEOp?i-oX*hEnIM^(SiaTGWgk!*KcPJ}8H!xg@Dx$kM*~hDr9>)XJL>r#HE z358J&K`UY`rMm2WI>NKo(PIP%KAcDE^L@^6(m9!=10kjCL%ROBNdfF^>R9cscW-ms zlg>#bvF`0|C%@%sZXmyZ!0D=kPxpa6x%NPaeY-k~R>gn=(1z!BbIMMtCEWu~ZV=kW| z`n{jhpzvrOy%Y`w+3={E2Xv0#tVlIB5v|=C{BGnAqKhnvu~DM?cZ2t!stp@ivM;if zi>j(Vv!wfb%EwngPQ_ND_43Pr#6I5cwsK%QLKeV+UT^U~Lo%nn_H3vUpt{%)NLvGT z_v1mbM@S_D0h(EMk_E6ZN$SoB(c!(xNCB{~Q-@fxC;abLyh8NqMzV)PoycGH$C4^S zTH*4d65uFGiySXkqo)=U)i)9?W0jjb@!XYK)DV=ii$QZV1#@qabW`(PBykXNp#5SX%37 z2B9VKB*CX&^&qy0ED?@X5>gTkZy2X53CU1V827K23W-mQQ!3!tFT+P>bz}*tV&P0) zmLUzBf~pB%BM=HSkWx$ABzEsuA)(xOHR`k`w}RIIwF+ zg88;FOd>V0n-~3S?6QnGSS-jz6^~f-v2H&{1azjaF?fQig2P7uE~UhVX3oSndDoEC zw|mPISM4aEnowT^bCV>Xvf92t{L40~3R%@4NMUEdrQdvFu&0)NUWPBp;reDwmad&i zz{$AY_m%~xoIadkQUywhhEUxE>Ac??w7EE-LU^yI+_U02(sSAZot!Ky;tdXH*4am6 zQ;?YUk^pQ4b(qc%A+>vV{YBRn%M(+m(6)+u_>GHcY|WvGEGNK2EW4dH^RUy>(DCCp z*qOBK_tPK{ShXZr_D2Dm^6Mdw7@k`yIJbH>X?;ZJa(0iVo6Q_7c(bUZ@Wb$KkO~YQ z*?ZEA0fewAU$py+$Frn!LCq#}$s}-VXh>CI-8Ms5r)lqrW7X0o9PZ7exw?<1VmNzH0Yg zGCsl9OhD-P;h>*TtKzkp5`iUk67ODsuA#n46T;2{=Miy-Ez*6D3e|!$(TF=ShKipR zXCAQCB&Szs_2OL-Q$1fN+UW!)C9S+SZExn@OUJ@VIZT|c%ef1f8pn#W~_KQdMw(y`np7HY92Qs zBBUlCj9o?$j`LQMm{dIt>GhV@k`V|gIE?+EH~2eI9v9M*z3S4rA|L7(xE0#PBR(@N z1WE6zM*lnLJYMSFU@+m8_GKzPB(boasTkR0wmA>w{F27U%{?6e&n+-+=SH{I0Cq^2nuK*^If3q$Q)fB{Hkf zy)k*_A#EuPRL6RQ3NtQ9w@G_|rJB-Obya=RN<=S}zG zo9o-$KF<8D66D7P&T)@+eCM=A-LL$2h&f2E5-FhKnMcQQr_~L)(kBU}eicXoiF+){ zp=8+RX8Dg{kwEe=OC;*e{o -
+ ); } diff --git a/src/app/_components/common/Header.tsx b/src/app/_components/common/Header.tsx index 08c833fe..a036dc30 100644 --- a/src/app/_components/common/Header.tsx +++ b/src/app/_components/common/Header.tsx @@ -9,14 +9,21 @@ import * as M from '@/app/_components/ui/menubars'; import { useState } from 'react'; import { useRouter, useParams } from 'next/navigation'; -import { ChevronLeft, MoreVertical, X } from 'lucide-react'; +import { ChevronLeft, MoreVertical, X, Plus } from 'lucide-react'; import { usePostsState } from '@/store/community/postsStore'; import { MODAL } from '@/constants/ui/common/modal'; import { useDebouncedCallback } from 'use-debounce'; import { useToast } from '@/app/_components/ui/use-toast'; function Header({ ...props }: Partial) { - const { title, isBack, isExit, isEllipsis, editHandler, exitHandler } = props; + const { + title, + isBack, + isExit, + isEllipsis, + editHandler, + routeTo, + } = props; const { toast } = useToast(); const { categoryId } = usePostsState(); const { mutate } = usePostListApi.useGetPostList(categoryId); @@ -55,7 +62,7 @@ function Header({ ...props }: Partial) { }; return ( -
+
- )} + {isExit && ( + + )} + {routeTo && ( + + )}
); } diff --git a/src/app/service/community/_components/Tabs.tsx b/src/app/_components/common/Tabs.tsx similarity index 64% rename from src/app/service/community/_components/Tabs.tsx rename to src/app/_components/common/Tabs.tsx index 75606982..2d3a4bcb 100644 --- a/src/app/service/community/_components/Tabs.tsx +++ b/src/app/_components/common/Tabs.tsx @@ -1,21 +1,20 @@ 'use client'; import clsx from 'clsx'; -import TABS from '@/constants/community/tabs'; -import { usePostsState } from '@/store/community/postsStore'; +import { Tab, TabsProps } from '@/types/common/tabs'; -function Tabs() { - const { categoryId, setCategoryId } = usePostsState(); +function Tabs({ tabs, useStateHook }: TabsProps) { + const { categoryId, setCategoryId } = useStateHook(); const liClassName = (id: number) => { - return clsx('flex border-b-4', { + return clsx('flex flex-1 border-b-4', { 'border-b-purple-600': categoryId === id, 'border-b-white': categoryId !== id, }); }; const buttonClassName = (id: number) => { - return clsx('w-full h-full flex justify-center items-center', { + return clsx('w-full h-full flex justify-center items-center bg-white', { 'text-disabled': categoryId !== id, }); }; @@ -25,8 +24,8 @@ function Tabs() { }; return ( -
    - {Object.values(TABS).map(tab => ( +
      + {Object.values(tabs).map((tab: Tab) => (
    • + 크루원 목록 +
+
    + {sortedCrewMembers && + sortedCrewMembers.map((member) => ( +
  • +
    +
    + 크루 멤버 프로필 이미지 +
    +
    + + {member.is_crew_creator && ( +
    + 크루장 +
    + )} + {member.nickname} +
    + + 안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요안녕하세요 + +
    +
    +
  • + ))} +
+ + ); +} + +export default MemberList; diff --git a/src/app/service/crew/[id]/_components/Schedules.tsx b/src/app/service/crew/[id]/_components/Schedules.tsx new file mode 100644 index 00000000..4d2fc3c3 --- /dev/null +++ b/src/app/service/crew/[id]/_components/Schedules.tsx @@ -0,0 +1,64 @@ +import { cn } from '@/lib/utils'; +import React, { useEffect, useState } from 'react'; + +type ButtonType = 'scheduled' | 'finished'; + +function Schedules() { + const [selectedButton, setSelectedButton] = useState('scheduled'); + + const handleButtonClick = (button: ButtonType) => { + setSelectedButton(button); + + if (button === 'scheduled') { + callScheduledApi(); + } else if (button === 'finished') { + callFinishedApi(); + } + }; + + const callScheduledApi = () => { + console.log('예정된 일정 API 호출'); + }; + + useEffect(() => { + callScheduledApi(); + },[]) + + const callFinishedApi = () => { + console.log('종료된 일정 API 호출'); + }; + return ( +
+ +
+ {/* 예정된 일정 버튼 */} + + + {/* 종료된 일정 버튼 */} + +
+
+ ); +} + +export default Schedules; diff --git a/src/app/service/crew/[id]/api/getCrewDetail.ts b/src/app/service/crew/[id]/api/getCrewDetail.ts new file mode 100644 index 00000000..851a202a --- /dev/null +++ b/src/app/service/crew/[id]/api/getCrewDetail.ts @@ -0,0 +1,22 @@ +import { axiosInstance } from '@/lib/axios/axios-instance'; +import { END_POINT } from '@/constants/api/end-point'; +import { TResponse } from '@/types/common/response'; +import { CrewDetailProps } from '@/types/crew/crew'; + +import CustomError from '@/error/CustomError'; + +const getCrewDetail = async (id: number) => { + try { + const { data: crewDetail } = await axiosInstance< + TResponse + >(END_POINT.crewController.getCrewDetail(id)); + + return crewDetail.data; + } catch (error: unknown) { + if (error instanceof CustomError) { + throw new Error(error.message); + } + } +}; + +export default getCrewDetail; diff --git a/src/app/service/crew/[id]/api/getCrewMembers.ts b/src/app/service/crew/[id]/api/getCrewMembers.ts new file mode 100644 index 00000000..e2a3e87a --- /dev/null +++ b/src/app/service/crew/[id]/api/getCrewMembers.ts @@ -0,0 +1,22 @@ +import { axiosInstance } from '@/lib/axios/axios-instance'; +import { END_POINT } from '@/constants/api/end-point'; +import { TResponse } from '@/types/common/response'; +import { CrewMembersProps } from '@/types/crew/crew'; + +import CustomError from '@/error/CustomError'; + +const getCrewMembers = async (id: number) => { + try { + const { data: crewMembers } = await axiosInstance< + TResponse + >(END_POINT.crewController.getCrewMembers(id)); + + return crewMembers.data; + } catch (error: unknown) { + if (error instanceof CustomError) { + throw new Error(error.message); + } + } +}; + +export default getCrewMembers; diff --git a/src/app/service/crew/[id]/api/postCrewApply.ts b/src/app/service/crew/[id]/api/postCrewApply.ts new file mode 100644 index 00000000..a9505106 --- /dev/null +++ b/src/app/service/crew/[id]/api/postCrewApply.ts @@ -0,0 +1,18 @@ +import CustomError from '@/error/CustomError'; +import { axiosInstance } from '@/lib/axios/axios-instance'; +import { END_POINT } from '@/constants/api/end-point'; + +const postCrewApply = async (id: number, content: string) => { + try { + await axiosInstance.post( + `${END_POINT.crewController.main}/${id}/applications`, + content, + ); + } catch (error: unknown) { + if (error instanceof CustomError) { + throw new Error(error.message); + } + } +}; + +export default postCrewApply; diff --git a/src/app/service/crew/[id]/edit/EditCrew.tsx b/src/app/service/crew/[id]/edit/EditCrew.tsx new file mode 100644 index 00000000..f4c8faa2 --- /dev/null +++ b/src/app/service/crew/[id]/edit/EditCrew.tsx @@ -0,0 +1,13 @@ +import { CrewDetailProps } from "@/types/crew/crew"; + +interface EditCrewProps { + crewDetail: CrewDetailProps; +} + +function EditCrew({ crewDetail }: EditCrewProps) { + return ( +
EditCrew
+ ) +} + +export default EditCrew \ No newline at end of file diff --git a/src/app/service/crew/[id]/edit/page.tsx b/src/app/service/crew/[id]/edit/page.tsx new file mode 100644 index 00000000..3652ef04 --- /dev/null +++ b/src/app/service/crew/[id]/edit/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +function page() { + return ( +
page
+ ) +} + +export default page \ No newline at end of file diff --git a/src/app/service/crew/[id]/page.tsx b/src/app/service/crew/[id]/page.tsx new file mode 100644 index 00000000..bf9aaaff --- /dev/null +++ b/src/app/service/crew/[id]/page.tsx @@ -0,0 +1,241 @@ +'use client'; + +import { CrewDetailProps } from '@/types/crew/crew'; +import clsx from 'clsx'; +import { MoreVertical } from 'lucide-react'; +import Image from 'next/image'; +import { useEffect, useState } from 'react'; + +import BottomSheetContainer from '@/app/_components/common/BottomSheetContainer'; +import HEADER from '@/constants/ui/common/header'; +import useBottomSheetStore, { + BottomSheetStore, +} from '@/store/crew/bottomSheetStore'; +import { ChevronLeft, ChevronRight, MapPin } from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import Loading from '../../loading'; +import ApplicantPage from '../_components/ApplicantPage'; +import Label from '../create/_components/UI/Label'; +import MemberList from './_components/MemberList'; +import Schedules from './_components/Schedules'; +import getCrewDetail from './api/getCrewDetail'; +import CreateCrewForm from '../create/_components/CreateCrewForm'; + +function Page({ params }: { params: { id: string } }) { + const { isSheetOpen, setIsSheetOpen }: BottomSheetStore = + useBottomSheetStore(); + + const router = useRouter(); + + const { id } = params; + const crewId = Number(id); + const [crewDetail, setCrewDetail] = useState(); + const [isMemberListClicked, setIsMemberListClicked] = useState(false); + const [IsOpenEdit, setIsOpenEdit] = useState(false); + const { + name, + tags, + head_profile_image, + user_images, + icon, + region, + capacity, + member_count, + description, + is_member, + head_name, + } = crewDetail || {}; + + useEffect(() => { + const fetchCrewDetail = () => { + getCrewDetail(crewId).then(response => setCrewDetail(response)); + }; + + fetchCrewDetail(); + }, [crewId]); + + const onDisMiss = () => { + setIsSheetOpen(false); + }; + + if (!crewDetail) { + return ; + } + + if (isMemberListClicked) { + return ( + + ); + } + + // if (IsOpenEdit) { + // return ; + // } + + return ( +
+
+ + {HEADER.crew} + +
+ +
+ {icon && ( +
+ {name +
+ )} + +
+ {tags + ? tags.map(tag => ( + + {tag} + + )) + : null} +
{name}
+
+ + {region && + region.map(region => ( + + {region} + + ))} +
+
+
+
+ 멤버 프로필사진 +
{head_name}
+
+
+ + {user_images && + user_images.slice(0, 10).map((img, index) => ( +
+ 멤버 프로필사진 +
+ ))} + +
+ {member_count} / {capacity} + {is_member && ( + + )} +
+
+
+
+ {is_member ? null : ( +
+
+ {description} +
+
+ +
+ 성별 + {`{gender}`} + 나이 + {`{min_age}~{max_age}세`} + 가입 + {`{permission_required}`} +
+
+
+ )} +
+ {crewDetail && ( + } + bottomSheetTitle="크루 가입" + isSheetOpen={isSheetOpen} + // setIsSheetOpen={setIsSheetOpen} + onDisMiss={onDisMiss} + /> + )} + {is_member ? ( +
+
일정
+ +
+ ) : ( + + )} +
+
+
+ ); +} + +export default Page; diff --git a/src/app/service/crew/_components/ApplicantForm.tsx b/src/app/service/crew/_components/ApplicantForm.tsx new file mode 100644 index 00000000..6c1ff088 --- /dev/null +++ b/src/app/service/crew/_components/ApplicantForm.tsx @@ -0,0 +1,53 @@ +import { useState } from 'react'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import useBottomSheetStore from '@/store/crew/bottomSheetStore'; +import postCrewApply from '../[id]/api/postCrewApply'; + +interface FormValues { + textInput: string; +} + +function ApplicantForm({ id }: { id: number }) { + const { register, handleSubmit } = useForm(); + const [textInputValue, setTextInputValue] = useState(''); + const { setIsSheetOpen } = useBottomSheetStore(); + + const onSubmit: SubmitHandler = async data => { + try { + await postCrewApply(id, data.textInput); + setIsSheetOpen(false); + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } + } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + setTextInputValue(e.target.value); + }; + + return ( +
+ +