Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
18a9826
api: 익스텐션 저장 api 연동
jllee000 Sep 11, 2025
3513d8b
Merge remote-tracking branch 'origin/develop' into api/#67/extension-…
jllee000 Sep 11, 2025
2cb25a8
feat: 일시 조회 및 포맷팅 연결
jllee000 Sep 11, 2025
b125950
feat: duplicate 기준 분리
jllee000 Sep 11, 2025
ba5af17
feat: 수정 / 추가 분기 로직
jllee000 Sep 11, 2025
06a4e4d
feat: 실제 북마크 저장 위치 수정
jllee000 Sep 11, 2025
4a7febf
feat: 오늘 날짜데이터 전달
jllee000 Sep 11, 2025
3f0f135
feat: 카테고리 얼럿 추가
jllee000 Sep 11, 2025
c79473c
feat: 메타데이터 에러 분기
jllee000 Sep 11, 2025
735632d
chore: 주석 제거
jllee000 Sep 11, 2025
6f480d3
feat: 카테고리 연동 및 데이터 구조 수정
jllee000 Sep 11, 2025
8a031d6
chore: 머지 충돌 에러 수정
jllee000 Sep 11, 2025
00b0400
feat: api 서버 도메인 수정
jllee000 Sep 11, 2025
a836ee8
feat: 토큰 크롬스토리지 구조로 수정
jllee000 Sep 11, 2025
7280bbe
feat: 카테고리 추가 바로 반영 및 얼럿 추가
jllee000 Sep 12, 2025
d2bea02
feat: 서비스명 수정
jllee000 Sep 12, 2025
1ee1580
feat: 리마인드 타임 onChange 조작 수정
jllee000 Sep 12, 2025
b414207
feat: 실시간 포맷 복구 및 blur일때만 부모한테 전달
jllee000 Sep 12, 2025
474075f
Merge remote-tracking branch 'origin/develop' into api/#67/extension-…
jllee000 Sep 12, 2025
806986e
feat: 스토리북 코드 수정
jllee000 Sep 12, 2025
659a7e2
feat: 스토리북 빌드 에러
jllee000 Sep 12, 2025
20a50e8
feat: 주석 제거
jllee000 Sep 12, 2025
7564555
feat: 스토리북 기본 인풋 placeholder로 채우기
jllee000 Sep 12, 2025
ac9d9a6
feat: 임시 토큰 제거 및 api 파일 구조 수정
jllee000 Sep 12, 2025
d2dd838
feat: api 쿼리 분기 제거
jllee000 Sep 13, 2025
8ebed5b
feat: TODO 표시
jllee000 Sep 13, 2025
90002f3
feat: TODO 표시
jllee000 Sep 13, 2025
ea1746c
chore: 빌드에러 수정
jllee000 Sep 13, 2025
165805e
chore: 머지 에러충돌 해결
jllee000 Sep 13, 2025
9cd52cf
feat: 코드리뷰 반영 디폴트 썸네일 이미지
jllee000 Sep 13, 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
194 changes: 29 additions & 165 deletions apps/extension/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,179 +1,43 @@
import './App.css';
import {
InfoBox,
Button,
Textarea,
DateTime,
Switch,
PopupContainer,
Dropdown,
validateDate,
validateTime
} from '@pinback/design-system/ui';
import { useState } from 'react';
import DuplicatePop from './pages/DuplicatePop';
import MainPop from './pages/MainPop';
import { useState, useEffect } from 'react';
import { useGetArticleSaved } from '@apis/query/queries';
import { usePageMeta } from './hooks/usePageMeta';
import { useSaveBookmark } from './hooks/useSaveBookmarks';
import { Icon } from '@pinback/design-system/icons';
const App = () => {
const [isRemindOn, setIsRemindOn] = useState(false);
const [memo, setMemo] = useState('');
const [isPopupOpen, setIsPopupOpen] = useState(false);

const [selected, setSelected] = useState<string | null>(null);
// 시간,날짜 검사 구간!
const [date, setDate] = useState('2025.10.10');
const [time, setTime] = useState('19:00');
const [dateError, setDateError] = useState('');
const [timeError, setTimeError] = useState('');
const App = () => {
const { url } = usePageMeta();
const { data: isSaved } = useGetArticleSaved(url);

const handleDateChange = (value: string) => {
setDate(value);
setDateError(validateDate(value));
};
const [isDuplicatePop, setIsDuplicatePop] = useState(false);
const [mainPopType, setMainPopType] = useState<"add" | "edit">("add");

const handleTimeChange = (value: string) => {
setTime(value);
setTimeError(validateTime(value));
};
useEffect(() => {
if (isSaved?.data) {
setIsDuplicatePop(true);
}
}, [isSaved]);
Comment on lines +12 to +19
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

재조회로 인한 DuplicatePop 재등장 방지(사용자 확인 상태 보존)

포커스/리패치 등으로 isSaved가 갱신되면 DuplicatePop이 다시 나타날 수 있습니다. 사용자가 한 번 확인하면 더 이상 토글되지 않도록 상태를 추가하세요.

   const [isDuplicatePop, setIsDuplicatePop] = useState(false);
   const [mainPopType, setMainPopType] = useState<"add" | "edit">("add");
+  const [dupAck, setDupAck] = useState(false); // 사용자가 중복 알림을 확인했는지

   useEffect(() => {
-    if (isSaved?.data) {
+    if (!dupAck && isSaved?.data) {
       setIsDuplicatePop(true);
     }
-  }, [isSaved]);
+  }, [dupAck, isSaved]);
 ...
   const handleDuplicateLeftClick = () => {
     setIsDuplicatePop(false);
     setMainPopType("edit");
+    setDupAck(true);
   };

Also applies to: 21-24

🤖 Prompt for AI Agents
In apps/extension/src/App.tsx around lines 12-19 (and similarly 21-24), the
DuplicatePop reopens whenever isSaved updates; add a new boolean state (e.g.,
hasConfirmedDuplicate) to persist that the user already acknowledged the
duplicate and update the useEffect(s) to set isDuplicatePop true only when
isSaved?.data is present AND hasConfirmedDuplicate is false; also ensure the
user confirmation handler sets hasConfirmedDuplicate true so future isSaved
changes do not reopen the popup.


// 스위치
const handleSwitchChange = (checked: boolean) => {
setIsRemindOn(checked);
const handleDuplicateLeftClick = () => {
setIsDuplicatePop(false);
setMainPopType("edit");
};

const { url, title, description, imgUrl } = usePageMeta();
const { save } = useSaveBookmark();

const handleSave = async () => {
save({
url,
title,
description,
imgUrl,
memo,
isRemindOn,
selectedCategory: selected,
date: isRemindOn ? date : null,
time: isRemindOn ? time : null,
});
const saveData = {
url,
title,
description,
imgUrl,
memo,
isRemindOn,
selectedCategory: selected,
date: isRemindOn ? date : null,
time: isRemindOn ? time : null,
createdAt: new Date().toISOString(),
};
console.log('저장된 데이터:', saveData);
const handleDuplicateRightClick = () => {
window.location.href = "/dashboard";
};
const [categoryTitle, setCategoryTitle] = useState('');
const [isPopError, setIsPopError] = useState(false);
const [errorTxt, setErrorTxt] = useState('');
const saveCategory = () => {
if (categoryTitle.length >20){
setIsPopError(true);
setErrorTxt('20자 이내로 작성해주세요');
} else{
setIsPopupOpen(false);
}
}


return (
<div className="App">
<div className="relative flex h-[56.8rem] w-[31.2rem] items-center justify-center">
{isPopupOpen && (
<PopupContainer
isOpen={isPopupOpen}
onClose={() => setIsPopupOpen(false)}
type="input"
title="카테고리 추가하기"
left="취소"
right="확인"
inputValue={categoryTitle}
isError={isPopError}
errortext={errorTxt}
onInputChange={setCategoryTitle}
placeholder="카테고리 제목을 입력해주세요"
onLeftClick={() => setIsPopupOpen(false)}
onRightClick={saveCategory}
/>
)}
<div className="flex flex-col justify-between gap-[1.6rem] rounded-[12px] bg-white px-[3.2rem] py-[2.4rem] text-black">
<div className="mr-auto">
<Icon name="main_logo" width={72} height={20} />
</div>

<InfoBox
title={title || '제목 없음'}
source={description || '웹페이지'}
imgUrl={imgUrl}
/>

<div>
<p className="caption1-sb mb-[0.4rem]">카테고리</p>
<Dropdown
options={['옵션1', '옵션2']}
selectedValue={selected}
onChange={(value) => setSelected(value)}
placeholder="선택해주세요"
onAddItem={() => setIsPopupOpen(true)}
addItemLabel="추가하기"
/>
</div>

<div>
<p className="caption1-sb mb-[0.4rem]">메모</p>
<Textarea
maxLength={100}
placeholder="나중에 내가 꺼내줄 수 있게 살짝 적어줘!"
onChange={(e) => setMemo(e.target.value)}
/>
</div>

<div>
<div className="mb-[0.4rem] flex items-center justify-between">
<p className="caption1-sb">리마인드</p>
<Switch
onCheckedChange={handleSwitchChange}
checked={isRemindOn}
/>
</div>

<div className="mb-[0.4rem] flex items-center justify-between gap-[0.8rem]">
<DateTime
type="date"
state={
dateError ? 'error' : isRemindOn ? 'default' : 'disabled'
}
value={date}
onChange={handleDateChange}
/>
<DateTime
type="time"
state={
timeError ? 'error' : isRemindOn ? 'default' : 'disabled'
}
value={time}
onChange={handleTimeChange}
/>
</div>

{/* 에러 메시지 출력 */}
{dateError && <p className="body3-r text-error">{dateError}</p>}
{timeError && <p className="body3-r text-error">{timeError}</p>}
</div>

<Button size="medium" onClick={handleSave}>
저장
</Button>
</div>
</div>
</div>
<>
{isDuplicatePop ? (
<DuplicatePop
onLeftClick={handleDuplicateLeftClick}
onRightClick={handleDuplicateRightClick}
/>
) : (
<MainPop type={mainPopType} savedData={isSaved?.data}/>
)}
</>
Comment on lines +31 to +40
Copy link
Member

Choose a reason for hiding this comment

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

컴포넌트를 분리해주셔서 app이 너무 깔끔해졌어요!!! 👍

);
};

Expand Down
88 changes: 87 additions & 1 deletion apps/extension/src/apis/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,25 @@
'Content-Type': 'application/json',
},
});

