diff --git a/src/apis/group/index.ts b/src/apis/group/index.ts index a59095ef6..8b7a4c5bb 100644 --- a/src/apis/group/index.ts +++ b/src/apis/group/index.ts @@ -14,7 +14,7 @@ const groupAPI = { `/service-api/book-groups?pageSize=10&groupCursorId=` + pageParam ), - createGroup: ({ group }: { group: APICreateGroup }) => + createGroup: (group: APICreateGroup) => publicApi.post('/service-api/book-groups', group), getGroupDetailInfo: ({ diff --git a/src/app/group/create/page.tsx b/src/app/group/create/page.tsx index 1fd6d532d..9151feea9 100644 --- a/src/app/group/create/page.tsx +++ b/src/app/group/create/page.tsx @@ -1,19 +1,7 @@ -'use client'; +import CreateBookGroupFunnel from '@/v1/bookGroup/create/CreateBookGroupFunnel'; -import AddGroupForm from '@/ui/Group/AddGroupForm'; -import { VStack } from '@chakra-ui/react'; -import TopNavigation from '@/ui/common/TopNavigation'; -import AuthRequired from '@/ui/AuthRequired'; - -const GroupCreatePage = () => { - return ( - - - - - - - ); +const GroupCreateFunnelPage = () => { + return ; }; -export default GroupCreatePage; +export default GroupCreateFunnelPage; diff --git a/src/hooks/useFunnel.tsx b/src/hooks/useFunnel.tsx index 90f4ad99b..ce841111c 100644 --- a/src/hooks/useFunnel.tsx +++ b/src/hooks/useFunnel.tsx @@ -1,12 +1,12 @@ 'use client'; import { useEffect, useMemo, useRef } from 'react'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import type { FunnelProps, StepProps } from '@/v1/base/Funnel/Funnel'; import { assert } from '@/utils/assert'; -import { Funnel, Step } from '@/v1/base/Funnel/Funnel'; -import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import type { FunnelProps, StepProps } from '@/v1/base/Funnel'; +import { Funnel, Step } from '@/v1/base/Funnel'; export type NonEmptyArray = readonly [T, ...T[]]; @@ -34,7 +34,11 @@ export const useFunnel = >( initialStep?: Steps[number]; onStepChange?: (name: Steps[number]) => void; } -): readonly [FunnelComponent, (step: Steps[number]) => void] => { +): readonly [ + FunnelComponent, + (step: Steps[number]) => void, + Steps[number] +] => { const router = useRouter(); const searchParams = useSearchParams(); const pathname = usePathname(); @@ -78,11 +82,12 @@ export const useFunnel = >( const params = new URLSearchParams(searchParams.toString()); params.set('funnel-step', `${step}`); - return router.replace(`?${params.toString()}`); + return router.replace(`?${params.toString()}`, { shallow: true }); }; - return [FunnelComponent, setStep] as unknown as readonly [ + return [FunnelComponent, setStep, step] as unknown as readonly [ FunnelComponent, - (step: Steps[number]) => Promise + (step: Steps[number]) => Promise, + Steps[number] ]; }; diff --git a/src/queries/group/useCreateBookGroupMutation.ts b/src/queries/group/useCreateBookGroupMutation.ts new file mode 100644 index 000000000..041841d99 --- /dev/null +++ b/src/queries/group/useCreateBookGroupMutation.ts @@ -0,0 +1,13 @@ +import { useMutation } from '@tanstack/react-query'; + +import type { APICreateGroup } from '@/types/group'; +import groupAPI from '@/apis/group'; + +const useCreateBookGroupMutation = () => { + return useMutation({ + mutationFn: (formData: APICreateGroup) => + groupAPI.createGroup(formData).then(({ data }) => data), + }); +}; + +export default useCreateBookGroupMutation; diff --git a/src/stories/bookGroup/create/funnel/EnterTitleStep.stories.tsx b/src/stories/bookGroup/create/steps/EnterTitleStep.stories.tsx similarity index 77% rename from src/stories/bookGroup/create/funnel/EnterTitleStep.stories.tsx rename to src/stories/bookGroup/create/steps/EnterTitleStep.stories.tsx index 80ea0097e..4267f3a6f 100644 --- a/src/stories/bookGroup/create/funnel/EnterTitleStep.stories.tsx +++ b/src/stories/bookGroup/create/steps/EnterTitleStep.stories.tsx @@ -1,11 +1,10 @@ import { Meta, StoryObj } from '@storybook/react'; -import { appLayoutMeta } from '@/stories/meta'; import { FormProvider, useForm } from 'react-hook-form'; -import { - EnterTitleStep, - type EnterTitleStepValues, -} from '@/v1/bookGroup/create/steps/EnterTitleStep'; +import type { EnterTitleStepFormValues } from '@/v1/bookGroup/create/types'; + +import { appLayoutMeta } from '@/stories/meta'; +import { EnterTitleStep } from '@/v1/bookGroup/create/steps'; const meta: Meta = { title: 'bookGroup/create/steps/EnterTitleStep', @@ -18,7 +17,7 @@ export default meta; type Story = StoryObj; const EnterTitleForm = () => { - const methods = useForm({ + const methods = useForm({ mode: 'all', defaultValues: { title: '', diff --git a/src/stories/bookGroup/create/funnel/SelectBookStep.stories.tsx b/src/stories/bookGroup/create/steps/SelectBookStep.stories.tsx similarity index 74% rename from src/stories/bookGroup/create/funnel/SelectBookStep.stories.tsx rename to src/stories/bookGroup/create/steps/SelectBookStep.stories.tsx index 5a9bba427..ea79bb43f 100644 --- a/src/stories/bookGroup/create/funnel/SelectBookStep.stories.tsx +++ b/src/stories/bookGroup/create/steps/SelectBookStep.stories.tsx @@ -1,13 +1,13 @@ import { Meta, StoryObj } from '@storybook/react'; import { FormProvider, useForm } from 'react-hook-form'; +import type { SelectBookStepFormValues } from '@/v1/bookGroup/create/types'; + import { appLayoutMeta } from '@/stories/meta'; -import SelectBookStep, { - SelectBookFormValue, -} from '@/v1/bookGroup/create/funnel/SelectBookStep'; +import { SelectBookStep } from '@/v1/bookGroup/create/steps'; const meta: Meta = { - title: 'bookGroup/funnel/SelectBookStep', + title: 'bookGroup/create/steps/SelectBookStep', component: SelectBookStep, ...appLayoutMeta, }; @@ -17,7 +17,7 @@ export default meta; type Story = StoryObj; const RenderSelectBookStep = () => { - const methods = useForm(); + const methods = useForm(); const goNextStep = () => { const book = methods.getValues('book'); diff --git a/src/stories/bookGroup/create/steps/SelectJoinTypeStep.stories.tsx b/src/stories/bookGroup/create/steps/SelectJoinTypeStep.stories.tsx index 82d283bf9..c8a84d4ff 100644 --- a/src/stories/bookGroup/create/steps/SelectJoinTypeStep.stories.tsx +++ b/src/stories/bookGroup/create/steps/SelectJoinTypeStep.stories.tsx @@ -1,11 +1,10 @@ import { Meta, StoryObj } from '@storybook/react'; import { FormProvider, useForm } from 'react-hook-form'; -import { appLayoutMeta } from '@/stories/meta'; -import { - SelectJoinTypeStep, - SelectJoinTypeStepFormValues, -} from '@/v1/bookGroup/create/steps/SelectJoinTypeStep'; +import type { SelectJoinTypeStepFormValues } from '@/v1/bookGroup/create/types'; + +import { appLayoutMeta } from '@/stories/meta'; +import { SelectJoinTypeStep } from '@/v1/bookGroup/create/steps'; const meta: Meta = { title: 'bookGroup/create/steps/SelectJoinTypeStep', @@ -20,15 +19,15 @@ type Story = StoryObj; const RenderSelectJoinTypeStep = () => { const methods = useForm({ defaultValues: { - hasJoinPasswd: 'false', + hasJoinPassword: 'false', }, mode: 'all', }); const onSubmit = () => { - const { hasJoinPasswd, joinPasswd, joinQuestion } = methods.getValues(); + const { hasJoinPassword, joinPassword, joinQuestion } = methods.getValues(); alert( - `가입 문제 유무: ${hasJoinPasswd}\n가입 문제: ${joinQuestion}\n정답: ${joinPasswd}` + `가입 문제 유무: ${hasJoinPassword}\n가입 문제: ${joinQuestion}\n정답: ${joinPassword}` ); }; diff --git a/src/stories/bookGroup/create/steps/SetUpDetailStep.stories.tsx b/src/stories/bookGroup/create/steps/SetUpDetailStep.stories.tsx index 35ee2de6b..47fbf10e3 100644 --- a/src/stories/bookGroup/create/steps/SetUpDetailStep.stories.tsx +++ b/src/stories/bookGroup/create/steps/SetUpDetailStep.stories.tsx @@ -1,13 +1,12 @@ -import { appLayoutMeta } from '@/stories/meta'; import { Meta, StoryObj } from '@storybook/react'; import { FormProvider, useForm } from 'react-hook-form'; +import type { SetUpDetailStepFormValues } from '@/v1/bookGroup/create/types'; + import { getTodayDate } from '@/utils/date'; -import { - SetUpDetailStep, - type SetUpDetailStepValues, -} from '@/v1/bookGroup/create/steps/SetUpDetailStep'; +import { appLayoutMeta } from '@/stories/meta'; +import { SetUpDetailStep } from '@/v1/bookGroup/create/steps'; const meta: Meta = { title: 'bookGroup/create/steps/SetUpDetailStep', @@ -20,19 +19,14 @@ export default meta; type Story = StoryObj; const SetUpDetailForm = () => { - const methods = useForm({ + const methods = useForm({ mode: 'all', defaultValues: { title: '', book: { bookId: 23, }, - introduce: '', - maxMemberCount: '', - customMemberCount: '', startDate: getTodayDate(), - endDate: '', - isPublic: false, }, }); diff --git a/src/ui/Group/AddGroupForm.tsx b/src/ui/Group/AddGroupForm.tsx deleted file mode 100644 index 909b52c9c..000000000 --- a/src/ui/Group/AddGroupForm.tsx +++ /dev/null @@ -1,340 +0,0 @@ -import { - Box, - Center, - Flex, - Image, - useDisclosure, - useTheme, - VStack, - Input, - Text, - InputGroup, -} from '@chakra-ui/react'; -import { FormProvider, useForm } from 'react-hook-form'; -import FormInput from '@/ui/FormInput'; -import FormRadio from '@/ui/FormRadio'; -import BottomSheet from '@/ui/common/BottomSheet'; -import BookSearch from '@/ui/BookSearch'; -import { useEffect, useState } from 'react'; -import { APIBook } from '@/types/book'; -import GroupAPI from '@/apis/group'; -import { useRouter } from 'next/navigation'; -import { APICreateGroup } from '@/types/group'; -import { - MAX_MEMBER_COUNT_VALUE, - IS_PUBLICK_VALUE, - HAS_JOIN_PASSWORD_VALUE, -} from '../../constants/groupRadioValues'; -import Button from '@/ui/common/Button'; - -interface FormValues - extends Omit< - APICreateGroup, - 'maxMemberCount' | 'hasJoinPasswd' | 'isPublic' - > { - maxMemberCount: number | string | null; - hasJoinPasswd: 'true' | 'false' | boolean; - isPublic: 'true' | 'false' | boolean; -} - -const AddGroupForm = () => { - const router = useRouter(); - const [memberCountInput, setMemberCountInput] = useState(''); - const memberCountInputAsNumber = Number(memberCountInput); - - const [selectedBook, setSeletedBook] = useState(); - const { - isOpen: isBookSearchOpen, - onClose: onBookSearchClose, - onOpen: onBookSearchOpen, - } = useDisclosure(); - const { - isOpen: isMaxMemberSetOpen, - onClose: onMaxMemberSetClose, - onOpen: onMaxMemberSetOpen, - } = useDisclosure(); - - const date = new Date(); - const today = Date.now(); - const startDate = new Date(today - date.getTimezoneOffset() * 60000) - .toISOString() - .split('T')[0]; - - const methods = useForm({ - mode: 'all', - defaultValues: { - bookId: 0, - title: '', - introduce: '', - maxMemberCount: 'null', - startDate, - endDate: '', - hasJoinPasswd: 'false', - joinQuestion: '', - joinPasswd: '', - isPublic: 'true', - }, - }); - - const { isValid } = methods.formState; - const { maxMemberCount, hasJoinPasswd, isPublic } = methods.watch(); - - useEffect(() => { - if (hasJoinPasswd === 'false') { - methods.setValue('joinPasswd', ''); - methods.setValue('joinQuestion', ''); - methods.clearErrors('joinPasswd'); - methods.clearErrors('joinQuestion'); - } - if (maxMemberCount === '직접입력') { - setMemberCountInput(''); - onMaxMemberSetOpen(); - } - }, [methods, hasJoinPasswd, maxMemberCount, onMaxMemberSetOpen]); - - const onSubmit = async (group: FormValues) => { - let maxMemberCount = group.maxMemberCount; - - if (maxMemberCount === 'null') maxMemberCount = null; - else if (maxMemberCount === '직접입력') - maxMemberCount = memberCountInputAsNumber; - else maxMemberCount = Number(maxMemberCount); - - const request = { - ...group, - maxMemberCount, - isPublic: group.isPublic === 'true' ? true : false, - hasJoinPasswd: group.hasJoinPasswd === 'true' ? true : false, - }; - - try { - await GroupAPI.createGroup({ group: request }); - router.replace('/group'); - } catch (error) { - console.error(error); - } - }; - - const onMaxMemberInputComplete = () => { - const { isValid } = validateMaxMemberCount(memberCountInputAsNumber); - if (isValid) { - onMaxMemberSetClose(); - } - }; - - const validateMaxMemberCount = (value: number) => { - if (value > 1000) - return { isValid: false, message: '1000명 이하의 인원을 입력해 주세요' }; - - if (value < 1) - return { isValid: false, message: '1명 이상의 인원을 입력해 주세요' }; - - return { isValid: true, message: null }; - }; - - const getMaxMemberCountViewer = () => { - if (maxMemberCount === 'null') { - return '제한없음'; - } - - if (maxMemberCount === '직접입력') { - const { isValid } = validateMaxMemberCount(memberCountInputAsNumber); - return isValid ? `${memberCountInput}명` : ''; - } - - return `${maxMemberCount}명`; - }; - - const validationMessage = memberCountInput - ? validateMaxMemberCount(memberCountInputAsNumber).message - : ''; - - return ( - <> - - - - - - - - - - - - - - - - - - - - - { - setSeletedBook(book); - methods.setValue('bookId', book.bookId); - onBookSearchClose(); - }} - /> - { - methods.setValue('maxMemberCount', 'null'); - }} - onComplete={onMaxMemberInputComplete} - inputValue={memberCountInput} - onInputChange={e => setMemberCountInput(e.target.value)} - validationMessage={validationMessage} - /> - - ); -}; - -const BookSelectBox = ({ - selectedBook, - onClick, - isShowError, -}: { - selectedBook?: APIBook; - isShowError?: boolean; - onClick: () => void; -}) => { - const theme = useTheme(); - return ( - - {selectedBook && selectedBook.imageUrl ? ( - book-cover - ) : ( -
- 책을 -
- 선택해주세요. -
- )} -
- ); -}; - -const BookSearchBottomSheet = ({ - isOpen, - onClose, - onBookClick, -}: { - isOpen: boolean; - onClose: () => void; - onBookClick: (book: APIBook) => void; -}) => { - return ( - - - - - - ); -}; - -const MaxMemberCountBottomSheet = ({ - inputValue, - onInputChange, - isOpen, - onClose, - onCancel, - onComplete, - validationMessage, -}: { - inputValue: string; - onInputChange: React.ChangeEventHandler; - isOpen: boolean; - onClose: () => void; - onCancel: () => void; - onComplete: () => void; - validationMessage: string | null; -}) => { - return ( - - - - 확인 - - 참여 인원 - - - - - {validationMessage} - - - - ); -}; - -export default AddGroupForm; diff --git a/src/v1/base/Funnel/Funnel.tsx b/src/v1/base/Funnel.tsx similarity index 90% rename from src/v1/base/Funnel/Funnel.tsx rename to src/v1/base/Funnel.tsx index 2145c701a..10173bf39 100644 --- a/src/v1/base/Funnel/Funnel.tsx +++ b/src/v1/base/Funnel.tsx @@ -4,6 +4,12 @@ import type { NonEmptyArray } from '@/hooks/useFunnel'; import { assert } from '@/utils/assert'; +export interface MoveFunnelStepProps { + onPrevStep?: () => void; + onNextStep?: () => void; + onSubmit?: () => void; +} + export interface FunnelProps> { steps: Steps; step: Steps[number]; diff --git a/src/v1/bookGroup/create/CreateBookGroupFunnel.tsx b/src/v1/bookGroup/create/CreateBookGroupFunnel.tsx new file mode 100644 index 000000000..e89dbd015 --- /dev/null +++ b/src/v1/bookGroup/create/CreateBookGroupFunnel.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { FormProvider, SubmitHandler, useForm } from 'react-hook-form'; + +import type { CreateBookGroupFormValues } from './types'; +import useCreateBookGroupMutation from '@/queries/group/useCreateBookGroupMutation'; + +import { useFunnel } from '@/hooks/useFunnel'; +import useToast from '@/v1/base/Toast/useToast'; +import { getTodayDate } from '@/utils/date'; +import { isAxiosErrorWithCustomCode } from '@/utils/helpers'; +import { SERVICE_ERROR_MESSAGE } from '@/constants'; + +import { IconArrowLeft } from '@public/icons'; +import TopNavigation from '@/v1/base/TopNavigation'; +import { + EnterTitleStep, + SelectBookStep, + SelectJoinTypeStep, + SetUpDetailStep, +} from './steps'; + +const FUNNEL_STEPS = [ + 'SelectBook', + 'EnterTitle', + 'SetUpDetail', + 'SelectJoinType', +] as const; + +const CreateBookGroupFunnel = () => { + const router = useRouter(); + const [Funnel, setStep, currentStep] = useFunnel(FUNNEL_STEPS, { + initialStep: 'SelectBook', + }); + const { show: showToast } = useToast(); + const { mutate } = useCreateBookGroupMutation(); + + const methods = useForm({ + mode: 'all', + defaultValues: { + title: '', + maxMemberCount: 9999, + startDate: getTodayDate(), + isPublic: false, + hasJoinPassword: 'false', + }, + }); + + const handleBackButtonClick = () => { + const currentStepIndex = FUNNEL_STEPS.indexOf(currentStep); + + if (currentStepIndex === 0 || currentStepIndex === -1) { + router.back(); + } else { + setStep(FUNNEL_STEPS[currentStepIndex - 1]); + } + + return; + }; + + const handleCreateGroupSubmit: SubmitHandler< + CreateBookGroupFormValues + > = formValues => { + const requestBody = { + bookId: formValues.book.bookId, + title: formValues.title, + introduce: formValues.introduce, + maxMemberCount: + formValues.maxMemberCount !== 'custom' + ? formValues.maxMemberCount + : formValues.customMemberCount, + startDate: formValues.startDate, + endDate: formValues.endDate, + isPublic: formValues.isPublic, + hasJoinPasswd: formValues.hasJoinPassword === 'true' ? true : false, + joinQuestion: formValues.joinQuestion, + joinPasswd: formValues.joinPassword, + }; + + mutate(requestBody, { + onSuccess: data => { + router.replace(`/group/${data.bookGroupId}`); + showToast({ type: 'success', message: '독서모임을 생성했어요! 🎉' }); + + return; + }, + onError: error => { + if (isAxiosErrorWithCustomCode(error)) { + const { code } = error.response.data; + const message = SERVICE_ERROR_MESSAGE[code]; + + showToast({ type: 'error', message }); + + return; + } + + showToast({ + type: 'error', + message: '독서 모임을 생성하지 못했어요 🥲', + }); + }, + }); + }; + + return ( + + + + + + + +
+ + + setStep('EnterTitle')} /> + + + setStep('SetUpDetail')} /> + + + setStep('SelectBook')} + onNextStep={() => setStep('SelectJoinType')} + /> + + + + + +
+
+ ); +}; + +export default CreateBookGroupFunnel; diff --git a/src/v1/bookGroup/create/funnel/index.ts b/src/v1/bookGroup/create/funnel/index.ts deleted file mode 100644 index 217d1205b..000000000 --- a/src/v1/bookGroup/create/funnel/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// export { default as SelectBookStep } from './SelectBookStep'; -export { default as SetUpDetailStep } from '../steps/SetUpDetailStep/SetUpDetailStep'; diff --git a/src/v1/bookGroup/create/steps/EnterTitleStep/EnterTitleStep.tsx b/src/v1/bookGroup/create/steps/EnterTitleStep/EnterTitleStep.tsx index c9f815315..b73fa8e8c 100644 --- a/src/v1/bookGroup/create/steps/EnterTitleStep/EnterTitleStep.tsx +++ b/src/v1/bookGroup/create/steps/EnterTitleStep/EnterTitleStep.tsx @@ -1,18 +1,13 @@ import { useFormContext } from 'react-hook-form'; +import type { MoveFunnelStepProps } from '@/v1/base/Funnel'; +import type { EnterTitleStepFormValues } from '../../types'; + import BottomActionButton from '@/v1/base/BottomActionButton'; import { TitleField } from './fields'; -interface MoveFunnelStepProps { - onNextStep?: () => void; -} - -export interface EnterTitleStepValues { - title: string; -} - const EnterTitleStep = ({ onNextStep }: MoveFunnelStepProps) => { - const { handleSubmit } = useFormContext(); + const { handleSubmit } = useFormContext(); return (
diff --git a/src/v1/bookGroup/create/steps/EnterTitleStep/fields/TitleField.tsx b/src/v1/bookGroup/create/steps/EnterTitleStep/fields/TitleField.tsx index 7f74cdf48..9bdada48a 100644 --- a/src/v1/bookGroup/create/steps/EnterTitleStep/fields/TitleField.tsx +++ b/src/v1/bookGroup/create/steps/EnterTitleStep/fields/TitleField.tsx @@ -1,13 +1,13 @@ import { useFormContext, useWatch } from 'react-hook-form'; -import type { EnterTitleStepValues } from '../EnterTitleStep'; +import type { EnterTitleStepFormValues } from '../../../types'; import ErrorMessage from '@/v1/base/ErrorMessage'; import Input from '@/v1/base/Input'; import InputLength from '@/v1/base/InputLength'; type DefaultFieldNameProps = { - name: keyof EnterTitleStepValues; + name: keyof EnterTitleStepFormValues; }; const TitleField = ({ name }: DefaultFieldNameProps) => { @@ -15,7 +15,7 @@ const TitleField = ({ name }: DefaultFieldNameProps) => { register, control, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const titleValue = useWatch({ control, name: name }); const titleErrors = errors[name]; diff --git a/src/v1/bookGroup/create/steps/EnterTitleStep/index.ts b/src/v1/bookGroup/create/steps/EnterTitleStep/index.ts deleted file mode 100644 index 90646c32e..000000000 --- a/src/v1/bookGroup/create/steps/EnterTitleStep/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { EnterTitleStepValues } from './EnterTitleStep'; -export { default as EnterTitleStep } from './EnterTitleStep'; diff --git a/src/v1/bookGroup/create/funnel/SelectBookStep.tsx b/src/v1/bookGroup/create/steps/SelectBookStep/SelectBookStep.tsx similarity index 91% rename from src/v1/bookGroup/create/funnel/SelectBookStep.tsx rename to src/v1/bookGroup/create/steps/SelectBookStep/SelectBookStep.tsx index f7492292c..9fd306c8f 100644 --- a/src/v1/bookGroup/create/funnel/SelectBookStep.tsx +++ b/src/v1/bookGroup/create/steps/SelectBookStep/SelectBookStep.tsx @@ -2,7 +2,8 @@ import { ComponentPropsWithoutRef, Suspense, useEffect, useState } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { useInView } from 'react-intersection-observer'; -import { SearchedBookWithId } from '@/types/book'; +import type { MoveFunnelStepProps } from '@/v1/base/Funnel'; +import type { SelectBookStepFormValues } from '../../types'; import useBookSearchQuery from '@/queries/book/useBookSearchQuery'; import debounce from '@/utils/debounce'; @@ -11,20 +12,9 @@ import Input from '@/v1/base/Input'; import Loading from '@/v1/base/Loading'; import BookSearchList from '@/v1/bookSearch/BookSearchList'; -// TODO: Funnel 파일 내부로 이동 -interface MoveFunnelStepProps { - onPrevStep?: () => void; - onNextStep?: () => void; -} - -export type SelectBookFormValue = { - book: SearchedBookWithId; - queryKeyword: string; -}; - const SelectBookStep = ({ onNextStep }: MoveFunnelStepProps) => { const { control, getValues, setValue } = - useFormContext(); + useFormContext(); const keywordValue = getValues('queryKeyword'); const [keyword, setKeyword] = useState(keywordValue || ''); diff --git a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/SelectJoinTypeStep.tsx b/src/v1/bookGroup/create/steps/SelectJoinTypeStep/SelectJoinTypeStep.tsx index 94037a13c..689e28858 100644 --- a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/SelectJoinTypeStep.tsx +++ b/src/v1/bookGroup/create/steps/SelectJoinTypeStep/SelectJoinTypeStep.tsx @@ -1,26 +1,16 @@ import { useFormContext } from 'react-hook-form'; -import BottomActionButton from '@/v1/base/BottomActionButton'; +import type { MoveFunnelStepProps } from '@/v1/base/Funnel'; +import type { SelectJoinTypeStepFormValues } from '../../types'; +import BottomActionButton from '@/v1/base/BottomActionButton'; import { JoinPasswordFieldset, JoinTypeFieldset } from './fields'; -interface MoveFunnelStepProps { - onPrevStep?: () => void; - onNextStep?: () => void; - onSubmit?: () => void; -} - -export type JoinTypeStepFormValues = { - hasJoinPasswd: 'true' | 'false'; - joinQuestion?: string; - joinPasswd?: string; -}; - -export type JoinTypeStepFieldName = keyof JoinTypeStepFormValues; +export type JoinTypeStepFieldName = keyof SelectJoinTypeStepFormValues; export type JoinTypeStepFieldProp = { name: JoinTypeStepFieldName }; const SelectJoinTypeStep = ({ onSubmit }: MoveFunnelStepProps) => { - const { handleSubmit } = useFormContext(); + const { handleSubmit } = useFormContext(); return (
@@ -28,12 +18,12 @@ const SelectJoinTypeStep = ({ onSubmit }: MoveFunnelStepProps) => {
- + - + - +
diff --git a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinPasswordFieldset.tsx b/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinPasswordFieldset.tsx index ee52151b7..3cefe1a36 100644 --- a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinPasswordFieldset.tsx +++ b/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinPasswordFieldset.tsx @@ -1,16 +1,16 @@ import { PropsWithChildren } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; -import ErrorMessage from '@/v1/base/ErrorMessage'; -import Input from '@/v1/base/Input'; -import InputLength from '@/v1/base/InputLength'; - -import { +import type { SelectJoinTypeStepFormValues } from '../../../types'; +import type { JoinTypeStepFieldName, - JoinTypeStepFormValues, JoinTypeStepFieldProp, } from '../SelectJoinTypeStep'; +import ErrorMessage from '@/v1/base/ErrorMessage'; +import Input from '@/v1/base/Input'; +import InputLength from '@/v1/base/InputLength'; + type JoinPasswordFieldsetProps = { joinTypeFieldName: JoinTypeStepFieldName; }; @@ -19,7 +19,7 @@ const JoinPasswordFieldset = ({ joinTypeFieldName, children, }: PropsWithChildren) => { - const { control } = useFormContext(); + const { control } = useFormContext(); const hasJoinPassword = useWatch({ control, name: joinTypeFieldName }); const shouldRender = hasJoinPassword === 'true'; @@ -38,7 +38,7 @@ const JoinQuestionField = ({ name }: JoinTypeStepFieldProp) => { register, control, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const joinQuestion = useWatch({ control, name }); @@ -76,7 +76,7 @@ const JoinAnswerField = ({ name }: JoinTypeStepFieldProp) => { register, control, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const joinPasswd = useWatch({ control, name }); diff --git a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinTypeFieldset.tsx b/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinTypeFieldset.tsx index 5489567d5..2270e6927 100644 --- a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinTypeFieldset.tsx +++ b/src/v1/bookGroup/create/steps/SelectJoinTypeStep/fields/JoinTypeFieldset.tsx @@ -1,9 +1,7 @@ import { useFormContext } from 'react-hook-form'; -import { - JoinTypeStepFormValues, - JoinTypeStepFieldProp, -} from '../SelectJoinTypeStep'; +import type { SelectJoinTypeStepFormValues } from '../../../types'; +import type { JoinTypeStepFieldProp } from '../SelectJoinTypeStep'; import JoinTypeRadioCard from './JoinTypeRadioCard'; @@ -12,7 +10,7 @@ const JoinTypeFieldset = ({ children }: { children?: React.ReactNode }) => { }; const RadioCardField = ({ name }: JoinTypeStepFieldProp) => { - const { register } = useFormContext(); + const { register } = useFormContext(); return ( <> diff --git a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/index.ts b/src/v1/bookGroup/create/steps/SelectJoinTypeStep/index.ts deleted file mode 100644 index d722f97c3..000000000 --- a/src/v1/bookGroup/create/steps/SelectJoinTypeStep/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { JoinTypeStepFormValues as SelectJoinTypeStepFormValues } from './SelectJoinTypeStep'; -export { default as SelectJoinTypeStep } from './SelectJoinTypeStep'; diff --git a/src/v1/bookGroup/create/steps/SetUpDetailStep/SetUpDetailStep.tsx b/src/v1/bookGroup/create/steps/SetUpDetailStep/SetUpDetailStep.tsx index cd4589671..55182f6e4 100644 --- a/src/v1/bookGroup/create/steps/SetUpDetailStep/SetUpDetailStep.tsx +++ b/src/v1/bookGroup/create/steps/SetUpDetailStep/SetUpDetailStep.tsx @@ -1,7 +1,7 @@ import { useFormContext, useWatch } from 'react-hook-form'; -import type { SearchedBookWithId } from '@/types/book'; -import type { APICreateGroup } from '@/types/group'; +import type { MoveFunnelStepProps } from '@/v1/base/Funnel'; +import type { SetUpDetailStepFormValues } from '../../types'; import { MAX_MEMBER_COUNT_OPTIONS } from '@/constants'; import { getTodayDate } from '@/utils/date'; @@ -16,12 +16,6 @@ import Switch from '@/v1/base/Switch'; import TextArea from '@/v1/base/TextArea'; import BookInfoCard from '@/v1/bookGroup/BookInfoCard'; -interface MoveFunnelStepProps { - onPrevStep?: () => void; - onNextStep?: () => void; - onSubmit?: () => void; -} - interface SetUpDetailStepProps extends MoveFunnelStepProps { goToSelectBookStep?: () => void; } @@ -31,21 +25,12 @@ interface SetUpDetailStepProps extends MoveFunnelStepProps { * Field 컴포넌트 분리 */ -export interface SetUpDetailStepValues - extends Pick< - APICreateGroup, - 'bookId' | 'title' | 'introduce' | 'startDate' | 'endDate' | 'isPublic' - > { - book: SearchedBookWithId; - maxMemberCount: string; - customMemberCount: string; -} - const SetUpDetailStep = ({ goToSelectBookStep, onNextStep, }: SetUpDetailStepProps) => { - const { handleSubmit, getValues } = useFormContext(); + const { handleSubmit, getValues } = + useFormContext(); return (
@@ -79,7 +64,7 @@ const SetUpDetailStep = ({ export default SetUpDetailStep; type SetUpDetailFieldProps = { - name: keyof SetUpDetailStepValues; + name: keyof SetUpDetailStepFormValues; }; const TitleField = ({ name }: SetUpDetailFieldProps) => { @@ -87,7 +72,7 @@ const TitleField = ({ name }: SetUpDetailFieldProps) => { register, control, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const titleValue = useWatch({ control, name: name }); const titleValueLength = @@ -125,7 +110,7 @@ const SelectedBookInfoField = ({ bookId?: number; onRemoveButtonClick?: () => void; }) => { - const { reset } = useFormContext(); + const { reset } = useFormContext(); const handleBookRemove = () => { onRemoveButtonClick?.(); @@ -149,7 +134,7 @@ const IntroduceField = ({ name }: SetUpDetailFieldProps) => { const { register, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const introduceErrors = errors[name]; @@ -176,7 +161,7 @@ const MaxMemberCountField = ({ name }: SetUpDetailFieldProps) => { const { register, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const maxMemberCountErrors = errors[name]; @@ -205,7 +190,7 @@ const CustomMemberCountField = ({ name }: SetUpDetailFieldProps) => { register, control, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const maxMemberCount = useWatch({ control, name: 'maxMemberCount' }); const isCustomInputCount = maxMemberCount === 'custom'; @@ -241,7 +226,7 @@ const PickStartDateField = ({ name }: SetUpDetailFieldProps) => { register, control, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const startDateErrors = errors[name]; const endDate = useWatch({ control, name: 'endDate' }); @@ -275,7 +260,7 @@ const PickEndDateField = ({ name }: SetUpDetailFieldProps) => { register, control, formState: { errors }, - } = useFormContext(); + } = useFormContext(); const startDate = useWatch({ control, name: 'startDate' }); const todayDate = getTodayDate(); @@ -302,14 +287,18 @@ const PickEndDateField = ({ name }: SetUpDetailFieldProps) => { }; const SwitchIsPublicField = ({ name }: SetUpDetailFieldProps) => { - const { register } = useFormContext(); + const { register, control } = useFormContext(); + + const isCommentPublic = useWatch({ control, name }); return (

댓글 공개 여부

- 모임에 가입하지 않은 사람도 댓글을 볼 수 있어요. + {isCommentPublic + ? '모임에 가입하지 않아도 댓글을 볼 수 있어요' + : '모임에 가입해야 댓글을 볼 수 있어요'}

diff --git a/src/v1/bookGroup/create/steps/SetUpDetailStep/index.ts b/src/v1/bookGroup/create/steps/SetUpDetailStep/index.ts deleted file mode 100644 index 381280758..000000000 --- a/src/v1/bookGroup/create/steps/SetUpDetailStep/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { SetUpDetailStepValues } from './SetUpDetailStep'; -export { default as SetUpDetailStep } from './SetUpDetailStep'; diff --git a/src/v1/bookGroup/create/steps/index.ts b/src/v1/bookGroup/create/steps/index.ts new file mode 100644 index 000000000..2b06facc9 --- /dev/null +++ b/src/v1/bookGroup/create/steps/index.ts @@ -0,0 +1,4 @@ +export { default as EnterTitleStep } from './EnterTitleStep/EnterTitleStep'; +export { default as SelectBookStep } from './SelectBookStep/SelectBookStep'; +export { default as SelectJoinTypeStep } from './SelectJoinTypeStep/SelectJoinTypeStep'; +export { default as SetUpDetailStep } from './SetUpDetailStep/SetUpDetailStep'; diff --git a/src/v1/bookGroup/create/types.ts b/src/v1/bookGroup/create/types.ts new file mode 100644 index 000000000..a42f16fd8 --- /dev/null +++ b/src/v1/bookGroup/create/types.ts @@ -0,0 +1,40 @@ +import { SearchedBookWithId } from '@/types/book'; + +export type CreateBookGroupFormValues = { + book: SearchedBookWithId; + queryKeyword: string; + title: string; + introduce: string; + maxMemberCount: 9999 | 50 | 100 | 200 | 500 | 'custom'; + customMemberCount: number; + startDate: string; + endDate: string; + isPublic: boolean; + hasJoinPassword: 'true' | 'false'; + joinQuestion: string; + joinPassword: string; +}; + +export type SelectBookStepFormValues = Pick< + CreateBookGroupFormValues, + 'book' | 'queryKeyword' +>; + +export type EnterTitleStepFormValues = Pick; + +export type SetUpDetailStepFormValues = Pick< + CreateBookGroupFormValues, + | 'book' + | 'title' + | 'introduce' + | 'maxMemberCount' + | 'customMemberCount' + | 'startDate' + | 'endDate' + | 'isPublic' +>; + +export type SelectJoinTypeStepFormValues = Pick< + CreateBookGroupFormValues, + 'hasJoinPassword' | 'joinQuestion' | 'joinPassword' +>;