Feat(재림): QA 반영 | 대시보드 랜딩 및 익스텐션 팝업 추가 수정#148
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough이 PR은 여러 앱에 걸쳐 소규모 동작/검증/도메인 업데이트와 UI 토스트 추가를 포함합니다. 익스텐션 MainPop의 편집 저장 흐름에 토스트 표시와 지연 종료가 추가되었고, 카테고리 제목 길이 제한이 10자로 통일되었습니다. 일부 외부 링크가 www 도메인으로 변경되었으며 경미한 리팩터링/포매팅이 반영되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as 사용자
participant MainPop as MainPop(Extension)
participant API as Backend API
participant Toast as AutoDismissToast
User->>MainPop: 편집 저장 클릭
MainPop->>API: putArticle(saveData)
API-->>MainPop: 200 OK
MainPop->>Toast: 토스트 표시(set toastIsOpen = true)
Note over MainPop,Toast: 성공 메시지 노출
MainPop-->>User: 1초 후 window.close()
alt 실패
API-->>MainPop: 에러
MainPop-->>User: 에러 처리(토스트 아님, 기존 흐름 유지)
end
sequenceDiagram
autonumber
actor User as 사용자
participant AlarmBox as AlarmBox
participant Parent as 부모 컴포넌트
participant TimePicker as TimePicker
User->>AlarmBox: 클릭(select===3)
AlarmBox->>TimePicker: 표시
User->>TimePicker: 시간 선택
TimePicker-->>AlarmBox: 시간 값
AlarmBox->>AlarmBox: 시간 포맷/상태 업데이트
AlarmBox->>TimePicker: 숨김
opt 선택적 후처리
AlarmBox->>Parent: onClick() 호출(있을 경우)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Storybook chromatic 배포 확인: |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (1)
80-98: 리마인드 저장 시 날짜/시간 에러 미차단
dateError/timeError가 존재해도remindTime을 생성해 저장 시도합니다. 서버 에러/누락 데이터 가능성이 있습니다.저장 전에 에러/빈값을 차단하세요:
const saveData = () => { if (!prevData?.id) { console.error('Article ID is missing, cannot save.'); setToastIsOpen(true); return; } - const remindTime = - isRemindOn && date && time ? buildUtcIso(date, time) : null; + if (isRemindOn) { + if (dateError || timeError || !date || !time) { + setToastIsOpen(true); + return; + } + } + const remindTime = isRemindOn ? buildUtcIso(date, time) : null;Also applies to: 219-233
apps/extension/src/pages/MainPop.tsx (1)
216-233: API 성공 확인 전에 토스트/창 닫힘 발생 — 성공 콜백으로 이동 필요현재 putArticle 호출 직후 토스트 표시 및 1초 후
window.close()가 실행되어, 실패해도 성공처럼 보일 수 있습니다. 성공/실패 콜백으로 분기하고, 닫기 타이밍도 토스트 표시 타이밍과 동기화하세요.권장 diff:
- setToastIsOpen(true); - putArticle({ - articleId: isArticleId, - data: { - categoryId: saveData.selectedCategory - ? parseInt(saveData.selectedCategory) - : 0, - memo: saveData.memo, - now: new Date().toISOString(), - remindTime: isRemindOn - ? combineDateTime(saveData.date ?? '', saveData.time ?? '') - : null, - }, - }); - setTimeout(() => { - window.close(); - }, 1000); + putArticle( + { + articleId: isArticleId, + data: { + categoryId: saveData.selectedCategory + ? parseInt(saveData.selectedCategory) + : 0, + memo: saveData.memo, + now: new Date().toISOString(), + remindTime: isRemindOn + ? combineDateTime(saveData.date ?? '', saveData.time ?? '') + : null, + }, + }, + { + onSuccess: () => { + setToastIsOpen(true); + // 토스트 duration(1000) + fadeMs(1000)과 동기화 + setTimeout(() => window.close(), 2000); + }, + onError: () => { + alert('저장에 실패했어요. 잠시 후 다시 시도해주세요.'); + }, + } + );추가 권장: 저장 직전 날짜/시간 유효성 재검증(에러 시 early-return)을 넣어 서버에 잘못된 값이 전달되지 않도록 하세요.
🧹 Nitpick comments (15)
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (3)
58-61: 카테고리 제목 길이 검증: 공백 처리 및 에러 상태 초기화 제안
trim()없이 길이만 체크하면 공백만 입력해도 통과합니다.- 성공 시 에러 상태와 메시지를 즉시 해제하는 편이 UX에 좋습니다.
다음처럼 보완 제안드립니다:
const saveCategory = () => { - if (categoryTitle.length > 10) { - setIsPopError(true); - setErrorTxt('10자 이내로 작성해주세요'); - } else { - setIsPopupOpen(false); - } + const title = categoryTitle.trim(); + if (title.length === 0) { + setIsPopError(true); + setErrorTxt('제목을 입력해주세요'); + return; + } + if (title.length > 10) { + setIsPopError(true); + setErrorTxt('10자 이내로 작성해주세요'); + return; + } + setIsPopError(false); + setErrorTxt(''); + setIsPopupOpen(false); }
90-97: categoryId의 -1 폴백 전송은 위험선택 불일치 시
-1을 전송하면 4xx/서버 오류를 유발할 수 있습니다. 저장 차단 또는 null 전송 정책 확인이 필요합니다.예시:
- categoryId: - category?.categories.find((cat) => cat.name === selectedCategory)?.id ?? -1, + const selected = category?.categories.find((cat) => cat.name === selectedCategory); + if (!selected) { + setToastIsOpen(true); + return; + } + categoryId: selected.id,Also applies to: 93-94
234-235: 구식 TODO 제거이미 저장 API 연동과 실패 토스트가 구현되어 TODO는 불필요합니다.
-{/* TODO: onClick 추후 저장 api 연결후 실패/성공 연결 */} <Button onClick={saveData}>저장하기</Button>apps/extension/src/hooks/useCategoryManager.ts (7)
28-31: 카테고리 제목 검증: 공백/빈값 처리 및 메시지 보완현재 길이만 검사합니다. 공백만 입력/빈 문자열 허용 가능성이 있습니다.
- if (categoryTitle.length > 10) { - setIsPopError(true); - setErrorTxt('10자 이내로 작성해주세요'); - return; - } + const title = categoryTitle.trim(); + if (title.length === 0) { + setIsPopError(true); + setErrorTxt('제목을 입력해주세요'); + return; + } + if (title.length > 10) { + setIsPopError(true); + setErrorTxt('10자 이내로 작성해주세요'); + return; + }
27-56: 중복/예약어 방지 및 서버 호출에 정규화된 제목 사용
- 기존 카테고리 중복명 방지 필요.
- 기본 카테고리 “안 읽은 정보”는 생성 금지(과거 학습 맥락).
- 서버엔
trim()된 제목 전송.- postCategories( - { categoryName: categoryTitle }, + // 중복/예약어 체크 + if (options.includes(title)) { + setIsPopError(true); + setErrorTxt('이미 존재하는 카테고리예요'); + return; + } + if (title === '안 읽은 정보') { + setIsPopError(true); + setErrorTxt('기본 카테고리 이름은 사용할 수 없어요'); + return; + } + + postCategories( + { categoryName: title }, { onSuccess: (res) => { const newCategory: Category = { categoryId: res.data.categoryId, - categoryName: categoryTitle, + categoryName: title, categoryColor: res.data.categoryColor ?? '#000000', }; setOptions((prev) => [...prev, newCategory.categoryName]);
49-52: 훅 내부alert사용 지양: UI 제어 역전훅은 사이드 이펙트 UI를 직접 띄우지 않는 것이 좋습니다. 에러 상태를 노출하고, 토스트는 상위 컴포넌트에서 처리하세요.
- alert( - err.response?.data?.message ?? - '카테고리 추가 중 오류가 발생했어요 😢' - ); + setIsPopError(true); + setErrorTxt(err.response?.data?.message ?? '카테고리 추가 중 오류가 발생했어요 😢');
17-19: options 초기화 중복 패턴 단순화초기
useState에서categoryData를 읽고, 다시useEffect에서 세팅합니다. 하나로 줄이면 깔끔합니다.- const [options, setOptions] = useState<string[]>( - categoryData?.data?.categories?.map((c: Category) => c.categoryName) ?? [] - ); + const [options, setOptions] = useState<string[]>([]); useEffect(() => { if (categoryData?.data?.categories) { setOptions(categoryData.data.categories.map((c) => c.categoryName)); } }, [categoryData]);Also applies to: 21-25
21-25: 기본 카테고리 포함 보장“안 읽은 정보”를 옵션 첫 항목으로 보장하십시오(삭제 불가/생성 금지).
useEffect(() => { if (categoryData?.data?.categories) { - setOptions(categoryData.data.categories.map((c) => c.categoryName)); + const names = categoryData.data.categories.map((c) => c.categoryName); + const base = '안 읽은 정보'; + const merged = names.includes(base) ? names : [base, ...names]; + setOptions(merged); } }, [categoryData]);
37-47: 카테고리 추가 후 쿼리 무효화/동기화 권장로컬
options만 갱신하면 다른 구독 지점과 불일치할 수 있습니다.categoriesExtension쿼리를 무효화하거나setQueryData로 동기화하세요.import { AxiosError } from 'axios'; +import { useQueryClient } from '@tanstack/react-query'; ... export const useCategoryManager = () => { + const queryClient = useQueryClient(); ... postCategories( { categoryName: title }, { onSuccess: (res) => { const newCategory: Category = { categoryId: res.data.categoryId, categoryName: title, categoryColor: res.data.categoryColor ?? '#000000', }; setOptions((prev) => [...prev, newCategory.categoryName]); + queryClient.invalidateQueries({ queryKey: ['categoriesExtension'] });
59-62: 입력 변화 시 에러 상태 자동 해제길이 제한 내로 수정되면 에러 표시를 자동 해제하면 좋습니다.
const resetPopup = () => { setCategoryTitle(''); setIsPopError(false); setErrorTxt(''); }; + +useEffect(() => { + if (categoryTitle.trim().length <= 10) { + setIsPopError(false); + setErrorTxt(''); + } +}, [categoryTitle]);apps/landing/src/components/FinalCTASection.tsx (2)
26-27: 네비게이션 처리: a 링크 사용 검토내비게이션만 수행한다면 버튼 onClick 보다는 시맨틱한 a 링크가 적합합니다. 디자인 시스템 Button이
asChild를 지원한다면 다음 형태를 권장합니다:<Button asChild><a href="https://www.pinback.today/onboarding">…</a></Button>.
22-22: 주석 오타 수정(가독성)'랜딩페이디' → '랜딩페이지'.
적용 diff:
- {/* TODO : 랜딩페이디 크롬스토어 설치페이지로 링크 추후 수정 필요! */} + {/* TODO : 랜딩페이지 크롬스토어 설치 페이지로 링크 추후 수정 필요! */}apps/extension/src/pages/MainPop.tsx (1)
277-278: 도메인 하드코딩 중복 — 상수화 권장App.tsx와 동일 도메인을 반복 사용 중입니다.
const PINBACK_WEB_ORIGIN = 'https://www.pinback.today'등으로 공통화하면 유지보수성이 향상됩니다.apps/extension/src/App.tsx (1)
26-28: PINBACK 도메인 상수화(PINBACK_WEB_ORIGIN) 적용 및 레거시 참조 교체앱 코드의 하드코딩 도메인을 상수로 추출하고 레포 전역의 레거시(https://pinback.today)를 www 포함 표준(origin)으로 통일하세요.
- 변경 대상: apps/extension/src/App.tsx (lines 26–28 — handleDuplicateRightClick) — 'https://www.pinback.today/' → PINBACK_WEB_ORIGIN 상수로 대체.
- 추가 발견된 레거시 참조(교체 필요):
- apps/landing/index.html — line 19 (meta og:url), line 22 (meta og:image)
- apps/client/index.html — line 19 (meta og:url), line 22 (meta og:image)
- 참고: 실행하신 스크립트에서 인용부호 오류로 일부 검사가 실패했습니다. 누락 가능성이 있으니 재검증 명령(인용 이스케이프 포함)으로 전체 검색 권장:
rg -nP "https?://(?:www.)?pinback.today[^\s\"']*"apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx (1)
76-85: TimePicker 렌더 조건 단순화 및 결합도 축소 제안
select === 3 && isDisabled에 종속시키면 실제 의도(3번 카드 클릭 시 항상 열기)와 어긋날 수 있습니다.showPicker만으로 제어하고, 외부 스타일 상태(isDisabled)와 독립시키는 편이 UX/테스트 측면에서 유리합니다.권장 구조 예시:
{select === 3 && ( <> {!!AlarmsType[2].time && ( <p className="caption2-m text-font-gray-3">{AlarmsType[2].time}</p> )} {showPicker && ( <TimePicker onSave={getTimePicker} onCancel={() => setShowPicker(false)} onClick={(e) => e.stopPropagation()} /> )} </> )}추가 확인:
TimePicker가onClickprop을 실제로 수용하는지 타입 정의를 확인해 주세요(필요 시 래퍼 div로 이벤트 중단).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
packages/design-system/src/icons/source/chippi_profile.svgis excluded by!**/*.svgpackages/design-system/src/icons/source/tooltip_1.svgis excluded by!**/*.svgpackages/design-system/src/icons/source/tooltip_2.svgis excluded by!**/*.svgpackages/design-system/src/icons/source/tooltip_3.svgis excluded by!**/*.svgpackages/design-system/src/icons/source/tooltip_4.svgis excluded by!**/*.svgpackages/design-system/src/icons/source/tooltip_5.svgis excluded by!**/*.svg
📒 Files selected for processing (6)
apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx(2 hunks)apps/client/src/shared/components/cardEditModal/CardEditModal.tsx(1 hunks)apps/extension/src/App.tsx(1 hunks)apps/extension/src/hooks/useCategoryManager.ts(3 hunks)apps/extension/src/pages/MainPop.tsx(6 hunks)apps/landing/src/components/FinalCTASection.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-15T20:00:13.756Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#80
File: apps/client/src/shared/components/ui/modalPop/ModalPop.tsx:36-41
Timestamp: 2025-07-15T20:00:13.756Z
Learning: In apps/client/src/shared/components/ui/modalPop/ModalPop.tsx, the InfoBox component uses hardcoded values for title, location, and icon URL as temporary test data. These should be replaced with dynamic data from props when implementing actual functionality and should be marked with TODO comments for future changes.
Applied to files:
apps/landing/src/components/FinalCTASection.tsxapps/extension/src/pages/MainPop.tsx
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.
Applied to files:
apps/extension/src/hooks/useCategoryManager.ts
🧬 Code graph analysis (3)
apps/extension/src/pages/MainPop.tsx (2)
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
AutoDismissToast(12-49)packages/design-system/src/components/toast/Toast.tsx (1)
Toast(7-21)
apps/extension/src/hooks/useCategoryManager.ts (3)
apps/extension/src/apis/query/queries.ts (2)
useGetCategoriesExtension(36-44)usePostCategories(28-32)apps/extension/src/apis/axios.ts (1)
postCategories(35-38)apps/extension/src/types/types.ts (1)
Category(16-20)
apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx (2)
apps/client/src/constants/alarms.ts (1)
AlarmsType(11-15)apps/client/src/pages/onBoarding/utils/formatRemindTime.ts (1)
normalizeTime(1-26)
🔇 Additional comments (10)
apps/client/src/shared/components/cardEditModal/CardEditModal.tsx (3)
41-41: 리마인드 토글 기본값 요구사항 확인 필요PR 목표(“리마인드 토글버튼 true 디폴트”)와 현재 기본값(false)이 상이합니다. 이 컴포넌트에도 적용 대상인지 확인 부탁드립니다.
필요 시:
-const [isRemindOn, setIsRemindOn] = useState(false); +const [isRemindOn, setIsRemindOn] = useState(true);
151-167: “카테고리 추가하기” 팝업 오픈 트리거 확인
isPopupOpen을 true로 만드는 경로가 보이지 않습니다.Dropdown의 “추가하기”가 팝업을 여는 콜백을 제공한다면 연결이 필요합니다.디자인시스템 API에 따라(예:
onAddClick) 다음과 같이 연결해 주세요:<Dropdown options={category?.categories.map((category) => category.name) || []} selectedValue={selectedCategory} onChange={(value) => setSelectedCategory(value)} placeholder="선택해주세요" addItemLabel="추가하기" + onAddClick={() => setIsPopupOpen(true)} />Also applies to: 188-196
200-207: 메모 글자 수 제한 합의 범위 확인
maxLength={500}적용됐습니다. 확정된 한도(예: 300/500/1000 등)와 앱 전역 일관성 확인 부탁드립니다.apps/extension/src/hooks/useCategoryManager.ts (1)
41-42: 기본 색상 확인 필요
'#000000'폴백이 디자인시스템/서버 기본과 일치하는지 확인해 주세요. 상이하면 DS 토큰(예:--gray-900) 또는 서버 기본으로 맞추는 게 안전합니다.apps/extension/src/pages/MainPop.tsx (4)
11-13: 토스트 컴포넌트 도입 LGTM디자인시스템 Toast/AutoDismissToast 활용 적절합니다.
88-91: 리마인드 기본값 true 전환 OKPR 목표(“리마인드 토글버튼 true 디폴트”)와 일치합니다. 편집 모드 분기에서도 savedData에 따라 값이 덮이는지 확인만 부탁드립니다.
239-249: 토스트 페이드아웃과 창 닫기 타이밍 싱크AutoDismissToast가
duration=1000,fadeMs=1000이므로 토스트는 총 2초 후 사라집니다. 위 수정대로 창 닫기를 2초로 맞추면 시각적 컷오프가 사라집니다. 별도 변경 없으면 OK.
309-309: 메모 글자수 제한 500자 적용 OK. 백엔드 제약과 메시지 동기화 확인서버/스키마가 500자를 허용하는지와 오류 메시지(i18n 포함)가 제한과 일치하는지 확인해 주세요.
apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx (2)
46-46: 표시 조건 불일치로 TimePicker가 열리지 않을 수 있음 — 검증 필요File: apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx
Lines: 46-46여기서
setShowPicker(true)로 강제하지만 렌더 조건은select === 3 && isDisabled라isDisabled === false면 TimePicker가 열리지 않습니다. 동작을 일치시키세요.빠른 수정 제안:
- setShowPicker(true); + setShowPicker(isDisabled);또는 렌더 조건에서
isDisabled를 제거하고showPicker만으로 제어(권장).isDisabled의 의미(선택됨/비활성화)를 상위 사용처에서 확인하세요.검증 스크립트(원래 스크립트가 "unrecognized file type: tsx" 오류로 출력 없음 — 아래로 재실행):
#!/bin/bash rg -nP -C3 '<AlarmBox\b' -g '**/*.{tsx,jsx,ts,js}'
71-73: 중요 — normalizeTime이 영문 AM/PM(H:MM)만 파싱하므로 '오전/오후'·'시' 표기가 들어가면 NaN이 표시됩니다.
- 원인: apps/client/src/pages/onBoarding/utils/formatRemindTime.ts의 정규식이 /(AM|PM)\s?(\d{1,2}):(\d{1,2})/i 으로 한국어 '오전/오후'나 '9시' 등을 처리하지 않음.
- 조치(우선순위 제안):
- 권장 — AlarmBox의 getTimePicker에서 저장 포맷을 24시간 'HH:MM'으로 변경해 저장(파일: apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx, getTimePicker 부분).
- 또는 normalizeTime에 '오전/오후'와 '시' 포맷 파싱 로직 추가(파일: apps/client/src/pages/onBoarding/utils/formatRemindTime.ts).
- 또는 normalizeTime 호출 전 입력 검증 후 파싱 실패 시 원본 문자열을 그대로 표시하는 폴백 추가(표시: AlarmBox.tsx lines 70–73, API 전송 전: MainCard.tsx lines 139–141).
- 확인 필요: TimePicker가 반환하는 meridiem 값이 'AM'/'PM'인지 '오전'/'오후'인지 확인해 주세요 (확인 불가 시 파싱 확장 권장).
| const getTimePicker = ({ | ||
| hour, | ||
| minute, | ||
| meridiem, | ||
| }: { | ||
| hour: string; | ||
| minute: string; | ||
| meridiem: string; | ||
| }) => { | ||
| const formatted = `${meridiem} ${hour}:${minute}`; | ||
| AlarmsType[2].time = formatted; | ||
| setShowPicker(false); | ||
| // 이거 나중에 api 연결때 쓸려고 표시한거.. 그떄 지우겠듬여 console.log('저장된 사용자 알람:', AlarmsType[2].time); | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
전역 상수(AlarmsType) 직접 변이 및 시간 포맷 불일치
- import된 상수(AlarmsType)를 컴포넌트에서 직접 수정하면 예측 불가한 사이드이펙트/렌더 불일치가 납니다.
- 저장은 "AM/PM hh:mm" 포맷, 표시(1·2번 카드)는 "오전/오후 N시"로 혼재되어 있어 일관성이 없습니다.
- meridiem이 '오전/오후'로 들어오면 normalizeTime의 정규식(AM|PM)과 안 맞아 NaN 위험이 있습니다.
최소 수정으로는 저장 시 24시간 포맷으로 정규화하여 넣어주세요(AM/PM 미지정 시 '오전/오후' 매핑). 주석 로그는 제거 권장.
아래 diff를 적용하면 파싱 실패를 방지하며 표시도 단순화됩니다.
}) => {
- const formatted = `${meridiem} ${hour}:${minute}`;
- AlarmsType[2].time = formatted;
- setShowPicker(false);
- // 이거 나중에 api 연결때 쓸려고 표시한거.. 그떄 지우겠듬여 console.log('저장된 사용자 알람:', AlarmsType[2].time);
+ // AM/PM 미정규 표기(오전/오후) 대응 후 24h 포맷으로 저장
+ const merMap: Record<string, string> = { 오전: 'AM', 오후: 'PM' };
+ const mer = merMap[meridiem] ?? meridiem;
+ const normalized = normalizeTime(`${mer} ${hour}:${minute}`);
+ AlarmsType[2].time = normalized;
+ setShowPicker(false);
};추가로 권장(선택): 전역 상수 변이 대신 로컬 상태로 관리하세요.
// (26행 인접) 추가
const [customTime, setCustomTime] = useState<string>('');
// 저장 시
const normalized = normalizeTime(`${mer} ${hour}:${minute}`);
setCustomTime(normalized);또한 하드코딩 인덱스(2)는 상수로 추출해 의미를 드러내면 안전합니다(e.g., const CUSTOM_ALARM_INDEX = 2).
검증: 레포 전체에서 AlarmsType[2] 사용이 더 있는지 확인해 주세요.
🏁 Script executed:
#!/bin/bash
rg -nP -C2 'AlarmsType\[2\]\.time'Length of output: 1394
중요: AlarmsType 전역 직접 변경 금지 및 시간 포맷 정규화 필요
검증: apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx — 37행(할당), 70–73행(표시)에서 AlarmsType[2].time 사용 확인.
- 전역 상수(AlarmsType)를 컴포넌트에서 직접 변경하고 있음 → 즉시 중단. 로컬 state(setState)나 상위 콜백/액션으로 값 저장하도록 변경.
- meridiem이 '오전'/'오후'로 들어올 수 있어 normalizeTime이 기대하는 'AM'/'PM'과 불일치해 파싱 실패 가능. 저장 전에 '오전'→'AM', '오후'→'PM' 매핑 후 normalizeTime에 전달하여 24시간 포맷으로 정규화해 저장.
- 하드코딩 인덱스(2)는 상수로 추출: const CUSTOM_ALARM_INDEX = 2.
- 디버그용 주석·console.log 삭제.
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx around lines
27–40 (and note AlarmsType[2].time is read at 70–73), stop mutating the global
AlarmsType directly; instead store the selected time in local component state or
call an onChange/onSave callback to propagate it upward. Replace the hardcoded
index with a constant like const CUSTOM_ALARM_INDEX = 2 and use that when
referring to the custom alarm externally (via the callback), not by assigning
into AlarmsType. Before saving, normalize meridiem by mapping '오전'→'AM' and
'오후'→'PM', pass the mapped meridiem plus hour/minute into your existing
normalizeTime utility to get a 24-hour string, and save that result via
state/callback. Remove the debug comment and console.log line entirely.
📌 Related Issues
📄 Tasks
⭐ PR Point (To Reviewer)
📷 Screenshot
Summary by CodeRabbit
New Features
Chores