localStorage.setItem("token", 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaW5iYWNrIiwiaWQiOiI4NjA1NTBiMS1kZDBhLTQyMjMtYjM4OS0wNTEwYWU3MmNkMzUiLCJzdWIiOiJBY2Nlc3NUb2tlbiIsImV4cCI6MTc1NzYyOTAyMn0.qm-zqkuG2rpLlbUKJd9lUdh-4SStittgzXiwBeUMzA6NuKh_aEJmgoVInhUU-VSFtTlXP8eO9Ivao5K29LCRJA');
chrome.storage.local.set(
{ token: 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaW5iYWNrIiwiaWQiOiI4NjA1NTBiMS1kZDBhLTQyMjMtYjM4OS0wNTEwYWU3MmNkMzUiLCJzdWIiOiJBY2Nlc3NUb2tlbiIsImV4cCI6MTc1NzYyOTAyMn0.qm-zqkuG2rpLlbUKJd9lUdh-4SStittgzXiwBeUMzA6NuKh_aEJmgoVInhUU-VSFtTlXP8eO9Ivao5K29LCRJA',
email:'test@gmail2.com'
},
() => {
console.log("토큰 저장 완료 ✅");

Check warning on line 15 in apps/extension/src/apis/axiosInstance.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
}
);
// apiRequest.interceptors.request.use((config) => {
// // signup은 토큰 필요 없음
// if (config.url !== "/auth/signup") {
// const token = localStorage.getItem("accessToken");
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// }
// return config;
// });
const fetchToken = async (email?: string) => {
const response = await axios.get(
`${import.meta.env.VITE_BASE_URL}/api/v1/auth/token`,
Expand All @@ -16,7 +34,7 @@
);
const newToken = response.data.data.token;
chrome.storage.local.set({ token: newToken }, () => {
console.log('Token re-saved to chrome storage');

Check warning on line 37 in apps/extension/src/apis/axiosInstance.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
});
return newToken;
};
Expand Down Expand Up @@ -72,3 +90,71 @@
);

