-
Notifications
You must be signed in to change notification settings - Fork 2
feat: SP1 매칭 정보 수정 페이지 퍼블리싱 #308
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 12 commits
073ea30
52a6640
80d8365
e77e61b
ebc7694
35bd25a
0a9db30
1be8cf3
aeb1d57
af76108
2791633
63642fd
731d6eb
96dbf08
38847dd
7fefbb5
5493c16
8dc3a8b
4172685
b47956a
cecb1dd
ff6def1
fb5eb06
514e955
9634c62
25ada84
cbbe807
85bfe7d
688a784
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,49 @@ | ||||||||||||||
| import Button from '@components/button/button/button'; | ||||||||||||||
| import { cn } from '@libs/cn'; | ||||||||||||||
|
|
||||||||||||||
| interface SelectionGroupProps { | ||||||||||||||
| title: string; | ||||||||||||||
| options: { id: number; label: string }[] | string[]; | ||||||||||||||
| selectedValue: string; | ||||||||||||||
| onSelect: (value: string) => void; | ||||||||||||||
| disabled?: boolean; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const SelectionGroup = ({ | ||||||||||||||
| title, | ||||||||||||||
| options, | ||||||||||||||
| selectedValue, | ||||||||||||||
| onSelect, | ||||||||||||||
| disabled, | ||||||||||||||
| }: SelectionGroupProps) => { | ||||||||||||||
| return ( | ||||||||||||||
| <div className="flex-col gap-[1.6rem]"> | ||||||||||||||
| <p className="body_16_m">{title}</p> | ||||||||||||||
| <div className="flex flex-wrap gap-[0.8rem]"> | ||||||||||||||
|
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. 🛠️ Refactor suggestion 선택 그룹 컨테이너에 접근성 역할 부여 (radiogroup) 단일 선택 그룹이면 radiogroup을 부여하고 제목으로 레이블링하세요. - <div className="flex flex-wrap gap-[0.8rem]">
+ <div
+ className="flex flex-wrap gap-[0.8rem]"
+ role="radiogroup"
+ aria-label={title}
+ >📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| {options.map((option) => { | ||||||||||||||
| const key = typeof option === 'string' ? option : option.id; | ||||||||||||||
| const label = typeof option === 'string' ? option : option.label; | ||||||||||||||
| const isSelected = selectedValue === label; | ||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
| <Button | ||||||||||||||
| key={key} | ||||||||||||||
| label={label} | ||||||||||||||
| variant={disabled ? 'disabled' : isSelected ? 'skyblue' : 'gray2'} | ||||||||||||||
| className={cn( | ||||||||||||||
| 'cap_14_sb w-auto px-[1.6rem] py-[0.6rem]', | ||||||||||||||
| disabled && 'cursor-not-allowed', | ||||||||||||||
| )} | ||||||||||||||
| onClick={() => { | ||||||||||||||
| if (disabled) return; | ||||||||||||||
| onSelect(label); | ||||||||||||||
| }} | ||||||||||||||
| /> | ||||||||||||||
|
Comment on lines
+29
to
+41
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. 💡 Verification agent 🧩 Analysis chainForm 내 의도치 않은 submit + 비활성화/선택 상태의 ARIA 미부여 Button이 form 내부에 있으면 기본 type=submit로 동작할 수 있습니다. 또한 비활성/선택 상태를 스크린리더가 인지하도록 role/aria/disabled를 명시하세요. Button 컴포넌트가 native props를 포워딩한다는 전제입니다. <Button
- key={key}
+ key={key}
label={label}
variant={disabled ? 'disabled' : isSelected ? 'skyblue' : 'gray2'}
className={cn(
'cap_14_sb w-auto px-[1.6rem] py-[0.6rem]',
disabled && 'cursor-not-allowed',
)}
+ type="button"
+ role="radio"
+ aria-checked={isSelected}
+ aria-disabled={disabled || undefined}
+ disabled={disabled}
+ data-selected={isSelected || undefined}
onClick={() => {
if (disabled) return;
onSelect(label);
}}
/>검증용 스크립트(위 변경이 유효하려면 Button이 해당 props를 지원해야 합니다): 🏁 Script executed: #!/bin/bash
# Button 컴포넌트가 HTMLButtonElement props(type/disabled/aria-*)를 포워딩하는지 확인
fd -a -t f -E node_modules '(?i)button\.(tsx|jsx)$'
rg -n -C2 -P 'export\s+(default\s+)?function\s+Button|type\s+ButtonProps|interface\s+ButtonProps' $(fd -t f -E node_modules '(?i)button\.(tsx|jsx)$')
rg -n -C2 -P '\bdisabled\b|\btype\??:\s*["'\'']?(button|submit)?' $(fd -t f -E node_modules '(?i)button\.(tsx|jsx)$')Length of output: 2836 Button 컴포넌트에 네이티브 버튼·ARIA 속성 포워딩 리팩터링 필요
🤖 Prompt for AI Agents |
||||||||||||||
| ); | ||||||||||||||
| })} | ||||||||||||||
| </div> | ||||||||||||||
| </div> | ||||||||||||||
| ); | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export default SelectionGroup; | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1 @@ | ||||||||||||
| export const PROFILE_SYNK_MATE = ['같은 팀 메이트', '상관없어요']; | ||||||||||||
|
||||||||||||
| export const PROFILE_SYNK_MATE = ['같은 팀 메이트', '상관없어요']; | |
| // src/pages/edit-profile/constants/edit-profile.ts | |
| export const PROFILE_SYNC_MATE = ['같은 팀 메이트', '상관없어요'] as const; | |
| export type ProfileSyncMateOption = typeof PROFILE_SYNC_MATE[number]; |
🤖 Prompt for AI Agents
In src/pages/edit-profile/constants/edit-profile.ts (line 1), rename the
constant PROFILE_SYNK_MATE to PROFILE_SYNC_MATE, append `as const` to the
exported array to make it readonly, and add a narrow union type (e.g., type
ProfileSyncMate = typeof PROFILE_SYNC_MATE[number]) to represent allowed values;
then update src/pages/edit-profile/edit-profile.tsx import to use
PROFILE_SYNC_MATE and change any usage from options={PROFILE_SYNK_MATE} to
options={PROFILE_SYNC_MATE}.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| import Button from '@components/button/button/button'; | ||
| import Input from '@components/input/input'; | ||
| import { cn } from '@libs/cn'; | ||
| import SelectionGroup from '@pages/edit-profile/components/selection-group'; | ||
| import { PROFILE_SYNK_MATE } from '@pages/edit-profile/constants/edit-profile'; | ||
| import { mockEditData } from '@pages/edit-profile/mocks/mockEditData'; | ||
| import { | ||
| GENDER, | ||
| NO_TEAM_OPTION, | ||
| TEAMS, | ||
| VIEWING_STYLE, | ||
| } from '@pages/onboarding/constants/onboarding'; | ||
| import { NICKNAME_RULE_MESSAGE } from '@pages/sign-up/constants/NOTICE'; | ||
| import { NICKNAME_PLACEHOLDER } from '@pages/sign-up/constants/validation'; | ||
| import { useMemo, useRef, useState } from 'react'; | ||
|
|
||
| const EditProfile = () => { | ||
| const [team, setTeam] = useState(mockEditData.team); | ||
| const [gender, setGender] = useState(mockEditData.genderPreference); | ||
| const [mateTeam, setMateTeam] = useState(mockEditData.teamAllowed || '상관없어요'); | ||
| const [viewStyle, setViewStyle] = useState(mockEditData.style); | ||
| const [isSubmit, setIsSubmit] = useState(false); | ||
|
|
||
| const initialValue = useRef({ | ||
| team: mockEditData.team, | ||
| gender: mockEditData.genderPreference, | ||
| mateTeam: mockEditData.teamAllowed, | ||
| viewStyle: mockEditData.style, | ||
| }); | ||
|
|
||
| const isDirty = useMemo(() => { | ||
| const init = initialValue.current; | ||
|
|
||
| return ( | ||
| team !== init.team || | ||
| gender !== init.gender || | ||
| mateTeam !== init.mateTeam || | ||
| viewStyle !== init.viewStyle | ||
| ); | ||
| }, [team, gender, mateTeam, viewStyle]); | ||
|
|
||
| const handleSaveClick = () => { | ||
| if (!isDirty) return; | ||
|
|
||
| setIsSubmit(true); | ||
|
|
||
| // TODO: 실제 API 호출 | ||
| }; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return ( | ||
| <div className="h-full bg-gray-white px-[1.6rem] pt-[1.6rem] pb-[4rem]"> | ||
| <h2 className="subhead_18_sb mb-[1.6rem]">프로필 수정</h2> | ||
| <section> | ||
| <Input | ||
| placeholder={NICKNAME_PLACEHOLDER} | ||
| label="닉네임" | ||
| defaultMessage={NICKNAME_RULE_MESSAGE} | ||
| /> | ||
| <div className="mb-[2.5rem] flex justify-end"> | ||
| <Button label="수정" className="cap_14_sb mt-[0.8rem] w-auto px-[1.6rem] py-[0.6rem]" /> | ||
| </div> | ||
|
|
||
| <Input | ||
| placeholder={NICKNAME_PLACEHOLDER} | ||
| label="한 줄 소개" | ||
| defaultMessage={NICKNAME_RULE_MESSAGE} | ||
| /> | ||
| <div className="flex justify-end"> | ||
| <Button label="수정" className="cap_14_sb mt-[0.8rem] w-auto px-[1.6rem] py-[0.6rem]" /> | ||
| </div> | ||
| </section> | ||
|
|
||
| <section className="flex-col pb-[5.6rem]"> | ||
| <h2 className="subhead_18_sb mb-[0.4rem]">매칭 조건 수정</h2> | ||
| <p className="cap_12_m mb-[1.6rem] text-gray-500"> | ||
| 수정한 조건을 기반으로 새로운 메이트를 추천해드려요! | ||
| </p> | ||
|
|
||
| <div className="flex-col gap-[3.2rem]"> | ||
| <div className="flex-col gap-[1.6rem]"> | ||
| <p className="body_16_m">응원팀</p> | ||
| <div className="flex flex-wrap gap-[0.8rem]"> | ||
| {TEAMS.map((option) => { | ||
| const selected = team === option; | ||
| return ( | ||
| <Button | ||
| key={option} | ||
| label={option} | ||
| variant={selected ? 'skyblue' : 'gray2'} | ||
| className="cap_14_sb w-auto px-[1.6rem] py-[0.6rem] text-gray-900" | ||
| onClick={() => setTeam(option)} | ||
| /> | ||
| ); | ||
| })} | ||
| <Button | ||
| label={NO_TEAM_OPTION} | ||
| variant={team === NO_TEAM_OPTION ? 'skyblue' : 'gray2'} | ||
| className="cap_14_sb w-fit px-[1.6rem] py-[0.6rem] text-gray-900" | ||
| onClick={() => setTeam(NO_TEAM_OPTION)} | ||
| /> | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| </div> | ||
| </div> | ||
|
|
||
| <SelectionGroup | ||
| title="직관 메이트의 응원팀" | ||
| options={PROFILE_SYNK_MATE} | ||
| selectedValue={team === NO_TEAM_OPTION ? '' : mateTeam} | ||
| onSelect={setMateTeam} | ||
| disabled={team === NO_TEAM_OPTION} | ||
| /> | ||
|
|
||
| <SelectionGroup | ||
| title="관람 스타일" | ||
| options={VIEWING_STYLE} | ||
| selectedValue={viewStyle} | ||
| onSelect={setViewStyle} | ||
| /> | ||
|
|
||
| <SelectionGroup | ||
| title="선호 성별" | ||
| options={GENDER} | ||
| selectedValue={gender} | ||
| onSelect={setGender} | ||
| /> | ||
| </div> | ||
| </section> | ||
|
|
||
| <Button | ||
| variant={!isDirty || isSubmit ? 'disabled' : 'blue'} | ||
| className={cn((!isDirty || isSubmit) && 'cursor-not-allowed')} | ||
| onClick={handleSaveClick} | ||
| label="매칭 조건 수정" | ||
| ariaLabel="매칭 조건 수정" | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default EditProfile; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,6 @@ | ||||||||||||||||||||||||||||||||
| export const mockEditData = { | ||||||||||||||||||||||||||||||||
| team: '응원하는 팀이 없어요.', | ||||||||||||||||||||||||||||||||
| teamAllowed: '상관없어요', | ||||||||||||||||||||||||||||||||
| style: '직관먹방러', | ||||||||||||||||||||||||||||||||
| genderPreference: '여성', | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+6
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. 🛠️ Refactor suggestion 타입 안전성 보강: 명시적 폼 타입으로 검증(satisfies) 적용 mock 객체에 명시적 타입을 부여해 키 누락/오타를 컴파일 타임에 차단하세요. 기존 폼 타입이 있다면 import 하여 satisfies를 쓰는 걸 권장합니다. +// TODO: 실제 폼 타입 경로로 교체
+import type { EditProfileFormValues } from '../types';
-export const mockEditData = {
+export const mockEditData = {
team: '응원하는 팀이 없어요.',
- teamAllowed: '상관없어요',
- style: '직관먹방러',
- genderPreference: '여성',
+ mateTeam: '상관없어요',
+ viewStyle: '직관먹방러',
+ gender: '여성',
-};
+} satisfies EditProfileFormValues;만약 공용 타입이 없다면, 모크에 중복 정의하지 말고 공용 타입을 새로 추가해 재사용하도록 제안합니다. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,11 @@ export const buttonVariants = cva( | |
| variant: { | ||
| blue: 'bg-main-900 text-white', | ||
| gray: 'bg-gray-400 text-white', | ||
| skyblue: 'bg-main-200 text-main-900', | ||
| skyblue: '!text-main-900 bg-main-200', | ||
|
||
| white: 'bg-white text-gray-700', | ||
| skyblueBorder: 'bg-main-200 text-main-900 outline outline-main-900', | ||
| gray2: 'bg-background text-gray-700', | ||
| disabled: 'bg-gray-100 text-gray-400', | ||
| }, | ||
| size: { | ||
| M: 'w-full px-[0.8rem] py-[1.2rem]', | ||
|
|
||
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.
flex 누락으로 레이아웃/간격 깨짐
gap이 적용되지 않고 수직 레이아웃도 동작하지 않습니다. flex를 추가하세요.
📝 Committable suggestion
🤖 Prompt for AI Agents