Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion apps/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
Expand Down
2 changes: 2 additions & 0 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
"@pivanov/vite-plugin-svg-sprite": "^3.0.0",
"@radix-ui/react-switch": "^1.2.5",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.81.5",
"axios": "^1.10.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"firebase": "^11.10.0",
"i": "^0.3.7",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
Expand Down
4 changes: 3 additions & 1 deletion apps/extension/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ const App = () => {
setTitle(imageUrl?.title ?? '');
setDescription(imageUrl?.description ?? '');
setImgUrl(imageUrl?.image ?? '');
localStorage.setItem('titleSave', title);
chrome.storage.local.set({ titleSave: title }, () => {
console.log('Title saved to chrome storage');
});
Comment on lines +41 to +43
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

title 상태 업데이트 타이밍 문제를 확인해주세요.

chrome.storage.local.set에서 title 상태 변수를 저장하고 있지만, 이 시점에서 title 상태가 아직 업데이트되지 않았을 수 있습니다. setTitle이 38번째 줄에서 호출되고 바로 41번째 줄에서 title 상태를 사용하고 있는데, React 상태 업데이트는 비동기적으로 처리됩니다.

다음과 같이 수정하는 것을 제안합니다:

        setTitle(imageUrl?.title ?? '');
        setDescription(imageUrl?.description ?? '');
        setImgUrl(imageUrl?.image ?? '');
-        chrome.storage.local.set({ titleSave: title }, () => {
+        chrome.storage.local.set({ titleSave: imageUrl?.title ?? '' }, () => {
          console.log('Title saved to chrome storage');
        });

또는 별도의 useEffect를 사용하여 title이 변경될 때 저장하는 방법도 고려해볼 수 있습니다.

📝 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
chrome.storage.local.set({ titleSave: title }, () => {
console.log('Title saved to chrome storage');
});
setTitle(imageUrl?.title ?? '');
setDescription(imageUrl?.description ?? '');
setImgUrl(imageUrl?.image ?? '');
chrome.storage.local.set({ titleSave: imageUrl?.title ?? '' }, () => {
console.log('Title saved to chrome storage');
});
🤖 Prompt for AI Agents
In apps/extension/src/App.tsx around lines 41 to 43, the issue is that the title
state may not be updated yet when calling chrome.storage.local.set, because
React state updates are asynchronous. To fix this, avoid using the possibly
stale title state immediately after setTitle. Instead, either pass the new title
value directly to chrome.storage.local.set or move the storage call into a
useEffect hook that triggers whenever title changes, ensuring the stored value
is always up to date.

}
});
}, []);
Expand Down
16 changes: 16 additions & 0 deletions apps/extension/src/api/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import apiRequest from './axiosInstance';

export const getCategoriesDash = async () => {
const { data } = await apiRequest.get('/api/v1/categories/dashboard', {});
return data;
};

