-
Notifications
You must be signed in to change notification settings - Fork 1
[FEATURE] 온보딩 페이지 구현 #3 #5
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| const Navigation = () => { | ||
| return ( | ||
| <> | ||
| <div className="flex h-16 w-full items-center px-4 shadow-md"> | ||
| <h1 className="text-2xl font-bold text-[#0D2D84]">Snowgent</h1> | ||
| </div> | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| export default Navigation; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| const FormButton = ({ type, onClick }: { type: string; onClick: () => void }) => { | ||
| const buttonStyle = | ||
| 'bg-[#1d4ed8] w-full text-white py-3 rounded-lg text-center font-medium cursor-pointer'; | ||
|
|
||
| const grayButtonStyle = | ||
| 'bg-gray-300 w-full text-gray-700 py-3 rounded-lg text-center font-medium cursor-pointer'; | ||
|
|
||
| const handleClick = () => { | ||
| onClick(); | ||
| }; | ||
|
|
||
| switch (type) { | ||
| case 'next': | ||
| return ( | ||
| <div className={buttonStyle} onClick={handleClick}> | ||
| 다음 | ||
| </div> | ||
| ); | ||
|
|
||
| case 'prev': | ||
| return ( | ||
| <div className={grayButtonStyle} onClick={handleClick}> | ||
| 이전 | ||
| </div> | ||
| ); | ||
|
|
||
| case 'submit': | ||
| return ( | ||
| <div className={buttonStyle} onClick={handleClick}> | ||
| 완료 | ||
| </div> | ||
| ); | ||
|
|
||
| default: | ||
| break; | ||
| } | ||
| }; | ||
|
|
||
| export default FormButton; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| const MultiSelect = ({ title }: { title: string }) => { | ||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| {/* 제목 */} | ||
| <p className="text-[24px] font-semibold">{title}을 선택하세요</p> | ||
| {/* 입력칸 */} | ||
| <input type="text" className="rounded-xl border border-gray-200 px-2 py-4 text-[20px]" /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default MultiSelect; | ||
|
Comment on lines
+1
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컴포넌트 이름과 구현의 불일치 컴포넌트 이름은 다음 중 하나의 방식을 권장합니다: 옵션 1: 임시 구현임을 명확히 표시 -const MultiSelect = ({ title }: { title: string }) => {
+// TODO: 실제 다중 선택 UI 구현 예정
+const MultiSelect = ({ title }: { title: string }) => {
return (
<div className="flex flex-col gap-6">
{/* 제목 */}
<p className="text-[24px] font-semibold">{title}을 선택하세요</p>
- {/* 입력칸 */}
+ {/* 임시 입력칸 - 추후 다중 선택 버튼으로 교체 예정 */}
<input type="text" className="rounded-xl border border-gray-200 px-2 py-4 text-[20px]" />
</div>
);
};옵션 2: 컴포넌트 이름 변경 -const MultiSelect = ({ title }: { title: string }) => {
+const MultiSelectPlaceholder = ({ title }: { title: string }) => {🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { useState } from 'react'; | ||
|
|
||
| const PriceInput = ({ title, placeholder }: { title: string; placeholder?: string }) => { | ||
| const [value, setValue] = useState(''); | ||
|
|
||
| const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| // 숫자만 입력 가능하도록 | ||
| const numericValue = e.target.value.replace(/[^0-9]/g, ''); | ||
| setValue(numericValue); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-6"> | ||
| {/* 제목 */} | ||
| <p className="text-[24px] font-semibold">{title}을 입력해주세요</p> | ||
|
|
||
| {/* 입력칸 */} | ||
| <div className="relative flex items-center rounded-xl border border-gray-200"> | ||
| <input | ||
| type="text" | ||
| value={value} | ||
| onChange={handleChange} | ||
| placeholder={placeholder || '0'} | ||
| className="flex-1 rounded-xl px-4 py-4 text-[20px] outline-none" | ||
| /> | ||
| <span className="pointer-events-none pr-4 text-[20px] text-gray-500">만원</span> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default PriceInput; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||||||||||||||||
| const defaultOptions = [ | ||||||||||||||||||||||||||||||||||||||||||
| { id: 1, name: 'option1' }, | ||||||||||||||||||||||||||||||||||||||||||
| { id: 2, name: 'option2' }, | ||||||||||||||||||||||||||||||||||||||||||
| { id: 3, name: 'option3' }, | ||||||||||||||||||||||||||||||||||||||||||
| { id: 4, name: 'option4' }, | ||||||||||||||||||||||||||||||||||||||||||
| { id: 5, name: 'option5' }, | ||||||||||||||||||||||||||||||||||||||||||
| { id: 6, name: 'option6' }, | ||||||||||||||||||||||||||||||||||||||||||
| { id: 7, name: 'option7' }, | ||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 실제 업종 옵션으로 교체 필요
다음과 같이 실제 업종 데이터를 사용하도록 수정하세요: const defaultOptions = [
- { id: 1, name: 'option1' },
- { id: 2, name: 'option2' },
- { id: 3, name: 'option3' },
- { id: 4, name: 'option4' },
- { id: 5, name: 'option5' },
- { id: 6, name: 'option6' },
- { id: 7, name: 'option7' },
+ { id: 1, name: '카페' },
+ { id: 2, name: '음식점' },
+ { id: 3, name: '소매점' },
+ { id: 4, name: '미용실' },
+ { id: 5, name: '편의점' },
+ { id: 6, name: '학원' },
+ { id: 7, name: '기타' },
];📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const SingleSelect = ({ title }: { title: string }) => { | ||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-6"> | ||||||||||||||||||||||||||||||||||||||||||
| {/* 제목 */} | ||||||||||||||||||||||||||||||||||||||||||
| <p className="text-[24px] font-semibold">{title} 선택하세요</p> | ||||||||||||||||||||||||||||||||||||||||||
| {/* 단일 선택 버튼 */} | ||||||||||||||||||||||||||||||||||||||||||
| <select className="rounded-xl border border-gray-200 px-2 py-4 text-[20px] outline-none focus:border-gray-200"> | ||||||||||||||||||||||||||||||||||||||||||
| <option className="" value=""> | ||||||||||||||||||||||||||||||||||||||||||
| 선택하세요 | ||||||||||||||||||||||||||||||||||||||||||
| </option> | ||||||||||||||||||||||||||||||||||||||||||
| {defaultOptions.map((option) => ( | ||||||||||||||||||||||||||||||||||||||||||
| <option key={option.id} value={option.id}> | ||||||||||||||||||||||||||||||||||||||||||
| {option.name} | ||||||||||||||||||||||||||||||||||||||||||
| </option> | ||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||
| </select> | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 포커스 상태 시각적 피드백 개선 필요 TextInput 컴포넌트와 마찬가지로, 다음과 같이 수정하세요: - <select className="rounded-xl border border-gray-200 px-2 py-4 text-[20px] outline-none focus:border-gray-200">
+ <select className="rounded-xl border border-gray-200 px-2 py-4 text-[20px] outline-none focus:border-blue-500">📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export default SingleSelect; | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||||||||||||||
| const TextInput = ({ title, placeholder }: { title: string; placeholder?: string }) => { | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <div className="flex flex-col gap-6"> | ||||||||||||||||||||||
| {/* 제목 */} | ||||||||||||||||||||||
| <p className="text-[24px] font-semibold">{title} 입력해주세요</p> | ||||||||||||||||||||||
| {/* 입력칸 */} | ||||||||||||||||||||||
| <input | ||||||||||||||||||||||
| type="text" | ||||||||||||||||||||||
| placeholder={placeholder} | ||||||||||||||||||||||
| className="rounded-xl border border-gray-200 px-2 py-4 text-[20px] outline-none focus:border-gray-200" | ||||||||||||||||||||||
| /> | ||||||||||||||||||||||
|
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 포커스 상태 시각적 피드백 개선 필요
다음과 같이 수정을 권장합니다: <input
type="text"
placeholder={placeholder}
- className="rounded-xl border border-gray-200 px-2 py-4 text-[20px] outline-none focus:border-gray-200"
+ className="rounded-xl border border-gray-200 px-2 py-4 text-[20px] outline-none focus:border-blue-500"
/>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export default TextInput; | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,82 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Navigation from '../../components/Navigation'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import FormButton from '../../components/onboarding/FormButton'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useNavigate } from 'react-router-dom'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import TextInput from '../../components/onboarding/TextInput'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import PriceInput from '../../components/onboarding/PriceInput'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import SingleSelect from '../../components/onboarding/SingleSelect'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import MultiSelect from '../../components/onboarding/MultiSelect'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const Onboarding = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <div>Onboarding</div>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [currentStep, setCurrentStep] = useState(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const totalSteps = 5; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handlePrev = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (currentStep > 1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCurrentStep(currentStep - 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleNext = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (currentStep < totalSteps) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setCurrentStep(currentStep + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleComplete = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| navigate('/chat'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const renderStepContent = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch (currentStep) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 1: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <SingleSelect key="step1" title="업종을" />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 2: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <TextInput key="step2" title="업체명을" placeholder="" />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 3: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <TextInput key="step3" title="위치(시군구)를" placeholder="" />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 4: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <PriceInput key="step4" title="평균 월매출" placeholder="숫자" />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case 5: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <PriceInput key="step5" title="목표 월매출" placeholder="숫자" />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <MultiSelect key="default" title="원하는 타겟층" />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. default 케이스 처리 개선 필요 switch 문의 default 케이스가 MultiSelect를 반환하고 있으나, 정상적인 플로우에서는 1-5단계만 존재하므로 default는 도달하지 않아야 합니다. 예기치 않은 상태에 대한 처리가 명확하지 않습니다. 다음과 같이 수정을 권장합니다: const renderStepContent = () => {
switch (currentStep) {
case 1:
return <SingleSelect key="step1" title="업종을" />;
case 2:
return <TextInput key="step2" title="업체명을" placeholder="" />;
case 3:
return <TextInput key="step3" title="위치(시군구)를" placeholder="" />;
case 4:
return <PriceInput key="step4" title="평균 월매출" placeholder="숫자" />;
case 5:
return <PriceInput key="step5" title="목표 월매출" placeholder="숫자" />;
default:
- return <MultiSelect key="default" title="원하는 타겟층" />;
+ return null;
}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex h-full flex-col"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Navigation /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-1 flex-col p-5"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mb-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mb-2 flex justify-between text-sm text-gray-600"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span>단계 {currentStep}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {currentStep} / {totalSteps} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="h-2 w-full rounded-full bg-gray-200"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="h-2 rounded-full bg-blue-500 transition-all duration-300" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| style={{ width: `${(currentStep / totalSteps) * 100}%` }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex-1 overflow-y-auto">{renderStepContent()}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mt-6 flex gap-3"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {currentStep > 1 && <FormButton type="prev" onClick={handlePrev} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {currentStep === totalSteps ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormButton type="submit" onClick={handleComplete} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormButton type="next" onClick={handleNext} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default Onboarding; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
접근성 문제: button 요소 대신 div 사용
클릭 가능한 요소에
<div>를 사용하면 키보드 네비게이션과 스크린 리더 지원이 제대로 작동하지 않습니다. 이는 웹 접근성(WCAG) 기준을 충족하지 못하는 중대한 문제입니다.다음과 같이
<button>요소를 사용하도록 수정하세요:switch (type) { case 'next': return ( - <div className={buttonStyle} onClick={handleClick}> + <button type="button" className={buttonStyle} onClick={handleClick}> 다음 - </div> + </button> ); case 'prev': return ( - <div className={grayButtonStyle} onClick={handleClick}> + <button type="button" className={grayButtonStyle} onClick={handleClick}> 이전 - </div> + </button> ); case 'submit': return ( - <div className={buttonStyle} onClick={handleClick}> + <button type="button" className={buttonStyle} onClick={handleClick}> 완료 - </div> + </button> ); default: - break; + return null; } };📝 Committable suggestion
🤖 Prompt for AI Agents