feat: 워크스페이스 설정페이지 구현 - #33
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough워크스페이스 설정 페이지와 워크스페이스·멤버 편집 기능을 추가했습니다. 설정 탭 내비게이션, 멤버 초대·목록, 프로필·워크스페이스 정보 폼과 mock 데이터를 연결했으며, RecentNotes의 워크스페이스별 조회 및 여러 UI 스타일을 조정했습니다. Changes워크스페이스 설정 기능
회의록 데이터 및 UI 변경
스타일 및 리팩터링 조정
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant WorkspaceSettingsPage
participant SettingsView
participant MemberManagementPanel
participant useMemberManagement
participant MemberInviteSection
User->>WorkspaceSettingsPage: 설정 페이지 요청
WorkspaceSettingsPage->>SettingsView: 워크스페이스·멤버·활성 탭 전달
SettingsView->>MemberManagementPanel: 멤버 탭 렌더링
MemberManagementPanel->>useMemberManagement: 멤버 관리 상태 초기화
useMemberManagement-->>MemberManagementPanel: 초대 상태와 멤버 목록 반환
MemberManagementPanel->>MemberInviteSection: 초대 UI와 핸들러 전달
User->>MemberInviteSection: 이메일 초대 제출
MemberInviteSection->>useMemberManagement: inviteByEmail 호출
useMemberManagement->>useMemberManagement: 초대 멤버 추가 및 이메일 초기화
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
src/widgets/side-project/dashboard-recent-notes/ui/RecentNotes.tsx (1)
19-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMagic default
workspaceId.Defaulting to the literal
'test'string is fragile mock-data coupling; if the widget catalog (context snippet 1) never passes a realworkspaceId, this widget will silently always show test data regardless of the actual workspace context.🤖 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/widgets/side-project/dashboard-recent-notes/ui/RecentNotes.tsx` at line 19, The RecentNotes component is using a fragile magic default for workspaceId by falling back to the literal test value. Update RecentNotes so it no longer silently defaults to mock data; require the caller or surrounding widget context to provide the real workspaceId, and if a fallback is still needed make it come from the actual workspace context rather than a hardcoded string. Use RecentNotesProps and RecentNotes to locate the prop handling and remove the test coupling.src/entities/workspace-member/config/labels.ts (1)
5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
BadgeToneis redefined here instead of imported fromshared/ui/badge.The comment notes this must match the Badge component's tone prop, but redefining the type creates two sources of truth that can silently drift. Since entities may import from
sharedunder the FSD direction (app → views → widgets → features → entities → shared), prefer importing the tone type fromsrc/shared/ui/badge.tsxinstead.♻️ Suggested refactor
-// Badge 컴포넌트의 tone 값과 일치시킵니다. -export type BadgeTone = 'brand' | 'neutral' | 'success' | 'warning'; +// Badge 컴포넌트의 tone 값과 동일한 타입을 그대로 재사용합니다. +import type { BadgeTone } from '`@/shared/ui/badge`'; +export type { BadgeTone };I don't have
src/shared/ui/badge.tsxin this review batch to confirm its exported tone type shape; please verify it exports a compatible type before applying this change.Based on coding guidelines: "Follow the FSD import direction
app → views → widgets → features → entities → shared."🤖 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/entities/workspace-member/config/labels.ts` at line 5, BadgeTone is being duplicated in the workspace-member labels config instead of reusing the shared Badge component type. Update the labels config to import the tone type from the shared badge module (the Badge component’s exported tone prop type) and remove the local redefinition so there is a single source of truth. Verify the shared badge export is compatible before wiring it in, and keep the import direction consistent with FSD by referencing shared from the entity layer.Source: Coding guidelines
src/shared/ui/badge.tsx (1)
6-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTone union duplicated in
workspace-member/config/labels.ts.The
BadgeTonetype inlabels.tsmanually re-declares this same union with a comment noting it "must match" the Badge tone values. Deriving it frombadgeVariantsinstead removes the risk of the two drifting out of sync.♻️ Proposed fix
type BadgeProps = React.ComponentProps<'span'> & VariantProps<typeof badgeVariants>; + +export type BadgeTone = NonNullable<VariantProps<typeof badgeVariants>['tone']>;Then in
labels.ts, import and reuse this type instead of redeclaring the literal union.🤖 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/badge.tsx` around lines 6 - 21, The Badge tone union is duplicated and can drift from the source of truth in badgeVariants. Expose the tone type from the badge component setup (or derive it directly from badgeVariants) and update workspace-member/config/labels.ts to import and reuse that type instead of redeclaring the literal union. Keep the tone variants defined in badgeVariants as the single authoritative list so any future changes stay in sync.src/views/settings/ui/SettingsView.tsx (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnnecessary
'use client'on a purely presentational shell.This component has no hooks/state/handlers of its own; it just branches on
activeTab. It could stay a Server Component, letting only the interactive children (already client components) cross the boundary, slightly trimming the client bundle.🤖 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/views/settings/ui/SettingsView.tsx` at line 1, The SettingsView component is marked as a client component unnecessarily even though it only renders based on activeTab and has no hooks, state, or event handlers. Remove the 'use client' directive from SettingsView so it can remain a Server Component, and keep the interactive tab content in the already client-side child components.src/features/manage-workspace-members/ui/MemberInviteSection.tsx (1)
82-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider linking the duplicate-email error to the input via
aria-describedby.
aria-invalidis set on the input but the error text at Line 129 isn't associated with it, so screen readers won't announce the reason for invalidity.♿️ Optional accessibility improvement
<input type="email" value={email} onChange={(event) => onChangeEmail(event.target.value)} placeholder="초대할 이메일" aria-invalid={isDuplicate || undefined} + aria-describedby={isDuplicate ? 'invite-email-error' : undefined} className="h-11 w-full rounded-2xl bg-slate-100 px-4 text-sm font-medium text-slate-900 outline-none placeholder:text-slate-400 focus:ring-2 focus:ring-indigo-300" /> ... {inviteMode === 'email' && isDuplicate ? ( - <p className="mt-1 text-sm font-medium text-rose-500">이미 초대된 이메일이에요.</p> + <p id="invite-email-error" className="mt-1 text-sm font-medium text-rose-500">이미 초대된 이메일이에요.</p> ) : null}Also applies to: 128-130
🤖 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/manage-workspace-members/ui/MemberInviteSection.tsx` around lines 82 - 92, The duplicate-email validation in MemberInviteSection is not associated with the email input for assistive tech. Update the email input in MemberInviteSection so it uses aria-describedby to point to the duplicate-error message rendered below, and ensure that message has a stable id the input can reference while keeping aria-invalid in sync with isDuplicate.
🤖 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/manage-workspace-info/ui/WorkspaceInfoForm.tsx`:
- Around line 1-6: The WorkspaceInfoForm component is using a React namespace
type without importing React, which breaks type-checking in this setup. Update
the imports at the top of WorkspaceInfoForm.tsx to bring in FormEvent from react
alongside useState, and change the form handler type annotation in
WorkspaceInfoForm to use FormEvent<HTMLFormElement> instead of
React.FormEvent<HTMLFormElement>.
- Around line 14-19: The WorkspaceInfoForm state is only initialized from
workspace.name and workspace.description on first mount, so switching workspaces
can leave stale values in the inputs. Update WorkspaceInfoForm to resync its
local form state when workspace.id changes, either by adding a key based on
workspace.id in the parent SettingsView or by using a useEffect inside
WorkspaceInfoForm to reset committedName, committedDescription, name,
description, and isSaved whenever the workspace changes.
---
Nitpick comments:
In `@src/entities/workspace-member/config/labels.ts`:
- Line 5: BadgeTone is being duplicated in the workspace-member labels config
instead of reusing the shared Badge component type. Update the labels config to
import the tone type from the shared badge module (the Badge component’s
exported tone prop type) and remove the local redefinition so there is a single
source of truth. Verify the shared badge export is compatible before wiring it
in, and keep the import direction consistent with FSD by referencing shared from
the entity layer.
In `@src/features/manage-workspace-members/ui/MemberInviteSection.tsx`:
- Around line 82-92: The duplicate-email validation in MemberInviteSection is
not associated with the email input for assistive tech. Update the email input
in MemberInviteSection so it uses aria-describedby to point to the
duplicate-error message rendered below, and ensure that message has a stable id
the input can reference while keeping aria-invalid in sync with isDuplicate.
In `@src/shared/ui/badge.tsx`:
- Around line 6-21: The Badge tone union is duplicated and can drift from the
source of truth in badgeVariants. Expose the tone type from the badge component
setup (or derive it directly from badgeVariants) and update
workspace-member/config/labels.ts to import and reuse that type instead of
redeclaring the literal union. Keep the tone variants defined in badgeVariants
as the single authoritative list so any future changes stay in sync.
In `@src/views/settings/ui/SettingsView.tsx`:
- Line 1: The SettingsView component is marked as a client component
unnecessarily even though it only renders based on activeTab and has no hooks,
state, or event handlers. Remove the 'use client' directive from SettingsView so
it can remain a Server Component, and keep the interactive tab content in the
already client-side child components.
In `@src/widgets/side-project/dashboard-recent-notes/ui/RecentNotes.tsx`:
- Line 19: The RecentNotes component is using a fragile magic default for
workspaceId by falling back to the literal test value. Update RecentNotes so it
no longer silently defaults to mock data; require the caller or surrounding
widget context to provide the real workspaceId, and if a fallback is still
needed make it come from the actual workspace context rather than a hardcoded
string. Use RecentNotesProps and RecentNotes to locate the prop handling and
remove the test coupling.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8f2faf01-c063-4f5d-a382-c527a8013e50
📒 Files selected for processing (32)
src/app/workspaces/[workspaceId]/settings/page.tsxsrc/entities/meeting-note/index.tssrc/entities/meeting-note/ui/MeetingNoteCard.tsxsrc/entities/side-project/meeting-note/index.tssrc/entities/side-project/meeting-note/model/meeting-note.mock.tssrc/entities/side-project/meeting-note/model/meeting-note.types.tssrc/entities/workspace-member/config/labels.tssrc/entities/workspace-member/index.tssrc/entities/workspace-member/model/mock-current-workspace-member.tssrc/entities/workspace-member/model/mock-workspace-members.tssrc/entities/workspace-member/model/workspace-member.types.tssrc/entities/workspace/model/mock-workspace.tssrc/entities/workspace/model/workspace.types.tssrc/features/dashboard/edit-layout/model/useDashboardLayout.tssrc/features/manage-meeting-notes/ui/MeetingNoteForm.tsxsrc/features/manage-meeting-notes/ui/MeetingNotesList.tsxsrc/features/manage-member-profile/index.tssrc/features/manage-member-profile/ui/MemberProfileForm.tsxsrc/features/manage-workspace-info/index.tssrc/features/manage-workspace-info/ui/WorkspaceInfoForm.tsxsrc/features/manage-workspace-members/index.tssrc/features/manage-workspace-members/model/use-member-management.tssrc/features/manage-workspace-members/ui/MemberInviteSection.tsxsrc/features/manage-workspace-members/ui/MemberList.tsxsrc/features/manage-workspace-members/ui/MemberManagementPanel.tsxsrc/shared/ui/badge.tsxsrc/views/settings/index.tssrc/views/settings/model/settings-tab.tssrc/views/settings/ui/SettingsTabs.tsxsrc/views/settings/ui/SettingsView.tsxsrc/widgets/side-project/dashboard-recent-notes/ui/RecentNotes.tsxsrc/widgets/store-operation/dashboard-work-schedule/ui/WorkScheduleSummary.tsx
💤 Files with no reviewable changes (3)
- src/entities/side-project/meeting-note/model/meeting-note.mock.ts
- src/entities/side-project/meeting-note/index.ts
- src/entities/side-project/meeting-note/model/meeting-note.types.ts
| const [committedName, setCommittedName] = useState(workspace.name); | ||
| const [committedDescription, setCommittedDescription] = useState(workspace.description ?? ''); | ||
|
|
||
| const [name, setName] = useState(workspace.name); | ||
| const [description, setDescription] = useState(workspace.description ?? ''); | ||
| const [isSaved, setIsSaved] = useState(false); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant files and inspect the component/parent usage.
git ls-files 'src/features/manage-workspace-info/ui/WorkspaceInfoForm.tsx' 'src/**/*SettingsView*' 'src/**/*settings*' 'src/**/*workspace*' | sed -n '1,200p'
echo
echo "=== WorkspaceInfoForm.tsx ==="
nl -ba src/features/manage-workspace-info/ui/WorkspaceInfoForm.tsx | sed -n '1,220p'
echo
echo "=== Search for WorkspaceInfoForm usage ==="
rg -n "WorkspaceInfoForm" src
echo
echo "=== Search for workspaceId / key usage near settings views ==="
rg -n "workspaceId|key=\\{workspace\\.id\\}|WorkspaceInfoForm" srcRepository: TeampleRun/syncly
Length of output: 2968
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== WorkspaceInfoForm.tsx ==="
cat -n src/features/manage-workspace-info/ui/WorkspaceInfoForm.tsx | sed -n '1,240p'
echo
echo "=== SettingsView.tsx ==="
cat -n src/views/settings/ui/SettingsView.tsx | sed -n '1,260p'
echo
echo "=== app settings page ==="
cat -n src/app/workspaces/[workspaceId]/settings/page.tsx | sed -n '1,240p'
echo
echo "=== Search for WorkspaceInfoForm usage and key props ==="
rg -n "WorkspaceInfoForm|key=\\{workspace\\.id\\}|key=\\{.*workspace.*\\}" src/views src/app src/featuresRepository: TeampleRun/syncly
Length of output: 8276
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== workspace layout ==="
cat -n src/app/workspaces/[workspaceId]/layout.tsx | sed -n '1,240p'
echo
echo "=== workspace page ==="
cat -n src/app/workspaces/[workspaceId]/page.tsx | sed -n '1,240p'
echo
echo "=== dashboard page (for key usage example) ==="
cat -n src/app/workspaces/[workspaceId]/dashboard/page.tsx | sed -n '1,240p'Repository: TeampleRun/syncly
Length of output: 2913
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== WorkspaceShell.tsx ==="
cat -n src/widgets/workspace-shell/ui/WorkspaceShell.tsx | sed -n '1,260p'
echo
echo "=== Search for workspaceId keys in workspace shell ==="
rg -n "key=\\{workspaceId\\}|key=\\{.*workspaceId.*\\}" src/widgets/workspace-shell src/views/settings src/app/workspacesRepository: TeampleRun/syncly
Length of output: 1842
Reset the form when workspace changes. useState only reads workspace.name/workspace.description on the first mount, and SettingsView doesn’t key WorkspaceInfoForm by workspaceId, so switching to another workspace can leave the previous workspace’s values in the inputs. Add a reset on workspace.id change (for example, a key={workspace.id} or a useEffect sync).
🤖 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/manage-workspace-info/ui/WorkspaceInfoForm.tsx` around lines 14
- 19, The WorkspaceInfoForm state is only initialized from workspace.name and
workspace.description on first mount, so switching workspaces can leave stale
values in the inputs. Update WorkspaceInfoForm to resync its local form state
when workspace.id changes, either by adding a key based on workspace.id in the
parent SettingsView or by using a useEffect inside WorkspaceInfoForm to reset
committedName, committedDescription, name, description, and isSaved whenever the
workspace changes.
seongjinss555
left a comment
There was a problem hiding this comment.
해당 사항 수정하시고 진행하면 좋을 거 같습니다~
Pull Request
작업 내용
설정메뉴(href: 'settings')로 진입하며, URL 쿼리(?tab=) 기반 3개 탭으로 구성됩니다.작업 결과
/workspaces/{workspaceId}/settings라우트 신설, 사이드바설정클릭 시 이동?tab=workspace|members|profile)로 관리되어 딥링크·뒤로가기 지원팀장/팀원)·상태(참여 중/초대됨) 뱃지변경 사항
Added
src/app/workspaces/[workspaceId]/settings/page.tsx— 설정 라우트(RSC).?tab=파싱 + 표시 데이터(워크스페이스·멤버·현재 사용자) 조회해 주입src/views/settings/— 설정 뷰ui/SettingsView.tsx— 레이아웃 + 데이터 주입 + 패널 스위칭ui/SettingsTabs.tsx— 탭 네비게이션(URL 기반, 활성 표시/접근성)model/settings-tab.ts— 탭 정의 및?tab=파싱 헬퍼index.ts— public 배럴src/features/manage-workspace-info/— 워크스페이스 정보 수정 폼src/features/manage-workspace-members/— 팀원 초대 섹션 + 멤버 목록 + 상태 훅(MemberManagementPanel/MemberInviteSection/MemberList/useMemberManagement)src/features/manage-member-profile/— 프로필 닉네임 수정 폼src/shared/ui/badge.tsx— 역할·상태 표시용 Badge(cva,brand/neutral/success/warning톤)src/entities/workspace-member/config/labels.ts— 역할/상태 → 한글 라벨·뱃지 톤 매핑Changed
src/entities/workspace/model/workspace.types.ts—Workspace.description?추가src/entities/workspace/model/mock-workspace.ts— 목업 워크스페이스 설명 반영src/entities/workspace-member/model/workspace-member.types.ts—email,status('joined' | 'invited'),WorkspaceMemberRole/WorkspaceMemberStatus타입 추가src/entities/workspace-member/model/mock-workspace-members.ts— email/status 반영 +team-workspace·side-workspace멤버 시드 추가src/entities/workspace-member/model/mock-current-workspace-member.ts— email/status 반영src/entities/workspace-member/index.ts— 타입·라벨맵 export 추가Fixed
실행화면
테스트
localhost:3000/workspaces/{id}/settings3개 탭 SSR 렌더 확인)tsc --noEmit,eslint src에러·경고 0)리뷰 체크리스트
feature/*->develop, 배포 시develop또는release/*->main)Type/#issue-number/description형식을 따릅니다.console.log, 주석, 임시 코드를 제거했습니다.리뷰 요청사항
searchParams→ prop 방식으로 URL 기반 탭을 구현했습니다.WorkspaceMember에email/status를 필수 필드로 추가하여 목업 데이터 전체를 갱신했습니다. 누락된 사용처가 없는지 봐주시면 좋겠습니다.알려진 한계 (후속 이슈 후보)
관련 이슈
Closes #31
Summary by CodeRabbit