Skip to content

Feature/policy api#43

Merged
haeni82 merged 3 commits into
developfrom
feature/policy-api
Mar 12, 2026
Merged

Feature/policy api#43
haeni82 merged 3 commits into
developfrom
feature/policy-api

Conversation

@haeni82
Copy link
Copy Markdown
Contributor

@haeni82 haeni82 commented Mar 12, 2026

이슈

  • closed #이슈번호

✔️ 체크리스트

  • : Merge할 브랜치를 확인해 주세요.

🔍 작업 내용

image

정책 페이지 사용자 정보 컴포넌트 api 연동 완료

⚠️ 주의 사항 / 기타

Summary by CodeRabbit

  • New Features

    • 라인(휴대폰) 관리 추가: 라인 목록 조회 및 계정 전환 지원
    • 정책 페이지에 "구성원별 정책 제어" 및 "권한 양도" 버튼 및 양도 모달 추가
    • 사용자 정보가 실시간 데이터로 대체되어 동적 표시 및 권한 기반 UI 제공
  • Refactor

    • 사용자 정보 컴포넌트 및 데이터 구조 통합·정리 (타입 정리 포함)
    • 메인 페이지 로딩 여백 조정 등 UI 마이너 정리

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e2a5c989-400f-46a6-8fa6-7734a0b8051c

📥 Commits

Reviewing files that changed from the base of the PR and between e111033 and 892f953.

📒 Files selected for processing (1)
  • src/page/Main/MainPage.tsx

📝 Walkthrough

Walkthrough

라인 서비스(lineService)와 관련 타입들을 추가하고, UserInfo 타입을 도입해 여러 컴포넌트를 userData 단일 prop으로 리팩토링했으며, 일부 컴포넌트가 중앙화된 유틸/서비스(apiClient, blockService 등)를 사용하도록 변경했습니다.

Changes

Cohort / File(s) Summary
API 엔트리 & 서비스
src/api/index.ts, src/api/services/lineService.ts
lineService 추가 및 공개(export). lineServicegetLines(GET /lines)와 switchLine(PATCH /lines?lineId=...) 구현.
타입 정의 추가
src/types/line.ts, src/types/user.ts
LineUserInfo 타입 추가 — 중앙 타입으로 사용자·라인 관련 필드 선언.
사용자 서비스 / 스토어 타입 통합
src/api/services/userService.ts, src/store/userStore.ts
UserInfo 타입 도입으로 userService.getMyInfo 반환 타입 변경(이전 UserBalance 제거), userStore가 중앙 UserInfo 타입 사용하도록 변경.
Policy 페이지 및 UserInfoCard 리팩토링
src/page/Policy/Policy.tsx, src/page/Policy/components/UserInfoCard.tsx
Policy에서 useUserStore 사용으로 런타임 userData 전달. UserInfoCard가 개별 props에서 userData: UserInfo 단일 prop으로 변경되며, lines 조회, 계정 전환(switchLine), blockStatus 조회 로직 통합.
UI 유틸리티 변경
src/page/Policy/components/DataRemainingCard.tsx
로컬 formatMB 제거, 중앙 유틸 formatData로 교체.
마이너 레이아웃 조정
src/page/Main/MainPage.tsx
로딩 상태 마진 조정 및 safe-area inset 반영.

Sequence Diagram

sequenceDiagram
    participant Policy as Policy.tsx
    participant UserStore as useUserStore
    participant UserInfoCard as UserInfoCard
    participant LineService as lineService
    participant BlockService as blockService
    participant APIClient as apiClient

    Policy->>UserStore: userData 조회
    UserStore-->>Policy: userData 반환
    Policy->>UserInfoCard: userData 전달
    UserInfoCard->>LineService: getLines()
    LineService->>APIClient: GET /lines
    APIClient-->>LineService: Line[] 반환
    LineService-->>UserInfoCard: lines 반환
    UserInfoCard->>BlockService: getBlockStatus(lineId)
    BlockService->>APIClient: blockStatus 요청
    APIClient-->>BlockService: blockStatus 반환
    BlockService-->>UserInfoCard: isBlocked 상태 반환
    UserInfoCard-->>Policy: 렌더된 사용자 정보/상태
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Feature/policy api #43: 동일 파일/심볼(라인 서비스, 타입, UserInfo 관련) 변경이 있어 직접적인 연관성 있음.
  • Policy tab layout #36: 라인 전환(account-switch) 동작과 라인 관련 타입/API를 추가한 PR으로 연관됨.
  • Feature/main api #40: 서비스 레이어 확장(새 서비스/엔드포인트 추가) 관련 변경으로 API 계층과 연관됨.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive PR 제목이 주요 변경사항인 정책 페이지 사용자 정보 컴포넌트의 API 연동과 부분적으로만 관련이 있습니다. 제목을 더 구체적으로 작성하세요. 예: 'feat: 정책 페이지 사용자 정보 및 회선 변경 API 연동' 또는 'Feature/policy api integration' 같이 API 연동의 구체적인 대상을 명시하면 좋습니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 템플릿 구조는 따르고 있으나 '작업 내용' 섹션이 매우 최소한으로만 작성되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/policy-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/page/Policy/Policy.tsx (2)

