From 301ea2d29d34f420a781153c7d357a04a8328e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=90=E1=85=A2=E1=84=92=E1=85=AE?= =?UTF-8?q?=E1=86=AB?= Date: Mon, 23 Sep 2024 13:00:17 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20BillStep=20hook=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useAddBillFunnel.ts | 32 +++++ client/src/hooks/useMembersStep.ts | 124 ++++++++++++++++++ client/src/hooks/usePriceStep.ts | 25 ++++ client/src/hooks/useTitleStep.ts | 61 +++++++++ client/src/pages/BillPage/AddBillFunnel.tsx | 17 +-- .../src/pages/BillPage/steps/MembersStep.tsx | 117 +++-------------- client/src/pages/BillPage/steps/PriceStep.tsx | 15 +-- client/src/pages/BillPage/steps/TitleStep.tsx | 58 +++----- 8 files changed, 281 insertions(+), 168 deletions(-) create mode 100644 client/src/hooks/useAddBillFunnel.ts create mode 100644 client/src/hooks/useMembersStep.ts create mode 100644 client/src/hooks/usePriceStep.ts create mode 100644 client/src/hooks/useTitleStep.ts diff --git a/client/src/hooks/useAddBillFunnel.ts b/client/src/hooks/useAddBillFunnel.ts new file mode 100644 index 000000000..299ca1650 --- /dev/null +++ b/client/src/hooks/useAddBillFunnel.ts @@ -0,0 +1,32 @@ +import {BillInfo} from '@pages/BillPage/AddBillFunnel'; +import {useEffect, useState} from 'react'; + +import useRequestGetCurrentMembers from './queries/member/useRequestGetCurrentMembers'; + +export type BillStep = 'title' | 'price' | 'members'; + +const useAddBillFunnel = () => { + const {currentMembers} = useRequestGetCurrentMembers(); + const [step, setStep] = useState('price'); + const [billInfo, setBillInfo] = useState({ + price: '', + title: '', + members: [], + }); + + useEffect(() => { + document.body.style.overflow = 'hidden'; + + return () => { + document.body.style.overflow = 'auto'; + }; + }, []); + + useEffect(() => { + currentMembers && setBillInfo(prev => ({...prev, members: currentMembers})); + }, [currentMembers]); + + return {step, setStep, billInfo, setBillInfo, currentMembers}; +}; + +export default useAddBillFunnel; diff --git a/client/src/hooks/useMembersStep.ts b/client/src/hooks/useMembersStep.ts new file mode 100644 index 000000000..ffc2b90b4 --- /dev/null +++ b/client/src/hooks/useMembersStep.ts @@ -0,0 +1,124 @@ +import {BillInfo} from '@pages/BillPage/AddBillFunnel'; +import {useEffect, useState} from 'react'; +import {Member} from 'types/serviceType'; +import useRequestPostMembers from './queries/member/useRequestPostMembers'; +import useRequestPostBill from './queries/bill/useRequestPostBill'; +import {useNavigate} from 'react-router-dom'; +import getEventIdByUrl from '@utils/getEventIdByUrl'; +import REGEXP from '@constants/regExp'; +import {BillStep} from './useAddBillFunnel'; + +interface Props { + billInfo: BillInfo; + setBillInfo: React.Dispatch>; + setStep: React.Dispatch>; + currentMembers: Member[]; +} + +const useMembersStep = ({billInfo, setBillInfo, currentMembers, setStep}: Props) => { + const [errorMessage, setErrorMessage] = useState(''); + const [nameInput, setNameInput] = useState(''); + + const { + postMembers, + responseMemberIds, + isSuccess: isSuccessPostMembers, + isPending: isPendingPostMembers, + } = useRequestPostMembers(); + + const {postBill, isSuccess: isSuccessPostBill, isPending: isPendingPostBill} = useRequestPostBill(); + const navigate = useNavigate(); + const eventId = getEventIdByUrl(); + + const onNameInputChange = (value: string) => { + if (REGEXP.memberName.test(value)) { + setNameInput(value); + } + }; + + const handleNameInputChange = (event: React.ChangeEvent) => { + if (event.target.value.length > 4) { + setErrorMessage('이름은 4자까지 입력 가능해요'); + onNameInputChange(nameInput.slice(0, 4)); + } else { + setErrorMessage(''); + onNameInputChange(event.target.value); + } + }; + + const canAddMembers = nameInput && !errorMessage; + + const canSubmitMembers = billInfo.members.length !== 0; + + const setBillInfoMemberWithId = (name: string) => { + const existingMember = currentMembers.find(currentMember => currentMember.name === name); + if (existingMember) { + setBillInfo(prev => ({...prev, members: [...prev.members, {id: existingMember.id, name: name}]})); + } else { + setBillInfo(prev => ({...prev, members: [...prev.members, {id: -1, name: name}]})); + } + }; + + const handleNameInputEnter = (event: React.KeyboardEvent) => { + if (event.nativeEvent.isComposing) { + return; + } + if (event.key === 'Enter' && canAddMembers) { + event.preventDefault(); + if (!billInfo.members.map(({name}) => name).includes(nameInput)) { + setBillInfoMemberWithId(nameInput); + } + setNameInput(''); + } + }; + + const handlePostBill = async () => { + if (billInfo.members.map(({id}) => id).includes(-1)) { + postMembers({ + members: billInfo.members.filter(member => member.id === -1), + }); + } else { + postBill({ + title: billInfo.title, + price: Number(billInfo.price.replace(',', '')), + members: billInfo.members.map(({id}) => id), + }); + } + }; + + useEffect(() => { + if (isSuccessPostMembers && responseMemberIds) { + postBill({ + title: billInfo.title, + price: Number(billInfo.price.replace(',', '')), + members: billInfo.members.map(member => + member.id === -1 ? responseMemberIds.members.find(m => m.name === member.name)?.id || member.id : member.id, + ), + }); + } + }, [isSuccessPostMembers, responseMemberIds]); + + useEffect(() => { + if (isSuccessPostBill) { + navigate(`/event/${eventId}/admin`); + } + }, [isSuccessPostBill]); + + const handlePrevStep = () => { + setStep('title'); + }; + + return { + errorMessage, + nameInput, + handleNameInputChange, + handleNameInputEnter, + isPendingPostBill, + isPendingPostMembers, + canSubmitMembers, + handlePostBill, + handlePrevStep, + }; +}; + +export default useMembersStep; diff --git a/client/src/hooks/usePriceStep.ts b/client/src/hooks/usePriceStep.ts new file mode 100644 index 000000000..f9fca6a68 --- /dev/null +++ b/client/src/hooks/usePriceStep.ts @@ -0,0 +1,25 @@ +import {useCallback} from 'react'; +import {BillInfo} from '@pages/BillPage/AddBillFunnel'; +import {BillStep} from './useAddBillFunnel'; + +interface Props { + setStep: React.Dispatch>; + setBillInfo: React.Dispatch>; +} + +const usePriceStep = ({setStep, setBillInfo}: Props) => { + const handleNumberKeyboardChange = useCallback( + (value: string) => { + setBillInfo(prev => ({...prev, price: value})); + }, + [setBillInfo], + ); + + const handleNextStep = () => { + setStep('title'); + }; + + return {handleNumberKeyboardChange, handleNextStep}; +}; + +export default usePriceStep; diff --git a/client/src/hooks/useTitleStep.ts b/client/src/hooks/useTitleStep.ts new file mode 100644 index 000000000..8e041ed98 --- /dev/null +++ b/client/src/hooks/useTitleStep.ts @@ -0,0 +1,61 @@ +import REGEXP from '@constants/regExp'; +import {BillInfo} from '@pages/BillPage/AddBillFunnel'; +import {useState} from 'react'; +import {BillStep} from './useAddBillFunnel'; + +interface Props { + billInfo: BillInfo; + setBillInfo: React.Dispatch>; + setStep: React.Dispatch>; +} + +const useTitleStep = ({billInfo, setBillInfo, setStep}: Props) => { + const [errorMessage, setErrorMessage] = useState(''); + + const onTitleInputChange = (value: string) => { + if (REGEXP.billTitle.test(value)) { + setBillInfo(prev => ({...prev, title: value})); + } + }; + + const handleTitleInputChange = (event: React.ChangeEvent) => { + if (event.target.value.length > 12) { + setErrorMessage('지출내역은 12자까지 입력 가능해요'); + onTitleInputChange(billInfo.title.slice(0, 12)); + } else { + setErrorMessage(''); + onTitleInputChange(event.target.value); + } + }; + + const canSubmitTitleInput = billInfo.title && !errorMessage; + + const handleTitleInputEnter = (event: React.KeyboardEvent) => { + if (event.nativeEvent.isComposing) { + return; + } + if (event.key === 'Enter' && canSubmitTitleInput) { + event.preventDefault(); + setStep('members'); + } + }; + + const handleNextStep = () => { + setStep('members'); + }; + + const handlePrevStep = () => { + setStep('price'); + }; + + return { + errorMessage, + handleTitleInputChange, + handleTitleInputEnter, + canSubmitTitleInput, + handleNextStep, + handlePrevStep, + }; +}; + +export default useTitleStep; diff --git a/client/src/pages/BillPage/AddBillFunnel.tsx b/client/src/pages/BillPage/AddBillFunnel.tsx index 379f72629..1fbec0762 100644 --- a/client/src/pages/BillPage/AddBillFunnel.tsx +++ b/client/src/pages/BillPage/AddBillFunnel.tsx @@ -8,8 +8,7 @@ import {Back, MainLayout, TopNav} from '@components/Design'; import PriceStep from './steps/PriceStep'; import {TitleStep} from './steps/TitleStep'; import MembersStep from './steps/MembersStep'; - -export type BillStep = 'title' | 'price' | 'members'; +import useAddBillFunnel from '@hooks/useAddBillFunnel'; export interface BillInfo { price: string; @@ -18,17 +17,7 @@ export interface BillInfo { } const AddBillFunnel = () => { - const {currentMembers} = useRequestGetCurrentMembers(); - const [step, setStep] = useState('price'); - const [billInfo, setBillInfo] = useState({ - price: '', - title: '', - members: [], - }); - - useEffect(() => { - currentMembers && setBillInfo(prev => ({...prev, members: currentMembers})); - }, [currentMembers]); + const {step, setStep, billInfo, setBillInfo, currentMembers} = useAddBillFunnel(); return ( @@ -38,7 +27,7 @@ const AddBillFunnel = () => { {step === 'price' && } {step === 'title' && } {step === 'members' && ( - + )} ); diff --git a/client/src/pages/BillPage/steps/MembersStep.tsx b/client/src/pages/BillPage/steps/MembersStep.tsx index 6e48f696f..7ff1e1433 100644 --- a/client/src/pages/BillPage/steps/MembersStep.tsx +++ b/client/src/pages/BillPage/steps/MembersStep.tsx @@ -1,20 +1,13 @@ import {css} from '@emotion/react'; -import {useEffect, useState} from 'react'; -import {useNavigate} from 'react-router-dom'; - -import useRequestPostMembers from '@hooks/queries/member/useRequestPostMembers'; -import useRequestPostBill from '@hooks/queries/bill/useRequestPostBill'; import Top from '@components/Design/components/Top/Top'; import ChipButton from '@components/Design/components/ChipButton/ChipButton'; import {Member} from 'types/serviceType'; import {FixedButton, Flex, LabelInput, Text} from '@components/Design'; -import getEventIdByUrl from '@utils/getEventIdByUrl'; - -import REGEXP from '@constants/regExp'; - -import {BillInfo, BillStep} from '../AddBillFunnel'; +import {BillInfo} from '../AddBillFunnel'; +import useMembersStep from '@hooks/useMembersStep'; +import {BillStep} from '@hooks/useAddBillFunnel'; interface Props { billInfo: BillInfo; @@ -23,98 +16,18 @@ interface Props { currentMembers: Member[]; } -const MembersStep = ({billInfo, setBillInfo, setStep, currentMembers}: Props) => { - const [errorMessage, setErrorMessage] = useState(''); - const [nameInput, setNameInput] = useState(''); - const [canRequestPostBill, setCanRequestPostBill] = useState(false); - +const MembersStep = ({billInfo, setBillInfo, currentMembers, setStep}: Props) => { const { - postMembers, - responseMemberIds, - isSuccess: isSuccessPostMembers, - isPending: isPendingPostMembers, - } = useRequestPostMembers(); - const {postBill, isSuccess: isSuccessPostBill, isPending: isPendingPostBill} = useRequestPostBill(); - const navigate = useNavigate(); - const eventId = getEventIdByUrl(); - - const onNameInputChange = (value: string) => { - if (REGEXP.memberName.test(value)) { - setNameInput(value); - } - }; - - const handleNameInputChange = (event: React.ChangeEvent) => { - if (event.target.value.length > 4) { - setErrorMessage('이름은 4자까지 입력 가능해요'); - onNameInputChange(nameInput.slice(0, 4)); - } else { - setErrorMessage(''); - onNameInputChange(event.target.value); - } - }; - - const canAddMembers = nameInput && !errorMessage; - - const canSubmitMembers = billInfo.members.length !== 0; - - const setBillInfoMemberWithId = (name: string) => { - const existingMember = currentMembers.find(currentMember => currentMember.name === name); - if (existingMember) { - setBillInfo(prev => ({...prev, members: [...prev.members, {id: existingMember.id, name: name}]})); - } else { - setBillInfo(prev => ({...prev, members: [...prev.members, {id: -1, name: name}]})); - } - }; - - const handleNameInputEnter = (event: React.KeyboardEvent) => { - if (event.nativeEvent.isComposing) { - return; - } - if (event.key === 'Enter' && canAddMembers) { - event.preventDefault(); - if (!billInfo.members.map(({name}) => name).includes(nameInput)) { - setBillInfoMemberWithId(nameInput); - } - setNameInput(''); - } - }; - - const setStepTitle = () => { - setStep('title'); - }; - - const handlePostBill = async () => { - if (billInfo.members.map(({id}) => id).includes(-1)) { - postMembers({ - members: billInfo.members.filter(member => member.id === -1), - }); - } else { - postBill({ - title: billInfo.title, - price: Number(billInfo.price.replace(',', '')), - members: billInfo.members.map(({id}) => id), - }); - } - }; - - useEffect(() => { - if (isSuccessPostMembers && responseMemberIds) { - postBill({ - title: billInfo.title, - price: Number(billInfo.price.replace(',', '')), - members: billInfo.members.map(member => - member.id === -1 ? responseMemberIds.members.find(m => m.name === member.name)?.id || member.id : member.id, - ), - }); - } - }, [isSuccessPostMembers, responseMemberIds]); - - useEffect(() => { - if (isSuccessPostBill) { - navigate(`/event/${eventId}/admin`); - } - }, [isSuccessPostBill]); + errorMessage, + nameInput, + handleNameInputChange, + handleNameInputEnter, + isPendingPostBill, + isPendingPostMembers, + canSubmitMembers, + handlePostBill, + handlePrevStep, + } = useMembersStep({billInfo, setBillInfo, currentMembers, setStep}); return ( <> @@ -176,7 +89,7 @@ const MembersStep = ({billInfo, setBillInfo, setStep, currentMembers}: Props) => variants={isPendingPostBill || isPendingPostMembers ? 'loading' : 'primary'} disabled={!canSubmitMembers} onClick={handlePostBill} - onBackClick={setStepTitle} + onBackClick={handlePrevStep} > 추가완료 diff --git a/client/src/pages/BillPage/steps/PriceStep.tsx b/client/src/pages/BillPage/steps/PriceStep.tsx index ee5e4c530..d84542d38 100644 --- a/client/src/pages/BillPage/steps/PriceStep.tsx +++ b/client/src/pages/BillPage/steps/PriceStep.tsx @@ -7,7 +7,9 @@ import Top from '@components/Design/components/Top/Top'; import {FixedButton} from '@components/Design'; -import {BillInfo, BillStep} from '../AddBillFunnel'; +import {BillInfo} from '../AddBillFunnel'; +import usePriceStep from '@hooks/usePriceStep'; +import {BillStep} from '@hooks/useAddBillFunnel'; interface Props { billInfo: BillInfo; @@ -17,14 +19,7 @@ interface Props { const PriceStep = ({billInfo, setBillInfo, setStep}: Props) => { const navigate = useNavigate(); - - const handleNumberKeyboardChange = (value: string) => { - setBillInfo(prev => ({...prev, price: value || prev.price})); - }; - - const setStepTitle = () => { - setStep('title'); - }; + const {handleNumberKeyboardChange, handleNextStep} = usePriceStep({setBillInfo, setStep}); return ( <> @@ -51,7 +46,7 @@ const PriceStep = ({billInfo, setBillInfo, setStep}: Props) => { > - navigate(-1)}> + navigate(-1)}> 다음으로 diff --git a/client/src/pages/BillPage/steps/TitleStep.tsx b/client/src/pages/BillPage/steps/TitleStep.tsx index d68a20dc9..b9ae1d1db 100644 --- a/client/src/pages/BillPage/steps/TitleStep.tsx +++ b/client/src/pages/BillPage/steps/TitleStep.tsx @@ -1,13 +1,12 @@ import {css} from '@emotion/react'; -import {useState} from 'react'; import Top from '@components/Design/components/Top/Top'; import {FixedButton, LabelInput} from '@components/Design'; -import REGEXP from '@constants/regExp'; - -import {BillInfo, BillStep} from '../AddBillFunnel'; +import {BillInfo} from '../AddBillFunnel'; +import useTitleStep from '@hooks/useTitleStep'; +import {BillStep} from '@hooks/useAddBillFunnel'; interface Props { billInfo: BillInfo; @@ -16,43 +15,18 @@ interface Props { } export const TitleStep = ({billInfo, setBillInfo, setStep}: Props) => { - const [errorMessage, setErrorMessage] = useState(''); - - const onTitleInputChange = (value: string) => { - if (REGEXP.billTitle.test(value)) { - setBillInfo(prev => ({...prev, title: value})); - } - }; - - const handleTitleInputChange = (event: React.ChangeEvent) => { - if (event.target.value.length > 12) { - setErrorMessage('지출내역은 12자까지 입력 가능해요'); - onTitleInputChange(billInfo.title.slice(0, 12)); - } else { - setErrorMessage(''); - onTitleInputChange(event.target.value); - } - }; - - const canSubmitTitleInput = billInfo.title && !errorMessage; - - const handleTitleInputEnter = (event: React.KeyboardEvent) => { - if (event.nativeEvent.isComposing) { - return; - } - if (event.key === 'Enter' && canSubmitTitleInput) { - event.preventDefault(); - setStep('members'); - } - }; - - const setStepPrice = () => { - setStep('price'); - }; - - const setStepMembers = () => { - setStep('members'); - }; + const { + errorMessage, + handleTitleInputChange, + handleTitleInputEnter, + canSubmitTitleInput, + handlePrevStep, + handleNextStep, + } = useTitleStep({ + billInfo, + setBillInfo, + setStep, + }); return ( <> @@ -80,7 +54,7 @@ export const TitleStep = ({billInfo, setBillInfo, setStep}: Props) => { onKeyDown={handleTitleInputEnter} /> - + 다음으로