-
Notifications
You must be signed in to change notification settings - Fork 1
feature: 온보딩 페이지 구현 #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
base: develop
Are you sure you want to change the base?
Changes from 1 commit
740acb2
d4c7314
a7fab5a
277928d
a6d4186
c960a1f
2be3c61
916852a
135b890
8510c75
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,39 @@ | ||
| export default function OnboardingComponent1() { | ||
|
|
||
| return ( | ||
| <div> | ||
| {/* nine blocks */} | ||
| <section className="mb-[23px]"> | ||
| <div className="h-[34px]"> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-growth` }}></div> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-stability` }}></div> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-autonomy` }}></div> | ||
| </div> | ||
| <div className="h-[34px]"> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-connection` }}></div> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-recognition` }}></div> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-fun` }}></div> | ||
| </div> | ||
| <div className="h-[34px]"> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-efficiency` }}></div> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-meaning` }}></div> | ||
| <div className="inline-block h-[28px] w-[28px] mr-[6px] mb-[6px]" | ||
| style={{ backgroundColor: `var(--color-responsibility` }}></div> | ||
| </div> | ||
| </section> | ||
| <section> | ||
| {/* content */} | ||
| <div className="text-[32px] font-[800] mb-[12px]">가치관을 묻지 않아요</div> | ||
| <div className="text-15px] font-[400]">대신 지금 고민 중인 선택 하나만 물어볼게요.<br/>'당신의 가치'는 무엇인가는 묻지 않지만,<br/>구체적인 갈림길 앞에서는 누구나 이유를 둘 수 있으니까요.</div> | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| </section> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| export default function OnboardingComponent2() { | ||
| return ( | ||
| <div> | ||
| <h1 className="text-[32px] font-[800] text-[#201E1D] mb-[38px] ">고르고, 이유를 남기면</h1> | ||
| <section> | ||
| <div className="h-[64px]"> | ||
| <div className="w-[10px] h-[64px] inline-block mr-[14px]"> | ||
| <div className="w-[10px] h-[10px] bg-[#3E2723]"></div> | ||
| <div className="w-[2px] h-[54px] bg-[#3e2723] opacity-[22%] ml-[4px]"></div> | ||
| </div> | ||
| <div className="inline-block align-top"> | ||
| <h3 className="text-[15px] font-[600] text-[#201E1D]">고민</h3> | ||
| <p className="text-[13px] font-[400] text-[#2A1F1C8C]">지금 하는 갈림길 하나를 이름 붙인다</p> | ||
| </div> | ||
| </div> | ||
| <div className="h-[64px]"> | ||
| <div className="w-[10px] h-[64px] inline-block mr-[14px]"> | ||
| <div className="w-[10px] h-[10px] bg-[#3E2723]"></div> | ||
| <div className="w-[2px] h-[54px] bg-[#3e2723] opacity-[22%] ml-[4px]"></div> | ||
| </div> | ||
| <div className="inline-block align-top"> | ||
| <h3 className="text-[15px] font-[600] text-[#201E1D]">오늘의 판단</h3> | ||
| <p className="text-[13px] font-[400] text-[#2A1F1C8C]">오늘, 어느 쪽으로 마음이 갔는지</p> | ||
| </div> | ||
| </div> | ||
| <div className="h-[64px]"> | ||
| <div className="w-[10px] h-[64px] inline-block mr-[14px]"> | ||
| <div className="w-[10px] h-[10px] bg-[#3E2723]"></div> | ||
| <div className="w-[2px] h-[54px] bg-[#3e2723] opacity-[22%] ml-[4px]"></div> | ||
| </div> | ||
| <div className="inline-block align-top"> | ||
| <h3 className="text-[15px] font-[600] text-[#201E1D]">이유</h3> | ||
| <p className="text-[13px] font-[400] text-[#2A1F1C8C]">한 줄이면 충분하다</p> | ||
| </div> | ||
| </div> | ||
| <div className="h-[64px]"> | ||
| <div className="w-[10px] h-[64px] inline-block mr-[14px]"> | ||
| <div className="w-[10px] h-[10px] bg-[#3E2723]"></div> | ||
| <div className="w-[2px] h-[54px] bg-[#3e2723] opacity-[22%] ml-[4px]"></div> | ||
| </div> | ||
| <div className="inline-block align-top"> | ||
| <h3 className="text-[15px] font-[600] text-[#201E1D]">가치</h3> | ||
| <p className="text-[13px] font-[400] text-[#2A1F1C8C]">가장 가까운 것 하나만 고른다</p> | ||
| </div> | ||
| </div> | ||
| </section> | ||
| <p className="text-[13px] font-[400] text-[#2A1F1C8C] mt-[15px]">쌓일수록, 당신의 선택이 보여요.</p> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import { useState, type ChangeEvent } from 'react' | ||
|
|
||
| import RecordValueButton from '@/components/common/button/RecordValueButton' | ||
| import TextArea from '@/components/common/input/TextArea' | ||
| import CategoryButton from '@/components/common/button/CategoryButton' | ||
|
|
||
| import { VALUE_LABELS, type ValueKey } from '@/constants/insights' | ||
| import { VALUE_DESCRIPTIONS } from '@/constants/values' | ||
|
|
||
|
|
||
|
|
||
| const VALUE_KEYS = Object.keys(VALUE_LABELS) as ValueKey[] | ||
|
|
||
| const CATEGORY_OPTIONS = [ | ||
| { value: 'work', title: '일·진로', description: '취업, 이직, 업무 판단' }, | ||
| { | ||
| value: 'relationship', | ||
| title: '관계', | ||
| description: '친구, 연인, 가족, 동료', | ||
| }, | ||
| { value: 'money', title: '돈', description: '소비, 저축, 투자' }, | ||
| { value: 'health', title: '건강·몸', description: '운동, 수면, 컨디션' }, | ||
| { value: 'self', title: '나 자신', description: '성격, 원칙, 새로운 시도' }, | ||
| { value: 'etc', title: '기타', description: '위 어디에도 해당 없음' }, | ||
| ] as const | ||
|
|
||
| type CategoryValue = (typeof CATEGORY_OPTIONS)[number]['value'] | ||
|
|
||
|
|
||
| export default function OnboardingComponent3() { | ||
|
|
||
| const [topic, setTopic] = useState(''); | ||
| const [selectedValue, setSelectedValue] = useState<ValueKey | null>(null) | ||
| const [selectedCategory, setSelectedCategory] = | ||
| useState<CategoryValue | null>(null) | ||
|
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect every controlled TextArea binding.
rg -n -C 3 'useState|<TextArea|value=\{|onChange=' \
src/components/onboarding/OnboardingComponent3.tsxRepository: IBAS-DEV-PROJECT/vac_client Length of output: 2066 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- component ---'
cat -n src/components/onboarding/OnboardingComponent3.tsx
printf '%s\n' '--- TextArea definition and relevant usages ---'
fd -i 'TextArea' src
rg -n -C 4 'export|function TextArea|const TextArea|interface.*TextArea|type.*TextArea|<TextArea' src/components/common/input src/components/onboardingRepository: IBAS-DEV-PROJECT/vac_client Length of output: 11982 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
path = Path("src/components/onboarding/OnboardingComponent3.tsx")
text = path.read_text()
areas = re.findall(
r"<TextArea\b(?P<body>.*?)\n\s*/>",
text,
flags=re.S,
)
for index, body in enumerate(areas, 1):
label = re.search(r'label="([^"]+)"', body)
value = re.search(r"value=\{([^}]+)\}", body)
setter = re.search(r"onChange=\{\(e\) => ([^(]+)\(", body)
print(
f"TextArea {index}: "
f"label={label.group(1) if label else None!r}, "
f"value={value.group(1).strip() if value else None!r}, "
f"setter={setter.group(1).strip() if setter else None!r}"
)
print("topic state declarations:", len(re.findall(r"\btopic\b", text)))
print("ChangeEvent references:", len(re.findall(r"\bChangeEvent\b", text)))
PY
printf '%s\n' '--- lint configuration references ---'
rg -n -S 'no-unused-vars|`@typescript-eslint/no-unused-vars`|eslint' \
package.json eslint.config.* .eslintrc* 2>/dev/null || trueRepository: IBAS-DEV-PROJECT/vac_client Length of output: 1324 판단과 이유 입력란에 독립 상태를 연결하세요. 현재 세 🤖 Prompt for AI Agents
Contributor
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. 컴포넌트 파일에는 UI 관련 코드만 작성하고, 상태 관리나 비즈니스 로직은 페이지에서 담당하도록 분리해주세요! 현재 OnboardingComponent3.tsx 내부에서 useState를 사용해 폼 상태를 직접 관리하고 있는데, topic, selectedValue, selectedCategory 등의 상태를 Onboarding.tsx 페이지로 끌어올려주세요. OnboardingComponent3.tsx는 페이지에서 상태값과 핸들러를 props로 전달받아 UI를 렌더링하는 구조로 변경 부탁드립니다. |
||
|
|
||
| return ( | ||
| <> | ||
| <div> | ||
| <h1 className="text-[32px] font-[800] text-[#201E1D] mb-[13px] ">지금 고민,<br/>하나씩 적어볼까요?</h1> | ||
| <p className="text-[13px] font-[400] text-[#2A1F1C8C] mb-[20px]">아직 연습이예요. 편하게 가볍게 한 번 써볼까요?</p> | ||
| <div className="w-[344px] h-[64px] px-[16px] py-[14px] bg-[#DDF0FA] text-[12px] font-[400] text-[#201E1D] mb-[20px]">고민은 취업, 관계, 돈, 건강처럼 <b>영역</b>으로 나눠서 적어볼 수 있어요. 아래에서 골라보세요.</div> | ||
| </div> | ||
| <section className="flex w-[352px] flex-col gap-5 mb-[20px]"> | ||
| <TextArea | ||
| label="무슨 고민인가요?" | ||
| placeholder="예: A사 vs B사" | ||
| value={topic} | ||
| onChange={(e) => setTopic(e.target.value)} | ||
| maxLength={50} | ||
| /> | ||
| </section> | ||
| <section className="flex w-[352px] flex-col gap-3 mb-[20px]"> | ||
| <h2 className="text-[13px] text-[#2A1F1C]/70">이 고민은 어떤 영역인가요?</h2> | ||
| <div className="flex flex-col gap-2"> | ||
| {CATEGORY_OPTIONS.map((option) => ( | ||
| <CategoryButton | ||
| key={option.value} | ||
| title={option.title} | ||
| description={option.description} | ||
| isSelected={selectedCategory === option.value} | ||
| onClick={() => setSelectedCategory(option.value)} | ||
| /> | ||
| ))} | ||
| </div> | ||
| </section> | ||
| <section className="flex w-[352px] flex-col gap-5 mb-[20px]"> | ||
| <TextArea | ||
| label="오늘, 어떤 판단을 내렸나요?" | ||
| placeholder="예: 아직 못 정함/ A로 마음이 기움" | ||
| value={topic} | ||
| onChange={(e) => setTopic(e.target.value)} | ||
| maxLength={50} | ||
| /> | ||
| </section> | ||
| <div className="w-[344px] h-[64px] px-[16px] py-[14px] bg-[#DDF0FA] text-[12px] font-[400] text-[#201E1D] mb-[20px]">이유는 '성장', '안정', '재미'처럼 9가지 <b>가치</b> 중 가장 가까운 하나로 정리돼요.</div> | ||
|
|
||
| <section className="flex w-[352px] flex-col gap-5 mb-[20px]"> | ||
| <TextArea | ||
| label="그건 왜인가요?" | ||
| placeholder="한 줄이면 충분해요" | ||
| value={topic} | ||
| onChange={(e) => setTopic(e.target.value)} | ||
| maxLength={100} | ||
| rows={3} | ||
| /> | ||
| </section> | ||
| <section className="flex w-[352px] flex-col gap-3 mb-[20px]"> | ||
| <h2 className="text-[13px] text-[#2A1F1C]/70">방금 그 이유, 가장 가까운 하나는?</h2> | ||
| <div className="flex flex-col gap-2"> | ||
| {VALUE_KEYS.map((key) => ( | ||
| <RecordValueButton | ||
| key={key} | ||
| valueKey={key} | ||
| title={VALUE_LABELS[key]} | ||
| description={VALUE_DESCRIPTIONS[key]} | ||
| isSelected={selectedValue === key} | ||
| onClick={() => setSelectedValue(key)} | ||
| /> | ||
| ))} | ||
| </div> | ||
| </section> | ||
| </> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import OnboardingComponent1 from '@/components/onboarding/OnboardingComponent1' | ||
| import OnboardingComponent2 from '@/components/onboarding/OnboardingComponent2' | ||
| import OnboardingComponent3 from '@/components/onboarding/OnboardingComponent3' | ||
|
|
||
| export default function OnboardingComponentChange(props : { pageNumber : number }) { | ||
| switch(props.pageNumber){ | ||
| case 1: return <OnboardingComponent1 /> | ||
| case 2: return <OnboardingComponent2 /> | ||
| case 3: return <OnboardingComponent3 /> | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| export default function OnboardingHeader(props: { pageNumber:number; }) { | ||
| {/*colored block numbers change by pageNumber */} | ||
| switch(props.pageNumber){ | ||
| case 1: | ||
| {/*on pageNumber == 1*/} | ||
| return ( | ||
| <header className="h-[31px] grow-0 px-[28px]"> | ||
| <div className="flex flex-row items-end h-[100%]"> | ||
| <div className="h-[3px] w-[32px] bg-[#3E2723] mr-[8px]"/> | ||
| <div className="h-[3px] w-[32px] bg-[#DDF0FA] mr-[8px]"/> | ||
| <div className="h-[3px] w-[32px] bg-[#DDF0FA] mr-[8px]"/> | ||
| </div> | ||
| </header> | ||
|
Contributor
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. 현재 pageNumber에 따라 활성화되는 바의 개수만 달라지는데, 동일한 JSX를 3번 작성하고 있습니다. 배열과 map을 활용해 선언적으로 처리할 수 있으니 아래와 같이 수정 부탁드립니다. |
||
| ); | ||
| case 2: | ||
| {/*on pageNumber == 2*/} | ||
| return ( | ||
| <header className="h-[31px] grow-0 px-[28px]"> | ||
| <div className="flex flex-row items-end h-[100%]"> | ||
| <div className="h-[3px] w-[32px] bg-[#3E2723] mr-[8px]"/> | ||
| <div className="h-[3px] w-[32px] bg-[#3E2723] mr-[8px]"/> | ||
| <div className="h-[3px] w-[32px] bg-[#DDF0FA] mr-[8px]"/> | ||
| </div> | ||
| </header> | ||
| ); | ||
| case 3: | ||
| {/*on pageNumber == 3*/} | ||
| return ( | ||
| <header className="h-[31px] grow-0 px-[28px]"> | ||
| <div className="flex flex-row items-end h-[100%]"> | ||
| <div className="h-[3px] w-[32px] bg-[#3E2723] mr-[8px]"/> | ||
| <div className="h-[3px] w-[32px] bg-[#3E2723] mr-[8px]"/> | ||
| <div className="h-[3px] w-[32px] bg-[#3E2723] mr-[8px]"/> | ||
| </div> | ||
| </header> | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { useState } from 'react' | ||
| import OnboardingComponentChange from '@/components/onboarding/OnboardingComponentChange' | ||
| import OnboardingHeader from '@/components/onboarding/OnboardingHeader' | ||
|
|
||
| export default function Onboarding () { | ||
|
|
||
| {/*onboarding page number*/} | ||
| const [pageNumber, setPageNumber] = useState(1); | ||
|
|
||
| {/*page move function*/} | ||
| const pageMove = () => { | ||
| if(pageNumber <= 2) | ||
| setPageNumber(pageNumber => (pageNumber + 1)); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="flex flex-col items-stretch min-h-screen bg-[#E1F5FE] pt-[34px]"> | ||
| {/* onboarding header*/} | ||
| <OnboardingHeader pageNumber={pageNumber} /> | ||
| <div className="grow-1 flex flex-col justify-between px-[28px] py-[48px]"> | ||
|
Contributor
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. Tailwind에 grow-1은 없습니다! grow 가 flex-grow: 1 에 해당합니다. 이렇게 되면 콘텐츠와 버튼 사이 공간이 의도대로 나뉘지 않습니다. |
||
| {/* onboarding contents, change by pageNumber*/} | ||
| <OnboardingComponentChange pageNumber={pageNumber}/> | ||
|
|
||
| {/*bottom buttons*/} | ||
| <div className="flex flex-row justify-between"> | ||
| <button className="w-[84px] h-[43px] rounded-[9px] text-[14px] font-[800] text-[#3E2723]">건너뛰기</button> | ||
| <button onClick={pageMove} className="w-[84px] h-[43px] rounded-[9px] text-[14px] font-[800] text-[#E1F5FE] bg-[#3E2723]">다음</button> | ||
|
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find the existing route or completion convention before wiring the handlers.
rg -n -C 4 'Onboarding|useNavigate|navigate\(|router|href=' srcRepository: IBAS-DEV-PROJECT/vac_client Length of output: 4811 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- onboarding page ---'
cat -n src/pages/Onboarding.tsx
printf '%s\n' '--- route and completion references ---'
rg -n -C 5 'Onboarding|onboarding|useNavigate|navigate\(|Navigate|BrowserRouter|createBrowserRouter|routes|localStorage|sessionStorage' src package.json README.md 2>/dev/null || true
printf '%s\n' '--- relevant files ---'
git ls-files | rg '(^|/)(App|main|router|routes|Onboarding|package)\.(tsx?|jsx?|json)$|(^|/)pages/' | head -200Repository: IBAS-DEV-PROJECT/vac_client Length of output: 7314 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- application entry and routing ---'
cat -n src/App.tsx
cat -n src/main.tsx
printf '%s\n' '--- package scripts and dependencies ---'
cat -n package.json
printf '%s\n' '--- deterministic handler check ---'
python3 - <<'PY'
from pathlib import Path
source = Path("src/pages/Onboarding.tsx").read_text()
assert 'onClick={pageMove}' in source
assert 'className="w-[84px] h-[43px] rounded-[9px] text-[14px] font-[800] text-[`#3E2723`]">건너뛰기</button>' in source
assert 'if(pageNumber <= 2)' in source
assert 'setPageNumber(pageNumber => (pageNumber + 1))' in source
# Model the pageMove guard for the reachable pages.
def next_page(page):
return page + 1 if page <= 2 else page
assert [next_page(page) for page in (1, 2, 3)] == [2, 3, 3]
print("pageMove transitions: 1->2, 2->3, 3->3")
print("skip button has no onClick attribute")
PYRepository: IBAS-DEV-PROJECT/vac_client Length of output: 2299 온보딩 진입과 완료 동작을 연결하세요.
🤖 Prompt for AI Agents
Contributor
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. 페이지 구현 시 나머지는 다른 페이지들이 아직 구현 중이라 지금 당장 연결하지 않아도 되고, 각 페이지 완성 시점에 추가하시면 됩니다. 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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- application entry ---'
for file in src/App.tsx src/main.tsx package.json; do
if [ -f "$file" ]; then
cat -n "$file"
fi
done
printf '%s\n' '--- page and router references ---'
rg -n -C 3 --glob '*.{ts,tsx,js,jsx,json}' \
'from [\"\x27]react-router-dom|<Routes|<Route|createBrowserRouter|BrowserRouter|HashRouter|Onboarding|Test2' \
src package.json 2>/dev/null || trueLength of output: 7414
온보딩을 URL 경로로 등록하려면 You are interacting with an AI system.
Contributor
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.
현재 해당 버튼과 동일한 스타일로 사용되고 있으므로, 중복된 버튼 구현 대신 공용 컴포넌트를 사용하도록 교체 부탁드립니다.
Contributor
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. 온보딩 페이지들 중 마지막 페이지의 버튼 이름이 '다음' -> '가볍게 시도해보기'로 바뀌어야 합니다! |
||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
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.
코드 곳곳에서 인라인 스타일을 사용한 부분이 확인됩니다.
저희 프로젝트에서는 Tailwind CSS를 스타일링 도구로 사용하고 있으므로, 인라인 스타일 대신 Tailwind CSS를 활용하는 방식으로 수정 부탁드립니다!