const validateEmail = (email: string) => {
const emailRegex = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/;
return emailRegex.test(email);
};
const validatePassword = (password: string) => {
const passwordPattern = /^(?=.*\d)(?=.*[a-zA-Z]).{8,}$/;
return passwordPattern.test(password);
};
const validateNickname = (nickname: string) => {
const nicknamePattern = /^.{2,8}$/;
return nicknamePattern.test(nickname);
};
export interface Signup {
email: string;
password: string;
passwordConfirm: string;
nickname: string;
}
export interface Error {
email: string;
password: string;
passwordConfirm: string;
nickname: string;
checkbox: string;
}
export interface CheckBoxInterface {
checkAll: boolean;
checkTerms: boolean;
checkPersonalInfo: boolean;
checkNewsletter: boolean;
}
const SignUpUi = () => {
const router: NextRouter = useRouter();
const [signUpState, setSignUpState] = useState<Signup>({
email: '',
password: '',
passwordConfirm: '',
nickname: '',
});
const [error, setError] = useState<Error>({
email: '',
password: '',
passwordConfirm: '',
nickname: '',
checkbox: '',
});
const [checkboxes, setCheckboxes] = useState<CheckBoxInterface>({
checkAll: false,
checkTerms: false,
checkPersonalInfo: false,
checkNewsletter: false,
});
const [checkboxErrorMessage, setCheckboxErrorMessage] = useState('');
const [serverError, setServerError] = useState<string>('');
const [serverNicknameError, setServerNicknameError] = useState<string>('');
const getForgotPwd = async (email: string) => {
const response = await axios.get(`http://localhost:3001/api/auth/${email}`);
return response.data;
};
const addUserMutation = useMutation(addUser, {
onSuccess: () => {
router.push('/signUpComplete/SignUpComplete');
},
onError: (error: any) => {
const message = error?.response?.data.errorMessage;
if (message) {
setServerError(message);
} else {
alert(
'회원가입에 실패하였습니다. 아이디 혹은 비밀번호를 다시 한번 확인해주세요'
);
}
if (
error?.response?.data.errorMessage === '이미 존재하는 닉네임입니다.'
) {
setServerNicknameError(error?.response?.data.errorMessage);
}
},
});
const moveSignInPageHandeler = useCallback(() => {
router.push('/auth/SignIn');
}, [router]);
const moveHomePageHandeler = useCallback(() => {
router.push('/');
}, [router]);
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement & { name: string }>
) => {
setSignUpState({
...signUpState,
[e.target.name]: e.target.value,
});
setError({ ...error, [e.target.name]: '' });
};
const handleCheckboxChange = useCallback(
(
e: React.ChangeEvent<HTMLInputElement & { name: keyof CheckBoxInterface }>
) => {
const { name, checked } = e.target;
setCheckboxes((prevState) => {
const newState = {
...prevState,
[name]: checked,
};
if (name === 'checkAll') {
newState.checkTerms = checked;
newState.checkPersonalInfo = checked;
newState.checkNewsletter = false;
} else {
newState.checkAll =
newState.checkTerms &&
newState.checkPersonalInfo &&
newState.checkNewsletter;
}
if (
newState.checkTerms ||
newState.checkPersonalInfo ||
newState.checkNewsletter
) {
setCheckboxErrorMessage('');
} else {
setCheckboxErrorMessage('필수 동의사항에 체크해주세요.');
}
return newState;
});
},
[]
);
const submitUser = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
let errors: any = {};
if (!signUpState.email) {
errors.email = '이메일 형식이 올바르지 않습니다.';
} else if (!validateEmail(signUpState.email)) {
errors.email = '이메일을 확인해주세요.';
}
if (
!signUpState.password &&
signUpState.password !== signUpState.passwordConfirm
) {
errors.password = '비밀번호를 확인해주세요';
} else if (!validatePassword(signUpState.password)) {
errors.password = '비밀번호를 확인해주세요';
}
if (!signUpState.nickname) {
errors.nickname = '닉네임을 입력해주세요.';
} else if (!validateNickname(signUpState.nickname)) {
errors.nickname = '2~8자를 입력해주세요.';
}
if (!checkboxes.checkTerms || !checkboxes.checkPersonalInfo) {
setCheckboxErrorMessage('필수 동의사항에 체크해주세요.');
return;
}
setCheckboxErrorMessage('');
if (Object.keys(errors).length > 0) {
setError(errors);
return;
} else {
setError({
email: '',
password: '',
passwordConfirm: '',
nickname: '',
checkbox: '',
});
}
if (Object.keys(errors).length === 0) {
const sendData = {
email: signUpState.email,
password: signUpState.password,
nickname: signUpState.nickname,
};
addUserMutation.mutate(sendData);
}
};
return (
<SignUpSection>
<MainHeadText onClick={moveHomePageHandeler}>HAPOOM</MainHeadText>
<SubHeadText color="#000" $marginBottom="12px">
회원가입
</SubHeadText>
{/* <SocialLogin /> */}
{/* <TextParagraphSns>SNS계정으로 간편 로그인/회원가입</TextParagraphSns> */}
<SnsLine></SnsLine>
<form name="register" onSubmit={submitUser}>
<StyledInputBox>
<TextParagraphInfo $marginBottom="12px">이메일</TextParagraphInfo>
<StyledInput
type="email"
name="email"
value={signUpState.email}
placeholder="example@gmail.com"
onChange={handleInputChange}
/>
{error.email && (
<TextErrorParagraph>{error.email}</TextErrorParagraph>
)}
{serverError && (
<TextErrorParagraph>{serverError}</TextErrorParagraph>
)}
{/* <SignUpBtn
style={{
margin: '8px 0 20px 0',
backgroundColor: signUpState.email ? '#0078FF' : '#B3B3B3',
borderColor: signUpState.email ? '#0078FF' : '#B3B3B3',
}}
onClick={handleEmailValidateSubmit}
disabled={!signUpState.email}
>
이메일 인증하기
</SignUpBtn> */}
</StyledInputBox>
<SignUpPwd
signUpState={signUpState}
handleInputChange={handleInputChange}
error={{
password: error.password,
passwordConfirm: error.passwordConfirm,
}}
/>
<SignUpNickname
signUpState={signUpState}
handleInputChange={handleInputChange}
error={{
nickname: error.nickname,
}}
/>
{serverNicknameError && (
<TextErrorParagraph>{serverNicknameError}</TextErrorParagraph>
)}
<SignUpCheck
checkboxErrorMessage={checkboxErrorMessage}
checkboxes={checkboxes}
handleCheckboxChange={handleCheckboxChange}
/>
<SignUpcontrol signUpState={signUpState} />
</form>
<SubHeadText
color="#0084FF"
style={{ cursor: 'pointer' }}
onClick={moveSignInPageHandeler}
>
이미 아이디가 있으신가요? 로그인
</SubHeadText>
<MobileBottomNav />
</SignUpSection>
);
};
export default React.memo(SignUpUi);
상태관리는 useState와 함수 메모이제이션을 위해 useCallback을 사용
입력된 이메일, 비밀번호, 닉네임이 유효한지 확인하기
위해 정규 표현식을 사용하는 유효성 검사 함수생성
각각의 필드가 올바르게 채워져 있는지 확인하고 있으며
만약 잘못된 값이 있다면 오류 메시지를 설정해서 출력설정
서버통신으로는 react-query 라이브러리의 useMutation 훅을 사용하여 서버와 비동기 통신.
성공적으로 회원 가입하면 /signUpComplete/SignUpComplete 경로로 리다이렉션하고,
실패하면 오류 메시지를 설정
체크박스 관련 로직 설정.
페이지 전환 처리 함수 정의정도로 여기 컴포넌트는 정의가 된다.
줄인다고 줄여봤는데 뭔가 긴것 같은 코드 ㅠㅠ
분리 된 한 컴포넌트만 더 적어보자면
비밀번호와 비밀번호 확인 필드를 처리하는데 사용되는 컴포넌트.
signUpState, handleInputChange, error 세 가지 props를 받음
signUpState: 상위 컴포넌트에서 관리되는 상태로, 사용자가 입력한 비밀번호와 비밀번호 확인 값을 포함
handleInputChange: 입력 필드의 값이 변경될 때 호출되는 함수
이 함수는 상위 컴포넌트에서 전달되며, 내부적으로 상태를 업데이트
error: 각 필드에 대한 오류 메시지를 담고 있는 객체입니다.
두 가지 로컬 상태를 관리하는데 passwordInputType과 passwordConfirmInputType.
이들은 각각 비밀번호와 비밀번호 확인 입력 필드의 타입을 결정하고,
'password' 또는 'text' 값을 가짐
비밀번호 가시성 토글 기능
사용자가 아이콘을 클릭하면 비밀번호 필드의 타입이 토글.
'password' 타입일 경우 텍스트가 마스크 처리되어 보이지 않으며,
'text' 타입일 경우 실제 입력된 텍스트가 그대로 보임
const togglePasswordVisibility = useCallback(() => {
setPasswordInputType(
passwordInputType === 'password' ? 'text' : 'password'
);
}, [passwordInputType]);
const togglePasswordConfirmVisibility = useCallback(() => {
setPasswordConfirmInputType(
passwordConfirmInputType === 'password' ? 'text' : 'password'
);
}, [passwordConfirmInputType]);
'WebDev > 항해99' 카테고리의 다른 글
실전프로젝트 세팅(인풋컴포넌트) (0) | 2023.09.05 |
---|---|
실전프로젝트 세팅컴포넌트 (0) | 2023.09.04 |
실전프로젝트 로그인 로직 (0) | 2023.09.01 |
실전프로젝트 피드 로직 (0) | 2023.08.31 |
실전프로젝트 인피니티 스크롤 (0) | 2023.08.29 |