Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import extImg from '/assets/onBoarding/icons/ext.svg';
import pinImg from '/assets/onBoarding/icons/pin.svg';
const FinalStep = () => {
return (
<div className="flex h-full flex-col items-center">
<div className="flex h-full flex-col items-center px-[3.2rem]">
<img src={dotori} className="mb-[1.2rem]" alt="dotori" />
<p className="head2 text-font-black-1">Pinback에 오신 걸 환영해요</p>
<p className="body2-m text-font-gray-3 mb-[1.4rem] mt-[0.8rem] text-center">
Expand Down
5 changes: 5 additions & 0 deletions apps/client/src/shared/apis/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,8 @@ export const deleteRemindArticle = async (id: number) => {
const response = await apiRequest.delete(`/api/v1/articles/${id}`);
return response;
};

export const getSideInfo = async () => {
const { data } = await apiRequest.get('/api/v2/users/me');
return data.data;
};
8 changes: 8 additions & 0 deletions apps/client/src/shared/apis/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getArticleDetail,
getAcorns,
deleteRemindArticle,
getSideInfo,
} from '@shared/apis/axios';
import { AxiosError } from 'axios';
import {
Expand Down Expand Up @@ -136,3 +137,10 @@ export const useGetPageMeta = (url: string) => {
retry: false,
});
};

export const useGetSideInfo = () => {
return useQuery({
queryKey: ['sideInfo'],
queryFn: () => getSideInfo(),
});
};
28 changes: 28 additions & 0 deletions apps/client/src/shared/components/sidebar/SideInfoPop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Icon } from '@pinback/design-system/icons';
const SideInfoPop = () => {
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 | 🔴 Critical

컴포넌트에 props 인터페이스를 추가하세요.

컴포넌트가 props를 받지 않아 동적 데이터를 표시할 수 없습니다. 유저 정보와 이벤트 핸들러를 받을 수 있도록 props 인터페이스를 정의해야 합니다.

🔎 제안하는 수정 방법
+ interface SideInfoPopProps {
+   sideInfo?: {
+     name: string;
+     email: string;
+     profileImage?: string;
+     remindTime?: string;
+   };
+   isLoading?: boolean;
+   isError?: boolean;
+   onClose?: () => void;
+   onLogout?: () => void;
+ }
+
- const SideInfoPop = () => {
+ const SideInfoPop = ({ sideInfo, isLoading, isError, onClose, onLogout }: SideInfoPopProps) => {
+   if (isLoading) return <div>로딩 중...</div>;
+   if (isError || !sideInfo) return null;
+
   return (

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

🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/SideInfoPop.tsx around line 2, the
component is declared without a props interface so it cannot accept dynamic
data; define and export a TypeScript interface (e.g., SideInfoPopProps) that
includes the needed fields such as user info (id, name, avatarUrl, email, etc.)
and event handler callbacks (onClose, onEdit, onLogout, etc.), update the
component signature to accept (props: SideInfoPopProps) or destructure the props
in the parameter list, and update any internal usage to reference these typed
props to enable type-checked dynamic rendering and event handling.

return (
<div className="bg-white-bg common-shadow absolute left-[19.6rem] top-[6.8rem] z-10 flex h-[32rem] w-[26rem] flex-col items-center justify-center rounded-[12px] py-[2.4rem]">
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 | 🟠 Major

Portal을 사용하여 팝업을 렌더링하세요.

PR 목표에 "Portal 처리"가 명시되어 있지만, 현재 팝업이 일반 DOM 구조로 렌더링되고 있습니다. position: absolute를 사용한 위치 지정은 스크롤이나 레이아웃 변경 시 문제를 일으킬 수 있습니다.

🔎 제안하는 수정 방법

React의 createPortal을 사용하여 팝업을 document body에 렌더링하세요:

import { createPortal } from 'react-dom';

const SideInfoPop = ({ sideInfo, onClose, onLogout }: SideInfoPopProps) => {
  if (!sideInfo) return null;
  
  return createPortal(
    <>
      <div 
        className="fixed inset-0 bg-black/20 z-40"
        onClick={onClose}
      />
      <div className="bg-white-bg common-shadow fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 flex h-[32rem] w-[26rem] flex-col items-center justify-center rounded-[12px] py-[2.4rem]">
        {/* 기존 내용 */}
      </div>
    </>,
    document.body
  );
};

참고: 기존 PopupPortal 컴포넌트의 패턴을 참고하여 일관성을 유지하세요.

🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/SideInfoPop.tsx around line 4, the
popup is currently rendered inline with position:absolute which breaks on
scroll/layout changes; refactor it to use ReactDOM.createPortal to render the
popup into document.body (or the existing PopupPortal root) instead of the
normal DOM tree, replace absolute positioning with fixed positioning (centered
or positioned relative to viewport), add a backdrop overlay element that
captures clicks to call onClose, and follow the existing PopupPortal component
pattern for consistent z-index and accessibility.

<img
src="https://blog.kakaocdn.net/dna/kXF6L/btrt5yaCuuH/AAAAAAAAAAAAAAAAAAAAAEC2uoT7qWaQUOtDL18xi3PXMOakg5-QzqBlHNEzXJJv/%EC%B9%B4%ED%86%A1%20%EA%B8%B0%EB%B3%B8%ED%94%84%EB%A1%9C%ED%95%84%20%EC%82%AC%EC%A7%84(%EC%97%B0%EC%97%B0%EB%91%90ver).jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1767193199&allow_ip=&allow_referer=&signature=nnJLy%2F48M51zZd6WvzDD0VJiMZA%3D&attach=1&knm=img.jpg"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이건 어떤 url인가요?

className="my-[0.8rem] h-[13.2rem] w-[13.2rem] rounded-full"
alt="유저 프로필 이미지"
/>
Comment on lines +6 to +9
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 | 🟠 Major

외부 이미지 URL을 안전한 방식으로 변경하세요.

카카오 CDN의 임시 자격 증명이 포함된 외부 URL을 하드코딩하는 것은 보안 위험이 있습니다. 자격 증명이 만료되면 이미지가 로드되지 않을 수 있습니다.

🔎 제안하는 수정 방법
       <img
-        src="https://blog.kakaocdn.net/dna/kXF6L/btrt5yaCuuH/AAAAAAAAAAAAAAAAAAAAAEC2uoT7qWaQUOtDL18xi3PXMOakg5-QzqBlHNEzXJJv/%EC%B9%B4%ED%86%A1%20%EA%B8%B0%EB%B3%B8%ED%94%84%EB%A1%9C%ED%95%84%20%EC%82%AC%EC%A7%84(%EC%97%B0%EC%97%B0%EB%91%90ver).jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1767193199&allow_ip=&allow_referer=&signature=nnJLy%2F48M51zZd6WvzDD0VJiMZA%3D&attach=1&knm=img.jpg"
+        src={sideInfo?.profileImage || '/default-profile.png'}
         className="my-[0.8rem] h-[13.2rem] w-[13.2rem] rounded-full"
         alt="유저 프로필 이미지"
       />

참고: 기본 프로필 이미지를 프로젝트 assets에 추가하세요.

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

🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/SideInfoPop.tsx around lines 6 to
9, replace the hardcoded Kakao CDN URL (which contains temporary credentials)
with a safe, local asset or a stable public URL: add a default profile image to
the project assets (or /public), import or reference that asset instead of the
external URL, and use it as the src value with an optional prop/fallback so
expiring credentials won’t break the UI; remove any query/credential fragments
and do not commit secret-bearing URLs.

<p className="sub1-sb text-font-black-1 mb-[0.4rem]">이름</p>
<div className="flex flex-col items-center justify-center gap-[0.2rem]">
<p className="body4-r text-font-gray-3">이메일</p>
<div className="mb-[1.86em] flex justify-between gap-[0.4rem]">
<Icon name="ic_clock_active" width={16} height={16} />
<p className="caption2-m text-font-gray-3">리마인드 알람</p>
<p className="caption2-m text-font-gray-3">AM 09:00</p>
</div>
Comment on lines +10 to +17
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 | 🔴 Critical

하드코딩된 데이터를 동적 데이터로 교체하세요.

"이름", "이메일", "AM 09:00" 등의 텍스트가 하드코딩되어 있습니다. API에서 받아온 실제 사용자 데이터를 표시해야 합니다.

Based on learnings, 하드코딩된 값은 임시 테스트 데이터로 실제 기능 구현 시 동적 데이터로 교체되어야 합니다.

🔎 제안하는 수정 방법
-      <p className="sub1-sb text-font-black-1 mb-[0.4rem]">이름</p>
+      <p className="sub1-sb text-font-black-1 mb-[0.4rem]">{sideInfo?.name}</p>
       <div className="flex flex-col items-center justify-center gap-[0.2rem]">
-        <p className="body4-r text-font-gray-3">이메일</p>
+        <p className="body4-r text-font-gray-3">{sideInfo?.email}</p>
         <div className="mb-[1.86em] flex justify-between gap-[0.4rem]">
           <Icon name="ic_clock_active" width={16} height={16} />
           <p className="caption2-m text-font-gray-3">리마인드 알람</p>
-          <p className="caption2-m text-font-gray-3">AM 09:00</p>
+          <p className="caption2-m text-font-gray-3">{sideInfo?.remindTime || 'AM 09:00'}</p>
         </div>
       </div>
📝 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
<p className="sub1-sb text-font-black-1 mb-[0.4rem]">이름</p>
<div className="flex flex-col items-center justify-center gap-[0.2rem]">
<p className="body4-r text-font-gray-3">이메일</p>
<div className="mb-[1.86em] flex justify-between gap-[0.4rem]">
<Icon name="ic_clock_active" width={16} height={16} />
<p className="caption2-m text-font-gray-3">리마인드 알람</p>
<p className="caption2-m text-font-gray-3">AM 09:00</p>
</div>
<p className="sub1-sb text-font-black-1 mb-[0.4rem]">{sideInfo?.name}</p>
<div className="flex flex-col items-center justify-center gap-[0.2rem]">
<p className="body4-r text-font-gray-3">{sideInfo?.email}</p>
<div className="mb-[1.86em] flex justify-between gap-[0.4rem]">
<Icon name="ic_clock_active" width={16} height={16} />
<p className="caption2-m text-font-gray-3">리마인드 알람</p>
<p className="caption2-m text-font-gray-3">{sideInfo?.remindTime || 'AM 09:00'}</p>
</div>
</div>
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/SideInfoPop.tsx around lines 10 to
17, several UI strings ("이름", "이메일", "AM 09:00") are hardcoded; replace them
with props or state populated from the user API (or context) so the component
renders actual user.name, user.email and user.reminderTime. Fetch or receive the
user object higher up (or use existing user store), pass it into SideInfoPop,
and change those text nodes to render the corresponding fields, handling
loading/undefined states (show skeleton or fallback) and formatting the reminder
time to the displayed "AM/PM hh:mm" locale before rendering. Ensure you do not
hardcode test values and add appropriate TypeScript types for the user
prop/state.

</div>
<button
type="button"
className="sub5-sb text-font-black-1 border-gray200 h-[3.6rem] w-[10.8rem] rounded-[4px] border py-[0.8rem] text-center"
>
로그아웃
</button>
Comment on lines +19 to +24
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 | 🔴 Critical

로그아웃 버튼에 핸들러를 추가하세요.

PR 목표에 "로그아웃 처리"가 명시되어 있지만, 로그아웃 버튼에 클릭 핸들러가 구현되지 않았습니다.

🔎 제안하는 수정 방법
       <button
         type="button"
         className="sub5-sb text-font-black-1 border-gray200 h-[3.6rem] w-[10.8rem] rounded-[4px] border py-[0.8rem] text-center"
+        onClick={onLogout}
       >
         로그아웃
       </button>

그리고 부모 컴포넌트(Sidebar.tsx)에서 로그아웃 로직을 구현하세요:

const handleLogout = () => {
  localStorage.removeItem('token');
  // 추가 로그아웃 처리 (예: 쿼리 캐시 초기화, 리다이렉트 등)
  navigate('/login');
};
📝 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
<button
type="button"
className="sub5-sb text-font-black-1 border-gray200 h-[3.6rem] w-[10.8rem] rounded-[4px] border py-[0.8rem] text-center"
>
로그아웃
</button>
<button
type="button"
className="sub5-sb text-font-black-1 border-gray200 h-[3.6rem] w-[10.8rem] rounded-[4px] border py-[0.8rem] text-center"
onClick={onLogout}
>
로그아웃
</button>
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/SideInfoPop.tsx around lines 19 to
24, the logout button has no click handler; add an onClick that calls a logout
prop (e.g., onLogout) passed from the parent, and in the parent Sidebar.tsx
implement that handler to remove the auth token from localStorage, clear any
relevant client/query caches, perform any additional cleanup, and navigate to
the login route; ensure the SideInfoPop component accepts the onLogout prop in
its props interface and that Sidebar passes handleLogout down.

</div>
);
};
export default SideInfoPop;
8 changes: 7 additions & 1 deletion apps/client/src/shared/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import { useSidebarNav } from '@shared/hooks/useSidebarNav';
import { useCategoryPopups } from '@shared/hooks/useCategoryPopups';
import OptionsMenuPortal from './OptionsMenuPortal';
import PopupPortal from './PopupPortal';
import SideInfoPop from './SideInfoPop';
import {
useGetDashboardCategories,
usePostCategory,
useGetArcons,
usePutCategory,
useDeleteCategory,
useGetSideInfo,
} from '@shared/apis/queries';
import { useEffect, useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
Expand All @@ -33,7 +35,9 @@ export function Sidebar() {
const { mutate: createCategory } = usePostCategory();
const { data, isPending } = useGetArcons();
const { mutate: deleteCategory } = useDeleteCategory();
const { data: sideInfo } = useGetSideInfo();
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 | 🔴 Critical

데이터 연동이 완료되지 않았습니다.

useGetSideInfo()로 데이터를 가져오고 있지만, 이 데이터가 SideInfoPop 컴포넌트에 전달되지 않고 있습니다. 또한 로딩 상태와 에러 처리도 필요합니다.

🔎 제안하는 수정 방법
- const { data: sideInfo } = useGetSideInfo();
+ const { data: sideInfo, isLoading: isSideInfoLoading, isError: isSideInfoError } = useGetSideInfo();

그리고 Line 134에서 SideInfoPop에 데이터를 전달하세요:

- <SideInfoPop />
+ <SideInfoPop 
+   sideInfo={sideInfo}
+   isLoading={isSideInfoLoading}
+   isError={isSideInfoError}
+ />
📝 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 { data: sideInfo } = useGetSideInfo();
const { data: sideInfo, isLoading: isSideInfoLoading, isError: isSideInfoError } = useGetSideInfo();
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/Sidebar.tsx around line 38 (and
update at line 134), the hook call const { data: sideInfo } = useGetSideInfo();
fetches data but the component never passes it to SideInfoPop and lacks
loading/error handling; update the component to: extract loading and error from
useGetSideInfo (e.g., { data: sideInfo, isLoading, error }), render a loading
state while isLoading, render an error message or fallback when error is
present, and pass sideInfo as a prop to SideInfoPop at line 134 (ensure
null/undefined safe-checks before accessing fields).


console.log('여기', sideInfo);
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 | 🟡 Minor

디버그용 console.log를 제거하세요.

프로덕션 코드에 디버그용 console.log가 남아있습니다.

🔎 제안하는 수정 방법
- console.log('여기', sideInfo);
📝 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
console.log('여기', sideInfo);
🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/Sidebar.tsx around line 40 there is
a leftover debug console.log('여기', sideInfo); — remove this console.log from
production code; if you need runtime diagnostics instead, replace it with a
proper logger(DEBUG) call behind an environment/dev check or use React devtools,
but do not leave console.log in the committed file.

const {
activeTab,
selectedCategoryId,
Expand Down Expand Up @@ -126,7 +130,9 @@ export function Sidebar() {
const canCreateMore = categoryCount < MAX_CATEGORIES;

return (
<aside className="bg-white-bg sticky top-0 h-screen w-[24rem] border-r border-gray-300">
<aside className="bg-white-bg relative sticky top-0 h-screen w-[24rem] border-r border-gray-300">
<SideInfoPop />
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 | 🟠 Major

팝업 표시/숨김 로직이 구현되지 않았습니다.

SideInfoPop 컴포넌트가 항상 렌더링되고 있습니다. 사용자 인터랙션(예: 프로필 클릭)에 따라 팝업을 표시/숨김 처리하는 로직이 필요합니다.

🔎 제안하는 수정 방법

상태를 추가하여 팝업의 표시 여부를 제어하세요:

+ const [isSideInfoOpen, setIsSideInfoOpen] = useState(false);

  return (
    <aside className="bg-white-bg relative sticky top-0 h-screen w-[24rem] border-r border-gray-300">
-     <SideInfoPop />
+     {isSideInfoOpen && (
+       <SideInfoPop 
+         sideInfo={sideInfo}
+         onClose={() => setIsSideInfoOpen(false)}
+       />
+     )}

그리고 프로필 이미지나 버튼을 추가하여 팝업을 열 수 있도록 하세요.

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

🤖 Prompt for AI Agents
In apps/client/src/shared/components/sidebar/Sidebar.tsx around line 134,
SideInfoPop is always rendered; add local state (e.g., isSideInfoOpen) to
control visibility, render <SideInfoPop /> only when that state is true, and
wire a clickable element (profile image or button) to toggle the state to
open/close the popup; also close the popup on outside clicks or Escape key by
adding an effect/listener to set the state to false so the popup hides
appropriately.


<div className="flex h-full flex-col px-[0.8rem]">
<header className="px-[0.8rem] py-[2.8rem]">
<Icon
Expand Down
Loading