Skip to content

Commit

Permalink
✨ feat: funnel 패턴 추가 정보 입력 step 1 구현 #5
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMirror21 committed Oct 18, 2024
1 parent 6f9baa1 commit b5bb219
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 41 deletions.
220 changes: 185 additions & 35 deletions src/components/Information/InformationStep.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import Input from '../Common/Input';
import { initialUserInfo, UserInfoRequestBody } from '@/types/api/users';
import { UserInfo } from '../../types/api/users';
import { useState } from 'react';
import { isValidFirstName } from '@/utils/information';
import { useEffect, useState } from 'react';
import {
formatPhoneNumber,
isValidName,
isValidPhoneNumber,
} from '@/utils/information';
import Dropdown from '../Common/Dropdown';
import { country, gender, phone, visa } from '@/constants/information';
import RadioButton from './RadioButton';

const InformationStep = ({
userInfo,
Expand All @@ -11,46 +18,189 @@ const InformationStep = ({
userInfo: UserInfoRequestBody;
onNext: (newInfo: UserInfoRequestBody) => void;
}) => {
// 현재 step내에서 입력받는 정보를 따로 관리할 state, 추후 다음 step으로 넘어갈 때 funnel 관리 페이지의 state로 통합된다.
const [newUserInfo, setNewUserInfo] = useState<UserInfo>(initialUserInfo);
const [invalidList, setInvalidList] = useState({
firstName: false,
lastName: false,
// 버튼 활성화 여부를 위한 플래그
const [isInvalid, setIsInvalid] = useState(true);
// 세 부분으로 나누어 입력받는 방식을 위해 전화번호만 별도의 state로 분리, 추후 유효성 검사 단에서 통합
const [phoneNum, setPhoneNum] = useState({
start: '',
middle: '',
end: '',
});

const checkFirstNameInvalid = () => {
if (
newUserInfo.first_name === undefined ||
newUserInfo.first_name == '' ||
!isValidFirstName(newUserInfo.first_name)
) {
setInvalidList({ ...invalidList, firstName: true });
/* 정보 입력 시마다 유효성을 검사해 모든 값이 유효하면 버튼이 활성화 */
useEffect(() => {
const { first_name, last_name, birth, nationality, visa } = newUserInfo;

const isFormValid =
isValidName(String(first_name)) &&
isValidName(String(last_name)) &&
birth !== '' &&
nationality !== '' &&
visa !== '' &&
isValidPhoneNumber(phoneNum);

if (isFormValid) {
setNewUserInfo((prevInfo) => ({
...prevInfo,
phone_number: formatPhoneNumber(phoneNum),
}));
}
};

const checkInvalidAll = () => {
checkFirstNameInvalid();
if (Object.values(invalidList).every((value) => value === true))
onNext({ ...userInfo, user_info: newUserInfo });
};
setIsInvalid(!isFormValid);
}, [newUserInfo, phoneNum]);

return (
<div>
<div>
<div>First Name</div>
<Input
inputType="INPUT"
placeholder="First Name"
value={newUserInfo.first_name}
clearInvalid={() =>
setInvalidList({ ...invalidList, firstName: false })
}
onChange={(value) =>
setNewUserInfo({ ...newUserInfo, first_name: value })
}
canDelete={false}
isInvalid={invalidList.firstName}
/>
<div className="w-full mx-auto">
<div className="w-full flex flex-col gap-[1.125rem]">
{/* 이름 작성 */}
<div className="w-full">
<div className="w-full flex items-center justify-start text-xs font-[Pretendard] color-[#222] px-[0.25rem] py-[0.375rem]">
First Name
</div>
<Input
inputType="INPUT"
placeholder="First Name"
value={newUserInfo.first_name}
onChange={(value) =>
setNewUserInfo({ ...newUserInfo, first_name: value })
}
canDelete={false}
/>
</div>
{/* 성 작성 */}
<div className="w-full">
<div className="w-full flex items-center justify-start text-xs font-[Pretendard] color-[#222] px-[0.25rem] py-[0.375rem]">
Last Name
</div>
<Input
inputType="INPUT"
placeholder="Last Name"
value={newUserInfo.last_name}
onChange={(value) =>
setNewUserInfo({ ...newUserInfo, last_name: value })
}
canDelete={false}
/>
</div>
<div className="w-full">
<div className="w-full flex items-center justify-start text-xs font-[Pretendard] color-[#222] px-[0.25rem] py-[0.375rem]">
Gender
</div>
<div className="w-full flex flex-row gap-[1.75rem]">
{gender.map((gender) => (
<RadioButton
value={gender}
setValue={(value: string) =>
setNewUserInfo({
...newUserInfo,
gender: value.toUpperCase(),
})
}
isOn={gender.toUpperCase() === newUserInfo.gender}
/>
))}
</div>
</div>
{/* 생년월일 선택 */}
<div className="w-full">
<div className="w-full flex items-center justify-start text-xs font-[Pretendard] color-[#222] px-[0.25rem] py-[0.375rem]">
Date of birth
</div>
<Dropdown
value={newUserInfo.birth}
placeholder="Select Date"
options={[]}
isCalendar={true}
setValue={(value) =>
setNewUserInfo({ ...newUserInfo, birth: value })
}
/>
</div>
{/* 국적 선택 */}
<div className="w-full">
<div className="w-full flex items-center justify-start text-xs font-[Pretendard] color-[#222] px-[0.25rem] py-[0.375rem]">
Nationality
</div>
<Dropdown
value={newUserInfo.nationality}
placeholder="Select Nationality"
options={country} // TODO: 국가명 데이터 받으면 교체해야 함.
setValue={(value: string) =>
setNewUserInfo({ ...newUserInfo, nationality: value })
}
/>
</div>
{/* 비자 선택 */}
<div className="w-full">
<div className="w-full flex items-center justify-start text-xs font-[Pretendard] color-[#222] px-[0.25rem] py-[0.375rem]">
Visa Status
</div>
<Dropdown
value={newUserInfo.visa}
placeholder="Select Visa Status"
options={visa} // TODO: 비자 데이터 받으면 교체해야
setValue={(value: string) =>
setNewUserInfo({ ...newUserInfo, visa: value })
}
/>
</div>
{/* 전화번호 선택, dropdown으로 앞 번호를, 중간 번호와 뒷 번호는 각각 input으로 입력 받음 */}
<div className="w-full">
<div className="w-full flex flex-row items-center justify-start text-xs font-[Pretendard] color-[#222] px-[0.25rem] py-[0.375rem]">
Telephone No.
</div>
<div className="w-full flex flex-row gap-2 justify-between mb-[30rem]">
<div className="w-full h-[2.75rem]">
<Dropdown
value={phoneNum.start}
placeholder="+82"
options={phone}
setValue={(value) => setPhoneNum({ ...phoneNum, start: value })}
/>
</div>
<Input
inputType="INPUT"
placeholder="0000"
value={phoneNum.middle}
onChange={(value) => setPhoneNum({ ...phoneNum, middle: value })}
canDelete={false}
/>
<Input
inputType="INPUT"
placeholder="0000"
value={phoneNum.end}
onChange={(value) => setPhoneNum({ ...phoneNum, end: value })}
canDelete={false}
/>
</div>
</div>
</div>
{/* 정보 입력 시마다 유효성을 검사해 모든 값이 유효하면 버튼이 활성화 */}
<div className="w-full fixed bottom-0 left-0 bg-gradient-to-b from-white/80 to-white flex flex-row items-start justify-start px-6 pb-[50px] pt-3 box-border text-center text-base text-[#1e1926] font-[Pretendard]">
<div className="w-full flex items-center justify-center">
{/* TODO : 버튼 컴포넌트 들어오면 변경 */}
{isInvalid ? (
<button className="w-[15rem] bg-[#F4F4F9]">Next</button>
) : (
<button
className="w-[15rem] bg-[#fef387]"
onClick={() =>
onNext({
...userInfo,
user_info: {
...newUserInfo,
nationality: newUserInfo.nationality?.toUpperCase(),
},
})
}
>
Next
</button>
)}
</div>
</div>
<button onClick={checkInvalidAll}>Next</button>
</div>
);
};
Expand Down
14 changes: 9 additions & 5 deletions src/pages/Information/InformationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import {
} from '@/types/api/users';
import { useState } from 'react';

// funnel 패턴으로 구현한 추가정보 입력 페이지. 총 3 step으로 구성
const InformationPage = () => {
const [currentStep, setCurrentStep] = useState(1);
const [userInfo, setUserInfo] = useState<UserInfoRequestBody>(
initialUserInfoRequestBody,
);

// 다음 step으로 넘어갈 때 호출되며, 각 step에서 입력한 정보를 userInfo에 저장, 다음 step으로 이동한다.
const handleNext = (newInfo: UserInfoRequestBody) => {
setUserInfo(newInfo);
setCurrentStep((prev) => prev + 1);
};
return (
<div className="m-auto max-w-[500px] py-6 h-screen flex flex-col items-center justify-start border border-black">
<div className="w-full flex flex-row items-center justify-between">
<div className="m-auto max-w-[500px] relative h-screen flex flex-col items-center justify-start border border-black overflow-y-scroll scrollbar-hide">
<div className="w-full flex flex-row py-6 items-center justify-between">
<div
className="relative w-full flex items-center justify-center text-[1.75rem] tracking-[-0.01em] leading-9 font-semibold font-[Pretendard] text-[#1e1926] text-left"
onClick={() => setCurrentStep(currentStep + 1)}
Expand All @@ -27,9 +29,11 @@ const InformationPage = () => {
</div>
<StepIndicator currentStep={currentStep} />
</div>
{currentStep === 1 && (
<InformationStep userInfo={userInfo} onNext={handleNext} />
)}
<div className="w-full flex justify-center px-6">
{currentStep === 1 && (
<InformationStep userInfo={userInfo} onNext={handleNext} />
)}
</div>
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/utils/information.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ export const formatPhoneNumber = (phone: {
start: string;
middle: string;
end: string;
}) => `${phone.start}-${phone.middle}-${phone.end}`;
}) => `${phone.start}-${phone.middle}-${phone.end}`;

0 comments on commit b5bb219

Please sign in to comment.