[feat] 알림 페이지 구현#41
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough알림 기능 전체를 추가했습니다. 타입·API 정의부터 React Query 훅, 뷰모델, 페이지 구현, UI 컴포넌트, Storybook 스토리, 그리고 라우팅 통합까지 아우릅니다. 사용자/매니저 scope 분기, 커서 기반 무한 페이징, swipe-to-delete 상호작용, 동의 설정 조건부 갱신 로직이 포함됩니다. Changes알림 기능 전체 추가
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Key Issues & Observations🚨 로직 위험: useNotificationSettingsViewModel의 조건부 갱신 로직이슈: // 예: substituteEnabled를 끔 + reputationEnabled도 이미 false
// → GENERAL을 추가로 false로 갱신
if (!reputationEnabled && !substituteEnabled) {
handleAllChange(false); // 이게 의도된 동작?
}제안:
|
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | 제목 '[feat] 알림 페이지 구현'은 이번 PR의 주요 변경사항(알림 페이지 및 설정 페이지 UI 구현)을 명확하고 간결하게 요약하고 있습니다. |
| Description check | ✅ Passed | PR 설명이 ID(ALT-232), 변경 내용, 상세한 구현 사항을 포함하고 있으며, 데모 영상과 참고 사항도 포함되어 있어 템플릿 요구사항을 충족합니다. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
feat/ALT-232
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.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
src/app/App.tsx (1)
29-30: ⚡ Quick win신규 알림 페이지 라우트도 lazy/Suspense로 분리해주세요.
NotificationPage,NotificationSettingsPage가 동기 import라 초기 번들에 포함됩니다. 기존SignupPage처럼 lazy route로 맞추는 게 일관성과 초기 로딩에 유리합니다.As per coding guidelines "
src/app/**: 라우팅 구조가 lazy loading을 활용하는지 확인" 및 "src/pages/**: React.lazy / Suspense를 통한 코드 스플리팅 적용 여부".Also applies to: 103-107
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/App.tsx` around lines 29 - 30, NotificationPage and NotificationSettingsPage are currently imported synchronously and should be converted to lazy-loaded routes to avoid including them in the initial bundle; replace their direct imports with React.lazy wrappers (e.g., const NotificationPage = React.lazy(() => import('...NotificationPage')) and const NotificationSettingsPage = React.lazy(() => import('...NotificationSettingsPage'))), then ensure the routes that render NotificationPage and NotificationSettingsPage are wrapped with a Suspense fallback (same pattern used for SignupPage) so code-splitting is applied consistently.src/pages/notification/index.tsx (1)
60-75: 🏗️ Heavy lift페이지에서 무한스크롤 관찰 로직을 분리해주세요.
IntersectionObserver생성/해제 로직이 페이지에 들어와 있어 페이지 조합 책임을 넘습니다.useNotificationViewModel또는 별도 hook으로 옮겨 페이지는 렌더링 조합만 담당하게 유지하는 편이 좋습니다.As per coding guidelines "
src/pages/**: 페이지 컴포넌트가 비즈니스 로직 없이 조합(Composition)만 하는지".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/notification/index.tsx` around lines 60 - 75, Move the IntersectionObserver creation/teardown out of the page component and into the notification view model or a new custom hook (e.g. useNotificationViewModel or useInfiniteScrollObserver) so the page only composes UI; specifically, take the useEffect block that references sentinelRef, creates new IntersectionObserver, observes el, and disconnects on cleanup, and implement it inside the view model/hook so it accepts sentinelRef (or a ref setter) and the control flags hasNextPage, isFetchingNextPage and action fetchNextPage; ensure the observer callback uses those flags and that the hook returns any necessary wiring for the page (e.g. sentinelRef or ref callback) and that cleanup is performed on unmount.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/notification/hooks/useNotifications.ts`:
- Line 19: NotificationPage's cursor is currently typed as string but must allow
null per API behavior; open the NotificationPage type in the notification types
(NotificationPage) and change the cursor property to string | null (matching
other page DTOs like ChatRoomPageDto/ApplicationPageDto/PostingPageDto), then
run a quick typecheck and adjust any callsites that assume a non-null cursor
(e.g., pagination helpers such as getNextPageParam) to handle null/undefined
accordingly.
In `@src/features/notification/hooks/useUpdateNotificationConsent.ts`:
- Around line 8-14: useUpdateNotificationConsent currently chooses updater based
on scope but lets scope=null implicitly fall back to
updateUserNotificationConsent; change the mutation to explicitly handle null by
preventing execution when scope is null: inside useUpdateNotificationConsent set
the useMutation's mutationFn to undefined (or an early-returning function) when
scope === null, or add a guard that throws/returns early so callers cannot call
mutate with scope null; update references to updater, useMutation,
updateManagerNotificationConsent and updateUserNotificationConsent accordingly
and ensure callers either never call mutate when scope is null or check
mutation.isIdle before invoking.
In `@src/features/notification/types/consent.ts`:
- Around line 18-21: Update the UpdateNotificationConsentRequest interface so
the type field is constrained to the CONSENT_TYPE union instead of plain string:
replace type: string with type: CONSENT_TYPE (or the appropriate union/enum
exported as CONSENT_TYPE) and ensure you import or reference CONSENT_TYPE in
this module; update any usages that construct UpdateNotificationConsentRequest
to use a valid CONSENT_TYPE value.
In `@src/features/notification/useNotificationSettingsViewModel.ts`:
- Around line 23-24: The substituteEnabled and reputationEnabled toggles in
useNotificationSettingsViewModel are only local state and reset to true on
refresh; persist them to avoid user confusion by reading initial values from
localStorage and writing updates back whenever setSubstituteEnabled or
setReputationEnabled change (use useEffect to sync), using distinct localStorage
keys (e.g., "notification.substituteEnabled" and
"notification.reputationEnabled") and fall back to true if absent; if you prefer
not to persist because the API is missing, update the UI behavior in the same
hook to expose a "coming soon"/"not saved" flag instead of implying the toggles
are saved.
In `@src/features/notification/useNotificationViewModel.ts`:
- Around line 56-63: The currentItems value is not being filtered by activeTab
so tabs don't change displayed items; update the logic that computes
currentItems (and the derived hasUnreadSubstitute and hasUnreadReputation) to
filter the full notifications list by activeTab (use the item's title or
category to distinguish '대타' vs '평판'), return only the items matching the
activeTab for currentItems, and compute hasUnreadSubstitute/hasUnreadReputation
from the filtered lists (keep using setActiveTab as-is). Ensure you reference
and update the existing symbols currentItems, activeTab, hasUnreadSubstitute,
hasUnreadReputation when adding the filter logic.
In `@src/pages/notification/index.tsx`:
- Around line 130-131: 현재 currentItems.map(...)에서 <li key={item.id ?? idx}>처럼
index를 fallback으로 사용하고 있어 삭제/페이지네이션 시 DOM 재사용 문제가 발생할 수 있습니다; 이 문제를 해결하려면 렌더링할 때
반드시 안정적인 고유 key만 사용하도록 item.id를 필수화하거나 렌더 전에 id가 없는 항목을 필터링하세요 (예: 처리 로직에서
currentItems를 생성하는 곳 또는 컴포넌트 내부에서 map 호출 전에 id가 없는 항목을 제거하거나 데이터 유효성 검사 추가).
currentItems, item.id, 그리고 해당 map 渲染 블록을 찾아 수정하세요.
In `@src/shared/ui/notification/NotificationItem.tsx`:
- Around line 84-133: The onClick can fire after a swipe because touchend is
followed by a click; track a didDrag flag and ignore clicks when a drag
happened. Modify startDrag/moveDrag/endDrag (and their callers
handleTouchStart/handleTouchMove/handleTouchEnd and handleMouseDown's
onMove/onUp) to set didDrag = false at start, set didDrag = true when moveDrag
detects sufficient movement, and reset didDrag to false after endDrag completes;
then in the button onClick handler check didDrag and return early if true
(instead of only checking offset). Use the existing functions startDrag,
moveDrag, endDrag and state setters like setOffset to locate where to add and
clear the didDrag flag.
- Around line 90-102: handleMouseDown currently attaches document 'mousemove'
and 'mouseup' handlers that are only removed in the 'mouseup' path, which leaks
listeners if the component unmounts mid-drag; fix by storing the onMove and onUp
listener references in refs (e.g. moveListenerRef, upListenerRef) when you
create them in handleMouseDown, and add a useEffect cleanup that checks those
refs and calls document.removeEventListener('mousemove',
moveListenerRef.current) and document.removeEventListener('mouseup',
upListenerRef.current) and clears the refs; ensure you still call endDrag() in
cleanup and keep using startDrag, moveDrag and endDrag as before so the drag
state is correctly finalized.
---
Nitpick comments:
In `@src/app/App.tsx`:
- Around line 29-30: NotificationPage and NotificationSettingsPage are currently
imported synchronously and should be converted to lazy-loaded routes to avoid
including them in the initial bundle; replace their direct imports with
React.lazy wrappers (e.g., const NotificationPage = React.lazy(() =>
import('...NotificationPage')) and const NotificationSettingsPage =
React.lazy(() => import('...NotificationSettingsPage'))), then ensure the routes
that render NotificationPage and NotificationSettingsPage are wrapped with a
Suspense fallback (same pattern used for SignupPage) so code-splitting is
applied consistently.
In `@src/pages/notification/index.tsx`:
- Around line 60-75: Move the IntersectionObserver creation/teardown out of the
page component and into the notification view model or a new custom hook (e.g.
useNotificationViewModel or useInfiniteScrollObserver) so the page only composes
UI; specifically, take the useEffect block that references sentinelRef, creates
new IntersectionObserver, observes el, and disconnects on cleanup, and implement
it inside the view model/hook so it accepts sentinelRef (or a ref setter) and
the control flags hasNextPage, isFetchingNextPage and action fetchNextPage;
ensure the observer callback uses those flags and that the hook returns any
necessary wiring for the page (e.g. sentinelRef or ref callback) and that
cleanup is performed on unmount.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 52985ef4-a7d0-44e7-ae4c-1ede75e2abf3
⛔ Files ignored due to path filters (2)
src/assets/alter-logo-vector.svgis excluded by!**/*.svgsrc/assets/icons/settings.svgis excluded by!**/*.svg
📒 Files selected for processing (19)
src/app/App.tsxsrc/features/notification/api/notificationConsent.tssrc/features/notification/api/notifications.tssrc/features/notification/hooks/useNotificationConsent.tssrc/features/notification/hooks/useNotifications.tssrc/features/notification/hooks/useUpdateNotificationConsent.tssrc/features/notification/types/consent.tssrc/features/notification/types/index.tssrc/features/notification/useNotificationSettingsViewModel.tssrc/features/notification/useNotificationViewModel.tssrc/pages/notification/index.tsxsrc/pages/notification/settings/index.tsxsrc/shared/constants/routes.tssrc/shared/lib/queryKeys.tssrc/shared/ui/common/Navbar.tsxsrc/shared/ui/common/Toggle.tsxsrc/shared/ui/notification/NotificationItem.tsxstorybook/stories/NotificationItem.stories.tsxtailwind.config.js
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/features/notification/useNotificationSettingsViewModel.ts (1)
24-25:⚠️ Potential issue | 🟠 Major | ⚡ Quick win대타/평판 토글 상태가 새로고침 시 유실됩니다.
substituteEnabled,reputationEnabled가useState(true)로 고정 초기화되어 사용자 변경값이 유지되지 않습니다. API 미지원 상태라면localStorage동기화 또는 “저장되지 않음/준비 중” 상태를 명시해 오해를 막아주세요.수정 예시
- const [substituteEnabled, setSubstituteEnabled] = useState(true) - const [reputationEnabled, setReputationEnabled] = useState(true) + const [substituteEnabled, setSubstituteEnabled] = useState( + () => localStorage.getItem('notification.substituteEnabled') !== 'false' + ) + const [reputationEnabled, setReputationEnabled] = useState( + () => localStorage.getItem('notification.reputationEnabled') !== 'false' + ) + + const handleSubstituteEnabledChange = (checked: boolean) => { + setSubstituteEnabled(checked) + localStorage.setItem('notification.substituteEnabled', String(checked)) + } + + const handleReputationEnabledChange = (checked: boolean) => { + setReputationEnabled(checked) + localStorage.setItem('notification.reputationEnabled', String(checked)) + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/notification/useNotificationSettingsViewModel.ts` around lines 24 - 25, substituteEnabled and reputationEnabled are hard-coded to true and lose user changes on refresh; initialize them from localStorage (e.g. keys like "notification.substituteEnabled" and "notification.reputationEnabled") instead of useState(true), and add an effect to persist updates via setSubstituteEnabled/setReputationEnabled to localStorage whenever they change; if the API is not available, surface an explicit "unsaved/preview" state (e.g. a boolean flag) so users know changes are local-only instead of silently reverting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/features/notification/useNotificationSettingsViewModel.ts`:
- Around line 24-25: substituteEnabled and reputationEnabled are hard-coded to
true and lose user changes on refresh; initialize them from localStorage (e.g.
keys like "notification.substituteEnabled" and "notification.reputationEnabled")
instead of useState(true), and add an effect to persist updates via
setSubstituteEnabled/setReputationEnabled to localStorage whenever they change;
if the API is not available, surface an explicit "unsaved/preview" state (e.g. a
boolean flag) so users know changes are local-only instead of silently
reverting.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 22e59437-784c-435f-b24c-6eb2f1d9fba1
📒 Files selected for processing (6)
src/features/notification/hooks/useUpdateNotificationConsent.tssrc/features/notification/types/consent.tssrc/features/notification/types/index.tssrc/features/notification/useNotificationSettingsViewModel.tssrc/pages/notification/settings/index.tsxsrc/shared/ui/notification/NotificationItem.tsx
dohy-eon
left a comment
There was a problem hiding this comment.
탭이랑 필터링 로직 추가 + 코멘트 읽어보시구 처리해주심 감사드리겠습니다.
@dohy-eon 다 햇으요 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/shared/ui/common/Navbar.tsx`:
- Around line 2-3: Navbar currently imports and calls the feature hook
useNotificationUnreadCount, breaking layer boundaries; change Navbar (component
name Navbar) to accept unread props from its parent (e.g. unreadCount and
optionally isUnreadLoading) and remove the import/use of
useNotificationUnreadCount from the file, then update any internal references to
unread state (render logic around the badge/count at lines referenced in the
review) to use the new props; update callers (pages/widgets/app or parent
container) to call useNotificationUnreadCount and pass the resulting unread
values into Navbar as props.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0c79ff65-2134-4833-8cfd-4556937420a3
📒 Files selected for processing (8)
src/features/notification/api/notifications.tssrc/features/notification/hooks/useMarkNotificationRead.tssrc/features/notification/hooks/useNotificationUnreadCount.tssrc/features/notification/types/index.tssrc/features/notification/useNotificationViewModel.tssrc/pages/notification/index.tsxsrc/shared/lib/queryKeys.tssrc/shared/ui/common/Navbar.tsx
✅ Files skipped from review due to trivial changes (1)
- src/features/notification/hooks/useNotificationUnreadCount.ts
| import { useAuthStore } from '@/shared/stores/useAuthStore' | ||
| import { useNotificationUnreadCount } from '@/features/notification/hooks/useNotificationUnreadCount' |
There was a problem hiding this comment.
shared 컴포넌트가 features 훅에 직접 의존해 레이어 경계를 깨고 있습니다.
Navbar는 공용 UI인데 unread 조회 로직을 내장하면서 feature에 결합됐습니다. unread 상태/카운트는 상위 레이어(pages/widgets/app)에서 조회 후 props로 주입하고, Navbar는 표시만 담당하도록 분리해주세요.
♻️ 최소 분리 방향 예시
-import { useNotificationUnreadCount } from '`@/features/notification/hooks/useNotificationUnreadCount`'
@@
interface NavbarProps {
@@
+ hasUnreadNotification?: boolean
}
@@
export function Navbar({
@@
+ hasUnreadNotification = false,
}: NavbarProps) {
@@
- const scope = useAuthStore(s => s.scope)
- const { data: unreadData } = useNotificationUnreadCount(isMain ? scope : null)
@@
- {unreadData?.hasUnread && (
+ {hasUnreadNotification && (
<span className="absolute right-0 top-0 h-2 w-2 rounded-full bg-error" />
)}As per coding guidelines src/shared/**: "다른 레이어를 import하지 않는지 (완전한 독립성)".
Also applies to: 35-37, 90-92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/shared/ui/common/Navbar.tsx` around lines 2 - 3, Navbar currently imports
and calls the feature hook useNotificationUnreadCount, breaking layer
boundaries; change Navbar (component name Navbar) to accept unread props from
its parent (e.g. unreadCount and optionally isUnreadLoading) and remove the
import/use of useNotificationUnreadCount from the file, then update any internal
references to unread state (render logic around the badge/count at lines
referenced in the review) to use the new props; update callers
(pages/widgets/app or parent container) to call useNotificationUnreadCount and
pass the resulting unread values into Navbar as props.
There was a problem hiding this comment.
@limtjdghks 이해합니다! 현재 구조가 기능적으로는 문제없이 동작하니까요. 다만 나중에 Navbar를 재사용하거나 테스트할 때 features 의존성이 걸림돌이 될 수 있어서, 추후 여유가 생길 때 리팩터링 이슈로 트래킹해두시는 걸 추천드립니다. 혹시 GitHub 이슈로 남겨드릴까요?
(´・ω・`) 🐇
🧠 Learnings used
Learnt from: dohy-eon
Repo: alter-app/alter-client PR: 7
File: src/pages/manager/home/index.tsx:96-101
Timestamp: 2026-03-11T19:27:41.867Z
Learning: In the alter-client repository, avoid hardcoding colors in TSX files. Use the Tailwind tokens already registered in tailwind.config.js: use text-sub and border-sub for the color '`#3a9982`' (sub.DEFAULT), and use bg-bg-dark for '`#efefef`'. Replace literals like text-[`#3A9982`] and bg-[`#EFEFEF`] with the appropriate tokens in src/pages/manager/home/index.tsx and src/shared/ui/manager/**/*.tsx.
|
@dohy-eon 와 레전드버그! |
좋아유 작업 완료되면 스샷 같이 첨부 부탁드림다 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/notification/useNotificationSettingsViewModel.ts`:
- Around line 33-45: handleSubstituteChange and handleReputationChange currently
read stale render-time flags (reputationEnabled/substituteEnabled) to decide
whether to set CONSENT_TYPE.GENERAL, which can miss updates if toggles happen
quickly; change the flow to base the GENERAL decision on the latest
server/returned state after the mutateAsync call (e.g., use the value returned
by mutateAsync or refetch the consent state) instead of
reputationEnabled/substituteEnabled so that after calling mutateAsync for
CONSENT_TYPE.SUBSTITUTE or CONSENT_TYPE.REPUTATION you then check the fresh
consent values and only call mutateAsync({ type: CONSENT_TYPE.GENERAL, consent:
false }) when both current substitute and reputation are false; keep the
existing function names handleSubstituteChange, handleReputationChange and
COMMIT using mutateAsync and CONSENT_TYPE constants.
In `@src/pages/notification/settings/index.tsx`:
- Line 4: The page is importing the internal symbol
useNotificationSettingsViewModel directly from the feature implementation;
change the page import to consume the feature's public API by importing
useNotificationSettingsViewModel from the feature root (the feature's index.ts
re-export). If the feature's index.ts does not yet export
useNotificationSettingsViewModel, add a re-export there (export {
useNotificationSettingsViewModel } from './useNotificationSettingsViewModel')
and then update the page import to import { useNotificationSettingsViewModel }
from '`@/features/notification`'.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1db3ffd3-16fe-468d-9e8d-4b3b4452a25b
📒 Files selected for processing (8)
src/app/App.tsxsrc/features/notification/types/consent.tssrc/features/notification/useNotificationSettingsViewModel.tssrc/pages/notification/settings/components/NotificationToggleRow.tsxsrc/pages/notification/settings/index.tsxsrc/shared/constants/routes.tssrc/shared/lib/queryKeys.tssrc/shared/ui/common/Navbar.tsx
✅ Files skipped from review due to trivial changes (2)
- src/features/notification/types/consent.ts
- src/shared/constants/routes.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/app/App.tsx
- src/shared/ui/common/Navbar.tsx
- src/shared/lib/queryKeys.ts
|
@dohy-eon 작업 완료했슴니다 2026-06-05.1.57.45.mov |



ID
변경 내용
구현 사항
알림 아이템 컴포넌트 (NotificationItem)
알림 페이지 (/notifications)
알림 설정 페이지 (/notifications/settings)
API 연동
구현 시연 (필요 시)
2026-05-20.10.08.43.mov
참고 사항 (필요 시)
필요한 API
디자인 수정되어야하는거
Summary by CodeRabbit
새로운 기능