export default apiRequest;

export interface PostArticleRequest {
url: string;
categoryId: number;
memo?: string | null;
remindTime?: string | null;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

이건 따로 타입 파일로 분리하는건 어떤가요??


export const postArticle = async (data: PostArticleRequest) => {
const response = await apiRequest.post("/api/v1/articles", data);
return response.data;
};


export interface postSignupRequest {
email: string;
remindDefault: string
fcmToken: string;
}

export const postSignup = async (data: postSignupRequest) => {
const response = await apiRequest.post("/api/v1/auth/signup", data);
return response.data;
};

export const getCategoriesExtension = async () => {
const response = await apiRequest.get("/api/v1/categories/extension");
return response.data;
};

export interface postCategoriesRequest {
categoryName: string;
}

export const postCategories = async (data: postCategoriesRequest) => {
const response = await apiRequest.post("/api/v1/categories", data);
return response.data;
}

export const getRemindTime = async () => {
const now = new Date().toISOString().split(".")[0];

const response = await apiRequest.get("/api/v1/users/remind-time", {
params: { now },
});

return response.data;
};


export const getArticleSaved=async (url:string) => {
const response = await apiRequest.get("/api/v1/articles/saved", {
params: { url },
});
return response.data;
}

export interface PutArticleRequest {
categoryId: number;
memo: string;
now: string;
remindTime: string | null;
}

export const putArticle = async (articleId: number, data: PutArticleRequest) => {
const response = await apiRequest.put(`/api/v1/articles/${articleId}`, data);
return response.data;
};
72 changes: 72 additions & 0 deletions apps/extension/src/apis/query/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useMutation,useQuery } from "@tanstack/react-query";
import { postArticle, PostArticleRequest,postSignup, postSignupRequest, getCategoriesExtension, postCategories, postCategoriesRequest, getRemindTime, getArticleSaved,putArticle, PutArticleRequest} from "../axiosInstance";
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