52-58: ⚠️ Potential issue | 🟠 Major

권한 양도 요청은 공용 API 클라이언트를 통해 처리해야 합니다.

여기서 fetch를 직접 쓰면 src/api/client.ts의 timeout, 401 처리, 공통 credential/XSRF 흐름을 모두 우회합니다. 게다가 응답 상태 확인 없이 모달을 닫고 새로고침해서 실패도 성공처럼 보이게 됩니다. 최소한 apiClient 또는 기존 service 레이어를 타고, 성공 응답일 때만 UI를 정리해 주세요.

수정 예시
+import { apiClient } from "@/api";
+
 async function handleTransfer() {
-  await fetch(`/api/families/owner?userId=${selectedTarget?.userId}`, {
-    method: "PATCH",
-  });
-  setIsTransferModalOpen(false);
-  window.location.reload();
+  if (!selectedTarget) return;
+
+  try {
+    await apiClient.patch("/families/owner", undefined, {
+      params: { userId: selectedTarget.userId },
+    });
+
+    setIsTransferModalOpen(false);
+    window.location.reload();
+  } catch {
+    // TODO: 실패 토스트/에러 처리
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Policy/Policy.tsx` around lines 52 - 58, Replace the direct fetch
call in handleTransfer with the shared API client/service so
timeout/401/credentials/XSRF flows are preserved: call the existing apiClient
(or the family service function) to PATCH the owner using
selectedTarget?.userId, await the response and verify success (e.g., check
response.ok or the service result) before calling setIsTransferModalOpen(false)
and window.location.reload(); on error, surface/log the error and keep the modal
open. Ensure you update the handleTransfer function to use the unique symbols
apiClient or the family service function instead of fetch and only clean up UI
after a confirmed success.

151-170: ⚠️ Potential issue | 🟠 Major

대표자 전용 액션은 isOwner일 때만 노출하세요.

이미 isOwner를 계산하고 있는데 권한 양도 버튼과 모달은 항상 렌더링되고 있습니다. 지금 상태면 구성원도 대표자 전용 액션을 열고 요청까지 시도할 수 있습니다. 버튼과 모달 모두 대표자일 때만 렌더링되도록 막는 편이 맞습니다.

수정 예시
-        {/* 권한 양도 버튼 */}
-        <button
-          onClick={() => setIsTransferModalOpen(true)}
-          className="w-full flex items-center gap-4 px-4 py-3 bg-white/60 rounded-2xl shadow-sm border border-gray-100"
-        >
-          {/* 아이콘 영역 */}
-          <div className="w-12 h-12 rounded-xl bg-lime-100 flex items-center justify-center flex-shrink-0">
-            <img src={AssignIcon} />
-          </div>
-
-          {/* 텍스트 */}
-          <div className="flex flex-col items-start">
-            <span className="text-sm font-semibold text-gray-800">
-              권한 양도
-            </span>
-            <span className="text-xs text-gray-400">
-              대표자 권한을 양도할 구성원을 고르세요.
-            </span>
-          </div>
-        </button>
+        {/* 권한 양도 버튼 */}
+        {isOwner && (
+          <button
+            onClick={() => setIsTransferModalOpen(true)}
+            className="w-full flex items-center gap-4 px-4 py-3 bg-white/60 rounded-2xl shadow-sm border border-gray-100"
+          >
+            <div className="w-12 h-12 rounded-xl bg-lime-100 flex items-center justify-center flex-shrink-0">
+              <img src={AssignIcon} />
+            </div>
+
+            <div className="flex flex-col items-start">
+              <span className="text-sm font-semibold text-gray-800">
+                권한 양도
+              </span>
+              <span className="text-xs text-gray-400">
+                대표자 권한을 양도할 구성원을 고르세요.
+              </span>
+            </div>
+          </button>
+        )}
 
-        {isTransferModalOpen &&
+        {isOwner && isTransferModalOpen &&
           createPortal(
             <>

Also applies to: 174-259

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Policy/Policy.tsx` around lines 151 - 170, The transfer button and
modal are being rendered for all users even though you already compute isOwner;
update the Policy component to conditionally render both the "권한 양도" button (the
element that calls setIsTransferModalOpen(true)) and the transfer modal
component so they are only included when isOwner is true—wrap the JSX for the
button and the modal in an {isOwner && (...) } check or return null when
!isOwner; ensure you reference the existing isOwner boolean and the existing
modal state (setIsTransferModalOpen / isTransferModalOpen) so non-owners cannot
open or see the transfer UI.
src/page/Policy/components/UserInfoCard.tsx (1)

51-57: ⚠️ Potential issue | 🟠 Major

API 호출 실패 시 에러 처리가 없습니다.

lineService.switchLine(lineId) 호출이 실패해도 바텀시트가 닫히고 페이지가 새로고침됩니다. 사용자는 회선 전환 실패 여부를 알 수 없어 혼란스러운 UX가 발생할 수 있습니다.

🐛 제안된 수정사항
 async function handleSelectLine(lineId: number) {
-  await lineService.switchLine(lineId);
-
-  setIsBottomSheetOpen(false);
-  // 전환 후 페이지 새로고침 or 상태 업데이트
-  window.location.reload();
+  try {
+    await lineService.switchLine(lineId);
+    setIsBottomSheetOpen(false);
+    // 전환 후 페이지 새로고침 or 상태 업데이트
+    window.location.reload();
+  } catch (error) {
+    // 에러 처리 (예: toast 알림)
+    console.error("회선 전환 실패:", error);
+    // TODO: 사용자에게 에러 알림 표시
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Policy/components/UserInfoCard.tsx` around lines 51 - 57,
handleSelectLine lacks error handling: if lineService.switchLine(lineId) fails,
the bottom sheet still closes and the page reloads, leaving the user unaware.
Wrap the async call in try/catch inside handleSelectLine, only call
setIsBottomSheetOpen(false) and window.location.reload() on success, and on
failure keep the sheet open (or reopen it) and surface the error to the user
(e.g., show a toast or set an error state). Reference handleSelectLine and
lineService.switchLine to locate the change, and ensure any UI feedback uses
existing mechanisms (toast/error state) instead of silent failure.
🧹 Nitpick comments (1)
src/page/Policy/components/UserInfoCard.tsx (1)

24-24: lineId 소스가 일관되지 않습니다.

Line 24에서는 스토어(useUserStore)에서 lineId를 가져오고, Line 196에서는 props인 userData.lineId를 사용합니다. 두 값이 동기화되지 않을 경우 예기치 않은 동작이 발생할 수 있습니다.

일관성을 위해 하나의 소스만 사용하는 것을 권장합니다. userData가 이미 props로 전달되므로, 스토어에서 별도로 lineId를 가져오는 대신 userData.lineId를 사용하는 방안을 검토해 주세요.

♻️ 제안된 수정사항
 export default function UserInfo({ userData }: Props) {
   const navigate = useNavigate();
   const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);
-  const lineId = useUserStore((state) => state.userInfo?.lineId);
+  const lineId = userData.lineId;

   const isOwner = userData.role === "OWNER";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Policy/components/UserInfoCard.tsx` at line 24, The component mixes
two sources for the same value: lineId from useUserStore and userData.lineId
prop, which can lead to inconsistent state; choose a single source—prefer props
since userData is already passed in—so remove the useUserStore selector for
lineId and replace all uses of lineId (from the store) inside UserInfoCard with
userData.lineId, updating references in the component (e.g., where lineId is
read or passed) and ensuring any dependent logic in functions or JSX inside
UserInfoCard uses the prop instead of useUserStore.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/page/Policy/components/UserInfoCard.tsx`:
- Around line 9-21: Rename the React component function UserInfo to UserInfoCard
to avoid the name collision with the imported UserInfo type; update the function
declaration (export default function UserInfoCard({...})) and the default export
if necessary, and search for any local references within this file to replace
UserInfo with UserInfoCard (e.g., the component identifier and any JSX usage or
tests) so the imported type UserInfo remains unchanged and no name conflicts
remain.

In `@src/page/Policy/Policy.tsx`:
- Around line 16-20: The component currently only reads user data from
useUserStore, which can be stale; add a fetch-on-mount that calls the server
endpoint (e.g., getMyInfo or fetchMyInfo) and updates the store so the page has
fresh user and permission state when opened. In the Policy component wrap that
call in a useEffect that runs on mount, handle loading/error, and dispatch the
returned data into the store updater (e.g., useUserStore().setUserInfo or
similar) before relying on isOwner (userData?.role === "OWNER") to render;
ensure the same mount-refresh logic is applied around the other occurrence noted
at line 65.

---

Outside diff comments:
In `@src/page/Policy/components/UserInfoCard.tsx`:
- Around line 51-57: handleSelectLine lacks error handling: if
lineService.switchLine(lineId) fails, the bottom sheet still closes and the page
reloads, leaving the user unaware. Wrap the async call in try/catch inside
handleSelectLine, only call setIsBottomSheetOpen(false) and
window.location.reload() on success, and on failure keep the sheet open (or
reopen it) and surface the error to the user (e.g., show a toast or set an error
state). Reference handleSelectLine and lineService.switchLine to locate the
change, and ensure any UI feedback uses existing mechanisms (toast/error state)
instead of silent failure.

In `@src/page/Policy/Policy.tsx`:
- Around line 52-58: Replace the direct fetch call in handleTransfer with the
shared API client/service so timeout/401/credentials/XSRF flows are preserved:
call the existing apiClient (or the family service function) to PATCH the owner
using selectedTarget?.userId, await the response and verify success (e.g., check
response.ok or the service result) before calling setIsTransferModalOpen(false)
and window.location.reload(); on error, surface/log the error and keep the modal
open. Ensure you update the handleTransfer function to use the unique symbols
apiClient or the family service function instead of fetch and only clean up UI
after a confirmed success.
- Around line 151-170: The transfer button and modal are being rendered for all
users even though you already compute isOwner; update the Policy component to
conditionally render both the "권한 양도" button (the element that calls
setIsTransferModalOpen(true)) and the transfer modal component so they are only
included when isOwner is true—wrap the JSX for the button and the modal in an
{isOwner && (...) } check or return null when !isOwner; ensure you reference the
existing isOwner boolean and the existing modal state (setIsTransferModalOpen /
isTransferModalOpen) so non-owners cannot open or see the transfer UI.

---

Nitpick comments:
In `@src/page/Policy/components/UserInfoCard.tsx`:
- Line 24: The component mixes two sources for the same value: lineId from
useUserStore and userData.lineId prop, which can lead to inconsistent state;
choose a single source—prefer props since userData is already passed in—so
remove the useUserStore selector for lineId and replace all uses of lineId (from
the store) inside UserInfoCard with userData.lineId, updating references in the
component (e.g., where lineId is read or passed) and ensuring any dependent
logic in functions or JSX inside UserInfoCard uses the prop instead of
useUserStore.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 690209a0-cb03-4a68-909e-ac0a6cfc41ac

📥 Commits

Reviewing files that changed from the base of the PR and between 9098345 and e111033.

📒 Files selected for processing (9)
  • src/api/index.ts
  • src/api/services/lineService.ts
  • src/api/services/userService.ts
  • src/page/Policy/Policy.tsx
  • src/page/Policy/components/DataRemainingCard.tsx
  • src/page/Policy/components/UserInfoCard.tsx
  • src/store/userStore.ts
  • src/types/line.ts
  • src/types/user.ts

Comment on lines +9 to +21
import type { UserInfo } from "@/types/user";
import { useUserStore } from "@/store/userStore";
import { useQuery } from "@tanstack/react-query";
import type { Line } from "@/types/line";
import { lineService } from "@/api";
import { blockService } from "@/api";

type Props = {
// 프로필
userName: string;
lineId: number;
isOwner?: boolean;
planName?: string;
isDualPhone?: boolean; // 투폰 여부 → 계정 전환 버튼 노출

// 데이터 잔여량
sharedDataRemaining: number; // MB
personalDataRemaining: number; // MB

// 차단 상태
isBlocked?: boolean;
userData: UserInfo;
};

export default function UserInfo({
userName,
lineId,
isOwner = false,
planName,
isDualPhone = false,
sharedDataRemaining,
personalDataRemaining,
isBlocked = false,
}: Props) {
export default function UserInfo({ userData }: Props) {
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

컴포넌트 이름 UserInfo가 임포트된 타입 UserInfo와 충돌합니다.

파일명이 UserInfoCard.tsx이므로 컴포넌트 이름을 UserInfoCard로 변경하는 것이 적절합니다. 현재 구조에서는 타입과 함수가 동일한 이름을 사용하여 혼란을 야기하고 정적 분석 도구에서 경고가 발생합니다.

🔧 제안된 수정사항
-export default function UserInfo({ userData }: Props) {
+export default function UserInfoCard({ userData }: Props) {
📝 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
import type { UserInfo } from "@/types/user";
import { useUserStore } from "@/store/userStore";
import { useQuery } from "@tanstack/react-query";
import type { Line } from "@/types/line";
import { lineService } from "@/api";
import { blockService } from "@/api";
type Props = {
// 프로필
userName: string;
lineId: number;
isOwner?: boolean;
planName?: string;
isDualPhone?: boolean; // 투폰 여부 → 계정 전환 버튼 노출
// 데이터 잔여량
sharedDataRemaining: number; // MB
personalDataRemaining: number; // MB
// 차단 상태
isBlocked?: boolean;
userData: UserInfo;
};
export default function UserInfo({
userName,
lineId,
isOwner = false,
planName,
isDualPhone = false,
sharedDataRemaining,
personalDataRemaining,
isBlocked = false,
}: Props) {
export default function UserInfo({ userData }: Props) {
import type { UserInfo } from "@/types/user";
import { useUserStore } from "@/store/userStore";
import { useQuery } from "@tanstack/react-query";
import type { Line } from "@/types/line";
import { lineService } from "@/api";
import { blockService } from "@/api";
type Props = {
// 프로필
userData: UserInfo;
};
export default function UserInfoCard({ userData }: Props) {
🧰 Tools
🪛 Biome (2.4.6)

[error] 21-21: Shouldn't redeclare 'UserInfo'. Consider to delete it or rename it.

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Policy/components/UserInfoCard.tsx` around lines 9 - 21, Rename the
React component function UserInfo to UserInfoCard to avoid the name collision
with the imported UserInfo type; update the function declaration (export default
function UserInfoCard({...})) and the default export if necessary, and search
for any local references within this file to replace UserInfo with UserInfoCard
(e.g., the component identifier and any JSX usage or tests) so the imported type
UserInfo remains unchanged and no name conflicts remain.

Comment on lines +16 to +20
// store에 저장된 user 정보 가져오기
const userData = useUserStore((state) => state.userInfo);

//대표자인가
const isOwner = userData?.role === "OWNER";
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

persisted store만 읽으면 사용자 정보와 권한 상태가 쉽게 stale해집니다.

이 컴포넌트는 useUserStore 값만 읽고 있고, 현재 파일 안에는 페이지 진입 시 서버에서 getMyInfo를 다시 받아 store를 동기화하는 흐름이 없습니다. 그래서 새로고침, 재로그인, 회선 전환 직후에는 사용자 카드가 비어 있거나 이전 회선/이전 사용자 기준으로 isOwner가 계산될 수 있습니다. 이 페이지를 열 때 한 번은 서버 응답으로 store를 갱신하는 경로가 필요합니다.

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Policy/Policy.tsx` around lines 16 - 20, The component currently
only reads user data from useUserStore, which can be stale; add a fetch-on-mount
that calls the server endpoint (e.g., getMyInfo or fetchMyInfo) and updates the
store so the page has fresh user and permission state when opened. In the Policy
component wrap that call in a useEffect that runs on mount, handle
loading/error, and dispatch the returned data into the store updater (e.g.,
useUserStore().setUserInfo or similar) before relying on isOwner (userData?.role
=== "OWNER") to render; ensure the same mount-refresh logic is applied around
the other occurrence noted at line 65.

@haeni82 haeni82 merged commit bf4735d into develop Mar 12, 2026
1 of 2 checks passed
This was referenced Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant