From 34ba35e83011df5dd493e067607ec33f4534c121 Mon Sep 17 00:00:00 2001 From: pakxe Date: Mon, 23 Sep 2024 20:26:11 +0900 Subject: [PATCH 01/22] =?UTF-8?q?feat:=20=ED=96=89=EC=82=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=9D=98=20path=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/constants/routerUrls.ts | 3 +-- client/src/pages/MainPage/Nav/Nav.tsx | 2 +- client/src/pages/MainPage/Section/MainSection.tsx | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/constants/routerUrls.ts b/client/src/constants/routerUrls.ts index 99e82b5fe..a277136b7 100644 --- a/client/src/constants/routerUrls.ts +++ b/client/src/constants/routerUrls.ts @@ -1,7 +1,6 @@ export const ROUTER_URLS = { main: '/', - eventCreateName: '/event/create/name', - eventCreatePassword: '/event/create/password', + createEvent: '/event/create/', eventCreateComplete: '/event/create/complete', event: '/event', // TODO: (@weadie) baseurl을 어떻게 관리할 것인가? eventLogin: '/event/:eventId/login', diff --git a/client/src/pages/MainPage/Nav/Nav.tsx b/client/src/pages/MainPage/Nav/Nav.tsx index 5fd1ba3f8..2a45267cb 100644 --- a/client/src/pages/MainPage/Nav/Nav.tsx +++ b/client/src/pages/MainPage/Nav/Nav.tsx @@ -18,7 +18,7 @@ const Nav = () => { 행동대장 - diff --git a/client/src/pages/MainPage/Section/MainSection.tsx b/client/src/pages/MainPage/Section/MainSection.tsx index 16bc67128..0033e009c 100644 --- a/client/src/pages/MainPage/Section/MainSection.tsx +++ b/client/src/pages/MainPage/Section/MainSection.tsx @@ -39,7 +39,7 @@ const MainSection = () => { {`행동대장을 통해 간편하게 정산하세요 `} - From 703714dce89bc7409ca6b9b53273f246d44c25be Mon Sep 17 00:00:00 2001 From: pakxe Date: Mon, 23 Sep 2024 20:26:38 +0900 Subject: [PATCH 02/22] =?UTF-8?q?fix:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84=EC=9D=B4=20=EB=B0=94=EB=80=9C?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=8B=A4=EB=A5=B8=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=B4=20=EC=A0=9C=EB=AA=A9=EC=9D=84=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/ErrorPage/ErrorPage.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/pages/ErrorPage/ErrorPage.tsx b/client/src/pages/ErrorPage/ErrorPage.tsx index 84f61b2a3..92810e668 100644 --- a/client/src/pages/ErrorPage/ErrorPage.tsx +++ b/client/src/pages/ErrorPage/ErrorPage.tsx @@ -1,9 +1,13 @@ -import {MainLayout, Title} from '@HDesign/index'; +import Top from '@components/Design/components/Top/Top'; + +import {MainLayout} from '@HDesign/index'; const ErrorPage = () => { return ( - + <Top> + <Top.Line text="알 수 없는 오류입니다." emphasize={['알 수 없는 오류입니다.']} /> + </Top> </MainLayout> ); }; From e36c678aa56b6079ff9838f368a2f8c5bd3c43a8 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:30:02 +0900 Subject: [PATCH 03/22] =?UTF-8?q?feat:=20=ED=96=89=EC=82=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A5=BC=20=ED=8D=BC?= =?UTF-8?q?=EB=84=90=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=B4=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateEventPage/CreateEventFunnel.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/src/pages/CreateEventPage/CreateEventFunnel.tsx diff --git a/client/src/pages/CreateEventPage/CreateEventFunnel.tsx b/client/src/pages/CreateEventPage/CreateEventFunnel.tsx new file mode 100644 index 000000000..82c4dd0fd --- /dev/null +++ b/client/src/pages/CreateEventPage/CreateEventFunnel.tsx @@ -0,0 +1,54 @@ +import {useNavigate} from 'react-router-dom'; + +import useFunnel from '@hooks/useFunnel'; +import useCreateEventData from '@hooks/useCreateEventData'; + +import {Back, MainLayout, TopNav} from '@components/Design'; + +import SetEventNameStep from './SetEventNameStep'; +import SetEventPasswordStep from './SetEventPasswordStep'; +import CompleteCreateEventPage from './CompleteCreateEventPage'; + +type CreateEventStep = 'eventName' | 'eventPassword' | 'complete'; +const STEP_SEQUENCE: CreateEventStep[] = ['eventName', 'eventPassword', 'complete']; + +const CreateEventFunnel = () => { + const navigate = useNavigate(); + const {moveToNextStep, moveToPrevStep, Step, Funnel, step} = useFunnel({ + defaultStep: 'eventName', + stepList: STEP_SEQUENCE, + }); + + const {eventNameProps, eventToken, setEventToken} = useCreateEventData(); + + const handleBack = () => { + if (step === STEP_SEQUENCE[0]) { + navigate('/'); + } else { + moveToPrevStep(); + } + }; + + return ( + <MainLayout backgroundColor="white"> + <TopNav>{step !== STEP_SEQUENCE[STEP_SEQUENCE.length - 1] && <Back onClick={handleBack} />}</TopNav> + <Funnel> + <Step name="eventName"> + <SetEventNameStep moveToNextStep={moveToNextStep} {...eventNameProps} /> + </Step> + <Step name="eventPassword"> + <SetEventPasswordStep + moveToNextStep={moveToNextStep} + eventName={eventNameProps.eventName} + setEventToken={setEventToken} + /> + </Step> + <Step name="complete"> + <CompleteCreateEventPage eventToken={eventToken} /> + </Step> + </Funnel> + </MainLayout> + ); +}; + +export default CreateEventFunnel; From 9f3671b67ded0bab9eab9ac1c4502559a211cc8a Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:32:13 +0900 Subject: [PATCH 04/22] =?UTF-8?q?feat:=20=ED=8D=BC=EB=84=90=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EA=B3=B5=ED=86=B5=EC=A0=81=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EA=B3=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20useFunnel=20=ED=9B=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useFunnel.tsx | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 client/src/hooks/useFunnel.tsx diff --git a/client/src/hooks/useFunnel.tsx b/client/src/hooks/useFunnel.tsx new file mode 100644 index 000000000..3db531031 --- /dev/null +++ b/client/src/hooks/useFunnel.tsx @@ -0,0 +1,55 @@ +import {useState} from 'react'; + +type UseFunnel = { + defaultStep: string; + stepList: string[]; +}; + +type StepProps = { + children: React.ReactNode; + name: string; +}; + +type FunnelProps = { + children: React.ReactElement<StepProps>[]; +}; + +const useFunnel = ({defaultStep, stepList}: UseFunnel) => { + const [step, setStep] = useState(defaultStep); + + const moveToNextStep = () => { + const curStepIndex = stepList.indexOf(step); + + if (curStepIndex === stepList.length - 1) return; + + setStep(stepList[curStepIndex + 1]); + }; + + const moveToPrevStep = () => { + const curStepIndex = stepList.indexOf(step); + + if (curStepIndex === 0) return; + + setStep(stepList[curStepIndex - 1]); + }; + + const Step = (stepProps: StepProps) => { + return <>{stepProps.children}</>; + }; + + const Funnel = ({children}: FunnelProps) => { + const targetStep = children.find(curStep => curStep.props.name === step); + + return <>{targetStep}</>; + }; + + return { + Step, + step, + Funnel, + moveToNextStep, + moveToPrevStep, + }; +}; + +export default useFunnel; From 072b2d1cc0a71f4ee2011bb42490214d7e69e11d Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:34:13 +0900 Subject: [PATCH 05/22] =?UTF-8?q?rename:=20=ED=8D=BC=EB=84=90=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=EB=90=A8?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20Page=20->=20Step=20=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=EC=9D=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ventNamePage.ts => useSetEventNameStep.ts} | 6 +- client/src/hooks/useSetEventPasswordPage.ts | 65 ----------------- client/src/hooks/useSetEventPasswordStep.ts | 72 +++++++++++++++++++ ...ntPage.tsx => CompleteCreateEventStep.tsx} | 14 ++-- .../CreateEventPage/SetEventNamePage.tsx | 68 ------------------ .../CreateEventPage/SetEventNameStep.tsx | 57 +++++++++++++++ .../CreateEventPage/SetEventPasswordPage.tsx | 62 ---------------- .../CreateEventPage/SetEventPasswordStep.tsx | 68 ++++++++++++++++++ 8 files changed, 208 insertions(+), 204 deletions(-) rename client/src/hooks/{useSetEventNamePage.ts => useSetEventNameStep.ts} (82%) delete mode 100644 client/src/hooks/useSetEventPasswordPage.ts create mode 100644 client/src/hooks/useSetEventPasswordStep.ts rename client/src/pages/CreateEventPage/{CompleteCreateEventPage.tsx => CompleteCreateEventStep.tsx} (78%) delete mode 100644 client/src/pages/CreateEventPage/SetEventNamePage.tsx create mode 100644 client/src/pages/CreateEventPage/SetEventNameStep.tsx delete mode 100644 client/src/pages/CreateEventPage/SetEventPasswordPage.tsx create mode 100644 client/src/pages/CreateEventPage/SetEventPasswordStep.tsx diff --git a/client/src/hooks/useSetEventNamePage.ts b/client/src/hooks/useSetEventNameStep.ts similarity index 82% rename from client/src/hooks/useSetEventNamePage.ts rename to client/src/hooks/useSetEventNameStep.ts index f5b5bce12..cd2a35302 100644 --- a/client/src/hooks/useSetEventNamePage.ts +++ b/client/src/hooks/useSetEventNameStep.ts @@ -2,7 +2,9 @@ import {useState} from 'react'; import validateEventName from '@utils/validate/validateEventName'; -const useSetEventNamePage = () => { +export type UseSetEventNameStepReturnType = ReturnType<typeof useSetEventNameStep>; + +const useSetEventNameStep = () => { const [eventName, setEventName] = useState(''); const [errorMessage, setErrorMessage] = useState<string | null>(null); const [canSubmit, setCanSubmit] = useState(false); @@ -29,4 +31,4 @@ const useSetEventNamePage = () => { }; }; -export default useSetEventNamePage; +export default useSetEventNameStep; diff --git a/client/src/hooks/useSetEventPasswordPage.ts b/client/src/hooks/useSetEventPasswordPage.ts deleted file mode 100644 index ba8549258..000000000 --- a/client/src/hooks/useSetEventPasswordPage.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {useEffect, useState} from 'react'; -import {useLocation, useNavigate} from 'react-router-dom'; - -import validateEventPassword from '@utils/validate/validateEventPassword'; - -import {ROUTER_URLS} from '@constants/routerUrls'; -import RULE from '@constants/rule'; - -import useRequestPostEvent from './queries/event/useRequestPostEvent'; - -const useSetEventPasswordPage = () => { - const [eventName, setEventName] = useState(''); - const [password, setPassword] = useState(''); - const [errorMessage, setErrorMessage] = useState(''); - const [canSubmit, setCanSubmit] = useState(false); - const navigate = useNavigate(); - const location = useLocation(); - const {postEvent, isPostEventPending} = useRequestPostEvent(); - - useEffect(() => { - if (!location.state) { - navigate(ROUTER_URLS.main); - } else { - setEventName(location.state.eventName); - } - }, []); - - const submitPassword = async (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - - onSuccess(); - }; - - const onSuccess = () => { - postEvent( - {eventName, password: String(password).padStart(4, '0')}, - { - onSuccess: data => { - navigate(`${ROUTER_URLS.eventCreateComplete}?${new URLSearchParams({eventId: data.eventId})}`, { - replace: true, - }); - }, - }, - ); - }; - - const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const newValue = event.target.value; - const validation = validateEventPassword(newValue); - - setCanSubmit(newValue.length === RULE.maxEventPasswordLength); - - if (validation.isValid) { - setPassword(newValue); - setErrorMessage(''); - } else { - event.target.value = password; - setErrorMessage(validation.errorMessage ?? ''); - } - }; - - return {submitPassword, errorMessage, password, handleChange, onSuccess, canSubmit, isPostEventPending}; -}; - -export default useSetEventPasswordPage; diff --git a/client/src/hooks/useSetEventPasswordStep.ts b/client/src/hooks/useSetEventPasswordStep.ts new file mode 100644 index 000000000..e88e0ec27 --- /dev/null +++ b/client/src/hooks/useSetEventPasswordStep.ts @@ -0,0 +1,72 @@ +import {useState} from 'react'; +import {useNavigate} from 'react-router-dom'; + +import validateEventPassword from '@utils/validate/validateEventPassword'; + +import RULE from '@constants/rule'; + +import useRequestPostEvent from './queries/event/useRequestPostEvent'; + +export type UseSetEventPasswordStepReturnType = ReturnType<typeof useSetEventPasswordStep>; + +const useSetEventPasswordStep = () => { + const [password, setPassword] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [canSubmit, setCanSubmit] = useState(false); + const {postEvent: requestPostEvent, isPostEventPending} = useRequestPostEvent(); + + const submitDataForPostEvent = async ({ + event, + eventName, + setEventToken, + }: { + event?: React.FormEvent<HTMLFormElement>; + eventName: string; + setEventToken: (eventToken: string) => void; + }) => { + if (event) event.preventDefault(); + + await postEvent(eventName, setEventToken); + }; + + const getPasswordWithPad = () => { + return String(password).padStart(4, '0'); + }; + + const postEvent = async (eventName: string, updateEventToken: (eventToken: string) => void) => { + await requestPostEvent( + {eventName, password: getPasswordWithPad()}, + { + onSuccess: ({eventId}) => { + updateEventToken(eventId); + }, + }, + ); + }; + + const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const newValue = event.target.value; + const validation = validateEventPassword(newValue); + + setCanSubmit(newValue.length === RULE.maxEventPasswordLength); + + if (validation.isValid) { + setPassword(newValue); + setErrorMessage(''); + } else { + event.target.value = password; + setErrorMessage(validation.errorMessage ?? ''); + } + }; + + return { + submitDataForPostEvent, + errorMessage, + handleChange, + canSubmit, + isPostEventPending, + password, + }; +}; + +export default useSetEventPasswordStep; diff --git a/client/src/pages/CreateEventPage/CompleteCreateEventPage.tsx b/client/src/pages/CreateEventPage/CompleteCreateEventStep.tsx similarity index 78% rename from client/src/pages/CreateEventPage/CompleteCreateEventPage.tsx rename to client/src/pages/CreateEventPage/CompleteCreateEventStep.tsx index 385d8c968..a2e19b5f7 100644 --- a/client/src/pages/CreateEventPage/CompleteCreateEventPage.tsx +++ b/client/src/pages/CreateEventPage/CompleteCreateEventStep.tsx @@ -8,12 +8,12 @@ import {FixedButton, MainLayout, Title, TopNav} from '@HDesign/index'; import {ROUTER_URLS} from '@constants/routerUrls'; -const CompleteCreateEventPage = () => { - const navigate = useNavigate(); - const location = useLocation(); +type CompleteCreateEventStepProps = { + eventToken: string; +}; - const params = new URLSearchParams(location.search); - const eventId = params.get('eventId'); +const CompleteCreateEventStep = ({eventToken}: CompleteCreateEventStepProps) => { + const navigate = useNavigate(); return ( <MainLayout backgroundColor="white"> @@ -32,9 +32,9 @@ const CompleteCreateEventPage = () => { </Top> <RunningDog /> </div> - <FixedButton onClick={() => navigate(`${ROUTER_URLS.event}/${eventId}/admin`)}>관리 페이지로 이동</FixedButton> + <FixedButton onClick={() => navigate(`${ROUTER_URLS.event}/${eventToken}/admin`)}>관리 페이지로 이동</FixedButton> </MainLayout> ); }; -export default CompleteCreateEventPage; +export default CompleteCreateEventStep; diff --git a/client/src/pages/CreateEventPage/SetEventNamePage.tsx b/client/src/pages/CreateEventPage/SetEventNamePage.tsx deleted file mode 100644 index f9fc9f3f5..000000000 --- a/client/src/pages/CreateEventPage/SetEventNamePage.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import {useNavigate} from 'react-router-dom'; -import {css} from '@emotion/react'; - -import Top from '@components/Design/components/Top/Top'; - -import useSetEventNamePage from '@hooks/useSetEventNamePage'; - -import {FixedButton, MainLayout, LabelInput, Title, TopNav, Back, Flex} from '@HDesign/index'; - -import {ROUTER_URLS} from '@constants/routerUrls'; - -const SetEventNamePage = () => { - const navigate = useNavigate(); - const {eventName, errorMessage, canSubmit, handleEventNameChange} = useSetEventNamePage(); - - const submitEventName = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - - onSuccessSubmint(); - }; - - const onSuccessSubmint = () => { - navigate(ROUTER_URLS.eventCreatePassword, {state: {eventName}}); - }; - - const handleGoNextStep = (event: React.KeyboardEvent<HTMLInputElement>) => { - if (event.key === 'Enter') { - onSuccessSubmint(); - } - }; - - return ( - <MainLayout backgroundColor="white"> - <TopNav> - <Back /> - </TopNav> - <div - css={css` - display: flex; - flex-direction: column; - gap: 1rem; - padding: 1rem; - `} - > - <Top> - <Top.Line text="정산을 시작하려는" /> - <Top.Line text="행사의 이름은 무엇인가요?" emphasize={['행사의 이름']} /> - </Top> - <form onSubmit={submitEventName}> - <LabelInput - labelText="행사 이름" - errorText={errorMessage ?? ''} - value={eventName} - type="text" - placeholder="행동대장 야유회" - onChange={handleEventNameChange} - isError={!!errorMessage} - autoFocus - onKeyDown={handleGoNextStep} - ></LabelInput> - <FixedButton disabled={!canSubmit}>다음</FixedButton> - </form> - </div> - </MainLayout> - ); -}; - -export default SetEventNamePage; diff --git a/client/src/pages/CreateEventPage/SetEventNameStep.tsx b/client/src/pages/CreateEventPage/SetEventNameStep.tsx new file mode 100644 index 000000000..2dd6d4a65 --- /dev/null +++ b/client/src/pages/CreateEventPage/SetEventNameStep.tsx @@ -0,0 +1,57 @@ +import {css} from '@emotion/react'; + +import Top from '@components/Design/components/Top/Top'; + +import {UseSetEventNameStepReturnType} from '@hooks/useSetEventNameStep'; + +import {FixedButton, Flex, LabelInput} from '@HDesign/index'; + +type SetEventNamePageProps = UseSetEventNameStepReturnType & { + moveToNextStep: () => void; +}; + +const SetEventNameStep = ({ + eventName, + moveToNextStep, + errorMessage, + handleEventNameChange, + canSubmit, +}: SetEventNamePageProps) => { + const moveToNextStepOnEnter = (event: React.KeyboardEvent<HTMLInputElement>) => { + if (event.key === 'Enter') { + moveToNextStep(); + } + }; + + return ( + <div + css={css` + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem; + `} + > + <Top> + <Top.Line text="정산을 시작하려는" /> + <Top.Line text="행사의 이름은 무엇인가요?" emphasize={['행사의 이름']} /> + </Top> + <form onSubmit={moveToNextStep}> + <LabelInput + labelText="행사 이름" + errorText={errorMessage ?? ''} + value={eventName} + type="text" + placeholder="행동대장 야유회" + onChange={handleEventNameChange} + isError={!!errorMessage} + autoFocus + onKeyDown={moveToNextStepOnEnter} + ></LabelInput> + <FixedButton disabled={!canSubmit}>다음</FixedButton> + </form> + </div> + ); +}; + +export default SetEventNameStep; diff --git a/client/src/pages/CreateEventPage/SetEventPasswordPage.tsx b/client/src/pages/CreateEventPage/SetEventPasswordPage.tsx deleted file mode 100644 index 914f0a25b..000000000 --- a/client/src/pages/CreateEventPage/SetEventPasswordPage.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import {css} from '@emotion/react'; - -import Top from '@components/Design/components/Top/Top'; - -import useSetEventPasswordPage from '@hooks/useSetEventPasswordPage'; - -import {FixedButton, MainLayout, LabelInput, Title, TopNav, Back} from '@HDesign/index'; - -import RULE from '@constants/rule'; -import {PASSWORD_LENGTH} from '@constants/password'; - -const SetEventPasswordPage = () => { - const {submitPassword, onSuccess, errorMessage, password, handleChange, canSubmit, isPostEventPending} = - useSetEventPasswordPage(); - - const handleGoNextStep = (event: React.KeyboardEvent<HTMLInputElement>) => { - if (event.key === 'Enter') { - onSuccess(); - } - }; - - return ( - <MainLayout backgroundColor="white"> - <TopNav> - <Back /> - </TopNav> - <div - css={css` - display: flex; - flex-direction: column; - gap: 1rem; - padding: 1rem; - `} - > - <Top> - <Top.Line text="관리에 필요한 네자리 숫자" emphasize={['네자리 숫자']} /> - <Top.Line text="비밀번호는 무엇으로 할까요?" emphasize={['비밀번호']} /> - </Top> - <form onSubmit={submitPassword}> - <LabelInput - labelText="비밀번호" - errorText={errorMessage} - value={password} - type="text" - maxLength={RULE.maxEventPasswordLength} - placeholder="1234" - onChange={handleChange} - isError={!!errorMessage} - autoFocus - onKeyDown={handleGoNextStep} - /> - {/* 가상 키패드 적용 예정 */} - <FixedButton variants={isPostEventPending ? 'loading' : 'primary'} disabled={!canSubmit}> - 행동 개시! - </FixedButton> - </form> - </div> - </MainLayout> - ); -}; - -export default SetEventPasswordPage; diff --git a/client/src/pages/CreateEventPage/SetEventPasswordStep.tsx b/client/src/pages/CreateEventPage/SetEventPasswordStep.tsx new file mode 100644 index 000000000..ee45edf5b --- /dev/null +++ b/client/src/pages/CreateEventPage/SetEventPasswordStep.tsx @@ -0,0 +1,68 @@ +import {css} from '@emotion/react'; + +import Top from '@components/Design/components/Top/Top'; + +import useSetEventPasswordStep, {UseSetEventPasswordStepReturnType} from '@hooks/useSetEventPasswordStep'; + +import {FixedButton, LabelInput} from '@HDesign/index'; + +import RULE from '@constants/rule'; + +type SetEventPasswordPageProps = { + eventName: string; + moveToNextStep: () => void; + setEventToken: (eventToken: string) => void; +}; + +const SetEventPasswordStep = ({eventName, moveToNextStep, setEventToken}: SetEventPasswordPageProps) => { + const {submitDataForPostEvent, errorMessage, password, handleChange, isPostEventPending, canSubmit} = + useSetEventPasswordStep(); + + const submitOnEnter = async (event: React.KeyboardEvent<HTMLInputElement>) => { + if (event.key === 'Enter') { + submit(); + } + }; + + const submit = async (event?: React.FormEvent<HTMLFormElement>) => { + await submitDataForPostEvent({event, eventName, setEventToken}); + + moveToNextStep(); + }; + + return ( + <div + css={css` + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem; + `} + > + <Top> + <Top.Line text="관리에 필요한 네자리 숫자" emphasize={['네자리 숫자']} /> + <Top.Line text="비밀번호는 무엇으로 할까요?" emphasize={['비밀번호']} /> + </Top> + <form onSubmit={submit}> + <LabelInput + labelText="비밀번호" + errorText={errorMessage} + value={password} + type="text" + maxLength={RULE.maxEventPasswordLength} + placeholder="1234" + onChange={handleChange} + isError={!!errorMessage} + autoFocus + onKeyDown={submitOnEnter} + /> + {/* 가상 키패드 적용 예정 */} + <FixedButton variants={isPostEventPending ? 'loading' : 'primary'} disabled={!canSubmit}> + 행동 개시! + </FixedButton> + </form> + </div> + ); +}; + +export default SetEventPasswordStep; From ec8beb5f2370ccb6185d71f3f4e9b28cf1328b53 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:34:51 +0900 Subject: [PATCH 06/22] =?UTF-8?q?feat:=20=ED=96=89=EC=82=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=8D=BC=EB=84=90=EC=97=90=EC=84=9C=20=EC=97=AC?= =?UTF-8?q?=EB=9F=AC=20step=EC=97=90=20=EA=B1=B8=EC=B3=90=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8A=94=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=ED=95=B4=20=EB=82=B4=EB=A0=A4=EC=A3=BC?= =?UTF-8?q?=EB=8A=94=20useCreateEventData=20=ED=9B=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useCreateEventData.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 client/src/hooks/useCreateEventData.tsx diff --git a/client/src/hooks/useCreateEventData.tsx b/client/src/hooks/useCreateEventData.tsx new file mode 100644 index 000000000..fed21d8ae --- /dev/null +++ b/client/src/hooks/useCreateEventData.tsx @@ -0,0 +1,17 @@ +import {useState} from 'react'; + +import useSetEventNameStep from './useSetEventNameStep'; + +// 행사 생성 페이지에서 여러 스텝에 걸쳐 사용되는 상태를 선언해 내려주는 용도의 훅입니다. +const useCreateEventData = () => { + const eventNameProps = useSetEventNameStep(); + const [eventToken, setEventToken] = useState(''); + + return { + eventNameProps, + eventToken, + setEventToken, + }; +}; + +export default useCreateEventData; From 25f93add420e09807bb28fb9e43a2c3d2044ef94 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:35:13 +0900 Subject: [PATCH 07/22] =?UTF-8?q?feat:=20=ED=96=89=EC=82=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=8D=BC=EB=84=90=EC=9D=84=20router=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/router.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/client/src/router.tsx b/client/src/router.tsx index e1592bd2e..e3e49643a 100644 --- a/client/src/router.tsx +++ b/client/src/router.tsx @@ -6,8 +6,8 @@ import ErrorPage from '@pages/ErrorPage/ErrorPage'; import EventLoginPage from '@pages/EventPage/AdminPage/EventLoginPage'; import Account from '@pages/Account/Account'; import AddBillFunnel from '@pages/BillPage/AddBillFunnel'; +import CreateEventFunnel from '@pages/CreateEventPage/CreateEventFunnel'; -import {CompleteCreateEventPage, SetEventNamePage, SetEventPasswordPage} from '@pages/CreateEventPage'; import {MainPage} from '@pages/MainPage'; import {EventPage} from '@pages/EventPage'; @@ -30,16 +30,9 @@ const router = createBrowserRouter([ element: <MainPage />, }, { - path: ROUTER_URLS.eventCreateName, - element: <SetEventNamePage />, - }, - { - path: ROUTER_URLS.eventCreatePassword, - element: <SetEventPasswordPage />, - }, - { - path: ROUTER_URLS.eventCreateComplete, - element: <CompleteCreateEventPage />, + path: ROUTER_URLS.createEvent, + + element: <CreateEventFunnel />, }, { path: ROUTER_URLS.event, From 4b0e1bcc39af86cf5fc1b68029b20bcfb50c7995 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:35:55 +0900 Subject: [PATCH 08/22] =?UTF-8?q?feat:=20=EB=92=A4=EB=A1=9C=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=EC=9D=B8=20Back=EC=97=90=20onCli?= =?UTF-8?q?ck=EC=9D=B8=EC=9E=90=EA=B0=80=20=EC=A3=BC=EC=96=B4=EC=A7=80?= =?UTF-8?q?=EB=A9=B4=20=EA=B7=B8=20=ED=95=A8=EC=88=98=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EA=B3=A0=20=EC=A3=BC=EC=96=B4=EC=A7=80?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9C=BC=EB=A9=B4=20-1=EB=A1=9C=20?= =?UTF-8?q?=EB=92=A4=EB=A1=9C=EA=B0=80=EB=8F=84=EB=A1=9D=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/Design/components/TopNav/Back.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/components/Design/components/TopNav/Back.tsx b/client/src/components/Design/components/TopNav/Back.tsx index 73065ebce..5bbd83618 100644 --- a/client/src/components/Design/components/TopNav/Back.tsx +++ b/client/src/components/Design/components/TopNav/Back.tsx @@ -1,14 +1,17 @@ /** @jsxImportSource @emotion/react */ -import React from 'react'; import {useNavigate} from 'react-router-dom'; import TextButton from '@HDcomponents/TextButton/TextButton'; -function Back() { +type BackProps = { + onClick?: () => void; +}; + +function Back({onClick}: BackProps) { const navigate = useNavigate(); return ( - <TextButton onClick={() => navigate(-1)} textSize="bodyBold" textColor="gray"> + <TextButton onClick={() => (onClick ? onClick() : navigate(-1))} textSize="bodyBold" textColor="gray"> 뒤로가기 </TextButton> ); From 888d32eba5a8ca394c44cfa6021f1430f317238e Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:36:40 +0900 Subject: [PATCH 09/22] =?UTF-8?q?feat:=20Funnel=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=97=90=EC=84=9C=20=ED=98=84=EC=9E=AC?= =?UTF-8?q?=EC=9D=98=20step=EC=9D=84=20=EC=9C=84=ED=95=9C=20Step=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20=EC=B0=BE?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=96=88=EB=8B=A4=EB=A9=B4=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=EB=A5=BC=20=EB=8D=98=EC=A7=80=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useFunnel.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/hooks/useFunnel.tsx b/client/src/hooks/useFunnel.tsx index 3db531031..196bb37b4 100644 --- a/client/src/hooks/useFunnel.tsx +++ b/client/src/hooks/useFunnel.tsx @@ -40,6 +40,9 @@ const useFunnel = ({defaultStep, stepList}: UseFunnel) => { const Funnel = ({children}: FunnelProps) => { const targetStep = children.find(curStep => curStep.props.name === step); + if (!targetStep) + throw new Error(`현재 ${step} 단계에 보여줄 컴포넌트가 존재하지 않습니다. Step 컴포넌트를 호출해 사용해주세요.`); + return <>{targetStep}</>; }; From 3e7e507194a030d5cdf4aedb37c326464953d03f Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:37:21 +0900 Subject: [PATCH 10/22] =?UTF-8?q?feat:=20=EC=9D=91=EB=8B=B5=EC=9D=B4=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=ED=95=B4=EC=95=BC=EB=A7=8C=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20mutateAsy?= =?UTF-8?q?nc=20=EC=82=AC=EC=9A=A9=ED=95=B4=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/queries/event/useRequestPostEvent.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/queries/event/useRequestPostEvent.ts b/client/src/hooks/queries/event/useRequestPostEvent.ts index ec4c6f2b0..d59f6b913 100644 --- a/client/src/hooks/queries/event/useRequestPostEvent.ts +++ b/client/src/hooks/queries/event/useRequestPostEvent.ts @@ -3,12 +3,13 @@ import {useMutation} from '@tanstack/react-query'; import {RequestPostEvent, requestPostEvent} from '@apis/request/event'; const useRequestPostEvent = () => { - const {mutate, ...rest} = useMutation({ + const {mutate, mutateAsync, ...rest} = useMutation({ mutationFn: ({eventName, password}: RequestPostEvent) => requestPostEvent({eventName, password}), }); + // 실행 순서를 await으로 보장하기 위해 mutateAsync 사용 return { - postEvent: mutate, + postEvent: mutateAsync, isPostEventPending: rest.isPending, ...rest, }; From fe3f79500e3a6e35a7847ddc01f208a3ce870c5e Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:37:39 +0900 Subject: [PATCH 11/22] =?UTF-8?q?feat:=20=ED=8D=BC=EB=84=90=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EA=B3=B5=ED=86=B5=EC=A0=81=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EA=B3=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20useFunnel=20=ED=9B=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/CreateEventPage/CreateEventFunnel.tsx | 4 ++-- client/src/pages/CreateEventPage/index.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/pages/CreateEventPage/CreateEventFunnel.tsx b/client/src/pages/CreateEventPage/CreateEventFunnel.tsx index 82c4dd0fd..58c802934 100644 --- a/client/src/pages/CreateEventPage/CreateEventFunnel.tsx +++ b/client/src/pages/CreateEventPage/CreateEventFunnel.tsx @@ -7,7 +7,7 @@ import {Back, MainLayout, TopNav} from '@components/Design'; import SetEventNameStep from './SetEventNameStep'; import SetEventPasswordStep from './SetEventPasswordStep'; -import CompleteCreateEventPage from './CompleteCreateEventPage'; +import CompleteCreateEventStep from './CompleteCreateEventStep'; type CreateEventStep = 'eventName' | 'eventPassword' | 'complete'; const STEP_SEQUENCE: CreateEventStep[] = ['eventName', 'eventPassword', 'complete']; @@ -44,7 +44,7 @@ const CreateEventFunnel = () => { /> </Step> <Step name="complete"> - <CompleteCreateEventPage eventToken={eventToken} /> + <CompleteCreateEventStep eventToken={eventToken} /> </Step> </Funnel> </MainLayout> diff --git a/client/src/pages/CreateEventPage/index.ts b/client/src/pages/CreateEventPage/index.ts index 6d3d6c808..8d5867dc6 100644 --- a/client/src/pages/CreateEventPage/index.ts +++ b/client/src/pages/CreateEventPage/index.ts @@ -1,3 +1,3 @@ -export {default as SetEventNamePage} from './SetEventNamePage'; -export {default as SetEventPasswordPage} from './SetEventPasswordPage'; -export {default as CompleteCreateEventPage} from './CompleteCreateEventPage'; +export {default as SetEventNameStep} from './SetEventNameStep'; +export {default as SetEventPasswordStep} from './SetEventPasswordStep'; +export {default as CompleteCreateEventStep} from './CompleteCreateEventStep'; From 8e0a7c5d83f9f781e92f18e72880880a0a023448 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:37:54 +0900 Subject: [PATCH 12/22] =?UTF-8?q?style:=20return=20=EC=9C=84=20=EA=B0=9C?= =?UTF-8?q?=ED=96=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/utils/validate/validateEventName.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/utils/validate/validateEventName.ts b/client/src/utils/validate/validateEventName.ts index 93f4ecef1..1c88fd4b3 100644 --- a/client/src/utils/validate/validateEventName.ts +++ b/client/src/utils/validate/validateEventName.ts @@ -7,6 +7,7 @@ const validateEventName = (name: string): ValidateResult => { if (name.length > RULE.maxEventNameLength) { return {isValid: false, errorMessage: ERROR_MESSAGE.eventName}; } + return {isValid: true, errorMessage: null}; }; From 979dd73eec3834ba1f1c0a1c501c6e115b78b50e Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 20:56:31 +0900 Subject: [PATCH 13/22] =?UTF-8?q?style:=20=EA=B0=80=EB=8F=85=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=B4=20Step=EA=B0=84=20=EA=B0=9C?= =?UTF-8?q?=ED=96=89=EC=9D=84=20=EB=84=A3=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/CreateEventPage/CreateEventFunnel.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/pages/CreateEventPage/CreateEventFunnel.tsx b/client/src/pages/CreateEventPage/CreateEventFunnel.tsx index 58c802934..d9347a389 100644 --- a/client/src/pages/CreateEventPage/CreateEventFunnel.tsx +++ b/client/src/pages/CreateEventPage/CreateEventFunnel.tsx @@ -36,6 +36,7 @@ const CreateEventFunnel = () => { <Step name="eventName"> <SetEventNameStep moveToNextStep={moveToNextStep} {...eventNameProps} /> </Step> + <Step name="eventPassword"> <SetEventPasswordStep moveToNextStep={moveToNextStep} @@ -43,6 +44,7 @@ const CreateEventFunnel = () => { setEventToken={setEventToken} /> </Step> + <Step name="complete"> <CompleteCreateEventStep eventToken={eventToken} /> </Step> From ea487e5cab7991d47729866a37e800b128d59a98 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 21:12:16 +0900 Subject: [PATCH 14/22] =?UTF-8?q?test:=20=ED=8D=BC=EB=84=90=EC=9D=B4=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85=EB=90=98=EC=96=B4=20=ED=96=89=EC=82=AC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B3=BC=EC=A0=95=EC=97=90=EC=84=9C=20url?= =?UTF-8?q?=EC=9D=B4=20=EB=B3=80=EA=B2=BD=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B8=B0=20=EB=95=8C=EB=AC=B8=EC=97=90=20url=EC=9D=B4=20?= =?UTF-8?q?=EC=98=AC=EB=B0=94=EB=A5=B4=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=98=EC=97=88=EB=8A=94=EC=A7=80=20=ED=8C=90=EB=8B=A8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=9D=BC=EC=9D=B8=EC=9D=84=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/cypress/e2e/createEvent.cy.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/client/cypress/e2e/createEvent.cy.ts b/client/cypress/e2e/createEvent.cy.ts index 5de7f4592..98f1d8bdf 100644 --- a/client/cypress/e2e/createEvent.cy.ts +++ b/client/cypress/e2e/createEvent.cy.ts @@ -8,7 +8,7 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl it('랜딩페이지에서 "행사 생성하기" 버튼을 눌러 행사 이름 입력 페이지로 이동해야 한다.', () => { cy.visit('/'); cy.get('header').find('button').click(); - cy.url().should('include', '/event/create/name'); + cy.url().should('include', '/event/create'); }); context('행사 이름 입력 페이지', () => { @@ -19,7 +19,6 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl it('행사 이름 입력 페이지에서 input이 포커싱 되어 있고, "다음" 버튼이 비활성화 되어 있어야 한다.', () => { cy.get('input').focused(); cy.get('button').contains('다음').should('have.attr', 'disabled'); - cy.url().should('include', '/event/create/name'); }); it('행사 이름이 1자 이상 입력된 경우 "다음" 버튼이 활성화 되고, 값이 없는 경우 "다음" 버튼이 비활성화 되어야 한다.', () => { @@ -28,13 +27,11 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl cy.get('input').clear(); cy.get('input').should('have.value', ''); cy.get('button').contains('다음').should('have.attr', 'disabled'); - cy.url().should('include', '/event/create/name'); }); it('행사 이름을 입력한 후 "다음" 버튼을 누르면 행사 비밀번호 설정 화면으로 이동해야 한다.', () => { cy.get('input').type(CONSTANTS.eventName); cy.get('button').contains('다음').click(); - cy.url().should('include', '/event/create/password'); }); }); @@ -46,7 +43,6 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl it('행사 비밀번호 입력 페이지에서 input이 포커싱 되어 있고, "행동 개시!" 버튼이 비활성화 되어 있어야 한다.', () => { cy.get('input').focused(); cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); - cy.url().should('include', '/event/create/password'); }); it('행사 비밀번호에 숫자가 아닌 입력을 할 경우 값이 입력되지 않아야 한다.', () => { @@ -65,7 +61,6 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl cy.get('input').clear(); cy.get('input').should('have.value', ''); cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); - cy.url().should('include', '/event/create/password'); }); it('행사 비밀번호을 입력한 후 "행동 개시!" 버튼을 누르면 행사 생성 완료 화면으로 이동해야 한다.', () => { @@ -73,8 +68,6 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl cy.interceptAPI({type: 'getEventName', statusCode: 200}); cy.get('input').type(CONSTANTS.eventPassword); cy.get('button').contains('행동 개시!').click(); - - cy.url().should('include', '/event/create/complete'); }); }); }); From 8f53bdf49de071a4e9fd1aebbd38df64d613c436 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 21:18:37 +0900 Subject: [PATCH 15/22] =?UTF-8?q?test:=20=ED=96=89=EC=82=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20url=EC=9D=B4=20=EB=B0=94=EB=80=90=20=EA=B2=83?= =?UTF-8?q?=EC=9D=84=20cypress=EC=97=90=EB=8F=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/cypress/e2e/createEvent.cy.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/cypress/e2e/createEvent.cy.ts b/client/cypress/e2e/createEvent.cy.ts index 98f1d8bdf..ddea3c3e1 100644 --- a/client/cypress/e2e/createEvent.cy.ts +++ b/client/cypress/e2e/createEvent.cy.ts @@ -1,3 +1,4 @@ +import {ROUTER_URLS} from '@constants/routerUrls'; import CONSTANTS from '../constants/constants'; beforeEach(() => { cy.blockSentry(); @@ -8,12 +9,12 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl it('랜딩페이지에서 "행사 생성하기" 버튼을 눌러 행사 이름 입력 페이지로 이동해야 한다.', () => { cy.visit('/'); cy.get('header').find('button').click(); - cy.url().should('include', '/event/create'); + cy.url().should('include', ROUTER_URLS.createEvent); }); context('행사 이름 입력 페이지', () => { beforeEach(() => { - cy.visit('/event/create/name'); + cy.visit(ROUTER_URLS.createEvent); }); it('행사 이름 입력 페이지에서 input이 포커싱 되어 있고, "다음" 버튼이 비활성화 되어 있어야 한다.', () => { From 39b70ffbe4300e3c74cd1bbddc28549f38602565 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 21:19:10 +0900 Subject: [PATCH 16/22] =?UTF-8?q?feat:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=B4=EC=A7=84=20=ED=96=89=EC=82=AC=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20path=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/constants/routerUrls.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/constants/routerUrls.ts b/client/src/constants/routerUrls.ts index a277136b7..ba1ed01ad 100644 --- a/client/src/constants/routerUrls.ts +++ b/client/src/constants/routerUrls.ts @@ -1,8 +1,7 @@ export const ROUTER_URLS = { main: '/', - createEvent: '/event/create/', - eventCreateComplete: '/event/create/complete', - event: '/event', // TODO: (@weadie) baseurl을 어떻게 관리할 것인가? + createEvent: '/event/create', + event: '/event', eventLogin: '/event/:eventId/login', eventManage: '/event/:eventId/admin', home: '/event/:eventId/home', From 359dfccaec85f379f4c3d860344bcb2ceab8b864 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 21:35:52 +0900 Subject: [PATCH 17/22] =?UTF-8?q?test:=20url=EC=9D=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EB=90=9C=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/cypress/support/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/cypress/support/commands.ts b/client/cypress/support/commands.ts index 70c8524ea..6ef75b4dc 100644 --- a/client/cypress/support/commands.ts +++ b/client/cypress/support/commands.ts @@ -1,3 +1,4 @@ +import {ROUTER_URLS} from '@constants/routerUrls'; import CONSTANTS from '../constants/constants'; type APIType = 'sentry' | 'postEvent' | 'getEventName'; @@ -46,10 +47,9 @@ Cypress.Commands.add('interceptAPI', ({type, delay = 0, statusCode = 200}: Inter }); Cypress.Commands.add('createEventName', (eventName: string) => { - cy.visit('/event/create/name'); + cy.visit(ROUTER_URLS.createEvent); cy.get('input').type(eventName); cy.get('button').contains('다음').click(); - cy.url().should('include', '/event/create/password'); }); declare global { From 1f4dc28a412c543530b517c28265a52a1acffc18 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Mon, 23 Sep 2024 21:36:03 +0900 Subject: [PATCH 18/22] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/cypress/e2e/createEvent.cy.ts | 64 +++++++++++++++------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/client/cypress/e2e/createEvent.cy.ts b/client/cypress/e2e/createEvent.cy.ts index ddea3c3e1..d86ce8d56 100644 --- a/client/cypress/e2e/createEvent.cy.ts +++ b/client/cypress/e2e/createEvent.cy.ts @@ -1,5 +1,6 @@ import {ROUTER_URLS} from '@constants/routerUrls'; import CONSTANTS from '../constants/constants'; + beforeEach(() => { cy.blockSentry(); cy.blockKakao(); @@ -33,42 +34,45 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl it('행사 이름을 입력한 후 "다음" 버튼을 누르면 행사 비밀번호 설정 화면으로 이동해야 한다.', () => { cy.get('input').type(CONSTANTS.eventName); cy.get('button').contains('다음').click(); + + // 다음 버튼을 클릭하면 /create/event 경로가 아니라 /create/event/?로 가네요.. 그래서 일단 제거함. + // cy.contains('비밀번호').should('exist'); }); }); - context('행사 비밀번호 입력 페이지', () => { - beforeEach(() => { - cy.createEventName(CONSTANTS.eventName); - }); + // context('행사 비밀번호 입력 페이지', () => { + // beforeEach(() => { + // cy.createEventName(CONSTANTS.eventName); + // }); - it('행사 비밀번호 입력 페이지에서 input이 포커싱 되어 있고, "행동 개시!" 버튼이 비활성화 되어 있어야 한다.', () => { - cy.get('input').focused(); - cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); - }); + // it('행사 비밀번호 입력 페이지에서 input이 포커싱 되어 있고, "행동 개시!" 버튼이 비활성화 되어 있어야 한다.', () => { + // cy.get('input').focused(); + // cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); + // }); - it('행사 비밀번호에 숫자가 아닌 입력을 할 경우 값이 입력되지 않아야 한다.', () => { - cy.get('input').type('테스트'); - cy.get('input').should('have.value', ''); - }); + // it('행사 비밀번호에 숫자가 아닌 입력을 할 경우 값이 입력되지 않아야 한다.', () => { + // cy.get('input').type('테스트'); + // cy.get('input').should('have.value', ''); + // }); - it('행사 비밀번호에 4자리 이상 입력을 할 경우 처음 네 자리만 입력되어야 한다.', () => { - cy.get('input').type('12345'); - cy.get('input').should('have.value', CONSTANTS.eventPassword); - }); + // it('행사 비밀번호에 4자리 이상 입력을 할 경우 처음 네 자리만 입력되어야 한다.', () => { + // cy.get('input').type('12345'); + // cy.get('input').should('have.value', CONSTANTS.eventPassword); + // }); - it('행사 비밀번호이 1자 이상 입력된 경우 "행동 개시!" 버튼이 활성화 되고, 값이 없는 경우 "행동 개시!" 버튼이 비활성화 되어야 한다.', () => { - cy.get('input').type(CONSTANTS.eventPassword); - cy.get('button').contains('행동 개시!').should('not.have.attr', 'disabled'); - cy.get('input').clear(); - cy.get('input').should('have.value', ''); - cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); - }); + // it('행사 비밀번호이 1자 이상 입력된 경우 "행동 개시!" 버튼이 활성화 되고, 값이 없는 경우 "행동 개시!" 버튼이 비활성화 되어야 한다.', () => { + // cy.get('input').type(CONSTANTS.eventPassword); + // cy.get('button').contains('행동 개시!').should('not.have.attr', 'disabled'); + // cy.get('input').clear(); + // cy.get('input').should('have.value', ''); + // cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); + // }); - it('행사 비밀번호을 입력한 후 "행동 개시!" 버튼을 누르면 행사 생성 완료 화면으로 이동해야 한다.', () => { - cy.interceptAPI({type: 'postEvent', statusCode: 200}); - cy.interceptAPI({type: 'getEventName', statusCode: 200}); - cy.get('input').type(CONSTANTS.eventPassword); - cy.get('button').contains('행동 개시!').click(); - }); - }); + // it('행사 비밀번호을 입력한 후 "행동 개시!" 버튼을 누르면 행사 생성 완료 화면으로 이동해야 한다.', () => { + // cy.interceptAPI({type: 'postEvent', statusCode: 200}); + // cy.interceptAPI({type: 'getEventName', statusCode: 200}); + // cy.get('input').type(CONSTANTS.eventPassword); + // cy.get('button').contains('행동 개시!').click(); + // }); + // }); }); From 9b204990cbed455f6b6f91d65072a1ad7f3a989e Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Tue, 24 Sep 2024 09:57:18 +0900 Subject: [PATCH 19/22] =?UTF-8?q?feat:=20=EC=A1=B0=EA=B1=B4=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=EC=9D=84=20=EB=A7=89=EB=8D=98=20=EA=B2=83?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=A1=B0=EA=B1=B4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useSetEventPasswordStep.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/useSetEventPasswordStep.ts b/client/src/hooks/useSetEventPasswordStep.ts index e88e0ec27..5705be855 100644 --- a/client/src/hooks/useSetEventPasswordStep.ts +++ b/client/src/hooks/useSetEventPasswordStep.ts @@ -20,11 +20,11 @@ const useSetEventPasswordStep = () => { eventName, setEventToken, }: { - event?: React.FormEvent<HTMLFormElement>; + event: React.FormEvent<HTMLFormElement>; eventName: string; setEventToken: (eventToken: string) => void; }) => { - if (event) event.preventDefault(); + event.preventDefault(); await postEvent(eventName, setEventToken); }; From 9f317eb782bac40527330459a325c657e42d4bb0 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Tue, 24 Sep 2024 09:57:46 +0900 Subject: [PATCH 20/22] =?UTF-8?q?feat:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=95=A8=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/CreateEventPage/SetEventPasswordStep.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/client/src/pages/CreateEventPage/SetEventPasswordStep.tsx b/client/src/pages/CreateEventPage/SetEventPasswordStep.tsx index ee45edf5b..c11c376f2 100644 --- a/client/src/pages/CreateEventPage/SetEventPasswordStep.tsx +++ b/client/src/pages/CreateEventPage/SetEventPasswordStep.tsx @@ -18,13 +18,7 @@ const SetEventPasswordStep = ({eventName, moveToNextStep, setEventToken}: SetEve const {submitDataForPostEvent, errorMessage, password, handleChange, isPostEventPending, canSubmit} = useSetEventPasswordStep(); - const submitOnEnter = async (event: React.KeyboardEvent<HTMLInputElement>) => { - if (event.key === 'Enter') { - submit(); - } - }; - - const submit = async (event?: React.FormEvent<HTMLFormElement>) => { + const submit = async (event: React.FormEvent<HTMLFormElement>) => { await submitDataForPostEvent({event, eventName, setEventToken}); moveToNextStep(); @@ -54,10 +48,9 @@ const SetEventPasswordStep = ({eventName, moveToNextStep, setEventToken}: SetEve onChange={handleChange} isError={!!errorMessage} autoFocus - onKeyDown={submitOnEnter} /> {/* 가상 키패드 적용 예정 */} - <FixedButton variants={isPostEventPending ? 'loading' : 'primary'} disabled={!canSubmit}> + <FixedButton type="submit" variants={isPostEventPending ? 'loading' : 'primary'} disabled={!canSubmit}> 행동 개시! </FixedButton> </form> From f92acbbdd85372c696eb8e92df7da4d9f2299621 Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Tue, 24 Sep 2024 09:58:58 +0900 Subject: [PATCH 21/22] =?UTF-8?q?feat:=20=ED=82=A4=EB=8B=A4=EC=9A=B4=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=EB=A5=BC=20=EC=97=86=EC=95=A0=20onS?= =?UTF-8?q?ubmit=EC=9C=BC=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/CreateEventPage/SetEventNameStep.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/src/pages/CreateEventPage/SetEventNameStep.tsx b/client/src/pages/CreateEventPage/SetEventNameStep.tsx index 2dd6d4a65..5653038ff 100644 --- a/client/src/pages/CreateEventPage/SetEventNameStep.tsx +++ b/client/src/pages/CreateEventPage/SetEventNameStep.tsx @@ -17,10 +17,10 @@ const SetEventNameStep = ({ handleEventNameChange, canSubmit, }: SetEventNamePageProps) => { - const moveToNextStepOnEnter = (event: React.KeyboardEvent<HTMLInputElement>) => { - if (event.key === 'Enter') { - moveToNextStep(); - } + const onSubmit = (event: React.FormEvent<HTMLFormElement>) => { + event.preventDefault(); + + moveToNextStep(); }; return ( @@ -36,7 +36,7 @@ const SetEventNameStep = ({ <Top.Line text="정산을 시작하려는" /> <Top.Line text="행사의 이름은 무엇인가요?" emphasize={['행사의 이름']} /> </Top> - <form onSubmit={moveToNextStep}> + <form onSubmit={onSubmit}> <LabelInput labelText="행사 이름" errorText={errorMessage ?? ''} @@ -46,7 +46,6 @@ const SetEventNameStep = ({ onChange={handleEventNameChange} isError={!!errorMessage} autoFocus - onKeyDown={moveToNextStepOnEnter} ></LabelInput> <FixedButton disabled={!canSubmit}>다음</FixedButton> </form> From e4e49c44ebc6600be328cacbf8bab98eabdd8b8b Mon Sep 17 00:00:00 2001 From: pakxe <pigkill40@naver.com> Date: Tue, 24 Sep 2024 09:59:11 +0900 Subject: [PATCH 22/22] =?UTF-8?q?test:=20=EC=A3=BC=EC=84=9D=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=ED=96=88=EB=8D=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/cypress/e2e/createEvent.cy.ts | 62 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/client/cypress/e2e/createEvent.cy.ts b/client/cypress/e2e/createEvent.cy.ts index d86ce8d56..edd4ddc6d 100644 --- a/client/cypress/e2e/createEvent.cy.ts +++ b/client/cypress/e2e/createEvent.cy.ts @@ -36,43 +36,43 @@ describe('Flow: 랜딩 페이지에서부터 이벤트를 생성 완료하는 fl cy.get('button').contains('다음').click(); // 다음 버튼을 클릭하면 /create/event 경로가 아니라 /create/event/?로 가네요.. 그래서 일단 제거함. - // cy.contains('비밀번호').should('exist'); + cy.contains('비밀번호').should('exist'); }); }); - // context('행사 비밀번호 입력 페이지', () => { - // beforeEach(() => { - // cy.createEventName(CONSTANTS.eventName); - // }); + context('행사 비밀번호 입력 페이지', () => { + beforeEach(() => { + cy.createEventName(CONSTANTS.eventName); + }); - // it('행사 비밀번호 입력 페이지에서 input이 포커싱 되어 있고, "행동 개시!" 버튼이 비활성화 되어 있어야 한다.', () => { - // cy.get('input').focused(); - // cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); - // }); + it('행사 비밀번호 입력 페이지에서 input이 포커싱 되어 있고, "행동 개시!" 버튼이 비활성화 되어 있어야 한다.', () => { + cy.get('input').focused(); + cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); + }); - // it('행사 비밀번호에 숫자가 아닌 입력을 할 경우 값이 입력되지 않아야 한다.', () => { - // cy.get('input').type('테스트'); - // cy.get('input').should('have.value', ''); - // }); + it('행사 비밀번호에 숫자가 아닌 입력을 할 경우 값이 입력되지 않아야 한다.', () => { + cy.get('input').type('테스트'); + cy.get('input').should('have.value', ''); + }); - // it('행사 비밀번호에 4자리 이상 입력을 할 경우 처음 네 자리만 입력되어야 한다.', () => { - // cy.get('input').type('12345'); - // cy.get('input').should('have.value', CONSTANTS.eventPassword); - // }); + it('행사 비밀번호에 4자리 이상 입력을 할 경우 처음 네 자리만 입력되어야 한다.', () => { + cy.get('input').type('12345'); + cy.get('input').should('have.value', CONSTANTS.eventPassword); + }); - // it('행사 비밀번호이 1자 이상 입력된 경우 "행동 개시!" 버튼이 활성화 되고, 값이 없는 경우 "행동 개시!" 버튼이 비활성화 되어야 한다.', () => { - // cy.get('input').type(CONSTANTS.eventPassword); - // cy.get('button').contains('행동 개시!').should('not.have.attr', 'disabled'); - // cy.get('input').clear(); - // cy.get('input').should('have.value', ''); - // cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); - // }); + it('행사 비밀번호이 1자 이상 입력된 경우 "행동 개시!" 버튼이 활성화 되고, 값이 없는 경우 "행동 개시!" 버튼이 비활성화 되어야 한다.', () => { + cy.get('input').type(CONSTANTS.eventPassword); + cy.get('button').contains('행동 개시!').should('not.have.attr', 'disabled'); + cy.get('input').clear(); + cy.get('input').should('have.value', ''); + cy.get('button').contains('행동 개시!').should('have.attr', 'disabled'); + }); - // it('행사 비밀번호을 입력한 후 "행동 개시!" 버튼을 누르면 행사 생성 완료 화면으로 이동해야 한다.', () => { - // cy.interceptAPI({type: 'postEvent', statusCode: 200}); - // cy.interceptAPI({type: 'getEventName', statusCode: 200}); - // cy.get('input').type(CONSTANTS.eventPassword); - // cy.get('button').contains('행동 개시!').click(); - // }); - // }); + it('행사 비밀번호을 입력한 후 "행동 개시!" 버튼을 누르면 행사 생성 완료 화면으로 이동해야 한다.', () => { + cy.interceptAPI({type: 'postEvent', statusCode: 200}); + cy.interceptAPI({type: 'getEventName', statusCode: 200}); + cy.get('input').type(CONSTANTS.eventPassword); + cy.get('button').contains('행동 개시!').click(); + }); + }); });