export const postArticles = async (payload: {
url: string;
categoryId: number | null;
memo: string;
remindTime: string | null;
}) => {
const response = await apiRequest.post('/api/v1/articles', payload);
return response.data;
};
19 changes: 8 additions & 11 deletions apps/extension/src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,20 @@ const fetchToken = async (email?: string) => {
}
);
const newToken = response.data.token;
localStorage.setItem('jwtToken', newToken);
return newToken;
};
const getChromeToken = async (): Promise<string | null> => {
return new Promise((resolve) => {
chrome.storage.local.get(['jwtToken'], (result) => {
resolve(result.jwtToken ?? null);
});
chrome.storage.local.set({ jwtToken: newToken }, () => {
console.log('Token saved to chrome storage');
});
return newToken;
};

apiRequest.interceptors.request.use(async (config) => {
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signin'];
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup'];
const isNoAuth = noAuthNeeded.some((url) => config.url?.includes(url));

if (!isNoAuth) {
let token = await getChromeToken();
let token =
'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaW5iYWNrIiwiaWQiOiJhOTA1NGFjOS03MTg0LTQ3NjktYWY4Mi1jNGViYTg0YzYxYTIiLCJzdWIiOiJBY2Nlc3NUb2tlbiIsImV4cCI6MTc1MjgwMDY1Nn0.hXti-Jlnhg8mRoPl5nB8Vi8UV6HPdZYAtgtpTuqtH39lQWle8T5GlX0ug0nNVUqu5B_Pyzafck7lhfXN6ArHOA';

Comment on lines +29 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

하드코딩된 JWT 토큰 보안 문제

코드에 하드코딩된 JWT 토큰이 포함되어 있습니다. PR 목표에서 언급한 임시 토큰이라고 하더라도 보안상 위험합니다.

다음과 같은 개선이 필요합니다:

  1. 하드코딩된 토큰을 환경 변수로 이동
  2. 또는 Chrome storage에서 토큰을 가져오는 로직으로 대체
-    let token =
-      'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaW5iYWNrIiwiaWQiOiJhOTA1NGFjOS03MTg0LTQ3NjktYWY4Mi1jNGViYTg0YzYxYTIiLCJzdWIiOiJBY2Nlc3NUb2tlbiIsImV4cCI6MTc1MjgwMDY1Nn0.hXti-Jlnhg8mRoPl5nB8Vi8UV6HPdZYAtgtpTuqtH39lQWle8T5GlX0ug0nNVUqu5B_Pyzafck7lhfXN6ArHOA';
+    const result = await new Promise<{ jwtToken?: string }>((resolve) => {
+      chrome.storage.local.get(['jwtToken'], resolve);
+    });
+    let token = result.jwtToken || '';
📝 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
let token =
'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwaW5iYWNrIiwiaWQiOiJhOTA1NGFjOS03MTg0LTQ3NjktYWY4Mi1jNGViYTg0YzYxYTIiLCJzdWIiOiJBY2Nlc3NUb2tlbiIsImV4cCI6MTc1MjgwMDY1Nn0.hXti-Jlnhg8mRoPl5nB8Vi8UV6HPdZYAtgtpTuqtH39lQWle8T5GlX0ug0nNVUqu5B_Pyzafck7lhfXN6ArHOA';
const result = await new Promise<{ jwtToken?: string }>((resolve) => {
chrome.storage.local.get(['jwtToken'], resolve);
});
let token = result.jwtToken || '';
🧰 Tools
🪛 Gitleaks (8.27.2)

30-30: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🤖 Prompt for AI Agents
In apps/extension/src/api/axiosInstance.ts around lines 29 to 31, the JWT token
is hardcoded, which poses a security risk. Remove the hardcoded token and
instead retrieve the token securely by either reading it from an environment
variable or implementing logic to fetch it from Chrome storage. Update the code
to dynamically obtain the token at runtime rather than embedding it directly in
the source.

if (!token || token === 'undefined') {
token = await fetchToken('test@gmail.com');
}
Expand All @@ -44,7 +41,7 @@ apiRequest.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signin'];
const noAuthNeeded = ['/api/v1/auth/token', '/api/v1/auth/signup'];
const isNoAuth = noAuthNeeded.some((url) =>
originalRequest.url?.includes(url)
);
Expand Down
1 change: 1 addition & 0 deletions apps/extension/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as apiRequest } from './axiosInstance';
9 changes: 9 additions & 0 deletions apps/extension/src/api/modalAxios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import apiRequest from './axiosInstance';

export const getRemindTime = async (time: String) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

원시 타입 'string' 사용을 권장합니다.

타입 일관성을 위해 대문자 String 대신 소문자 string을 사용하세요.

-export const getRemindTime = async (time: String) => {
+export const getRemindTime = async (time: string) => {
📝 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 getRemindTime = async (time: String) => {
export const getRemindTime = async (time: string) => {
🧰 Tools
🪛 Biome (1.9.4)

[error] 3-3: Don't use 'String' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'string' instead

(lint/complexity/noBannedTypes)

🤖 Prompt for AI Agents
In apps/extension/src/api/modalAxios.ts at line 3, replace the type annotation
from the primitive wrapper type 'String' to the primitive type 'string' for the
'time' parameter in the getRemindTime function to ensure type consistency and
best practices.

const { data } = await apiRequest.get(
`/api/v1/users/remind-time?now=${time}`,
{}
);
return data;
};
9 changes: 9 additions & 0 deletions apps/extension/src/api/modalQueries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { getRemindTime } from './modalAxios';

export const useGetRemindTime = (time: string) => {
return useQuery({
queryKey: ['remindTime'],
queryFn: () => getRemindTime(time),
});
Comment on lines +5 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

쿼리 키에 시간 매개변수를 포함하세요.

현재 쿼리 키가 ['remindTime']로 정적이어서, time 매개변수가 변경되어도 이전 캐시된 데이터가 반환될 수 있습니다. 적절한 캐싱과 데이터 무효화를 위해 쿼리 키에 time 매개변수를 포함하는 것이 좋습니다.

  return useQuery({
-    queryKey: ['remindTime'],
+    queryKey: ['remindTime', time],
    queryFn: () => getRemindTime(time),
  });
📝 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
return useQuery({
queryKey: ['remindTime'],
queryFn: () => getRemindTime(time),
});
return useQuery({
- queryKey: ['remindTime'],
+ queryKey: ['remindTime', time],
queryFn: () => getRemindTime(time),
});
🤖 Prompt for AI Agents
In apps/extension/src/api/modalQueries.ts around lines 5 to 8, the query key for
useQuery is static as ['remindTime'], which causes caching issues when the time
parameter changes. To fix this, include the time parameter in the query key
array, such as ['remindTime', time], so that the cache correctly differentiates
queries based on the time argument and returns updated data accordingly.

};
14 changes: 14 additions & 0 deletions apps/extension/src/api/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getCategoriesDash, postArticles } from './axios';
import { useQuery, useMutation } from '@tanstack/react-query';
export const useGetCategoriesDash = () => {
return useQuery({
queryKey: ['categoriesDash'],
queryFn: getCategoriesDash,
});
};

export const usePostArticles = () => {
return useMutation({
mutationFn: postArticles,
});
};
Comment on lines +1 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

TypeScript 타입 정의 추가를 권장합니다.

API 응답과 요청에 대한 TypeScript 타입을 정의하면 타입 안정성을 높일 수 있습니다.

다음과 같이 타입을 정의하는 것을 제안합니다:

interface Category {
  id: string;
  name: string;
  // 기타 카테고리 속성들
}

interface PostArticleRequest {
  url: string;
  categoryId: string;
  memo?: string;
  remindTime?: string;
}

export const useGetCategoriesDash = (): UseQueryResult<Category[]> => {
  return useQuery({
    queryKey: ['categoriesDash'],
    queryFn: getCategoriesDash,
  });
};

export const usePostArticles = (): UseMutationResult<any, Error, PostArticleRequest> => {
  return useMutation({
    mutationFn: postArticles,
  });
};
🤖 Prompt for AI Agents
In apps/extension/src/api/queries.ts lines 1 to 14, add TypeScript type
definitions for API request and response data to improve type safety. Define
interfaces for Category and PostArticleRequest reflecting the expected data
structure. Then, update the return types of useGetCategoriesDash and
usePostArticles hooks to UseQueryResult<Category[]> and UseMutationResult<any,
Error, PostArticleRequest> respectively, ensuring the hooks are strongly typed.

4 changes: 2 additions & 2 deletions apps/extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
chrome.identity.getProfileUserInfo(function (info) {
console.log('google email:', info.email);

setTimeout(() => {
chrome.tabs.create({
url: `http://localhost:5180/onboarding?email=${info.email}`,
Expand All @@ -18,6 +17,7 @@ chrome.runtime.onInstalled.addListener((details) => {
});
}
});

chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.type === 'SAVE_BOOKMARK') {
const { url, title } = message.payload;
Expand All @@ -28,7 +28,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {

chrome.bookmarks.create(
{
title: localStorage.getItem('titleSave') ?? '핀백 저장소',
title: '핀백 저장소',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

하드코딩된 북마크 제목 사용을 재검토해주세요.

북마크 제목이 고정된 문자열 '핀백 저장소'로 하드코딩되어 있습니다. 이는 사용자 경험을 해칠 수 있으며, 실제 페이지 제목이나 사용자가 설정한 제목을 사용하는 것이 더 적절할 것 같습니다.

메시지 페이로드에서 제목을 받아서 사용하는 것을 제안합니다:

-        title: '핀백 저장소',
+        title: title || '핀백 저장소',

또는 Chrome 저장소에서 저장된 제목을 가져와서 사용하는 방법도 고려해볼 수 있습니다.

📝 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
title: '핀백 저장소',
title: title || '핀백 저장소',
🤖 Prompt for AI Agents
In apps/extension/src/background.ts at line 31, the bookmark title is hardcoded
as '핀백 저장소', which reduces flexibility and user experience. Modify the code to
dynamically obtain the bookmark title either from the message payload or by
retrieving a stored title from Chrome storage, and use that value instead of the
fixed string.

url: url,
parentId: '1',
},
Expand Down
73 changes: 67 additions & 6 deletions apps/extension/src/components/modalPop/ModalPop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { POP_TEXTAREA_MAX_LENGTH } from '@constants/index';
import { useState, useEffect } from 'react';
import {
CategoryDropDown,
CommonBtn,
Expand All @@ -8,31 +8,72 @@ import {
TimePicker,
ToggleButton,
} from '@pinback/design-system/ui';
import { useState } from 'react';
import ModalHeader from './ModalHeader';
import { POP_TEXTAREA_MAX_LENGTH } from '@constants/index';
import { useGetCategoriesDash, usePostArticles } from '@api/queries';
import { useGetRemindTime } from '../../api/modalQueries';
import { fomatToday } from '@pinback/design-system/utils';

interface ModalPopProps {
urlInfo: string;
imgInfo?: string;
titleInfo?: string;
desInfo?: string;
}

interface Category {
categoryId: number;
categoryName: string;
unreadCount: number;
}
const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
const [formState, setFormState] = useState({
date: '',
dateError: '',
time: '',
timeError: '',
});
const [categories, setCategories] = useState(['']);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

categories 상태 초기값 수정 필요

빈 문자열이 포함된 배열 ['']로 초기화되어 있습니다. 빈 배열로 초기화해야 합니다.

-  const [categories, setCategories] = useState(['']);
+  const [categories, setCategories] = useState<string[]>([]);
📝 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
const [categories, setCategories] = useState(['']);
const [categories, setCategories] = useState<string[]>([]);
🤖 Prompt for AI Agents
In apps/extension/src/components/modalPop/ModalPop.tsx at line 33, the
categories state is initialized with an array containing an empty string [''],
which is incorrect. Change the initial state to an empty array [] to properly
represent no categories initially.

const [memo, setMemo] = useState('');
const { mutate: postArticle } = usePostArticles();

const {
data: remindTime,
isLoading,
error,
} = useGetRemindTime(fomatToday(new Date()));

if (error) {
console.error('Error fetching remind time:', error);
return <div>Error loading remind time</div>;
}
const { data: categoriesData } = useGetCategoriesDash();
useEffect(() => {
if (categoriesData) {
const categoryNames: string[] = categoriesData.data.categories.map(
(item: Category) => item.categoryName
);
setCategories(categoryNames);
}
}, [categoriesData]);
const [categoryPopupMode, setCategoryPopupMode] = useState<
'edit' | 'add' | ''
>('');
const [selectedCategory, setSelectedCategory] = useState('');
const [categories, setCategories] = useState(['기획', '취미', '요리']);
const [matchId, setMatchedId] = useState<number | null>(null);

useEffect(() => {
if (categoriesData?.data?.categories && selectedCategory) {
const matched = categoriesData.data.categories.find(
(item: Category) => item.categoryName === selectedCategory
);
setMatchedId(matched?.categoryId ?? null);
}
}, [selectedCategory, categoriesData]);

const handleSave = () => {
chrome.storage.local.set({ savedTitle: titleInfo }, () => {
console.log('📦 storage 저장 완료:', titleInfo);
});
chrome.runtime.sendMessage(
{
type: 'SAVE_BOOKMARK',
Expand All @@ -45,7 +86,24 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
console.log('✅ 응답 받음:', response);
}
);
window.close();
postArticle(
{
url: urlInfo,
categoryId: matchId,
memo: memo,
remindTime: fomatToday(new Date()),
},
{
onSuccess: (data) => {
console.log('✅ 저장 성공:', data);
// TODO : 저장 시. 창 원래 닫아야하나 우선 개발 중이라 열어두었습니당
// window.close();
},
onError: (error) => {
console.error('❌ 저장 실패:', error);
},
}
);
};

const handleFieldChange = (
Expand All @@ -67,6 +125,7 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
setCategoryPopupMode('add');
} else {
setSelectedCategory(value);
console.log(value);
setCategoryPopupMode('');
}
};
Expand All @@ -93,7 +152,7 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;

return (
<div className="flex h-[54.7rem] w-[32rem] flex-col items-center justify-between rounded-[1rem] bg-white px-[2rem] py-[2rem]">
<div className="relative flex h-[54.7rem] w-[32rem] flex-col items-center justify-between rounded-[1rem] bg-white px-[2rem] py-[2rem]">
<div>
<ModalHeader onClick={() => window.close()} />
<div className="px-[1rem] pt-[1.9rem]">
Expand All @@ -118,6 +177,8 @@ const ModalPop = ({ urlInfo, imgInfo, titleInfo, desInfo }: ModalPopProps) => {
size="medium"
maxLength={POP_TEXTAREA_MAX_LENGTH}
placeholder="메모를 입력하고 도토리를 받아보세요!"
value={memo}
onChange={(e) => setMemo(e.target.value)}
/>
</section>
<section>
Expand Down
13 changes: 12 additions & 1 deletion apps/extension/src/popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ import * as React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './App.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();
const rootEl = document.getElementById('root');
if (rootEl) {
createRoot(rootEl).render(<App />);
createRoot(rootEl).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
createRoot(rootEl).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
Comment on lines +16 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

중복된 렌더링 호출을 제거하세요.

동일한 루트 엘리먼트에 대해 createRoot(rootEl).render()가 두 번 호출되고 있습니다. 이는 예상치 못한 동작을 일으킬 수 있으며, 두 번째 렌더링이 첫 번째를 덮어쓸 수 있습니다.

다음과 같이 중복된 렌더링 호출을 제거하세요:

  createRoot(rootEl).render(
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  );
-  createRoot(rootEl).render(
-    <QueryClientProvider client={queryClient}>
-      <App />
-    </QueryClientProvider>
-  );
📝 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
createRoot(rootEl).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
createRoot(rootEl).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
🤖 Prompt for AI Agents
In apps/extension/src/popup.tsx around lines 16 to 20, there are duplicate calls
to createRoot(rootEl).render() on the same root element, which can cause
unexpected behavior by overwriting the first render. Remove the redundant
createRoot(rootEl).render() call so that the root element is rendered only once
with the QueryClientProvider and App components.

} else {
console.error('❌ root element not found!');
}
1 change: 1 addition & 0 deletions apps/extension/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { queryClient } from '@utils/queryClient';
11 changes: 11 additions & 0 deletions apps/extension/src/utils/queryClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnWindowFocus: false,
throwOnError: true,
},
},
});
2 changes: 1 addition & 1 deletion apps/extension/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@api': resolve(__dirname, './api'),
'@api': resolve(__dirname, './src/api'),
'@utils': resolve(__dirname, './src/utils'),
'@constants': resolve(__dirname, './src/constants'),
'@shared-types': resolve(__dirname, './src/types'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Icon } from '@pinback/design-system/icons';
import { cva } from 'class-variance-authority';

Expand Down Expand Up @@ -42,14 +42,18 @@ const CategoryDropDown = ({
onSelect,
}: CategoryDropDownProps) => {
const [isDropDownOpen, setIsDropDownOpen] = useState(false);
const [selectedCategory, setSelectedCategory] = useState(
categories.length > 0 ? categories[0] : ''
);
const [selectedCategory, setSelectedCategory] = useState('');

useEffect(() => {
if (categories.length > 0) {
setSelectedCategory(categories[0]);
}
}, [categories]);

const handleCategoryClick = (category: string) => {
setSelectedCategory(category);
setIsDropDownOpen(false);
// onSelect?.(category);
onSelect?.(category);
};

return (
Expand Down
12 changes: 12 additions & 0 deletions packages/design-system/src/lib/fomatToday.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function formatToday(date: Date): string {
const year = date.getFullYear();
const month = `${date.getMonth() + 1}`.padStart(2, '0');
const day = `${date.getDate()}`.padStart(2, '0');
const hours = `${date.getHours()}`.padStart(2, '0');
const minutes = `${date.getMinutes()}`.padStart(2, '0');
const seconds = `${date.getSeconds()}`.padStart(2, '0');

return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
}

export default formatToday;
1 change: 1 addition & 0 deletions packages/design-system/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as fomatToday } from './fomatToday';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

함수명 오타를 수정하세요.

fomatTodayformatToday의 오타로 보입니다. 실제 함수명과 export명이 일치하지 않으면 개발자들에게 혼란을 줄 수 있습니다.

다음과 같이 수정하세요:

-export { default as fomatToday } from './fomatToday';
+export { default as formatToday } from './fomatToday';

또는 모듈 파일명도 함께 수정하는 것을 고려하세요:

-export { default as fomatToday } from './fomatToday';
+export { default as formatToday } from './formatToday';
📝 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 { default as fomatToday } from './fomatToday';
export { default as formatToday } from './fomatToday';
🤖 Prompt for AI Agents
In packages/design-system/src/lib/index.ts at line 1, there is a typo in the
exported function name 'fomatToday' which should be corrected to 'formatToday'
to match the actual function name. Update the export statement to use
'formatToday' instead of 'fomatToday'. Additionally, verify if the module file
name './fomatToday' also contains the typo and consider renaming the file to
'./formatToday' to maintain consistency.

export * from './utils';
Loading