queryClient를 임포트하여 성공 시 캐시 무효화가 가능하도록 준비

후속 코멘트들에서 제안하는 invalidate를 위해 useQueryClient를 함께 임포트하세요.

-import { useMutation,useQuery } from "@tanstack/react-query";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useMutation,useQuery } from "@tanstack/react-query";
import { postArticle, PostArticleRequest,postSignup, postSignupRequest, getCategoriesExtension, postCategories, postCategoriesRequest, getRemindTime, getArticleSaved,putArticle, PutArticleRequest} from "../axiosInstance";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { postArticle, PostArticleRequest,postSignup, postSignupRequest, getCategoriesExtension, postCategories, postCategoriesRequest, getRemindTime, getArticleSaved,putArticle, PutArticleRequest} from "../axiosInstance";
🤖 Prompt for AI Agents
In apps/extension/src/apis/query/queries.ts around lines 1 to 2, the file
imports useMutation and useQuery but not useQueryClient; import useQueryClient
from "@tanstack/react-query" alongside the existing imports so you can call
useQueryClient() in hooks and perform cache invalidation (invalidateQueries) on
successful mutations as suggested by later comments.


export const usePostArticle = () => {
return useMutation({
mutationFn: (data: PostArticleRequest) => postArticle(data),
onSuccess: (data) => {
console.log("저장 성공:", data);

Check warning on line 8 in apps/extension/src/apis/query/queries.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
},
onError: (error) => {
console.error("저장 실패:", error);

Check warning on line 11 in apps/extension/src/apis/query/queries.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 부분은 따로 호출하는 페이지에서 handle 처리하는건 어떤가요? 실패시 ui가 있다면 따로 처리해도 좋을것같아요-!

},
});
};
Comment on lines +4 to +8
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

아티클 저장 성공 시 관련 캐시 무효화 (UI 동기화 필수)

저장 후 중복 여부/상태를 사용하는 화면과 동기화를 위해 ["articleSaved", data.url] 키를 무효화하세요.

 export const usePostArticle = () => {
-  return useMutation({
+  const queryClient = useQueryClient();
+  return useMutation({
     mutationFn: (data: PostArticleRequest) => postArticle(data),
     onSuccess: (data) => {
-      console.log("저장 성공:", data);
+      // 중복 여부 재조회
+      if (data?.url) {
+        queryClient.invalidateQueries({ queryKey: ["articleSaved", data.url] });
+      }
+      if (process.env.NODE_ENV !== "production") {
+        // eslint-disable-next-line no-console
+        console.log("저장 성공:", data);
+      }
     },
     onError: (error) => {
-      console.error("저장 실패:", error);
+      if (process.env.NODE_ENV !== "production") {
+        // eslint-disable-next-line no-console
+        console.error("저장 실패:", error);
+      }
     },
   });
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const usePostArticle = () => {
return useMutation({
mutationFn: (data: PostArticleRequest) => postArticle(data),
onSuccess: (data) => {
console.log("저장 성공:", data);
},
onError: (error) => {
console.error("저장 실패:", error);
},
});
};
export const usePostArticle = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: PostArticleRequest) => postArticle(data),
onSuccess: (data) => {
// 중복 여부 재조회
if (data?.url) {
queryClient.invalidateQueries({ queryKey: ["articleSaved", data.url] });
}
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.log("저장 성공:", data);
}
},
onError: (error) => {
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.error("저장 실패:", error);
}
},
});
};
🧰 Tools
🪛 GitHub Check: lint

[warning] 11-11:
Unexpected console statement


[warning] 8-8:
Unexpected console statement

🤖 Prompt for AI Agents
In apps/extension/src/apis/query/queries.ts around lines 4 to 14, the onSuccess
handler for usePostArticle currently only logs success and does not invalidate
related cache; update onSuccess to call the query client to invalidate the
["articleSaved", data.url] key so UI using that key is refreshed after save
(e.g., obtain the queryClient via useQueryClient() and call
queryClient.invalidateQueries(["articleSaved", data.url]) inside onSuccess).


export const usePostSignup = () => {
return useMutation({
mutationFn: (data: postSignupRequest) => postSignup(data),
onSuccess: (data) => {
console.log("회원가입 성공:", data);

Check warning on line 20 in apps/extension/src/apis/query/queries.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
},
onError: (error) => {
console.error("회원가입 실패:", error);

Check warning on line 23 in apps/extension/src/apis/query/queries.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
},
});
}

export const usePostCategories = () => {
return useMutation({
mutationFn: (data: postCategoriesRequest) => postCategories(data),
onSuccess: (data) => {
console.log("카테고리 저장", data);

Check warning on line 32 in apps/extension/src/apis/query/queries.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
},
onError: (error) => {
console.error("카테고리 저장 실패", error);

Check warning on line 35 in apps/extension/src/apis/query/queries.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
},
});
}
Comment on lines +28 to +20
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

카테고리 생성 성공 시 목록 캐시 무효화

생성 후 ["categoriesExtension"]을 무효화해 드롭다운/목록을 즉시 최신화하세요.

 export const usePostCategories = () => {
-  return useMutation({
+  const queryClient = useQueryClient();
+  return useMutation({
     mutationFn: (data: postCategoriesRequest) => postCategories(data),
     onSuccess: (data) => {
-      console.log("카테고리 저장", data);
+      queryClient.invalidateQueries({ queryKey: ["categoriesExtension"] });
+      if (process.env.NODE_ENV !== "production") {
+        // eslint-disable-next-line no-console
+        console.log("카테고리 저장", data);
+      }
     },
     onError: (error) => {
-      console.error("카테고리 저장 실패", error);
+      if (process.env.NODE_ENV !== "production") {
+        // eslint-disable-next-line no-console
+        console.error("카테고리 저장 실패", error);
+      }
     },
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const usePostCategories = () => {
return useMutation({
mutationFn: (data: postCategoriesRequest) => postCategories(data),
onSuccess: (data) => {
console.log("카테고리 저장", data);
},
onError: (error) => {
console.error("카테고리 저장 실패", error);
},
});
}
export const usePostCategories = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: postCategoriesRequest) => postCategories(data),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["categoriesExtension"] });
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.log("카테고리 저장", data);
}
},
onError: (error) => {
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.error("카테고리 저장 실패", error);
}
},
});
}
🧰 Tools
🪛 GitHub Check: lint

[warning] 35-35:
Unexpected console statement


[warning] 32-32:
Unexpected console statement

🤖 Prompt for AI Agents
In apps/extension/src/apis/query/queries.ts around lines 28 to 38, the onSuccess
handler for usePostCategories currently only logs success and does not update
cached category lists; after a successful post, call useQueryClient() at the top
of the hook to get queryClient and then invoke
queryClient.invalidateQueries(["categoriesExtension"]) inside onSuccess (after
any logging) so the dropdown/list query is immediately refreshed; ensure
useQueryClient is imported from react-query (or @tanstack/react-query) if not
already.

export const useGetCategoriesExtension = () => {
return useQuery({
queryKey: ["categoriesExtension"],
queryFn: getCategoriesExtension,
});
};

export const useGetRemindTime = () => {
return useQuery({
queryKey: ["remindTime"],
queryFn: getRemindTime,
});
}

export const useGetArticleSaved = (url:string) => {
return useQuery({
queryKey: ["articleSaved", url],
queryFn: () => getArticleSaved(url),
enabled: !!url,
});
}
Comment on lines +35 to +41
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

