-
Notifications
You must be signed in to change notification settings - Fork 1
Api(extension): 익스텐션 api (수정,생성) 연결 및 페이지 구조 수정 #80
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 14 commits
18a9826
3513d8b
2cb25a8
b125950
ba5af17
06a4e4d
4a7febf
3f0f135
c79473c
735632d
6f480d3
8a031d6
00b0400
a836ee8
7280bbe
d2bea02
1ee1580
b414207
474075f
806986e
659a7e2
20a50e8
7564555
ac9d9a6
d2dd838
8ebed5b
90002f3
ea1746c
165805e
9cd52cf
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 |
|---|---|---|
| @@ -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]); | ||
|
|
||
| // 스위치 | ||
| 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
Member
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. 컴포넌트를 분리해주셔서 app이 너무 깔끔해졌어요!!! 👍 |
||
| ); | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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("토큰 저장 완료 ✅"); | ||
| } | ||
| ); | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // 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`, | ||
|
|
@@ -16,7 +34,7 @@ | |
| ); | ||
| const newToken = response.data.data.token; | ||
| chrome.storage.local.set({ token: newToken }, () => { | ||
| console.log('Token re-saved to chrome storage'); | ||
| }); | ||
| return newToken; | ||
| }; | ||
|
|
@@ -72,3 +90,71 @@ | |
| ); | ||
|
|
||
| export default apiRequest; | ||
|
|
||
| export interface PostArticleRequest { | ||
| url: string; | ||
| categoryId: number; | ||
| memo?: string | null; | ||
| remindTime?: string | null; | ||
| } | ||
|
||
|
|
||
| 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; | ||
| }; | ||
| 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"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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.
Outdated
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.
이 부분은 따로 호출하는 페이지에서 handle 처리하는건 어떤가요? 실패시 ui가 있다면 따로 처리해도 좋을것같아요-!
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.
🛠️ 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.
| 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).
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.
🛠️ 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.
| 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.
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.
🛠️ 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.
| 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.
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.
url이 있어야만 호출을 하는거죠??
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.
넵네!! 그래서 enabled처리해두었습니당
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.
🛠️ 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.
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.
🛠️ 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