Skip to content

Commit

Permalink
[FE] fix: 비밀번호 검증 훅 분리 및 비밀번호 길이 유효성 검사 문구 추가 (#434)
Browse files Browse the repository at this point in the history
* refactor: Input 컴포넌트에 onBlur 이벤트 추가

* refactor: 비밀번호 Input에 onBlur 이벤트를 추가해 길이 유효성 검증 에러 문구 출력

* fix: 잘못된 useEffect 의존성 배열 값 수정

* refactor: 비밀번호 검증 로직 훅 분리

* refactor: early return으로 리팩토링

* refactor: boolean형 변수 이름을 보다 명확하게 변경

* fix: 안내 문구를 -요 체로 수정

* chore: 불필요한 주석 제거

* fix: 안내 문구를 -요 체로 수정

* refactor: 비밀번호 유효성 검사 여부를 기존의 유틸 함수를 이용해 검증하도록 수정

* refactor: URL 생성 폼의 타이틀 수정
  • Loading branch information
ImxYJL authored Aug 20, 2024
1 parent 67fc09e commit 51312e1
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 29 deletions.
4 changes: 3 additions & 1 deletion frontend/src/components/common/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@ export interface InputStyleProps {
interface InputProps extends InputStyleProps {
value: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
onBlur?: () => void;
type: string;
id?: string;
name?: string;
placeholder?: string;
}

const Input = ({ id, value, name, onChange, type, placeholder, $style }: InputProps) => {
const Input = ({ id, value, name, onChange, onBlur, type, placeholder, $style }: InputProps) => {
return (
<S.Input
id={id}
value={value}
type={type}
name={name}
onChange={onChange}
onBlur={onBlur}
placeholder={placeholder}
style={$style}
/>
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/hooks/usePasswordValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useState, useEffect } from 'react';

import {
isWithinLengthRange,
isAlphanumeric,
MAX_PASSWORD_INPUT,
MIN_PASSWORD_INPUT,
} from '@/pages/HomePage/utils/validateInput';

const INVALID_CHAR_ERROR_MESSAGE = `영문(대/소문자) 및 숫자만 입력해주세요`;
const PASSWORD_LENGTH_ERROR_MESSAGE = `${MIN_PASSWORD_INPUT}자부터 ${MAX_PASSWORD_INPUT}자까지 입력할 수 있어요`;

export const usePasswordValidation = (password: string) => {
const [passwordErrorMessage, setPasswordErrorMessage] = useState('');
const [isBlurredOnce, setIsBlurredOnce] = useState(false);

const validatePassword = () => {
if (!isWithinLengthRange(password, MAX_PASSWORD_INPUT, MIN_PASSWORD_INPUT)) {
return setPasswordErrorMessage(PASSWORD_LENGTH_ERROR_MESSAGE);
}
if (!isAlphanumeric(password)) {
return setPasswordErrorMessage(INVALID_CHAR_ERROR_MESSAGE);
}
return setPasswordErrorMessage('');
};

const handlePasswordBlur = () => {
setIsBlurredOnce(true);
validatePassword();
};

useEffect(() => {
if (isBlurredOnce) validatePassword();
}, [password, isBlurredOnce]);

return {
passwordErrorMessage,
handlePasswordBlur,
};
};
40 changes: 12 additions & 28 deletions frontend/src/pages/HomePage/components/URLGeneratorForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,22 @@ import { debounce } from '@/utils/debounce';

import usePostDataForReviewRequestCode from '../../queries/usePostDataForReviewRequestCode';
import {
isNotEmptyInput,
isValidReviewGroupDataInput,
isWithinLengthRange,
isAlphanumeric,
MAX_PASSWORD_INPUT,
MAX_VALID_REVIEW_GROUP_DATA_INPUT,
MIN_PASSWORD_INPUT,
isValidPasswordInput,
} from '../../utils/validateInput';
import { FormLayout, ReviewZoneURLModal } from '../index';

import { usePasswordValidation } from './../../../../hooks/usePasswordValidation';
import * as S from './styles';

// TODO: 디바운스 시간을 모든 경우에 0.3초로 고정할 것인지(전역 상수로 사용) 논의하기
const DEBOUNCE_TIME = 300;

const INVALID_CHAR_ERROR_MESSAGE = `영문(대/소문자) 및 숫자만 입력할 수 있습니다`;
const GROUP_DATA_LENGTH_ERROR_MESSAGE = `최대 ${MAX_VALID_REVIEW_GROUP_DATA_INPUT}자까지 입력할 수 있습니다.`;
const PASSWORD_LENGTH_ERROR_MESSAGE = `${MIN_PASSWORD_INPUT}자부터 ${MAX_PASSWORD_INPUT}자까지 입력할 수 있습니다.`;
const GROUP_DATA_LENGTH_ERROR_MESSAGE = `최대 ${MAX_VALID_REVIEW_GROUP_DATA_INPUT}자까지 입력할 수 있어요`;

const MODAL_KEYS = {
confirm: 'CONFIRM',
Expand All @@ -35,24 +33,26 @@ const MODAL_KEYS = {
const URLGeneratorForm = () => {
const [revieweeName, setRevieweeName] = useState('');
const [projectName, setProjectName] = useState('');
// NOTE: 이 password는 groupAccessCode로 사용됨.
// NOTE: 이 password는 groupAccessCode로 사용됩니다.
// groupAccessCode로 통일하기로 했지만 이미 이 페이지에서는 pwd로 작업한 게 많아서 놔두고
// API 요청 함수와 리액트 쿼리 코드에서는 groupAccessCode: password로 전달합니다
// API 요청 함수와 리액트 쿼리 코드에서는 groupAccessCode: password로 전달합니다.
const [password, setPassword] = useState('');
const [reviewZoneURL, setReviewZoneURL] = useState('');

const [revieweeNameErrorMessage, setRevieweeNameErrorMessage] = useState('');
const [projectNameErrorMessage, setProjectNameErrorMessage] = useState('');
const [passwordErrorMessage, setPasswordErrorMessage] = useState('');

const mutation = usePostDataForReviewRequestCode();
const { isOff, handleEyeButtonToggle } = useEyeButton();
const { isOpen, openModal, closeModal } = useModals();
const { passwordErrorMessage, handlePasswordBlur } = usePasswordValidation(password);

const isFormValid =
isValidReviewGroupDataInput(revieweeName) &&
isValidReviewGroupDataInput(projectName) &&
isValidPasswordInput(password);
!isNotEmptyInput(passwordErrorMessage); // NOTE: 에러 메세지가 빈 문자열이라면 비밀번호는 유효하다.
// TODO: 현재 비밀번호만 다른 방식으로 유효성 검증을 하고 있으므로
// 코드의 통일성을 위해 revieweeName, projectName에 대한 검증도 비밀번호와 비슷한 형식으로 리팩토링하기

const postDataForURL = () => {
const dataForReviewRequestCode: DataForReviewRequestCode = { revieweeName, projectName, groupAccessCode: password };
Expand Down Expand Up @@ -105,28 +105,11 @@ const URLGeneratorForm = () => {
isWithinLengthRange(projectName, MAX_VALID_REVIEW_GROUP_DATA_INPUT)
? setProjectNameErrorMessage('')
: setProjectNameErrorMessage(GROUP_DATA_LENGTH_ERROR_MESSAGE);
}, [revieweeName]);

useEffect(() => {
// NOTE: URL 요청 버튼 활성화 조건에서는 최소 4자 조건도 체크하지만,
// 여기서(비밀번호 에러 메세지 설정)는 min 값을 검사하지 않음
// 현재 onFocus 등의 처리가 없어 항상 에러 메세지가 뜨기 때문
// 추후 textarea처럼 onfocus, onblur에 대한 훅 사용 예정
if (!isWithinLengthRange(password, MAX_PASSWORD_INPUT)) {
setPasswordErrorMessage(PASSWORD_LENGTH_ERROR_MESSAGE);
return;
}
if (!isAlphanumeric(password)) {
setPasswordErrorMessage(INVALID_CHAR_ERROR_MESSAGE);
return;
}

setPasswordErrorMessage('');
}, [password]);
}, [projectName]);

return (
<S.URLGeneratorForm>
<FormLayout title="함께한 팀원에게 리뷰를 받아보세요!" direction="column">
<FormLayout title="함께한 팀원으로부터 리뷰를 받아보세요!" direction="column">
<S.InputContainer>
<S.Label htmlFor="reviewee-name">본인의 이름을 적어주세요</S.Label>
<Input
Expand Down Expand Up @@ -157,6 +140,7 @@ const URLGeneratorForm = () => {
id="password"
value={password}
onChange={handlePasswordInputChange}
onBlur={handlePasswordBlur}
type={isOff ? 'password' : 'text'}
placeholder="abc123"
$style={{ width: '100%', paddingRight: '3rem' }}
Expand Down

0 comments on commit 51312e1

Please sign in to comment.