Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9333e39
feat: 온보딩 랜딩 분기 추가
jllee000 Sep 14, 2025
679b3f0
feat: 이메일 로컬스토리지 저장
jllee000 Sep 17, 2025
607759c
Merge remote-tracking branch 'origin/develop' into feat/#109/jl-QA-2
jllee000 Sep 17, 2025
6a064db
feat: 로컬스토리지 저장 방식 수정
jllee000 Sep 17, 2025
9a88958
feat: dateTIme 공컴 로직 수정
jllee000 Sep 17, 2025
4c6e350
feat: 타임피커 취소버튼 기능 수정
jllee000 Sep 17, 2025
8c5eca3
feat: 리마인드 타임 전닫ㄹ
jllee000 Sep 17, 2025
2275203
feat: 초기 생성팝업 시, get리마인드 시간으로 세팅
jllee000 Sep 17, 2025
ffcba4e
feat: 카테고리 추가 바로 반영 로직
jllee000 Sep 17, 2025
1419a3f
feat: 온보딩 fcm 지점 수정 및 스토리지 네이밍 수종
jllee000 Sep 18, 2025
0168c30
feat: 아이콘 이미지 수정
jllee000 Sep 18, 2025
0875203
feat: 스텝 분기 맥 아닐때 로직 수정
jllee000 Sep 18, 2025
faff62a
feat: 개발용 주석 제거
jllee000 Sep 18, 2025
328f78e
feat: 콘솔로그 제거하기
jllee000 Sep 18, 2025
86e003b
feat: 카테고리 업데이트 시점 수정
jllee000 Sep 20, 2025
24fceed
feat: 콘솔 제거
jllee000 Sep 20, 2025
e3ac97a
feat: 카테고리 추가버튼 10개이상 때 삭제
jllee000 Sep 20, 2025
94c82fb
feat: 대시보드 이동 크롬방식으로
jllee000 Sep 20, 2025
c5c7d4f
feat: 팝 창닫기 로직 복구
jllee000 Sep 20, 2025
5de163f
feat: 리마인드 시간 null 경우 적용
jllee000 Sep 20, 2025
6a4ca39
chore: 머지 충돌에러 수정
jllee000 Sep 20, 2025
60908e1
feat: 익스텐션 랜딩 도메인 수정
jllee000 Sep 22, 2025
67dce9d
feat: 익스텐션 팝업 리마인드 토글 true 및 메모 글자수 반영
jllee000 Sep 22, 2025
b3cbad5
feat: 수정 시에 토스트 띄우기
jllee000 Sep 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions apps/client/src/pages/onBoarding/components/funnel/AlarmBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,26 @@ const boxStyle = cva(

const AlarmBox = ({ select, isDisabled, onClick }: AlarmBoxProps) => {
const [showPicker, setShowPicker] = useState(false);
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);
}
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);
};
Comment on lines +27 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 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.

