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: 생년월일 입력 - 변경된 디자인 적용 및 리팩터링 #233

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
85cdf20
chore: DATE_SAPARATOR를 constants화 해서 사용
Doeunnkimm Mar 26, 2024
eb57da4
refactor: 변경된 디자인 적용 및 기능 리팩터링
Doeunnkimm Mar 26, 2024
ec42fab
chore: 정보수정 페이지에서 생년월일 부분 반영
Doeunnkimm Mar 26, 2024
484ad32
chore: default값으로 인해 붙는 separator를 예외시키기 위해 filter 메서드 추가
Doeunnkimm Mar 26, 2024
27c5939
fix: formattedValue가 format과 일치하는지 검사하는 검증 추가
Doeunnkimm Mar 26, 2024
e32c85e
feat: member/new/birth에서 유효성 검증 추가
Doeunnkimm Mar 26, 2024
5dbf066
feat: 타임라인 조회 api 반영 (#231)
newminkyung Mar 27, 2024
c7470ee
feat: 댓글 기능 api 연동 및 ios에서 키패드 이슈 대응 (#223)
Doeunnkimm Mar 29, 2024
1042a1e
refactor: 오류 페이지 컴포넌트 리팩터링 (#236)
Doeunnkimm Apr 2, 2024
4645efa
fix: 존재하지 않는 유저 지도 접근시 에러 처리 (#237)
Doeunnkimm Apr 2, 2024
a55e0c7
feat: 응원한 사람 조회 api 응답 값 수정 반영 / 응원하기 성공 시에도 에러 토스트가 뜨는 버그 수정 (#232)
newminkyung Apr 2, 2024
0264c2f
feat: 피드 이모지 추가/삭제시 낙관적 업데이트 적용 (#234)
hjy0951 Apr 2, 2024
c360db8
feat: 목표 수정 기능 개발 (#192)
deepbig Apr 2, 2024
0f7f44b
fix: 타임라인 조회 쿼리키 수정 (#238)
newminkyung Apr 2, 2024
4afb2d9
fix: 피드 페이지에서 댓글 API 요청시 누락된 목표 ID 추가 (#239)
hjy0951 Apr 2, 2024
d5f69b6
chore: DATE_SAPARATOR를 constants화 해서 사용
Doeunnkimm Mar 26, 2024
87f1f66
refactor: 변경된 디자인 적용 및 기능 리팩터링
Doeunnkimm Mar 26, 2024
f11ca1b
chore: 정보수정 페이지에서 생년월일 부분 반영
Doeunnkimm Mar 26, 2024
9b97ac6
chore: default값으로 인해 붙는 separator를 예외시키기 위해 filter 메서드 추가
Doeunnkimm Mar 26, 2024
b9200f5
fix: formattedValue가 format과 일치하는지 검사하는 검증 추가
Doeunnkimm Mar 26, 2024
cf4542e
feat: member/new/birth에서 유효성 검증 추가
Doeunnkimm Mar 26, 2024
288018c
Merge branch 'chore/dateInput' of https://github.com/depromeet/amazin…
Doeunnkimm Apr 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/constants/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DATE_SEPARATOR = '.';
1 change: 1 addition & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { blueDataURL } from './blurDataURL';
export * from './date';
export * from './input';
export { META } from './metadata';
export * from './ogImage.size';
Expand Down
6 changes: 3 additions & 3 deletions src/features/goal/components/new/DateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useController, useFormContext } from 'react-hook-form';
import Link from 'next/link';

import { Button, Span, Typography } from '@/components';
import { MAX_DATE_LENGTH_UNTIL_MONTH } from '@/constants';
import type { GoalFormValues } from '@/features/goal/types';

import { NEW_GOAL_FORM_ORDERS } from '../../constants';
import { isValidDateFormat } from '../../utils/date';

import { DateInput } from './DateInput';
import FormHeader from './FormHeader';
Expand All @@ -29,12 +29,12 @@ export const DateForm = () => {
}
body={
<div {...register('date')} className="pt-sm">
<DateInput maxLength={MAX_DATE_LENGTH_UNTIL_MONTH} onChange={onChange} />
<DateInput onChange={onChange} requireDateType={['YYYY', 'MM']} />
</div>
}
footer={
<Link href="/goal/new/tag">
<Button disabled={value ? value.length !== MAX_DATE_LENGTH_UNTIL_MONTH : true}>다음</Button>
<Button disabled={!isValidDateFormat(value, 'YYYY.MM')}>다음</Button>
</Link>
}
/>
Expand Down
73 changes: 30 additions & 43 deletions src/features/goal/components/new/DateInput.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,50 @@
'use client';

import type { ChangeEvent } from 'react';
import { useState } from 'react';
import { useEffect } from 'react';

import { Input, Typography } from '@/components';
import { MAX_DATE_LENGTH_UNTIL_MONTH } from '@/constants';

import { formatDate, isValidDate } from '../../utils/date';
import { useDateInput } from '@/hooks';
import { type DateProps, type DateValueProps, typeToMaxLength } from '@/hooks/useDateInput';
import { formatDate } from '@/utils/date';

interface DateInputProps {
labelName?: string;
intitalValue?: string;
maxLength?: number;
initialValue?: DateValueProps;
requireDateType?: DateProps[];
onChange?: (value: string) => void;
}

export const DateInput = ({ labelName = '', intitalValue = '', maxLength, onChange }: DateInputProps) => {
const [formattedValue, setFormattedValue] = useState<string>(intitalValue);
const placeholder = maxLength === MAX_DATE_LENGTH_UNTIL_MONTH ? 'YYYY.MM' : 'YYYY.MM.DD';

const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

와 여기부터 함수 토나오네

미안하다 도은... 최고야...👍

const inputValue = event.target.value.replace(/\D/g, '');
let formatted = inputValue;
let year, month, day;

if (inputValue.length > 4 && inputValue.length <= 6) {
year = inputValue.slice(0, 4);
month = inputValue.slice(4, 6);
month = +month > 12 ? '12' : +month === 0 ? '0' : month;
formatted = formatDate([year, month], '.');
} else if (inputValue.length > 6) {
year = inputValue.slice(0, 4);
month = inputValue.slice(4, 6);
day = inputValue.slice(6, 8);
export const DateInput = ({
labelName = '',
initialValue = { YYYY: '', MM: '', DD: '' },
requireDateType = ['YYYY', 'MM', 'DD'],
onChange,
}: DateInputProps) => {
const { inputRefs, dateValues, handleInputChange, handleInputBlur } = useDateInput({ initialValue });

if (inputValue.length < 8) {
formatted = formatDate([year, month, day], '.');
} else {
formatted = isValidDate(year, month, day)
? formatDate([year, month, day], '.')
: formatDate([year, month], '.');
}
}
setFormattedValue(formatted);
onChange && onChange(formatted);
};
useEffect(() => {
onChange && onChange(formatDate(requireDateType.map((type) => dateValues[type])));
}, [dateValues, onChange, requireDateType]);

return (
<div className="flex flex-col gap-5xs">
<Typography type="title3" className="text-gray-50">
{labelName}
</Typography>
<Input
type="text"
placeholder={placeholder}
value={formattedValue}
maxLength={maxLength}
onChange={handleInputChange}
/>
<div className="flex gap-5xs">
{requireDateType.map((type) => (
<Input
key={type}
ref={inputRefs[type]}
placeholder={type}
className="text-center"
maxLength={typeToMaxLength[type]}
value={dateValues[type]}
onChange={(e) => handleInputChange(e, type)}
onBlur={(e) => handleInputBlur(e, type)}
/>
))}
</div>
</div>
);
};
22 changes: 21 additions & 1 deletion src/features/goal/utils/date.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export const formatDate = (splitedDate: string[], separator: string) => {
import { DATE_SEPARATOR } from '@/constants';
import type { DateProps } from '@/hooks/useDateInput';
import { typeToMaxLength } from '@/hooks/useDateInput';

export const formatDate = (splitedDate: string[], separator = DATE_SEPARATOR) => {
return splitedDate.join(separator);
};

Expand All @@ -11,3 +15,19 @@ export const isValidDate = (year: string, month: string, day: string) => {

return date.getFullYear() === yearNum && date.getMonth() === monthNum && date.getDate() === dayNum;
};

/**
*
* @param string DATE_SEPARATOR를 기준으로 split했을 때, 모든 아이템이 number여야만, 길이를 만족해야만 true를 반환합니다.
* @param format ex. 'YYYY.MM.DD'
*/
export const isValidDateFormat = (formattedDate = '', format = 'YYYY.MM.DD') => {
const splittedFormat = format.split(DATE_SEPARATOR) as DateProps[];

return (
formattedDate
.split(DATE_SEPARATOR)
.every((date, index) => !isNaN(+date) && date.length === typeToMaxLength[splittedFormat[index]]) &&
formattedDate.split(DATE_SEPARATOR).length === splittedFormat.length
);
};
5 changes: 4 additions & 1 deletion src/features/member/components/new/BirthInputForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation';
import BirthIcon from '@/assets/icons/birth-icon.svg';
import { Button } from '@/components';
import { DateInput } from '@/features/goal/components/new/DateInput';
import { isValidDateFormat } from '@/features/goal/utils/date';

import type { NewMemberFormValues } from '../../types';

Expand Down Expand Up @@ -36,7 +37,9 @@ export const BirthInputForm = () => {
<div {...register('birth')}>
<DateInput onChange={onChange} />
</div>
<Button type="submit">{value?.length ? '완료' : '건너뛰기'}</Button>
<Button type="submit" disabled={value?.length ? !isValidDateFormat(value) : false}>
{value?.length ? '완료' : '건너뛰기'}
</Button>
</div>
</div>
</FormLayout>
Expand Down
7 changes: 4 additions & 3 deletions src/features/my/components/update/UpdateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import { useController, useFormContext } from 'react-hook-form';

import { Button } from '@/components';
import { MAX_DATE_LENGTH_UNTIL_DAY, MAX_NICKNAME_LENGTH, MAX_USERNAME_LENGTH } from '@/constants';
import { MAX_NICKNAME_LENGTH, MAX_USERNAME_LENGTH } from '@/constants';
import { DateInput } from '@/features/goal/components/new/DateInput';
import { TextInput } from '@/features/goal/components/new/TextInput';
import { useGetMemberData } from '@/hooks/reactQuery/auth';
Expand All @@ -26,6 +26,8 @@ export const UpdateForm = () => {
const { field: usernameField } = useController({ name: 'username', control });
const isValidBirth = useValidBirth(birthField.value);

const [defaultYYYY, defaultMM, defaultDD] = (memberData?.birth ?? '').split('-');

const [isDisabledSubmit, setIsDisabledSubmit] = useState(true);

useEffect(() => {
Expand Down Expand Up @@ -85,8 +87,7 @@ export const UpdateForm = () => {
<div {...register('birth')}>
<DateInput
labelName="생년월일"
intitalValue={memberData.birth?.replace(/\-/g, '.')}
maxLength={MAX_DATE_LENGTH_UNTIL_DAY}
initialValue={{ YYYY: defaultYYYY, MM: defaultMM, DD: defaultDD }}
onChange={birthField.onChange}
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useAuth } from './useAuth';
export { useDateInput } from './useDateInput';
export { useDebounceCall } from './useDebounceCall';
export { useDownloadImage } from './useDownloadImage';
export { useFocusInput } from './useFocusInput';
Expand Down
55 changes: 55 additions & 0 deletions src/hooks/useDateInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { ChangeEvent, FocusEvent } from 'react';
import { useRef, useState } from 'react';

export type DateProps = 'YYYY' | 'MM' | 'DD';

export type DateValueProps = Record<DateProps, string>;

interface UseDateInputProps {
initialValue: DateValueProps;
}

export const typeToMaxLength: Record<DateProps, number> = {
YYYY: 4,
MM: 2,
DD: 2,
} as const;

export const useDateInput = ({ initialValue }: UseDateInputProps) => {
const [dateValues, setDateValues] = useState<DateValueProps>(initialValue);

const yearInputRef = useRef<HTMLInputElement>(null);
const monthInputRef = useRef<HTMLInputElement>(null);
const dayInputRef = useRef<HTMLInputElement>(null);

const inputRefs = {
YYYY: yearInputRef,
MM: monthInputRef,
DD: dayInputRef,
};

const handleInputChange = (event: ChangeEvent<HTMLInputElement>, type: DateProps) => {
let inputValue = event.target.value;

if (type === 'YYYY' && inputValue.length === typeToMaxLength[type]) {
monthInputRef.current?.focus();
}

if (type === 'MM') {
inputValue = +inputValue > 12 ? '12' : inputValue;
if (inputValue.length === typeToMaxLength.MM) dayInputRef.current?.focus();
}

setDateValues((prev) => ({ ...prev, [type]: inputValue }));
};

const handleInputBlur = (event: FocusEvent<HTMLInputElement>, type: DateProps) => {
const inputValue = event.target.value;

if ((type === 'MM' || type === 'DD') && inputValue.length === 1) {
setDateValues((prev) => ({ ...prev, [type]: `0${inputValue}` }));
}
};

return { dateValues, handleInputChange, handleInputBlur, inputRefs };
};
6 changes: 3 additions & 3 deletions src/utils/date.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HOURS_PER_DAY, MINUTES_PER_HOUR, SECONDS_PER_MINUTE } from '@/constants';
import { DATE_SEPARATOR, HOURS_PER_DAY, MINUTES_PER_HOUR, SECONDS_PER_MINUTE } from '@/constants';

export const formatDate = (splitedDate: string[], separator: string) => {
return splitedDate.join(separator);
export const formatDate = (splitedDate: string[], separator = DATE_SEPARATOR) => {
return splitedDate.filter((date) => date !== '').join(separator);
};

export const isValidDate = (year: string, month: string, day: string) => {
Expand Down