-
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 all 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 | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,68 @@ | ||||||||||||||||||||||||||||||||||||||
| import apiRequest from "./axiosInstance"; | ||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
+29
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.
Suggested change
바로 구조 분해 할당도 가능합니다!! |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+48
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. now 포맷에서 타임존 소실 — 잘못된 시간 해석 위험 toISOString().split(".")[0]는 밀리초와 ‘Z’(UTC)를 제거해 서버가 로컬 시각으로 오해할 수 있습니다. 전체 ISO(또는 초 단위+Z 유지)로 전송하세요. -export const getRemindTime = async () => {
- const now = new Date().toISOString().split(".")[0];
+export const getRemindTime = async () => {
+ const now = new Date().toISOString(); // 예: 2025-09-12T03:21:45.123Z초 단위만 필요하면: - const now = new Date().toISOString().split(".")[0];
+ const now = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 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,48 @@ | ||||||||||||||||||||||||||||||||||||
| import { useMutation,useQuery } from "@tanstack/react-query"; | ||||||||||||||||||||||||||||||||||||
| import { postArticle, PostArticleRequest,postSignup, postSignupRequest, getCategoriesExtension, postCategories, postCategoriesRequest, getRemindTime, getArticleSaved,putArticle, PutArticleRequest} from "@apis/axios"; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export const usePostArticle = () => { | ||||||||||||||||||||||||||||||||||||
| return useMutation({ | ||||||||||||||||||||||||||||||||||||
| mutationFn: (data: PostArticleRequest) => postArticle(data), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export const usePostSignup = () => { | ||||||||||||||||||||||||||||||||||||
| return useMutation({ | ||||||||||||||||||||||||||||||||||||
| mutationFn: (data: postSignupRequest) => postSignup(data) | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export const usePostCategories = () => { | ||||||||||||||||||||||||||||||||||||
| return useMutation({ | ||||||||||||||||||||||||||||||||||||
| mutationFn: (data: postCategoriesRequest) => postCategories(data), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| 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
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 URL 검증 및 리패치로 인한 깜빡임 방지
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
Suggested change
🤖 Prompt for AI Agents
Comment on lines
+35
to
+41
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. url이 있어야만 호출을 하는거죠??
Collaborator
Author
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. 넵네!! 그래서 enabled처리해두었습니당 |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export const usePutArticle = () => { | ||||||||||||||||||||||||||||||||||||
| return useMutation({ | ||||||||||||||||||||||||||||||||||||
| mutationFn: ({ articleId, data }: { articleId: number; data: PutArticleRequest }) => | ||||||||||||||||||||||||||||||||||||
| putArticle(articleId, data) | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import { useState } 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 [isPopError, setIsPopError] = useState(false); | ||
| const [errorTxt, setErrorTxt] = useState(""); | ||
|
|
||
| const options = | ||
| categoryData?.data?.categories?.map((c: Category) => c.categoryName) ?? []; | ||
|
|
||
| const saveCategory = (onSuccess?: (category: Category) => void) => { | ||
| if (categoryTitle.length > 20) { | ||
| setIsPopError(true); | ||
| setErrorTxt("20자 이내로 작성해주세요"); | ||
| return; | ||
| } | ||
|
|
||
| postCategories( | ||
| { categoryName: categoryTitle }, | ||
| { | ||
| onSuccess: (res) => { | ||
| const newCategory: Category = { | ||
| categoryId: res.data.categoryId, | ||
| categoryName: categoryTitle, | ||
| categoryColor: res.data.categoryColor ?? "#000000", | ||
|
Collaborator
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. 이건 왜 토큰지정 안된 컬러인가요??
Collaborator
Author
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. 이건 카테고리 컬러 못받는 에러가 나면, 디폴트 컬러가 피그마상 따로 지정이 없어서 우선 토큰을 쓰기보다 임의 색상코드 지정을 해두었습니다! 이부분 내일 QA때 여쭤보겠습니다! |
||
| }; | ||
| onSuccess?.(newCategory); | ||
| resetPopup(); | ||
| }, | ||
| onError: (err: AxiosError<{ code: string; message: string }>) => { | ||
| alert(err.response?.data?.message ?? "카테고리 추가 중 오류가 발생했어요 😢"); | ||
| }, | ||
|
Comment on lines
+36
to
+38
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. 요렇게 해당 부분에서 타입을 지정해도 되지만, 쿼리 결과 값을 타입 지정할 수 있게 tanstack query에서 타입을 제공해주는데 제네릭에 첫 번째 값은 예시 코드) export const useGetBookmarkArticles = (
page: number,
size: number
): UseQueryResult<BookmarkArticleResponse, AxiosError> => {
return useQuery({
queryKey: ['bookmarkReadArticles', page, size],
queryFn: () => getBookmarkArticles(page, size),
});
};리팩토링 할 때 더 공부해서 한번에 해도 될 것 같아요 👍 참고 정도로!
Collaborator
Author
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. 오오 감사합니다!! usemutaion, useQuery를 사용한다면 이 쿼리함수 기능 등을 더 잘 공부해서 적용해봐야겠어요.! |
||
| } | ||
| ); | ||
| }; | ||
|
|
||
| const resetPopup = () => { | ||
| setCategoryTitle(""); | ||
| setIsPopError(false); | ||
| setErrorTxt(""); | ||
| }; | ||
|
Comment on lines
+43
to
+47
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. 함수 분리 굿굿 👍 |
||
|
|
||
| return { | ||
| options, | ||
| categoryTitle, | ||
| setCategoryTitle, | ||
| isPopError, | ||
| errorTxt, | ||
| saveCategory, | ||
| resetPopup, | ||
| }; | ||
| }; | ||
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