return (
<div
className={boxStyle({ disabled: isDisabled })}
onClick={() => {
if (select === 3) {
setShowPicker(true);
setShowPicker(true);
} else {
setShowPicker(false);
}
Expand All @@ -60,18 +68,20 @@ const AlarmBox = ({ select, isDisabled, onClick }: AlarmBoxProps) => {
{select === 3 && isDisabled && (
<>
{AlarmsType[2].time && (
<p className="caption2-m text-font-gray-3">{normalizeTime(AlarmsType[2].time)}</p>
<p className="caption2-m text-font-gray-3">
{normalizeTime(AlarmsType[2].time)}
</p>
)}

{showPicker && ( <TimePicker
{showPicker && (
<TimePicker
onSave={getTimePicker}
onCancel={() => {
setShowPicker(false);
}}
onClick={(e) => e.stopPropagation()}
/>)}


/>
)}
</>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ export default function CardEditModal({
const [errorTxt, setErrorTxt] = useState('');

const saveCategory = () => {
if (categoryTitle.length > 20) {
if (categoryTitle.length > 10) {
setIsPopError(true);
setErrorTxt('20자 이내로 작성해주세요');
setErrorTxt('10자 이내로 작성해주세요');
} else {
setIsPopupOpen(false);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/extension/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const App = () => {
};

const handleDuplicateRightClick = () => {
chrome.tabs.create({ url: 'https://pinback.today' });
chrome.tabs.create({ url: 'https://www.pinback.today/' });
};

return (
Expand Down
34 changes: 20 additions & 14 deletions apps/extension/src/hooks/useCategoryManager.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { useState,useEffect } from "react";
import { usePostCategories, useGetCategoriesExtension } from "@apis/query/queries";
import type { Category } from "@shared-types/types";
import { AxiosError } from "axios";
import { useState, useEffect } from 'react';
import {
usePostCategories,
useGetCategoriesExtension,
} from '@apis/query/queries';
import type { Category } from '@shared-types/types';
import { AxiosError } from 'axios';

export const useCategoryManager = () => {
const { data: categoryData } = useGetCategoriesExtension();
const { mutate: postCategories } = usePostCategories();

const [categoryTitle, setCategoryTitle] = useState("");
const [categoryTitle, setCategoryTitle] = useState('');
const [isPopError, setIsPopError] = useState(false);
const [errorTxt, setErrorTxt] = useState("");
const [errorTxt, setErrorTxt] = useState('');

const [options, setOptions] = useState<string[]>(
categoryData?.data?.categories?.map((c: Category) => c.categoryName) ?? []
);
categoryData?.data?.categories?.map((c: Category) => c.categoryName) ?? []
);

useEffect(() => {
if (categoryData?.data?.categories) {
Expand All @@ -22,9 +25,9 @@ export const useCategoryManager = () => {
}, [categoryData]);

const saveCategory = (onSuccess?: (category: Category) => void) => {
if (categoryTitle.length > 20) {
if (categoryTitle.length > 10) {
setIsPopError(true);
setErrorTxt("20자 이내로 작성해주세요");
setErrorTxt('10자 이내로 작성해주세요');
return;
}

Expand All @@ -35,24 +38,27 @@ export const useCategoryManager = () => {
const newCategory: Category = {
categoryId: res.data.categoryId,
categoryName: categoryTitle,
categoryColor: res.data.categoryColor ?? "#000000",
categoryColor: res.data.categoryColor ?? '#000000',
};
setOptions((prev) => [...prev, newCategory.categoryName]);

onSuccess?.(newCategory);
resetPopup();
},
onError: (err: AxiosError<{ code: string; message: string }>) => {
alert(err.response?.data?.message ?? "카테고리 추가 중 오류가 발생했어요 😢");
alert(
err.response?.data?.message ??
'카테고리 추가 중 오류가 발생했어요 😢'
);
},
}
);
};

const resetPopup = () => {
setCategoryTitle("");
setCategoryTitle('');
setIsPopError(false);
setErrorTxt("");
setErrorTxt('');
};

return {
Expand Down
26 changes: 22 additions & 4 deletions apps/extension/src/pages/MainPop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
Dropdown,
validateDate,
validateTime,
Toast,
AutoDismissToast,
} from '@pinback/design-system/ui';
import { useState, useEffect } from 'react';
import { usePageMeta } from '@hooks/usePageMeta';
import { useSaveBookmark } from '@hooks/useSaveBookmarks';
import { Icon } from '@pinback/design-system/icons';

import {
usePostArticle,
useGetCategoriesExtension,
Expand Down Expand Up @@ -80,10 +83,11 @@
} else {
setImgUrl(initialImgUrl);
}
}, [initialImgUrl]);

Check warning on line 86 in apps/extension/src/pages/MainPop.tsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has a missing dependency: 'defaultImageUrl'. Either include it or remove the dependency array

// 아티클 팝업 정보들 상태
const [isRemindOn, setIsRemindOn] = useState(false);
const [toastIsOpen, setToastIsOpen] = useState(false);
const [isRemindOn, setIsRemindOn] = useState(true);
const [memo, setMemo] = useState('');
const [isPopupOpen, setIsPopupOpen] = useState(false);
const [isArticleId, setIsArticleId] = useState(0);
Expand Down Expand Up @@ -186,7 +190,6 @@
time: isRemindOn ? currentTime : time,
createdAt: new Date().toISOString(),
};
console.log(combineDateTime(saveData.date ?? '', saveData.time ?? ''));
if (type === 'add') {
save({
url,
Expand All @@ -210,6 +213,7 @@
: null,
});
} else {
setToastIsOpen(true);
putArticle({
articleId: isArticleId,
data: {
Expand All @@ -223,12 +227,26 @@
: null,
},
});
setTimeout(() => {
window.close();
}, 1000);
}
};

return (
<div className="App">
<div className="relative flex h-[56.8rem] w-[31.2rem] items-center justify-center">
{toastIsOpen && (
<div className="absolute bottom-[5rem] left-1/2 -translate-x-1/2">
<AutoDismissToast
duration={1000}
fadeMs={1000}
onClose={() => setToastIsOpen(false)}
>
<Toast text={`수정내용을 저장했어요`} />
</AutoDismissToast>
</div>
)}
{isPopupOpen && (
<PopupContainer
isOpen={isPopupOpen}
Expand Down Expand Up @@ -256,7 +274,7 @@
width={72}
height={20}
onClick={() => {
chrome.tabs.create({ url: 'https://pinback.today' });
chrome.tabs.create({ url: 'https://www.pinback.today/' });
}}
/>
</div>
Expand Down Expand Up @@ -288,7 +306,7 @@
<div>
<p className="caption1-sb mb-[0.4rem]">메모</p>
<Textarea
maxLength={100}
maxLength={500}
placeholder="나중에 내가 꺼내줄 수 있게 살짝 적어줘!"
value={memo}
onChange={(e) => setMemo(e.target.value)}
Expand Down
3 changes: 2 additions & 1 deletion apps/landing/src/components/FinalCTASection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ const FinalCTASection = () => {
도토리를 모아볼까요?
</p>
<div>
{/* TODO : 랜딩페이디 크롬스토어 설치페이지로 링크 추후 수정 필요! */}
<Button
variant="primary"
onClick={() => {
window.location.href = 'https://pinback.today/onboarding';
window.location.href = 'https://www.pinback.today/onboarding';
}}
>
지금 시작하기
Expand Down
12 changes: 1 addition & 11 deletions packages/design-system/src/icons/source/chippi_profile.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/design-system/src/icons/source/tooltip_1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/design-system/src/icons/source/tooltip_2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 1 addition & 10 deletions packages/design-system/src/icons/source/tooltip_3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions packages/design-system/src/icons/source/tooltip_4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions packages/design-system/src/icons/source/tooltip_5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading