dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

commit bd878e4847802fb013b70a34b923a2206c2e4594
parent 345c460e6dccdf81e61982e8f869f73c8d025985
Author: Yongbin Kim <iam@yongbin.kim>
Date:   Mon, 30 Jan 2023 12:05:57 +0900

fix: 회원가입 폼 제대로 동작하지 않던 문제 수정

Signed-off-by: Yongbin Kim <iam@yongbin.kim>

Diffstat:
Mlib/hooks/use_form.ts | 2++
Mpages/users/signup/[id].tsx | 88+++++++++++++++++++++++++++++++------------------------------------------------
Mpages/users/signup/index.tsx | 54++++++++++++++++++++----------------------------------
3 files changed, 56 insertions(+), 88 deletions(-)

diff --git a/lib/hooks/use_form.ts b/lib/hooks/use_form.ts @@ -49,6 +49,7 @@ export const useForm = <T extends FormFields, Result = unknown> ( input: string | { method: string, url: string | URL }, initial: T, onSuccess?: (result: Result) => void, + onError?: (status: number, error: ApiError) => void, ): [fields: T, updateFields: UpdateFieldOrDispatch<T>, submit: () => void, isLoading: boolean, result: Result | null, error: ApiError | null] => { const [fields, updateFields] = useFields<T>(initial) @@ -79,6 +80,7 @@ export const useForm = <T extends FormFields, Result = unknown> ( if (!res.ok) { setError(data) + onError?.(res.status, data) return } diff --git a/pages/users/signup/[id].tsx b/pages/users/signup/[id].tsx @@ -7,6 +7,7 @@ import Container from '@/components/layout/Container' import Hero from '@/components/layout/Hero' import Section from '@/components/layout/Section' import { isApiError } from '@/lib/apierror' +import { useForm } from '@/lib/hooks/use_form' import { getUnconfirmedSignupRequest } from '@/lib/models/signup_request' import { GetServerSideProps } from 'next' import { useRouter } from 'next/router' @@ -46,28 +47,23 @@ export default function SignUpCompletePage (props: PageProps) { const router = useRouter() const requestId = router.query.id as string - const [password, setPassword] = useState('') - const [passwordError, setPasswordError] = useState<string | null>(null) const [passwordRepeat, setPasswordRepeat] = useState('') + + const [passwordError, setPasswordError] = useState<string | null>(null) const [passwordRepeatError, setPasswordRepeatError] = useState<string | null>(null) - const [nickname, setNickname] = useState('') const [nicknameError, setNicknameError] = useState<string | null>(null) - const [isLoading, setLoading] = useState(false) - // 비밀번호 확인 오류 표시 - useEffect(() => { - if (passwordRepeat?.length === 0) { - return - } - setPasswordRepeatError( - password !== passwordRepeat - ? 'password-unmatched' - : null, - ) - }, [password, passwordRepeat]) - - const handleSubmit = useCallback(() => { - const handleError = (status: number, err: any) => { + const [fields, updateFields, submit, isLoading] = useForm( + { method: 'POST', url: `/api/users/signup/${requestId}` }, + { + password: '', + nickname: '', + }, + () => { + router.push('/users/login') + .catch(console.error) + }, + (status, err) => { if (!isApiError(err)) { setPasswordError('알 수 없는 오류가 발생했습니다.') return @@ -97,43 +93,23 @@ export default function SignUpCompletePage (props: PageProps) { setPasswordError('알 수 없는 오류가 발생했습니다.') } + ) - setLoading(false) - setPasswordError(null) - setNicknameError(null) - if (password !== passwordRepeat) { - setPasswordRepeatError('비밀번호가 일치하지 않습니다.') + // 비밀번호 확인 오류 표시 + useEffect(() => { + if (passwordRepeat?.length === 0) { return } - - !(async () => { - const res = await fetch(`/api/users/signup/${requestId}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - password, - nickname, - }), - }) - - if (res.ok) { - await router.push('/users/login') - return - } - - const err = await res.json() - handleError(res.status, err) - })().catch((err) => { - console.error(err) - setPasswordError('알 수 없는 오류가 발생했습니다.') - }) - }, []) + setPasswordRepeatError( + fields.password !== passwordRepeat + ? '비밀번호가 일치하지 않습니다.' + : null, + ) + }, [fields.password, passwordRepeat]) return ( - <Form onSubmit={handleSubmit}> + <Form onSubmit={submit}> <Hero> <Title kind="headline">Sign up</Title> </Hero> @@ -144,20 +120,23 @@ export default function SignUpCompletePage (props: PageProps) { <Field type="text" placeholder="Email" + disabled={isLoading} readOnly value={props.email} /> <Field type="password" placeholder="Password" + disabled={isLoading} color={passwordError ? 'error' : undefined} message={passwordError ?? undefined} - value={password} - onValueChange={setPassword} + value={fields.password} + onValueChange={updateFields.bind(null, 'password')} /> <Field type="password" placeholder="Password (Repeat)" + disabled={isLoading} color={passwordRepeatError ? 'error' : undefined} message={passwordRepeatError ?? undefined} value={passwordRepeat} @@ -166,17 +145,18 @@ export default function SignUpCompletePage (props: PageProps) { <Field type="text" placeholder="Nickname" + disabled={isLoading} color={nicknameError ? 'error' : undefined} message={nicknameError ?? undefined} - value={nickname} - onValueChange={setNickname} + value={fields.nickname} + onValueChange={updateFields.bind(null, 'nickname')} /> </Fields> <SubmitButton color="primary" disabled={isLoading} - value={'Sign up'} + value="Sign up" /> </Section> </Container> diff --git a/pages/users/signup/index.tsx b/pages/users/signup/index.tsx @@ -6,8 +6,13 @@ import Form from '@/components/form/Form' import Container from '@/components/layout/Container' import Hero from '@/components/layout/Hero' import Section from '@/components/layout/Section' +import { useForm } from '@/lib/hooks/use_form' import { useRouter } from 'next/router' -import { useCallback, useState } from 'react' +import { useState } from 'react' + +interface SignUpFormFields { + email: string +} export default function SignUpPage () { return ( @@ -27,12 +32,14 @@ export default function SignUpPage () { function SignUpForm () { const router = useRouter() - const [email, setEmail] = useState('') - const [isLoading, setLoading] = useState(false) - const [errorMessage, setErrorMessage] = useState<string | null>(null) - - const handleSubmit = useCallback(() => { - const handleError = (status: number) => { + const [fields, updateFields, submit, isLoading] = useForm<SignUpFormFields>( + { method: 'POST', url: '/api/users/signup' }, + { email: '' }, + () => { + router.push('/users/signup/sent') + .catch(console.error) + }, + (status) => { switch (status) { case 401: setErrorMessage('이메일 혹은 비밀번호가 잘못되었습니다.') @@ -42,42 +49,21 @@ function SignUpForm () { setErrorMessage('알 수 없는 오류가 발생했습니다.') break } - } - - setLoading(true) - setErrorMessage(null) - - !(async () => { - const res = await fetch('/api/users/signup', { - method: 'POST', - body: JSON.stringify({ email }), - }) - - if (!res.ok) { - handleError(res.status) - setLoading(false) - return - } - - await router.push('/users/signup/sent') - })().catch((err) => { - console.error(err) - setErrorMessage('알 수 없는 오류가 발생했습니다.') - setLoading(false) - }) - }, [router, email]) + }, + ) + const [errorMessage, setErrorMessage] = useState<string | null>(null) return ( - <Form onSubmit={handleSubmit}> + <Form onSubmit={submit}> <Fields> <Field type="text" disabled={isLoading} placeholder="Email" color={errorMessage == null ? undefined : 'error'} - value={email} + value={fields.email} message={errorMessage ?? undefined} - onValueChange={setEmail} + onValueChange={updateFields.bind(null, 'email')} /> </Fields>