From 68c13bd0710ac571984d2562f7c17898d0a3b33d Mon Sep 17 00:00:00 2001 From: Ryan-TheLion Date: Thu, 9 May 2024 20:34:42 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=EC=BB=A4=ED=94=BC=EC=B1=97=20?= =?UTF-8?q?=EC=86=8C=EA=B0=9C=EA=B8=80=20=EC=A0=9C=ED=95=9C=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 - 10자이상 1000자 이하로 유효성 검사 및 관련 메시지 수정 --- src/constants/limitation.ts | 2 ++ src/constants/message/validation.ts | 1 + .../create/components/sections/content/ContentSection.tsx | 4 ++-- .../create/controls/rules/chat-content-rules.ts | 8 ++++---- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/constants/limitation.ts b/src/constants/limitation.ts index 060df4f1..ff88f597 100644 --- a/src/constants/limitation.ts +++ b/src/constants/limitation.ts @@ -30,6 +30,8 @@ const Limitation = { mentoring_time: 10, title_limit_under: 5, title_limit_over: 100, + chat_content_min_length: 10, + chat_content_max_length: 1000, chat_introduction_limit_under: 10, chat_introduction_limit_over: 150, content_limit_under: 10, diff --git a/src/constants/message/validation.ts b/src/constants/message/validation.ts index 1bcaedcd..3775d5e6 100644 --- a/src/constants/message/validation.ts +++ b/src/constants/message/validation.ts @@ -10,6 +10,7 @@ export const validationMessage = { noTime: "정확한 시간대를 설정해주세요", noLocation: "모임 위치를 설정해주세요", noHeadCnt: "모임 인원을 설정해주세요", + chatContentLength: `소개글은 최소 ${Limitation.chat_content_min_length}자 이상 ${Limitation.chat_content_max_length}자 이하이어야 합니다.`, underContentLimit: `본문 내용은 최소 ${Limitation.content_limit_under}자 이상이어야 합니다.`, overContentLimit: `본문 내용은 최대 ${Limitation.content_limit_over}자 이하이어야 합니다.`, underAnswerLimit: `댓글 내용은 최소 ${Limitation.answer_limit_under}자 이상이어야 합니다.`, diff --git a/src/page/coffee-chat/create/components/sections/content/ContentSection.tsx b/src/page/coffee-chat/create/components/sections/content/ContentSection.tsx index 905a1fb9..60386a92 100644 --- a/src/page/coffee-chat/create/components/sections/content/ContentSection.tsx +++ b/src/page/coffee-chat/create/components/sections/content/ContentSection.tsx @@ -33,8 +33,8 @@ function CoffeeChatContentSection({ control }: CoffeeChatContentSectionProps) { diff --git a/src/page/coffee-chat/create/controls/rules/chat-content-rules.ts b/src/page/coffee-chat/create/controls/rules/chat-content-rules.ts index 698c21ab..9780c7a5 100644 --- a/src/page/coffee-chat/create/controls/rules/chat-content-rules.ts +++ b/src/page/coffee-chat/create/controls/rules/chat-content-rules.ts @@ -11,11 +11,11 @@ type ChatContentRules = Omit< export const chatContentRules: ChatContentRules = { required: validationMessage.noContent, minLength: { - value: Limitation.content_limit_under, - message: validationMessage.underContentLimit, + value: Limitation.chat_content_min_length, + message: validationMessage.chatContentLength, }, maxLength: { - value: Limitation.content_limit_over, - message: validationMessage.overContentLimit, + value: Limitation.chat_content_max_length, + message: validationMessage.chatContentLength, }, } From 31fdd45ba642ac289d884d6456e48989b2fb9022 Mon Sep 17 00:00:00 2001 From: Ryan-TheLion Date: Fri, 10 May 2024 15:47:56 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EB=94=94=EB=B0=94=EC=9D=B4=EC=8A=A4=20=EC=BA=98=EB=A6=B0?= =?UTF-8?q?=EB=8D=94=20active=20css?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 휴대폰에서 active 된 tile 이 리액트 캘린더 기본 배경색상이 적용되는 현상 수정 - border-radius 속성 제거 --- src/styles/react-calendar/Base.css | 13 +++++++++++-- src/styles/react-calendar/CoffeeChatTile.css | 3 --- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/styles/react-calendar/Base.css b/src/styles/react-calendar/Base.css index bd3c1c8c..6e578cc4 100644 --- a/src/styles/react-calendar/Base.css +++ b/src/styles/react-calendar/Base.css @@ -67,6 +67,11 @@ color: white; } +.react-calendar__tile--active:enabled:hover, +.react-calendar__tile--active:enabled:focus { + background: #00c471; +} + .react-calendar__tile--range { background: #f8f8fa; color: #00c471; @@ -99,7 +104,6 @@ .react-calendar__tile:enabled:focus { background: #00c47133; color: #00c471; - border-radius: 6px; } .react-calendar__tile--now:enabled:hover, @@ -114,7 +118,6 @@ .react-calendar__tile--hasActive:enabled:focus { background: #f8f8fa; border-color: #00c471; - border: 1px; } .react-calendar__tile--active:enabled:hover, @@ -129,6 +132,12 @@ } @media (hover: none) { + .react-calendar__tile--active:hover, + .react-calendar__tile--active:focus { + background: #00c471; + color: white; + } + .react-calendar__tile--active:enabled:hover, .react-calendar__tile--active:enabled:focus { background: #00c471; diff --git a/src/styles/react-calendar/CoffeeChatTile.css b/src/styles/react-calendar/CoffeeChatTile.css index 903f5d12..dbdd2d8e 100644 --- a/src/styles/react-calendar/CoffeeChatTile.css +++ b/src/styles/react-calendar/CoffeeChatTile.css @@ -1,19 +1,16 @@ .react-calendar__tile:not(.disabled).reservation-possible { background: #fbf8ce; - border-radius: 6px; } .react-calendar__tile:not(.disabled).reservation-confirm { background: lightgray; color: #fff; font-weight: bold; - border-radius: 6px; } .react-calendar__tile:not(.disabled).mentoring { background: #00c471; color: #fff; - border-radius: 6px; } .react-calendar__tile--active.mentoring abbr { color: #ffff80; From bcebd94e384c577eb9d7e201f93819e2ae2d6469 Mon Sep 17 00:00:00 2001 From: Ryan-TheLion Date: Fri, 10 May 2024 16:31:22 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=EA=B0=80=EB=93=9C=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인 되지 않은 유저 가드 페이지 컴포넌트로 분리 - 클라이언트 세션도 초기화 --- .../_components/guard-page/Unauthorized.tsx | 25 +++++++++++++++++++ src/app/coding-meetings/create/page.tsx | 11 ++------ 2 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 src/app/coding-meetings/_components/guard-page/Unauthorized.tsx diff --git a/src/app/coding-meetings/_components/guard-page/Unauthorized.tsx b/src/app/coding-meetings/_components/guard-page/Unauthorized.tsx new file mode 100644 index 00000000..9b554baa --- /dev/null +++ b/src/app/coding-meetings/_components/guard-page/Unauthorized.tsx @@ -0,0 +1,25 @@ +"use client" + +import Mentee from "@/components/shared/animation/Mentee" +import notificationMessage from "@/constants/message/notification" +import { useClientSession } from "@/hooks/useClientSession" +import { useEffect } from "react" + +function CreateCodingMeetingUnauthorized() { + const { clientSessionReset } = useClientSession() + + useEffect(() => { + clientSessionReset() + }, []) /* eslint-disable-line */ + + return ( +
+
+ +
+
{notificationMessage.unauthorized}.
+
+ ) +} + +export default CreateCodingMeetingUnauthorized diff --git a/src/app/coding-meetings/create/page.tsx b/src/app/coding-meetings/create/page.tsx index 787c954b..90f49e5a 100644 --- a/src/app/coding-meetings/create/page.tsx +++ b/src/app/coding-meetings/create/page.tsx @@ -5,6 +5,7 @@ import CreateCodingMeetingPage from "@/page/coding-meetings/create/CreateCodingM import { getServerSession } from "@/util/auth" import { Metadata } from "next" import { notFound } from "next/navigation" +import CreateCodingMeetingUnauthorized from "../_components/guard-page/Unauthorized" export const metadata: Metadata = { title: `모각코 생성`, @@ -33,17 +34,9 @@ export const metadata: Metadata = { export default async function CreateCodingMeetingsPage() { const { user } = getServerSession() - // [TODO] try { if (!user) { - return ( -
-
- -
-
{notificationMessage.unauthorized}.
-
- ) + return } return From 226324440eca845bd36cc4308bedf27411abf7ec Mon Sep 17 00:00:00 2001 From: Ryan-TheLion Date: Mon, 20 May 2024 22:08:00 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=EB=AA=A8=EA=B0=81=EC=BD=94=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1,=EC=88=98=EC=A0=95=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - react hook form 의 FormProvider, Controller, FieldArray 를 활용하여, 불필요한 리코일 atom (전역상태) 제거 - react hook form field의 rules로 필드 유효성 검증의 관심사 분리 - invalid form 에 대한 에러 처리로직 간소화 - 생성, 수정 이후 정상적으로 리다이렉트 될 수 있도록 리다이렉트 주소 수정 - 타입 개선, 타입 안정성을 확보하려고 함(함수 override 등) - base 캘린더 컴포넌트에 tileDisabled prop 추가, 모각코 캘린더 타일 css 추가 --- package.json | 2 + pnpm-lock.yaml | 31 ++ src/app/coding-meetings/create/page.tsx | 3 - src/app/coding-meetings/post/[token]/page.tsx | 3 +- src/app/layout.tsx | 7 +- src/components/LinkToListPage.tsx | 4 +- src/components/shared/TextCounter.tsx | 2 +- src/constants/limitation.ts | 15 + src/constants/select.ts | 1 - src/constants/timeOptions.ts | 56 +-- src/interfaces/form.ts | 39 +- src/mocks/db/coding-meetings.ts | 6 +- src/mocks/db/questions.ts | 2 +- .../coding-meeting/create-coding-meeting.ts | 29 +- .../coding-meeting/update-coding-meeting.ts | 44 +- .../create/CodingMeetingFormProvider.tsx | 26 + .../create/CreateCodingMeetingPage.tsx | 473 ++++-------------- .../create/CreateCodingMeetingPage.types.ts | 63 --- .../components/CodingMeetingSection.tsx | 26 +- .../components/CustomCalendar/Calendar.css | 6 - .../CustomCalendar/CustomCalendar.tsx | 63 --- .../create/components/CustomMap/kakaoMap.tsx | 115 ----- .../create/components/DateTimeSection.tsx | 143 ------ .../create/components/HashTagsSection.tsx | 119 ----- .../create/components/HeadCountSection.tsx | 59 --- .../create/components/LocationSection.tsx | 195 -------- .../calendar/CreateCodingMeetingCalendar.tsx | 60 +++ .../section/content/ContentSection.tsx | 20 + .../section/date-time/DateTimeSection.tsx | 81 +++ .../section/hash-tags/HashTagsController.tsx | 213 ++++++++ .../section/hash-tags/HashTagsSection.tsx | 14 + .../section/location/LocationDialog.tsx | 33 ++ .../section/location/LocationSection.tsx | 23 + .../location/SearchResultPlaceMaps.tsx | 120 +++++ .../section/location/search/AutoComplete.tsx | 76 +++ .../location/search/HighlightPlaceName.tsx | 69 +++ .../location/search/SearchController.tsx | 278 ++++++++++ .../member-count/MemberCountSection.tsx | 20 + .../components/section/title/TitleSection.tsx | 12 + .../create/controls/ContentController.tsx | 45 ++ .../create/controls/LocationController.tsx | 112 +++++ .../create/controls/MemberCountController.tsx | 78 +++ .../create/controls/TitleController.tsx | 57 +++ .../CodingMeetingEndHourController.tsx | 77 +++ .../CodingMeetingEndMinuteController.tsx | 59 +++ .../content/coding-meeting-content-rules.ts | 20 + .../date/day/coding-meeting-day-rules.ts | 21 + .../end-time/coding-meeting-end-hour-rules.ts | 24 + .../coding-meeting-end-minute-rules.ts | 43 ++ .../coding-meeting-start-hour-rules.ts | 24 + .../coding-meeting-start-minute-rules.ts | 43 ++ .../controls/rules/location/location-rules.ts | 9 + .../rules/location/location-search-rules.ts | 11 + .../rules/member-count/member-count-rules.ts | 20 + .../controls/rules/title/title-rules.ts | 21 + .../CodingMeetingStartHourController.tsx | 77 +++ .../CodingMeetingStartMinuteController.tsx | 59 +++ .../create/hooks/useCreateCodingMeeting.tsx | 113 +++++ .../useHandleCreateCodingMeetingTime.tsx | 72 --- .../create/hooks/useKakaoMapPlaceApi.tsx | 30 ++ .../create/hooks/useUpdateCodingMeeting.tsx | 112 +++++ src/page/coding-meetings/util/parser.ts | 112 +++++ .../CreateCoffeeChatReservationPage.tsx | 5 +- .../components/sections/date/TimeOptions.tsx | 11 +- .../create/controls/rules/hashtag-rules.ts | 8 + .../coffee-chat/create/controls/util/form.ts | 26 - .../calendar-base/ReservationCalendarBase.tsx | 48 +- src/react-query/coding-meeting.ts | 65 --- src/recoil/atoms/coding-meeting/dateTime.tsx | 28 -- src/recoil/atoms/coding-meeting/hashtags.tsx | 6 - src/recoil/atoms/coding-meeting/headcount.tsx | 6 - src/recoil/atoms/coding-meeting/mapData.tsx | 47 +- .../react-calendar/CodingMeetingTile.css | 11 + src/util/historySession/path.ts | 5 + src/util/hook-form/error.ts | 50 ++ src/util/validate.ts | 3 + 76 files changed, 2654 insertions(+), 1455 deletions(-) delete mode 100644 src/constants/select.ts create mode 100644 src/page/coding-meetings/create/CodingMeetingFormProvider.tsx delete mode 100644 src/page/coding-meetings/create/CreateCodingMeetingPage.types.ts delete mode 100644 src/page/coding-meetings/create/components/CustomCalendar/Calendar.css delete mode 100644 src/page/coding-meetings/create/components/CustomCalendar/CustomCalendar.tsx delete mode 100644 src/page/coding-meetings/create/components/CustomMap/kakaoMap.tsx delete mode 100644 src/page/coding-meetings/create/components/DateTimeSection.tsx delete mode 100644 src/page/coding-meetings/create/components/HashTagsSection.tsx delete mode 100644 src/page/coding-meetings/create/components/HeadCountSection.tsx delete mode 100644 src/page/coding-meetings/create/components/LocationSection.tsx create mode 100644 src/page/coding-meetings/create/components/calendar/CreateCodingMeetingCalendar.tsx create mode 100644 src/page/coding-meetings/create/components/section/content/ContentSection.tsx create mode 100644 src/page/coding-meetings/create/components/section/date-time/DateTimeSection.tsx create mode 100644 src/page/coding-meetings/create/components/section/hash-tags/HashTagsController.tsx create mode 100644 src/page/coding-meetings/create/components/section/hash-tags/HashTagsSection.tsx create mode 100644 src/page/coding-meetings/create/components/section/location/LocationDialog.tsx create mode 100644 src/page/coding-meetings/create/components/section/location/LocationSection.tsx create mode 100644 src/page/coding-meetings/create/components/section/location/SearchResultPlaceMaps.tsx create mode 100644 src/page/coding-meetings/create/components/section/location/search/AutoComplete.tsx create mode 100644 src/page/coding-meetings/create/components/section/location/search/HighlightPlaceName.tsx create mode 100644 src/page/coding-meetings/create/components/section/location/search/SearchController.tsx create mode 100644 src/page/coding-meetings/create/components/section/member-count/MemberCountSection.tsx create mode 100644 src/page/coding-meetings/create/components/section/title/TitleSection.tsx create mode 100644 src/page/coding-meetings/create/controls/ContentController.tsx create mode 100644 src/page/coding-meetings/create/controls/LocationController.tsx create mode 100644 src/page/coding-meetings/create/controls/MemberCountController.tsx create mode 100644 src/page/coding-meetings/create/controls/TitleController.tsx create mode 100644 src/page/coding-meetings/create/controls/end-time/CodingMeetingEndHourController.tsx create mode 100644 src/page/coding-meetings/create/controls/end-time/CodingMeetingEndMinuteController.tsx create mode 100644 src/page/coding-meetings/create/controls/rules/content/coding-meeting-content-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/date/day/coding-meeting-day-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/date/end-time/coding-meeting-end-hour-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/date/end-time/coding-meeting-end-minute-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/date/start-time/coding-meeting-start-hour-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/date/start-time/coding-meeting-start-minute-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/location/location-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/location/location-search-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/member-count/member-count-rules.ts create mode 100644 src/page/coding-meetings/create/controls/rules/title/title-rules.ts create mode 100644 src/page/coding-meetings/create/controls/start-time/CodingMeetingStartHourController.tsx create mode 100644 src/page/coding-meetings/create/controls/start-time/CodingMeetingStartMinuteController.tsx create mode 100644 src/page/coding-meetings/create/hooks/useCreateCodingMeeting.tsx delete mode 100644 src/page/coding-meetings/create/hooks/useHandleCreateCodingMeetingTime.tsx create mode 100644 src/page/coding-meetings/create/hooks/useKakaoMapPlaceApi.tsx create mode 100644 src/page/coding-meetings/create/hooks/useUpdateCodingMeeting.tsx create mode 100644 src/page/coding-meetings/util/parser.ts delete mode 100644 src/page/coffee-chat/create/controls/util/form.ts delete mode 100644 src/react-query/coding-meeting.ts delete mode 100644 src/recoil/atoms/coding-meeting/dateTime.tsx delete mode 100644 src/recoil/atoms/coding-meeting/hashtags.tsx delete mode 100644 src/recoil/atoms/coding-meeting/headcount.tsx create mode 100644 src/styles/react-calendar/CodingMeetingTile.css create mode 100644 src/util/hook-form/error.ts diff --git a/package.json b/package.json index 4a5b8bd0..c7f4a3c2 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "react-calendar": "^4.7.0", "react-dom": "^18", "react-error-boundary": "^4.0.12", + "react-highlight-words": "^0.20.0", "react-hook-form": "^7.49.0", "react-icons": "^4.12.0", "react-kakao-maps-sdk": "^1.1.26", @@ -90,6 +91,7 @@ "@types/prismjs": "^1.26.3", "@types/react": "^18", "@types/react-dom": "^18", + "@types/react-highlight-words": "^0.16.7", "@types/react-paginate": "^7.1.4", "@types/react-syntax-highlighter": "^15.5.11", "@types/sockjs": "^0.3.36", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3803378..df17cfa6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -149,6 +149,9 @@ dependencies: react-error-boundary: specifier: ^4.0.12 version: 4.0.12(react@18.2.0) + react-highlight-words: + specifier: ^0.20.0 + version: 0.20.0(react@18.2.0) react-hook-form: specifier: ^7.49.0 version: 7.49.0(react@18.2.0) @@ -238,6 +241,9 @@ devDependencies: '@types/react-dom': specifier: ^18 version: 18.2.17 + '@types/react-highlight-words': + specifier: ^0.16.7 + version: 0.16.7 '@types/react-paginate': specifier: ^7.1.4 version: 7.1.4 @@ -2307,6 +2313,12 @@ packages: dependencies: '@types/react': 18.2.43 + /@types/react-highlight-words@0.16.7: + resolution: {integrity: sha512-+upXTIaRd3rGvh1aDQSs9z5X+sV3UM6Jrmjk03GN2GXl4v/+iOJKQj2LZHo6Vp2IoTvMdtxgME26feqo12xXLg==} + dependencies: + '@types/react': 18.2.43 + dev: true + /@types/react-paginate@7.1.4: resolution: {integrity: sha512-6fqZvDzRJHubOGl6c7cGFC9ysgQSWYy0Gpus9HjORpydlcXgPnT8x+aKgqwCdtpZrRTwcBz2Q7JWAOYaRrsXGg==} dependencies: @@ -4113,6 +4125,10 @@ packages: resolution: {integrity: sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==} dev: true + /highlight-words-core@1.2.2: + resolution: {integrity: sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==} + dev: false + /highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} dev: false @@ -4716,6 +4732,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /memoize-one@4.0.3: + resolution: {integrity: sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==} + dev: false + /memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} dev: false @@ -5483,6 +5503,17 @@ packages: react: 18.2.0 dev: false + /react-highlight-words@0.20.0(react@18.2.0): + resolution: {integrity: sha512-asCxy+jCehDVhusNmCBoxDf2mm1AJ//D+EzDx1m5K7EqsMBIHdZ5G4LdwbSEXqZq1Ros0G0UySWmAtntSph7XA==} + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 + dependencies: + highlight-words-core: 1.2.2 + memoize-one: 4.0.3 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + /react-hook-form@7.49.0(react@18.2.0): resolution: {integrity: sha512-gf4qyY4WiqK2hP/E45UUT6wt3Khl49pleEVcIzxhLBrD6m+GMWtLRk0vMrRv45D1ZH8PnpXFwRPv0Pewske2jw==} engines: {node: '>=18', pnpm: '8'} diff --git a/src/app/coding-meetings/create/page.tsx b/src/app/coding-meetings/create/page.tsx index 90f49e5a..24c2dcfc 100644 --- a/src/app/coding-meetings/create/page.tsx +++ b/src/app/coding-meetings/create/page.tsx @@ -1,7 +1,4 @@ -import Mentee from "@/components/shared/animation/Mentee" -import notificationMessage from "@/constants/message/notification" import CreateCodingMeetingPage from "@/page/coding-meetings/create/CreateCodingMeetingPage" - import { getServerSession } from "@/util/auth" import { Metadata } from "next" import { notFound } from "next/navigation" diff --git a/src/app/coding-meetings/post/[token]/page.tsx b/src/app/coding-meetings/post/[token]/page.tsx index 360953a4..8d0a2a5e 100644 --- a/src/app/coding-meetings/post/[token]/page.tsx +++ b/src/app/coding-meetings/post/[token]/page.tsx @@ -69,12 +69,13 @@ export default async function UpdateCodingMeetingsPage({ /> ) } + return (
) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 44e8e505..0399493f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,6 +15,7 @@ import ScrollTop from "@/components/shared/ScrollTop" import ToastDismissEventListener from "@/components/layout/ToastDismissListener" import GoogleAnalyticsProvider from "@/google-analytics/GoogleAnalyticsProvider" import HistorySession from "@/components/history/HistorySession" +import CodingMeetingFormProvider from "@/page/coding-meetings/create/CodingMeetingFormProvider" export const dynamic = "force-dynamic" @@ -68,8 +69,10 @@ export default function RootLayout({ - {children} - + + {children} + + diff --git a/src/components/LinkToListPage.tsx b/src/components/LinkToListPage.tsx index 614360a9..18c752e1 100644 --- a/src/components/LinkToListPage.tsx +++ b/src/components/LinkToListPage.tsx @@ -4,7 +4,7 @@ import { getHistorySessionPath } from "@/util/historySession/path" import { useRouter } from "next/navigation" import { DirectionIcons } from "./icons/Icons" -export type TargetPage = "qna" | "chat" +export type TargetPage = "qna" | "chat" | "coding-meetings" interface LinkToListPageProps { to: TargetPage @@ -13,11 +13,13 @@ interface LinkToListPageProps { const initialPath: Record = { qna: "/qna?page=0", chat: "/chat?page=0", + "coding-meetings": "/coding-meetings?page=0&size=10&filter=all", } as const const targetPathname: Record = { qna: "/qna", chat: "/chat", + "coding-meetings": "/coding-meetings", } as const function LinkToListPage({ to }: LinkToListPageProps) { diff --git a/src/components/shared/TextCounter.tsx b/src/components/shared/TextCounter.tsx index fa95a47e..fbe1e207 100644 --- a/src/components/shared/TextCounter.tsx +++ b/src/components/shared/TextCounter.tsx @@ -17,7 +17,7 @@ interface TextCounterProps { } function TextCounter({ - text, + text = "", min, max, className, diff --git a/src/constants/limitation.ts b/src/constants/limitation.ts index ff88f597..89723399 100644 --- a/src/constants/limitation.ts +++ b/src/constants/limitation.ts @@ -62,3 +62,18 @@ const Limitation = { } as const export default Limitation + +export const CODING_MEETING_LIMITS = { + title: { + minLength: 5, + maxLength: 100, + }, + memberCount: { + min: 3, + max: 6, + }, + content: { + minLength: 10, + maxLength: 10000, + }, +} diff --git a/src/constants/select.ts b/src/constants/select.ts deleted file mode 100644 index 4db07061..00000000 --- a/src/constants/select.ts +++ /dev/null @@ -1 +0,0 @@ -export const HeadCountValue = ["3", "4", "5", "6"] as const diff --git a/src/constants/timeOptions.ts b/src/constants/timeOptions.ts index 7cc5c06b..180e39b6 100644 --- a/src/constants/timeOptions.ts +++ b/src/constants/timeOptions.ts @@ -1,9 +1,32 @@ +export type CodingMeetingHourMinuteTime = { + hour: + | (typeof CODING_MEETING_HOURS)["AM"][number] + | (typeof CODING_MEETING_HOURS)["PM"][number] + minute: (typeof CODING_MEETING_MINUTES)[number] +} + +export type CodingMeetingTimeOption = { + AM: `${(typeof CODING_MEETING_HOURS)["AM"][number]}:${(typeof CODING_MEETING_MINUTES)[number]}` + PM: `${(typeof CODING_MEETING_HOURS)["PM"][number]}:${(typeof CODING_MEETING_MINUTES)[number]}` +} +export type CodingMeetingTimeOptions = { + AM: Array + PM: Array +} + export const enum TimeZone { AM = "AM", PM = "PM", } -export const AM = [ +export const CODING_MEETING_HOURS = { + AM: ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11"], + PM: ["12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"], +} as const + +export const CODING_MEETING_MINUTES = ["00", "30"] as const + +export const CODING_MEETING_AM_OPTIONS: CodingMeetingTimeOptions["AM"] = [ "00:00", "00:30", "01:00", @@ -30,7 +53,7 @@ export const AM = [ "11:30", ] -export const PM = [ +export const CODING_MEETING_PM_OPTIONS: CodingMeetingTimeOptions["PM"] = [ "12:00", "12:30", "13:00", @@ -56,32 +79,3 @@ export const PM = [ "23:00", "23:30", ] - -const hours = [ - "00", - "01", - "02", - "03", - "04", - "05", - "06", - "07", - "08", - "09", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19", - "20", - "21", - "22", - "23", -] -const minutes = ["00", "30"] -export const timeSelect = { hours, minutes } diff --git a/src/interfaces/form.ts b/src/interfaces/form.ts index a3e3cbc3..bc7b1512 100644 --- a/src/interfaces/form.ts +++ b/src/interfaces/form.ts @@ -1,3 +1,5 @@ +import { CodingMeetingHourMinuteTime } from "@/constants/timeOptions" +import { CodingMeetingHashTag } from "./coding-meetings" export interface LoginFormData { email: string password: string @@ -20,7 +22,42 @@ export interface AnswerFormData { answer: string } -// comment +// coding-meeting + +// [coding-meeting] form data +export type CodingMeetingPageMode = "create" | "update" + +export interface CodingMeetingFormData { + title: string + content: string + member_upper_limit: number + date: { + day: Date + start_time: [ + CodingMeetingHourMinuteTime["hour"], + CodingMeetingHourMinuteTime["minute"], + ] + end_time: [ + CodingMeetingHourMinuteTime["hour"], + CodingMeetingHourMinuteTime["minute"], + ] + } + location: { + id: string + longitude: string + latitude: string + place_name: string + } + hashtags: { tag: CodingMeetingHashTag }[] +} + +export interface CodingMeetingLocationSearchFormData { + keyword: string +} + +export type CodingMeetingFormInitialValues = CodingMeetingFormData + +// [coding-meeting] comment export interface CommentFormData { comment: string } diff --git a/src/mocks/db/coding-meetings.ts b/src/mocks/db/coding-meetings.ts index 5a5a5e87..22aaaed2 100644 --- a/src/mocks/db/coding-meetings.ts +++ b/src/mocks/db/coding-meetings.ts @@ -11,7 +11,7 @@ const mockCodingMeetings: MockCodingMeeting[] = [ member_level_image_url: badge_url[mockUsers[0].level], created_date: "2024-02-23T04:30:00.696Z", coding_meeting_closed: false, - coding_meeting_token: "CMT00000000", + coding_meeting_token: "cm_T00000000", coding_meeting_title: "봉은사역에서 모각코할 분들 모집합니다.", coding_meeting_start_time: "2024-02-29T07:30:53.696Z", coding_meeting_end_time: "2024-02-29T09:00:53.696Z", @@ -64,7 +64,7 @@ const mockCodingMeetings: MockCodingMeeting[] = [ member_level_image_url: badge_url[mockUsers[1].level], created_date: "2024-02-22T05:28:00.696Z", coding_meeting_closed: true, - coding_meeting_token: "CMT00000001", + coding_meeting_token: "cm_T00000001", coding_meeting_title: "[신림] 정기 오프라인 모각코 스터디 (주2회 이상 참여 必)", coding_meeting_start_time: "2024-02-29T15:30:00.696Z", @@ -87,7 +87,7 @@ const mockCodingMeetings: MockCodingMeeting[] = [ member_level_image_url: badge_url[mockUsers[2].level], created_date: "2024-02-23T04:36:53.696Z", coding_meeting_closed: false, - coding_meeting_token: "CMT00000002", + coding_meeting_token: "cm_T00000002", coding_meeting_title: "[사당역] Next.js 스터디 같이 하실 초보분들 모십니다.", coding_meeting_start_time: "2024-03-01T08:00:53.696Z", diff --git a/src/mocks/db/questions.ts b/src/mocks/db/questions.ts index d452b8e8..13c6a6e2 100644 --- a/src/mocks/db/questions.ts +++ b/src/mocks/db/questions.ts @@ -1,7 +1,7 @@ import badge_url from "@/assets/images/badges" import { Question } from "@/interfaces/question" import { mockUsers } from "./user" -// 2023-12-16T23:10:12 + export const mockQuestions: Array = [ { id: 1, diff --git a/src/mocks/handler/coding-meeting/create-coding-meeting.ts b/src/mocks/handler/coding-meeting/create-coding-meeting.ts index b8a8d46c..a6351a57 100644 --- a/src/mocks/handler/coding-meeting/create-coding-meeting.ts +++ b/src/mocks/handler/coding-meeting/create-coding-meeting.ts @@ -7,7 +7,6 @@ import { RouteMap } from "@/service/route-map" import { HttpResponse, PathParams, http } from "msw" import jwt, { JwtPayload } from "jsonwebtoken" import { mockUsers } from "@/mocks/db/user" -import { HttpStatusCode } from "axios" import mockCodingMeetings from "@/mocks/db/coding-meetings" import { MockCodingMeeting } from "@/interfaces/coding-meetings" import badge_url from "@/assets/images/badges" @@ -23,10 +22,11 @@ export const mockCreateCodingMeetingApi = http.post< const { ...createPayload } = await request.json() const header = request.headers - const header_token = header.get("Authorization") + const authHeader = header.get("Authorization") - if (!header_token) { - const { Code, HttpStatus } = ApiStatus.QnA.updateQustion.Unauthorized + if (!authHeader) { + const { Code, HttpStatus } = + ApiStatus.CodingMeetings.createCodingMeeting.Unauthorized return HttpResponse.json( { code: Code, @@ -36,25 +36,30 @@ export const mockCreateCodingMeetingApi = http.post< ) } - const decoded_token = jwt.decode(header_token) as JwtPayload & { + const accessToken = authHeader.replace(/^Bearer /, "") + + const decoded_token = jwt.decode(accessToken) as JwtPayload & { id: number } const targetMember = mockUsers.find((user) => user.id === decoded_token.id) if (!targetMember) { + const { Code, HttpStatus } = + ApiStatus.CodingMeetings.createCodingMeeting.NotFound + return HttpResponse.json( { - code: -1, - msg: "답변을 입력할 권한이 없습니다.", + code: Code, + msg: "유저를 찾을 수 없습니다.", }, { - status: HttpStatusCode.Forbidden, + status: HttpStatus, }, ) } - const token = "CMT" + (mockCodingMeetings.length + 10000) + const token = "cm_" + (mockCodingMeetings.length + 10000) const newCodingMeetingPost: MockCodingMeeting = { member_id: targetMember.id, @@ -71,14 +76,16 @@ export const mockCreateCodingMeetingApi = http.post< mockCodingMeetings.push(newCodingMeetingPost) + const { Code, HttpStatus } = ApiStatus.CodingMeetings.createCodingMeeting.Ok + return HttpResponse.json( { - code: 5144, + code: Code, msg: "모각코 생성 성공", data: { coding_meeting_token: token }, }, { - status: HttpStatusCode.Ok, + status: HttpStatus, }, ) }, diff --git a/src/mocks/handler/coding-meeting/update-coding-meeting.ts b/src/mocks/handler/coding-meeting/update-coding-meeting.ts index c3d5cb3d..c6ac4644 100644 --- a/src/mocks/handler/coding-meeting/update-coding-meeting.ts +++ b/src/mocks/handler/coding-meeting/update-coding-meeting.ts @@ -6,6 +6,7 @@ import { import mockCodingMeetings from "@/mocks/db/coding-meetings" import { RouteMap } from "@/service/route-map" import { HttpResponse, http } from "msw" +import jwt, { JwtPayload } from "jsonwebtoken" export const mockUpdateCodingMeetingApi = http.put< { coding_meeting_token: string }, @@ -18,10 +19,11 @@ export const mockUpdateCodingMeetingApi = http.put< async ({ params, request }) => { try { const header = request.headers - const token = header.get("Authorization") + const authHeader = header.get("Authorization") - if (!token) { - const { Code, HttpStatus } = ApiStatus.QnA.updateQustion.Unauthorized + if (!authHeader) { + const { Code, HttpStatus } = + ApiStatus.CodingMeetings.updateCodingMeeting.Unauthorized return HttpResponse.json( { code: Code, @@ -31,29 +33,47 @@ export const mockUpdateCodingMeetingApi = http.put< ) } - const targetToken = params.coding_meeting_token + const accessToken = authHeader.replace(/^Bearer /g, "") + const decoded_token = jwt.decode(accessToken) as JwtPayload & { + id: number + } - const updatePayload = await request.json() + const targetCodingMeetingToken = params.coding_meeting_token - const targetMockIdx = mockCodingMeetings.findIndex( - (post) => post.coding_meeting_token === targetToken, + const targetMockCodingMeetingIdx = mockCodingMeetings.findIndex( + (post) => post.coding_meeting_token === targetCodingMeetingToken, ) - if (targetMockIdx < 0) { + if (targetMockCodingMeetingIdx < 0) { const { Code, HttpStatus } = ApiStatus.CodingMeetings.updateCodingMeeting.NotFound return HttpResponse.json( { code: Code, - msg: "존재하지 않는 질문", + msg: "존재하지 않는 모각코", }, { status: HttpStatus }, ) } - mockCodingMeetings[targetMockIdx] = { - ...mockCodingMeetings[targetMockIdx], + const targetMockCodingMeeting = + mockCodingMeetings[targetMockCodingMeetingIdx] + + if (targetMockCodingMeeting.member_id !== decoded_token.id) { + const { Code, HttpStatus } = + ApiStatus.CodingMeetings.updateCodingMeeting.Forbidden + + return HttpResponse.json( + { code: Code, msg: "해당 모각코를 수정할 권한이 없는 유저입니다" }, + { status: HttpStatus }, + ) + } + + const updatePayload = await request.json() + + mockCodingMeetings[targetMockCodingMeetingIdx] = { + ...mockCodingMeetings[targetMockCodingMeetingIdx], ...updatePayload, } @@ -63,7 +83,7 @@ export const mockUpdateCodingMeetingApi = http.put< return HttpResponse.json( { code: Code, - msg: "질문 수정 성공", + msg: "모각코 수정 성공", }, { status: HttpStatus }, ) diff --git a/src/page/coding-meetings/create/CodingMeetingFormProvider.tsx b/src/page/coding-meetings/create/CodingMeetingFormProvider.tsx new file mode 100644 index 00000000..5e835edf --- /dev/null +++ b/src/page/coding-meetings/create/CodingMeetingFormProvider.tsx @@ -0,0 +1,26 @@ +"use client" + +import { CodingMeetingFormData } from "@/interfaces/form" +import { usePathname } from "next/navigation" +import { FormProvider, useForm } from "react-hook-form" + +function CodingMeetingFormProvider({ + children, +}: { + children: React.ReactNode +}) { + const pathname = usePathname() + + const methods = useForm() + + if ( + pathname === "/coding-meetings/create" || + pathname.startsWith("/coding-meetings/post/") + ) { + return {children} + } + + return children +} + +export default CodingMeetingFormProvider diff --git a/src/page/coding-meetings/create/CreateCodingMeetingPage.tsx b/src/page/coding-meetings/create/CreateCodingMeetingPage.tsx index ce30fe93..8f598d5e 100644 --- a/src/page/coding-meetings/create/CreateCodingMeetingPage.tsx +++ b/src/page/coding-meetings/create/CreateCodingMeetingPage.tsx @@ -1,410 +1,131 @@ "use client" -import { useClientSession } from "@/hooks/useClientSession" -import { CodingMeetingHashTagList } from "@/recoil/atoms/coding-meeting/hashtags" -import { useQueryClient } from "@tanstack/react-query" -import { useRouter } from "next/navigation" -import { FieldErrors, useForm } from "react-hook-form" +import { FieldErrors, useFormContext } from "react-hook-form" import { toast } from "react-toastify" -import { useRecoilState, useRecoilValue } from "recoil" -import CodingMeetingSection from "./components/CodingMeetingSection" -import { Input } from "@/components/shared/input/Input" import Spacing from "@/components/shared/Spacing" -import Textarea from "@/components/shared/textarea/Textarea" import Button from "@/components/shared/button/Button" -import HashTagsSection from "./components/HashTagsSection" -import LocationSection from "./components/LocationSection" -import HeadCountSection from "./components/HeadCountSection" -import DateTimeSection from "./components/DateTimeSection" -import { CodingMeetingQueries } from "@/react-query/coding-meeting" -import { CodingMeetingHeadCount } from "@/recoil/atoms/coding-meeting/headcount" -import { DirectionIcons } from "@/components/icons/Icons" -import { EndTime, StartTime } from "@/recoil/atoms/coding-meeting/dateTime" -import { LocationForSubmit } from "@/recoil/atoms/coding-meeting/mapData" -import Limitation from "@/constants/limitation" -import type { CodingMeetingDetailPayload } from "@/interfaces/dto/coding-meeting/get-coding-meeting-detail.dto" -import NotFound from "@/app/not-found" -import { revalidatePage } from "@/util/actions/revalidatePage" -import queryKey from "@/constants/queryKey" -import useHandleCreateCodingMeetingTime from "./hooks/useHandleCreateCodingMeetingTime" -import { AxiosError } from "axios" -import { APIResponse } from "@/interfaces/dto/api-response" -import TextCounter from "@/components/shared/TextCounter" -import { twJoin } from "tailwind-merge" -import notificationMessage from "@/constants/message/notification" -import { validationMessage } from "@/constants/message/validation" -import { errorMessage } from "@/constants/message/error" +import HashTagsSection from "./components/section/hash-tags/HashTagsSection" +import DateTimeSection from "./components/section/date-time/DateTimeSection" +import { CodingMeetingFormData, CodingMeetingPageMode } from "@/interfaces/form" +import TitleSection from "./components/section/title/TitleSection" +import LocationSection from "./components/section/location/LocationSection" +import MemberCountSection from "./components/section/member-count/MemberCountSection" +import ContentSection from "./components/section/content/ContentSection" +import LinkToListPage from "@/components/LinkToListPage" +import { pickFirstError } from "@/util/hook-form/error" +import { useCreateCodingMeeting } from "./hooks/useCreateCodingMeeting" +import { formDataToPayload, payloadToFormData } from "../util/parser" +import { useUpdateCodingMeeting } from "./hooks/useUpdateCodingMeeting" +import { CodingMeetingDetailPayload } from "@/interfaces/dto/coding-meeting/get-coding-meeting-detail.dto" interface CreateCodingMeetingPageProps { - editMode: "create" | "update" - initialValues?: CodingMeetingDetailPayload + editMode: CodingMeetingPageMode coding_meeting_token?: string + initialCodingMeeting?: CodingMeetingDetailPayload } -interface CodingMeetingFormData { - title: string - content: string -} - -const CreateCodingMeetingPage = ({ +function CreateCodingMeetingPage(props: { editMode: "create" }): JSX.Element +function CreateCodingMeetingPage(props: { + editMode: "update" + coding_meeting_token: string + initialCodingMeeting: CodingMeetingDetailPayload +}): JSX.Element +function CreateCodingMeetingPage({ editMode, - initialValues, coding_meeting_token, -}: CreateCodingMeetingPageProps) => { - const [hash_tags, setHash_tags] = useRecoilState(CodingMeetingHashTagList) - const [head_cnt, setHead_cnt] = useRecoilState(CodingMeetingHeadCount) - const startTime = useRecoilValue(StartTime) - const endTime = useRecoilValue(EndTime) - const [location, setLocation] = useRecoilState(LocationForSubmit) - const queryClient = useQueryClient() - const { replace } = useRouter() - const { user } = useClientSession() - const { - resetDateTimes, - formatTime, - formatByUTC, - formattedStartTime, - formattedEndTime, - } = useHandleCreateCodingMeetingTime() - - const { register, handleSubmit, watch, getValues } = - useForm( - initialValues - ? { - defaultValues: { - title: initialValues.coding_meeting_title, - content: initialValues.coding_meeting_content, - }, - } - : {}, - ) + initialCodingMeeting, +}: CreateCodingMeetingPageProps): JSX.Element { + const { handleSubmit } = useFormContext() - const { createCodingMeetingPost } = - CodingMeetingQueries.useCreateCodingMeeting() - const { updateCodingMeeting } = CodingMeetingQueries.useUpdateCodingMeeting() + const { createCodingMeetingApi, createCodingMeetingApiStatus } = + useCreateCodingMeeting() - const goToListPage = () => replace("/coding-meetings") + const { updateCodingMeetingApi, updateCodingMeetingApiStatus } = + useUpdateCodingMeeting() - const onSubmit = async (data: CodingMeetingFormData) => { - // 사용자 권한 인증 - if (!user) - return toast.error(notificationMessage.unauthorized, { - toastId: "unauthorizedToCreateCodingMeeting", - position: "top-center", - }) - // 장소 유효성 검사 - if (!location) - return toast.error(validationMessage.noLocation, { - toastId: "emptyLocation", - position: "top-center", - }) - // 인원수 유효성 검사 - if (head_cnt === "0") - return toast.error(validationMessage.noHeadCnt, { - toastId: "emptyHeadCnt", - position: "top-center", - }) - // 시간 유효성 검사 - // 시간 값이 없을 경우 - if (!startTime || !endTime) - return toast.error(validationMessage.noTime, { - toastId: "emptyCodingMeetingTime", - position: "top-center", - }) - // 종료 시간이 시작 시간보다 빠를 경우 - if (formattedEndTime.isBefore(formattedStartTime)) - return toast.error(validationMessage.timeError, { - toastId: "codingMeetingTimeError", - position: "top-center", - }) - // 시작 시간이 종료 시간과 같을 경우 - if (formattedEndTime.isSame(formattedStartTime, "minute")) - return toast.error(validationMessage.sameTime, { - toastId: "codingMeetingSameTimeError", - position: "top-center", - }) - - const payload = { - coding_meeting_title: data.title, - coding_meeting_content: data.content, - coding_meeting_hashtags: hash_tags, - coding_meeting_location_id: location?.coding_meeting_location_id, - coding_meeting_location_place_name: - location.coding_meeting_location_place_name, - coding_meeting_location_longitude: - location.coding_meeting_location_longitude, - coding_meeting_location_latitude: - location.coding_meeting_location_latitude, - coding_meeting_member_upper_limit: Number(head_cnt), - coding_meeting_start_time: formatByUTC(formatTime(startTime)), - coding_meeting_end_time: formatByUTC(formatTime(endTime)), - } + const initialFormData = initialCodingMeeting + ? payloadToFormData(initialCodingMeeting) + : null + const onSubmit = async (formData: CodingMeetingFormData) => { if (editMode === "create") { - createCodingMeetingPost(payload, { - onSuccess: (res) => { - queryClient.invalidateQueries({ - queryKey: [queryKey.codingMeeting], - }) - - replace(`/coding-meetings/${res.data.data?.coding_meeting_token}`) - - setHash_tags([]) - setHead_cnt("3") - resetDateTimes() - setLocation(undefined) - }, - onError: (error: Error | AxiosError) => { - if (error instanceof AxiosError) { - const { response } = error as AxiosError - - toast.error( - response?.data.msg ?? errorMessage.createCodingMeeting, - { - toastId: "failToCreateCodingMeeting", - position: "top-center", - }, - ) - return - } - - toast.error(errorMessage.createCodingMeeting, { - toastId: "failToCreateCodingMeeting", - position: "top-center", - }) - }, - }) - } - - if (!coding_meeting_token) { - return NotFound() + createCodingMeetingApi(formDataToPayload("create", formData)) + return } - if (editMode === "update") { - const editPayload = { - ...payload, - coding_meeting_token, - } - updateCodingMeeting(editPayload, { - onSuccess: async (res) => { - queryClient.resetQueries({ - queryKey: [queryKey.codingMeeting], - }) - - await revalidatePage("/coding-meetings/[token]", "page") - - setTimeout(() => { - replace(`/coding-meetings/${coding_meeting_token}`) - - setHash_tags([]) - setHead_cnt("3") - resetDateTimes() - setLocation(undefined) - }, 0) - }, - onError: (error: Error | AxiosError) => { - if (error instanceof AxiosError) { - const { response } = error as AxiosError - - toast.error( - response?.data.msg ?? errorMessage.updateCodingMeeting, - { - toastId: "failToUpdateCodingMeeting", - position: "top-center", - }, - ) - return - } - - toast.error(errorMessage.updateCodingMeeting, { - toastId: "failToUpdateCodingMeeting", - position: "top-center", - }) - }, - }) - } + // update + updateCodingMeetingApi( + formDataToPayload("update", formData, coding_meeting_token!), + ) } - const onInvalid = async (errors: FieldErrors) => { - if (errors?.title) { - const titleErrorMessage = ((type: typeof errors.title.type) => { - switch (type) { - case "required": - return validationMessage.notitle - case "minLength": - return validationMessage.underTitleLimit - case "maxLength": - return validationMessage.overTitleLimit - } - })(errors.title.type) - - toast.error(titleErrorMessage, { - position: "top-center", - toastId: "createCodingMeetingTitle", - }) - - window.scroll({ - top: 0, - behavior: "smooth", - }) - - return - } - if (errors?.content) { - const contentErrorMessage = ((type: typeof errors.content.type) => { - switch (type) { - case "required": - return validationMessage.noContent - case "minLength": - return validationMessage.underContentLimit - case "maxLength": - return validationMessage.overContentLimit - } - })(errors.content.type) + const onInvalid = async (errors: FieldErrors) => { + const error = pickFirstError(errors) - toast.error(contentErrorMessage, { - position: "top-center", - toastId: "createCodingMeetingContent", - }) - return - } + toast.error(error.message, { + position: "top-center", + toastId: "codingMeetingInvalidForm", + }) } - const TitleInputClass = twJoin([ - "text-base placeholder:text-base", - watch("title") && - (watch("title")?.length < Limitation.title_limit_under || - watch("title")?.length > Limitation.title_limit_over) && - "focus:border-danger border-danger", - ]) - return ( -
-
- -
목록 보기
+
+
+ +
-
모각코 모집하기
+

+ {editMode === "create" ? "모각코 모집하기" : "모각코 수정하기"} +

- - - 제목 - -
- -
- {watch("title") && - (watch("title")?.length < Limitation.title_limit_under || - watch("title")?.length > Limitation.title_limit_over) && ( - - {"제목은 5자 이상 100자 이하여야 합니다."} - - )} -
-
-
- - - - - - - - - - - - 소개글 - -
-