Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: 로그인/회원가입 폼에 react-hook-form 적용 #223

Merged
merged 10 commits into from
Jun 23, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export const AgreeTerms = ({ onConfirm }: AgreeTermsProps) => {
})}
</StyledDetailTermList>
<BoxButton
type="button"
size="large"
rounding={8}
variant="filled"
Expand Down
4 changes: 3 additions & 1 deletion src/home/components/SignupContents/EmailAuth/EmailAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const EmailAuth = ({ email, onConfirm }: EmailAuthProps) => {
{error && <StyledErrorText>{error}</StyledErrorText>}
</div>
<BoxButton
type="button"
rounding={8}
size="large"
variant="filled"
Expand All @@ -51,6 +52,7 @@ export const EmailAuth = ({ email, onConfirm }: EmailAuthProps) => {
</BoxButton>
<StyledPlainButtonContainer>
<PlainButton
type="button"
size="medium"
isPointed={false}
isWarned={false}
Expand All @@ -61,7 +63,7 @@ export const EmailAuth = ({ email, onConfirm }: EmailAuthProps) => {
</PlainButton>
<span>|</span>
<Link to="https://outlook.office.com/mail/" target="_blank" rel="noopener noreferrer">
<PlainButton size="medium" isPointed={false} isWarned={false}>
<PlainButton type="button" size="medium" isPointed={false} isWarned={false}>
학교 메일 열기
</PlainButton>
</Link>
Expand Down
7 changes: 5 additions & 2 deletions src/home/components/SignupContents/EmailForm/EmailForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,21 @@ export const EmailForm = ({ onConfirm }: EmailFormProps) => {
</div>
<StyledButtonsContainer>
<StyledPlainButtonWrapper>
<PlainButton size="medium" isPointed={false} isWarned={false}>
<PlainButton type="button" size="medium" isPointed={false} isWarned={false}>
<StyledSignupButtonText>학교 메일 찾기</StyledSignupButtonText>
</PlainButton>
</StyledPlainButtonWrapper>
<BoxButton
type="submit"
size="large"
variant="filled"
rounding={8}
disabled={email === '' || disabled}
onClick={() => handleClick(onEmailSubmit)}
>
<StyledSignupButtonText>인증 메일 받기</StyledSignupButtonText>
<StyledSignupButtonText>
{disabled ? '잠시만 기다려주세요...' : '인증 메일 받기'}
</StyledSignupButtonText>
</BoxButton>
</StyledButtonsContainer>
</StyledSignupContentContainer>
Expand Down
5 changes: 3 additions & 2 deletions src/home/components/SignupContents/EmailForm/useEmailForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import { useState } from 'react';

import { postAuthVerificationEmail } from '@/home/apis/authVerification.ts';
import { EmailFormProps } from '@/home/components/SignupContents/EmailForm/EmailForm.type.ts';
import { useFullEmail } from '@/hooks/useFullEmail';
import { useParseFullEmail } from '@/hooks/useParseFullEmail';

export const useEmailForm = ({ onConfirm }: EmailFormProps) => {
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState<string | undefined>(undefined);
const fullEmail = useFullEmail(email);
const parseFullEmail = useParseFullEmail();

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};

const onEmailSubmit = async () => {
const fullEmail = parseFullEmail(email);
const res = await postAuthVerificationEmail({ email: fullEmail, verificationType: 'SIGN_UP' });
if (res.data) {
onConfirm(fullEmail);
Expand Down
2 changes: 1 addition & 1 deletion src/home/components/SignupContents/SignupContents.style.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from 'styled-components';

export const StyledSignupContentContainer = styled.div`
export const StyledSignupContentContainer = styled.form`
width: 100%;
display: flex;
flex-direction: column;
Expand Down
2 changes: 1 addition & 1 deletion src/home/components/SignupContents/SignupEnd/SignupEnd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const SignupEnd = () => {
<StyledSignupContentContainer>
<StyledSignupContentTitle>회원가입이 완료되었습니다</StyledSignupContentTitle>
<StyledLinkWrapper to="/login">
<BoxButton rounding={8} size="large" variant="filled">
<BoxButton type="button" rounding={8} size="large" variant="filled">
<StyledSignupButtonText>로그인 페이지로 이동</StyledSignupButtonText>
</BoxButton>
</StyledLinkWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ export interface SignupFormProps {
onConfirm: () => void;
email: string;
}

export interface SignupFormStates {
nickname: string;
password: string;
}
56 changes: 33 additions & 23 deletions src/home/components/SignupContents/SignupForm/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,71 @@
import { BoxButton, PasswordTextField, SimpleTextField } from '@yourssu/design-system-react';
import { useForm } from 'react-hook-form';

import { SignupFormProps } from '@/home/components/SignupContents/SignupForm/SignUpForm.type.ts';
import { useSignUpForm } from '@/home/components/SignupContents/SignupForm/useSignUpForm.ts';
import { usePreventDuplicateClick } from '@/hooks/usePreventDuplicateClick.ts';
import { useSignupFormValidation } from '@/hooks/useSignupFormValidator.ts';
import {
SignupFormProps,
SignupFormStates,
} from '@/home/components/SignupContents/SignupForm/SignUpForm.type.ts';
import { useSignupFormValidation } from '@/hooks/useSignupFormValidation';

import {
StyledSignupButtonText,
StyledSignupContentContainer,
StyledSignupContentTitle,
} from '../SignupContents.style';

import { useSignupFormConfirm } from './useSignupFormConfirm';

export const SignupForm = ({ email, onConfirm }: SignupFormProps) => {
const { nickname, password, onFormConfirm, setNickname, setPassword } = useSignUpForm({
email,
onConfirm,
const { register, handleSubmit, setValue, watch, formState } = useForm<SignupFormStates>({
defaultValues: {
nickname: '',
password: '',
},
});

const { nicknameValidOnce, passwordValidOnce, isFormValid, isNicknameValid, isPasswordValid } =
useSignupFormValidation(nickname, password);
const onFormConfirm = useSignupFormConfirm({ email, onConfirm });

const { disabled, handleClick } = usePreventDuplicateClick();
const { nicknameValidOnce, passwordValidOnce, isFormValid, isNicknameValid, isPasswordValid } =
useSignupFormValidation({
nickname: watch('nickname'),
password: watch('password'),
});
Comment on lines -15 to +32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/react-hook-form/resolvers RHF에 resolver를 사용할 수도 있습니다 ㅎㅎ


return (
<StyledSignupContentContainer>
<StyledSignupContentContainer onSubmit={handleSubmit(onFormConfirm)}>
<StyledSignupContentTitle>회원가입</StyledSignupContentTitle>
<SimpleTextField
value={nickname}
{...register('nickname', {
onChange: () => {
if (isNicknameValid) nicknameValidOnce.current = true;
},
})}
fieldLabel="사용할 닉네임을 입력해주세요."
helperLabel="한글, 영어, 숫자를 사용해 2~12자로 입력해주세요"
placeholder="ppushoong"
isNegative={!isNicknameValid && nicknameValidOnce.current}
onChange={(e) => {
if (isNicknameValid) nicknameValidOnce.current = true;
setNickname(e.target.value);
}}
onClickClearButton={() => {
nicknameValidOnce.current = false;
setNickname('');
setValue('nickname', '');
}}
/>
<PasswordTextField
{...register('password', {
onChange: () => {
if (isPasswordValid) passwordValidOnce.current = true;
},
})}
fieldLabel="사용할 비밀번호를 입력해주세요."
helperLabel="숫자, 영문자, 특수문자 조합으로 8자 이상 입력해주세요"
placeholder="비밀번호"
isNegative={!isPasswordValid && passwordValidOnce.current}
onChange={(e) => {
if (isPasswordValid) passwordValidOnce.current = true;
setPassword(e.target.value);
}}
/>
<BoxButton
type="submit"
rounding={8}
size="large"
variant="filled"
onClick={() => handleClick(onFormConfirm)}
disabled={!isFormValid || disabled}
disabled={!isFormValid || formState.isSubmitting}
>
<StyledSignupButtonText>회원가입</StyledSignupButtonText>
</BoxButton>
Expand Down
43 changes: 0 additions & 43 deletions src/home/components/SignupContents/SignupForm/useSignUpForm.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { AxiosError } from 'axios';
import { SubmitHandler } from 'react-hook-form';

import { STORAGE_KEYS } from '@/constants/storage.constant';
import { postAuthSignUp } from '@/home/apis/postAuthSignUp';
import { AuthErrorData } from '@/home/types/Auth.type';

import { SignupFormProps, SignupFormStates } from './SignUpForm.type';

export const useSignupFormConfirm = ({ email, onConfirm }: SignupFormProps) => {
const onSignupError = (error: AxiosError) => {
alert(
(error as AxiosError<AuthErrorData>).response?.data.message || '회원가입에 실패했습니다.'
);
};

const onSignupSuccess = () => {
sessionStorage.removeItem(STORAGE_KEYS.EMAIL_AUTH_SESSION_TOKEN);
sessionStorage.removeItem(STORAGE_KEYS.EMAIL_AUTH_SESSION_TOKEN_EXPIRED_IN);
onConfirm();
};

const onFormConfirm: SubmitHandler<SignupFormStates> = async ({ nickname, password }) => {
const sessionToken = sessionStorage.getItem(STORAGE_KEYS.EMAIL_AUTH_SESSION_TOKEN);

if (!sessionToken) {
alert('세션 토큰이 없습니다.');
return;
}

const signUpParams = {
nickName: nickname,
password: password,
sessionToken: sessionToken,
email: email,
};

const { data, error } = await postAuthSignUp(signUpParams);

if (data) onSignupSuccess();
else if (error) onSignupError(error);
};

return onFormConfirm;
};
2 changes: 1 addition & 1 deletion src/home/pages/Login/Login.style.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from 'styled-components';

export const StyledLoginContainer = styled.div`
export const StyledLoginContainer = styled.form`
width: 100%;

display: flex;
Expand Down
Loading