refactor(frontend): constants, shared hooks, component decomposition, and structure alignment - #103
Open
geuna0204 wants to merge 11 commits into
Open
refactor(frontend): constants, shared hooks, component decomposition, and structure alignment#103geuna0204 wants to merge 11 commits into
geuna0204 wants to merge 11 commits into
Conversation
- Delete teamsDummyData.ts (468 lines, zero imports) and its orphaned wire-type copies - Delete unused alertStore + test; its role is served by noticeStore (drop now-orphaned TAlert type) - Drop dummy team ids (t_a/t_e) from TreeDetailView fallback selection and default expansion — they never match real API data - Route /ui-test only in dev builds via a DEV-gated lazy import so the 1,084-line showcase page and its chunk stay out of production Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…py in constants Add three constants modules as single sources for strings that were hardcoded per file, so a wording or wire-value change is a one-line edit and typos become compile errors: - constants/apiConstants.ts — wire-contract vocabulary (INVITATION_STATUS, SESSION_STATUS, WORKSPACE_STATUS, SYSTEM_UPDATE_STATE, TEAM_MEMBER_ROLE, ERROR_CODES); the matching union types are now derived from these objects, and the status→label maps in styleConstants are pinned with satisfies so a vocabulary change fails compilation until the labels follow - constants/errorConstants.ts — error-code→copy maps (TEAM_REASON, BATCH_REASON, ADD_MEMBER_REASON, BATCH_REASON_FALLBACK), replacing five per-file re-declarations; the duplicate-team-name copy is shared with the create/rename modals' client-side check. Unmapped-code fallback behavior is unchanged and now documented per call site - constants/noticeConstants.ts — showNotice copy per flow (NOTICE_TEXT, 11 flows, 20 call sites), collapsing cross-file duplicates (resend invitation, create team, remove membership); titles reference MODAL_TITLES where the notice reports that modal's action Also add DEFAULT_PAGE_SIZE to commonConstants (was declared as PAGE_SIZE=10 in three files) and drop UITestPage's duplicate ROLE_OPTIONS in favor of the teamOptions export. MemberStatus chip props stay literal on purpose — they are chip vocabulary behind the CHIP_STATUS seam, not wire values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e build
MemberDetailDrawer copied user.memberships into useState once on mount,
so the fresher GET /users/{id} payload and every post-mutation refetch
were silently ignored in favor of locally patched rows. Server truth
now flows straight from props and the drawer stages only the user's own
edits (pendingRoles map + checkedIds set), re-applied as a diff on top
of whatever the server currently says — the manual state mirroring
after add/remove/role mutations is gone, since those mutations already
invalidate the user detail/list queries. Batch partial-failure behavior
is unchanged: failed targets keep their staged edits for a retry.
Adds a regression test: refetched memberships render without a remount
and staged picks survive.
TreeDetailView rebuilt the whole team tree on every render (each
keystroke/checkbox/staged edit) with an O(n^2) filter-per-parent scan.
flatById/teamNodes are now memoized on the teams array and the build
runs as a single pass over a children index; ancestorIds reuses the
memoized map instead of building its own per render.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, and page titles Extend commonConstants with the remaining repeated UI copy so a wording change lands in one place: - PAGE_TITLES — page/section vocabulary; the nav, every page's section aria-label, and MODAL_TITLES.workspaceManage now derive from it - TABLE_HEADERS — column headers shared by list tables, modal tables (ModalTable head arrays + hand-rolled th), and sort-option labels. TreeDetailView's 역할 vs 권한 drift is kept verbatim as roleAlt with a comment pending a copy decision - INPUT_LABELS / PLACEHOLDERS — invite & add-member form copy (팀 선택 4x, 권한 선택 4x, 추가할 팀 없음, ...) - ARIA_LABELS — fixes the 전체선택/전체 선택 drift to one spelling - FEEDBACK_TEXT — the refresh-retry line repeated on three pages Add utils/email.ts (EMAIL_PATTERN + EMAIL_FORMAT_ERROR) following the username.ts pattern-with-copy convention — the regex was declared twice and the error copy had already drifted on punctuation; both modals now share one with-period version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… hooks
Add the UI-state hook layer the pages were missing (the reference
project's useXxxForm/behavior-hook pattern) and point all consumers at
it:
- usePageScopedSelection — the Set-of-ids checkbox selection duplicated
across UsersPage, TreeDetailView, and MemberDetailDrawer (toggleOne/
toggleAll/clearSelection + setSelectedIds for batch reconciliation)
- useServerPagination — one pagination state machine replacing three
divergent implementations, standardized on the strictest variant:
totalPages tracks the last response as state and the returned page is
clamped BEFORE the query call, so an out-of-range request never fires.
Fixes UsersPage passing the raw (unclamped) page to the query for one
cycle, and gives TreeDetailView clamping it never had. Callers report
totals back via useSyncPaginationTotal
- useBatchFailureModal + toBatchFailureRows — the partial-failure modal
state and failed→{label, reason} mapping repeated five times across
three files; the per-flow fallback policy (generic retry copy vs raw
code) stays a caller choice
Each hook ships with unit tests (14 new).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re table state rows - Delete teams/RoleChangeConfirmModal and teams/RemoveMembershipModal — TreeDetailView now uses the users versions, which were designed for reuse (subjectLabel/targets props, ModalTable). Kills the hand-rolled duplicate modal-table styles and the th padding drift (py-2 vs py-1.5). Intended UX change on SC-06: role-change/removal results now render inside the confirm modal (wireframe v0.12 states E-1/E-2), matching the SC-13 drawer; partial-failure handling and staged-edit retention are unchanged. Tests updated to the new contract - Add MODAL_STYLE_VAR (message/body/footer/footerCompact) to styleConstants and replace 27 class literals across 12 modal files; resolves the body gap-4/gap-5 drift. The two footer spacings in active use are kept as named variants pending a design pass - Add TableLoadingRow/TableEmptyRow beside TableErrorRow and replace the inline state rows in SessionsPage/UsersPage/TreeDetailView, resolving the py-6/py-8 and text-faint/text-muted-foreground drift - Drop the now-orphaned NOTICE_TEXT.roleChange entry and removeMembership.failure (both render in-modal now) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure moves/renames — no runtime behavior change: - Single store home: src/stores/* → src/state/store/ (reference convention), stores/ folder removed, 20 import sites updated - Resolve the TTeamNode name collision: the recursive UI tree node is now TTeamViewNode in teamTypes; TTeamNode remains only the flat wire row, so the same name can no longer resolve to two shapes - Split workspace types out of commonTypes into types/workspaceTypes; commonTypes keeps only genuinely cross-cutting shapes - Relocate non-component .ts out of components/: teamHierarchy and invitePreview → utils/; teamOptions split into constants/teamConstants (pattern/rule text/ROLE_OPTIONS) + utils/buildTeamOptions; memberStatusMap → constants/userConstants - Fold single-file folders: drawer/MembershipRow → components/users (its only consumer domain), toast/ToastContainer → components/elements (reference keeps Toast in elements) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onents 720-line component → 428-line composition plus five focused modules, behavior preserved (all existing tests pass unchanged): - hooks/useStagedRoleEdits — the staged role-edit machine (pendingRoles/ savedRoles, un-stage on picking the base value back, partial-failure reconciliation committing only what succeeded) - hooks/useTeamCrud — create/rename/delete orchestration with the TEAM_REASON error mapping and success notices; TeamsPage's duplicated empty-state create flow now uses it too, removing the last near-verbatim logic duplication between the two files - components/teams/TeamCard, TeamMembersToolbar, TeamMembersTable — pure views; staging/selection state stays in the parent's hooks - buildTeamNodes/findTeamNode/ancestorIds move to utils/teamHierarchy beside the existing tree lookups Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ooks and views Behavior preserved (all existing tests pass unchanged): - hooks/useMembershipDrafts — the SC-13 membership machine extracted from the drawer: props-derived rows with diff staging (pendingRoles/ checkedIds), the add-team flow, and the role-change/removal confirm reconciliation incl. the batch-failure surface. The two 25-line async confirm closures that lived inside modal JSX become one-line handler references - components/users/MembershipSection — the 소속 팀 block (table + action bar + add-picker row) as a pure view - hooks/useUserBatchActions — the SC-11 bulk flows (invite/resend/ batch delete) with their notice/batch-failure wiring; selection reconciliation and drawer close-out stay caller-injected - components/users/UsersToolbar, UserRow — pure views; membershipSummary moves next to its only consumer Drawer: 660 → 362 lines, UsersPage: 580 → 370. Every page component is now composition over the shared hook layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pages - formatDate constructed a new Intl.DateTimeFormat on every call — one of the costlier Intl operations, running in every table timestamp cell on every render. The formatter options never change, so it is now a module-level constant - Pagination rendered one button per page unbounded; the session history table grows without bound, so hundreds of pages meant hundreds of buttons. It now shows a sliding 5-page window centered on the current page, clamped at both ends so the button count never jumps while paging (1→[1..5], 4→[2..6], 9 of 10→[6..10]); page counts of 5 or fewer render unchanged. Window/clamp regression tests added Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The team/workspace confirm modals spaced their button rows with gap-2 while the users flows used gap-4 — since the modal-fork unification both spacings could appear on the same screen (SC-06). Drop the footerCompact variant and point its seven call sites at MODAL_STYLE_VAR.footer (gap-4, items-center). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mlaszcz-cryptolab
approved these changes
Aug 3, 2026
Contributor
|
Need to change base branch to |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
개요
frontend 전면 코드 리뷰 후 진행한 리팩토링입니다. 커밋 11개, 전 구간 tsc/eslint/vitest 그린 유지 (테스트 313 → 329개).
주요 변경
죽은 코드 제거
teamsDummyData.ts(468줄, import 0) ·alertStore(소비처 0) 삭제, TreeDetailView의 더미 팀 ID 제거/ui-test라우트를 DEV 전용 lazy import로 게이트 — 1,084줄 showcase가 프로덕션 번들에서 제외상수화 (문구/값의 단일 소스)
apiConstants— wire 값 6종(상태/역할/에러 코드), 타입은 상수에서 파생 + styleConstants 상태 맵에satisfies고정errorConstants/noticeConstants— 파일마다 재선언되던 에러 문구 맵·showNotice 문구 통합TABLE_HEADERS·INPUT_LABELS·PLACEHOLDERS·ARIA_LABELS·PAGE_TITLES·FEEDBACK_TEXT·DEFAULT_PAGE_SIZE— 이미 발생해 있던 문구 드리프트 3건("전체 선택"/"전체선택", 이메일 오류 문구 마침표, 모달 body gap) 해소버그 수정
공유 훅 레이어 (참고 프로젝트 es2-cloud-fe 패턴)
usePageScopedSelection·useServerPagination·useBatchFailureModal— 3개 파일에 갈라져 있던 중복 구현 통일 (단위 테스트 14건)useStagedRoleEdits·useTeamCrud·useMembershipDrafts·useUserBatchActions— 대형 컴포넌트의 상태 머신 추출컴포넌트 분해
구조 정합
state/store/),TTeamNode이름 충돌 해소(TTeamViewNode), workspace 타입 분리, components/ 내 순수 .ts를 utils/·constants/로 재배치, 단일 파일 폴더(drawer/·toast/) 정리성능
검증
tsc -b/eslint ./vitest run329 테스트 전부 통과, 프로덕션 빌드로 번들 제외 확인