URL 검증 및 리패치로 인한 깜빡임 방지

  • chrome://, edge://, about: 같은 내부 페이지와 http(s)가 아닌 스킴은 호출을 막으세요.
  • 포커스 리패치로 DuplicatePop이 다시 뜨는 현상을 줄이기 위해 옵션을 지정하세요.
 export const useGetArticleSaved = (url:string) => {
+  const isHttpUrl = /^https?:\/\//i.test(url);
   return useQuery({
     queryKey: ["articleSaved", url],
-    queryFn: () => getArticleSaved(url),
-    enabled: !!url, 
+    queryFn: () => getArticleSaved(url),
+    enabled: !!url && isHttpUrl,
+    refetchOnWindowFocus: false,
+    staleTime: 30 * 1000,
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const useGetArticleSaved = (url:string) => {
return useQuery({
queryKey: ["articleSaved", url],
queryFn: () => getArticleSaved(url),
enabled: !!url,
});
}
export const useGetArticleSaved = (url:string) => {
const isHttpUrl = /^https?:\/\//i.test(url);
return useQuery({
queryKey: ["articleSaved", url],
queryFn: () => getArticleSaved(url),
enabled: !!url && isHttpUrl,
refetchOnWindowFocus: false,
staleTime: 30 * 1000,
});
}
🤖 Prompt for AI Agents
In apps/extension/src/apis/query/queries.ts around lines 53 to 59, validate the
incoming url and prevent queries for internal/non-http schemes and disable
focus-triggered refetching: only enable the query when url is a well-formed
HTTP/HTTPS URL (parse with the URL constructor or a small regex and reject
schemes like chrome:, edge:, about:, file:, data:, etc.), and add react-query
options to reduce refetch flicker such as refetchOnWindowFocus: false and
refetchOnMount: false (and optionally refetchOnReconnect: false) so the hook
doesn't re-run on focus and cause DuplicatePop to reappear.

Comment on lines +35 to +41
Copy link
Member

Choose a reason for hiding this comment

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

url이 있어야만 호출을 하는거죠??

Copy link
Collaborator Author

@jllee000 jllee000 Sep 13, 2025

Choose a reason for hiding this comment

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

넵네!! 그래서 enabled처리해두었습니당


export const usePutArticle = () => {
return useMutation({
mutationFn: ({ articleId, data }: { articleId: number; data: PutArticleRequest }) =>
putArticle(articleId, data),
onSuccess: (data) => {
console.log("아티클 수정 성공:", data);

Check warning on line 66 in apps/extension/src/apis/query/queries.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
},
onError: (error) => {
console.error("아티클 수정 실패:", error);
},
});
};
Comment on lines +61 to +48
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

putArticle 성공 시 캐시 무효화 누락 + 콘솔 가드

수정 후 저장 상태 쿼리를 무효화하여 UI 동기화를 보장하세요.

 export const usePutArticle = () => {
-  return useMutation({
+  const queryClient = useQueryClient();
+  return useMutation({
     mutationFn: ({ articleId, data }: { articleId: number; data: PutArticleRequest }) =>
       putArticle(articleId, data),
     onSuccess: (data) => {
-      console.log("아티클 수정 성공:", data);
+      if (data?.url) {
+        queryClient.invalidateQueries({ queryKey: ["articleSaved", data.url] });
+      }
+      if (process.env.NODE_ENV !== "production") {
+        // eslint-disable-next-line no-console
+        console.log("아티클 수정 성공:", data);
+      }
     },
     onError: (error) => {
-      console.error("아티클 수정 실패:", error);
+      if (process.env.NODE_ENV !== "production") {
+        // eslint-disable-next-line no-console
+        console.error("아티클 수정 실패:", error);
+      }
     },
   });
 };

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: lint

[warning] 69-69:
Unexpected console statement


[warning] 66-66:
Unexpected console statement

🤖 Prompt for AI Agents
In apps/extension/src/apis/query/queries.ts around lines 61 to 72, the
usePutArticle mutation currently logs directly to console and does not
invalidate related queries after a successful put; update onSuccess to call the
react-query (or trpc) queryClient.invalidateQueries for the article list/detail
keys (and any "articles" or "article-{id}" cache entries) to refresh UI, and
replace raw console.log/console.error with a guarded logger or conditional that
only logs in development (e.g., check __DEV__ or use the app's logger) so
production consoles aren't polluted.

Loading
Loading