-
Notifications
You must be signed in to change notification settings - Fork 1
Api(client): 아티클 읽음 상태 변경 API 연결 #96
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
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,9 +9,23 @@ import { useGetRemindArticles } from '@pages/remind/apis/queries'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { formatLocalDateTime } from '@shared/utils/formatDateTime'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import NoReadArticles from '@pages/remind/components/noReadArticles/NoReadArticles'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import NoUnreadArticles from '@pages/remind/components/noUnreadArticles/NoUnreadArticles'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { usePutArticleReadStatus } from '@shared/apis/queries'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useQueryClient } from '@tanstack/react-query'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const Remind = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isEditOpen, setIsEditOpen] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [activeBadge, setActiveBadge] = useState<'read' | 'notRead'>('notRead'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const formattedDate = formatLocalDateTime(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const queryClient = useQueryClient(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { mutate: updateToReadStatus } = usePutArticleReadStatus(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = useGetRemindArticles( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formattedDate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| activeBadge === 'read', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+28
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. formattedDate가 매 렌더마다 바뀌어 쿼리 키가 계속 생성됩니다. 쿼리 키(['remindArticles', formattedDate, ...])가 리렌더마다 달라져 과도한 캐시/리패치가 발생합니다. 마운트 시 1회만 고정하세요. - const formattedDate = formatLocalDateTime();
+ const [formattedDate] = useState(() => formatLocalDateTime());📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state: menu, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,15 +37,6 @@ const Remind = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const getItemTitle = (id: number | null) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id == null ? '' : (REMIND_MOCK_DATA.find((d) => d.id === id)?.title ?? ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [activeBadge, setActiveBadge] = useState<'read' | 'notRead'>('notRead'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const formattedDate = formatLocalDateTime(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = useGetRemindArticles( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formattedDate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| activeBadge === 'read', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleBadgeClick = (badgeType: 'read' | 'notRead') => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setActiveBadge(badgeType); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -72,6 +77,20 @@ const Remind = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onOptionsClick: (e) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openMenu(article.category.categoryId, e.currentTarget), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| window.open(article.url, '_blank'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updateToReadStatus(article.articleId, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onSuccess: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryClient.invalidateQueries({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryKey: ['remindArticles'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onError: (error) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+80
to
+93
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 새 탭 열기 보안(Reverse Tabnabbing) 및 불필요한 중복 호출 방지.
- onClick={() => {
- window.open(article.url, '_blank');
-
- updateToReadStatus(article.articleId, {
- onSuccess: () => {
- queryClient.invalidateQueries({
- queryKey: ['remindArticles'],
- });
- },
- onError: (error) => {
- console.error(error);
- },
- });
- }}
+ onClick={() => {
+ const win = window.open(
+ article.url,
+ '_blank',
+ 'noopener,noreferrer'
+ );
+ if (win) win.opener = null;
+
+ if (activeBadge === 'notRead') {
+ updateToReadStatus(article.articleId, {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: ['remindArticles'],
+ });
+ },
+ onError: (error) => {
+ console.error(error);
+ },
+ });
+ }
+ }}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -39,6 +39,13 @@ export const postSignUp = async (responsedata: postSignUpRequest) => { | |||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export const putArticleReadStatus = async (articleId: number) => { | ||||||||||||||||||||||||||||||||||
| const { data } = await apiRequest.put( | ||||||||||||||||||||||||||||||||||
| `/api/v1/articles/${articleId}/readStatus` | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+47
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. 💡 Verification agent 🧩 Analysis chain반환 형태 일관화 및 응답 타입 지정. 다른 API는 +import { ArticleReadStatusResponse } from '@shared/types/api';
-export const putArticleReadStatus = async (articleId: number) => {
- const { data } = await apiRequest.put(
- `/api/v1/articles/${articleId}/readStatus`
- );
- return data;
-};
+export const putArticleReadStatus = async (
+ articleId: number
+): Promise<ArticleReadStatusResponse> => {
+ const { data } = await apiRequest.put<{ data: ArticleReadStatusResponse }>(
+ `/api/v1/articles/${articleId}/readStatus`
+ );
+ return data.data;
+};🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== search for putArticleReadStatus ==="
rg -n --hidden --no-ignore -S "putArticleReadStatus" || true
echo "=== search for axios.ts under apps/client/src/shared/apis ==="
rg -n --hidden --no-ignore -S "apps/client/src/shared/apis/.*axios\.ts" || true
echo "=== show apps/client/src/shared/apis/axios.ts (if exists) ==="
if [ -f "apps/client/src/shared/apis/axios.ts" ]; then
sed -n '1,240p' apps/client/src/shared/apis/axios.ts || true
else
echo "file not found at apps/client/src/shared/apis/axios.ts; listing axios.ts files in repo..."
rg -n --hidden --no-ignore -S --files | rg "axios\.ts" || true
fi
echo "=== occurrences of 'return data.data' ==="
rg -n --hidden --no-ignore -S "return\s+data\.data" || true
echo "=== search for ArticleReadStatusResponse type ==="
rg -n --hidden --no-ignore -S "ArticleReadStatusResponse" || true
echo "=== locate apiRequest definition/usages ==="
rg -n --hidden --no-ignore -S "apiRequest" || true
echo "=== show top matches for files defining apiRequest (first 200 lines each) ==="
for f in $(rg -n --hidden --no-ignore -S --files-with-matches "apiRequest" | head -n 10); do
echo "---- $f ----"
sed -n '1,200p' "$f" || true
doneLength of output: 11952 반환 형태 일관화 및 응답 타입 지정 — apps/client/src/shared/apis/axios.ts:42-47 다른 API들이 data.data를 반환하므로 이 함수도 ArticleReadStatusResponse 제네릭을 지정하고 data.data를 반환하도록 수정하세요. apps/client/src/shared/types/api.ts에 ArticleReadStatusResponse 타입이 존재합니다. +import { ArticleReadStatusResponse } from '@shared/types/api';
-export const putArticleReadStatus = async (articleId: number) => {
- const { data } = await apiRequest.put(
- `/api/v1/articles/${articleId}/readStatus`
- );
- return data;
-};
+export const putArticleReadStatus = async (
+ articleId: number
+): Promise<ArticleReadStatusResponse> => {
+ const { data } = await apiRequest.put<{ data: ArticleReadStatusResponse }>(
+ `/api/v1/articles/${articleId}/readStatus`
+ );
+ return data.data;
+};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export const deleteCategory = async (id: number) => { | ||||||||||||||||||||||||||||||||||
| const response = await apiRequest.delete(`/api/v1/categories/${id}`); | ||||||||||||||||||||||||||||||||||
| return response; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,3 +12,8 @@ export type AcornsResponse = { | |||||||||||||||||||||||||||||
| acornCount: number; | ||||||||||||||||||||||||||||||
| remindDateTime: string; | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export interface ArticleReadStatusResponse { | ||||||||||||||||||||||||||||||
| acornCount: number; | ||||||||||||||||||||||||||||||
| acornCollected: boolean; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
+19
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. 💡 Verification agent 🧩 Analysis chainAPI 응답 래핑 일관성 점검 필요 (data vs data.data). axios.ts의 다른 API들은 아래처럼 axios 측을 통일하는 것을 권장합니다(실제 응답 스키마에 맞춰 조정). // apps/client/src/shared/apis/axios.ts
+import { ArticleReadStatusResponse } from '@shared/types/api';
-export const putArticleReadStatus = async (articleId: number) => {
- const { data } = await apiRequest.put(
- `/api/v1/articles/${articleId}/readStatus`
- );
- return data;
-};
+export const putArticleReadStatus = async (
+ articleId: number
+): Promise<ArticleReadStatusResponse> => {
+ const { data } = await apiRequest.put<{ data: ArticleReadStatusResponse }>(
+ `/api/v1/articles/${articleId}/readStatus`
+ );
+ return data.data;
+};🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== find definitions/usages ==="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' "ArticleReadStatusResponse" || true
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' "putArticleReadStatus" || true
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' "apiRequest.put" || true
echo "=== show types file (if exists) ==="
if [ -f apps/client/src/shared/types/api.ts ]; then
sed -n '1,240p' apps/client/src/shared/types/api.ts || true
else
echo "apps/client/src/shared/types/api.ts not found"
fi
echo "=== show axios api file (if exists) ==="
if [ -f apps/client/src/shared/apis/axios.ts ]; then
sed -n '1,320p' apps/client/src/shared/apis/axios.ts || true
else
echo "apps/client/src/shared/apis/axios.ts not found"
fi
echo "=== scan for return patterns in apis directory ==="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' "return data;|return data.data;" apps/client/src/shared/apis -S || true
echo "=== show occurrences of ArticleReadStatusResponse usage ==="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' "ArticleReadStatusResponse" -S || true
echo "=== show occurrences of axios response wrapper typing (\"{ data:\") ==="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \"<\\{ data: [^}]+\\}\" -S || true || trueLength of output: 2573 API 응답 래핑 불일치 — putArticleReadStatus 반환형 일관화 필요 검증 결과 apps/client/src/shared/apis/axios.ts에서 대부분 엔드포인트는 data.data를 반환하나 putArticleReadStatus는 data를 그대로 반환하고 있어 ArticleReadStatusResponse와 런타임 불일치가 발생할 수 있습니다. 아래처럼 axios 측 반환을 통일하세요.
// apps/client/src/shared/apis/axios.ts
+import { ArticleReadStatusResponse } from '@shared/types/api';
-export const putArticleReadStatus = async (articleId: number) => {
- const { data } = await apiRequest.put(
- `/api/v1/articles/${articleId}/readStatus`
- );
- return data;
-};
+export const putArticleReadStatus = async (
+ articleId: number
+): Promise<ArticleReadStatusResponse> => {
+ const { data } = await apiRequest.put<{ data: ArticleReadStatusResponse }>(
+ `/api/v1/articles/${articleId}/readStatus`
+ );
+ return data.data;
+};
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Comment on lines
+15
to
+19
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. 요거 /aritcles/{articleId}/readStatus메서드인 것 같은데 |
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,14 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface BaseCardProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick?: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children: React.ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const BaseCard = ({ children }: BaseCardProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const BaseCard = ({ children, onClick }: BaseCardProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
1
to
+6
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. 그냥 onClick이 아닌, 좀더 네이밍이 어떤 기능인지에 맞게 지어주면 좋을 것 같다는 생각도 듭니다! |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="border-gray200 w-[24.8rem] overflow-hidden rounded-[1.2rem] border bg-white"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={onClick} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="border-gray200 w-[24.8rem] overflow-hidden rounded-[1.2rem] border bg-white" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2
to
+11
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 클릭 가능한 div의 접근성/키보드 조작 지원 보완. onClick이 있을 때 role/tabIndex/키보드 핸들링이 없어 키보드 사용자가 사용할 수 없습니다. 최소한 Enter/Space를 지원하고 시각적으로도 클릭 가능함을 표현해 주세요. -const BaseCard = ({ children, onClick }: BaseCardProps) => {
+const BaseCard = ({ children, onClick }: BaseCardProps) => {
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
+ if (!onClick) return;
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ onClick();
+ }
+ };
return (
- <div
- onClick={onClick}
- className="border-gray200 w-[24.8rem] overflow-hidden rounded-[1.2rem] border bg-white"
- >
+ <div
+ onClick={onClick}
+ onKeyDown={handleKeyDown}
+ role={onClick ? 'button' : undefined}
+ tabIndex={onClick ? 0 : undefined}
+ className={`border-gray200 w-[24.8rem] overflow-hidden rounded-[1.2rem] border bg-white ${onClick ? 'cursor-pointer' : ''}`}
+ >📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,10 +18,11 @@ const MyBookmarkCard = ({ | |||||||||||||||||||||||||||||||||||||||||||||
| category, | ||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl, | ||||||||||||||||||||||||||||||||||||||||||||||
| date, | ||||||||||||||||||||||||||||||||||||||||||||||
| onClick, | ||||||||||||||||||||||||||||||||||||||||||||||
| onOptionsClick, | ||||||||||||||||||||||||||||||||||||||||||||||
| }: MyBookmarkCardProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <BaseCard> | ||||||||||||||||||||||||||||||||||||||||||||||
| <BaseCard onClick={onClick}> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex h-[12rem] w-full items-center justify-center overflow-hidden bg-[#F8F8FA]"> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
26
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. 옵션 버튼 클릭 버블링 방지 누락. RemindCard와 동일하게 옵션 버튼 클릭이 카드 onClick으로 전파될 수 있습니다. 전파를 막아 주세요. - <button
+ <button
type="button"
aria-label="카테고리 상세"
className="cursor-pointer self-start"
- onClick={(e) => onOptionsClick?.(e)}
+ onClick={(e) => {
+ e.stopPropagation();
+ onOptionsClick?.(e);
+ }}
>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| {imageUrl ? ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <img src={imageUrl} className="h-full w-full object-cover" /> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,10 +18,11 @@ const RemindCard = ({ | |||||||||||||||||||||||||||||||||||||||||||||
| category, | ||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl, | ||||||||||||||||||||||||||||||||||||||||||||||
| timeRemaining, | ||||||||||||||||||||||||||||||||||||||||||||||
| onClick, | ||||||||||||||||||||||||||||||||||||||||||||||
| onOptionsClick, | ||||||||||||||||||||||||||||||||||||||||||||||
| }: RemindCardProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <BaseCard> | ||||||||||||||||||||||||||||||||||||||||||||||
| <BaseCard onClick={onClick}> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div className="bg-gray900 flex items-center gap-[0.4rem] py-[1.2rem] pl-[1.6rem] text-sm text-white"> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
26
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. 옵션 버튼 클릭 시 카드 onClick까지 전파되는 버그 가능성. 카드에 onClick이 추가되어 옵션 버튼 클릭이 부모로 버블링될 수 있습니다. 옵션 버튼 onClick에서 - <button
+ <button
type="button"
aria-label="카테고리 상세"
className="cursor-pointer self-start"
- onClick={(e) => onOptionsClick?.(e)}
+ onClick={(e) => {
+ e.stopPropagation();
+ onOptionsClick?.(e);
+ }}
>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| <Icon name="ic_clock_active" /> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span className="body2-m text-main400 mr-[0.2rem]"> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
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
새 탭 열기 보안 + 읽음 상태 중복 업데이트 방지.
Remind와 동일한 개선이 필요합니다. reverse tabnabbing 차단, notRead에서만 업데이트 호출 권장.
📝 Committable suggestion