@vtex/faststore-plugin-buyer-portal 2.0.7 → 2.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yaml +4 -4
- package/AGENTS.md +6 -0
- package/CHANGELOG.md +30 -1
- package/docs/superpowers/plans/2026-06-26-contracts-listing-selection-search-pagination.md +1070 -0
- package/docs/superpowers/specs/2026-06-26-contracts-listing-selection-search-pagination-design.md +157 -0
- package/package.json +1 -1
- package/plugin.config.js +8 -0
- package/public/buyer-portal-icons.svg +37 -1
- package/specs/001-suma-phase-2/checklists/requirements.md +36 -0
- package/specs/001-suma-phase-2/contracts/contracts-api.md +187 -0
- package/specs/001-suma-phase-2/contracts/feature-flag.md +91 -0
- package/specs/001-suma-phase-2/data-model.md +119 -0
- package/specs/001-suma-phase-2/plan.md +123 -0
- package/specs/001-suma-phase-2/quickstart.md +156 -0
- package/specs/001-suma-phase-2/research.md +150 -0
- package/specs/001-suma-phase-2/spec.md +186 -0
- package/specs/001-suma-phase-2/tasks.md +253 -0
- package/specs/suma-add-contracts.md +224 -0
- package/specs/suma-contract-listing.md +295 -0
- package/specs/suma-contract-switching.md +201 -0
- package/specs/suma-remove-contracts.md +233 -0
- package/specs/suma-set-default-contract.md +208 -0
- package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +113 -105
- package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +2 -2
- package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +60 -46
- package/src/features/contracts/clients/ContractsClient.ts +45 -0
- package/src/features/contracts/components/AddContractsDrawer/AddContractsDrawer.tsx +323 -0
- package/src/features/contracts/components/AddContractsDrawer/add-contracts-drawer.scss +282 -0
- package/src/features/contracts/components/ConfirmRemoveDrawer/ConfirmRemoveDrawer.tsx +58 -0
- package/src/features/contracts/components/ConfirmRemoveDrawer/confirm-remove-drawer.scss +18 -0
- package/src/features/contracts/components/ContractListItem/ContractListItem.tsx +120 -0
- package/src/features/contracts/components/ContractSelectAllRow/ContractSelectAllRow.tsx +37 -0
- package/src/features/contracts/components/ContractSelectAllRow/index.ts +2 -0
- package/src/features/contracts/components/ContractSelectionBar/ContractSelectionBar.tsx +47 -0
- package/src/features/contracts/components/ContractSelectionBar/index.ts +2 -0
- package/src/features/contracts/components/ContractSelectionList/ContractSelectionList.tsx +55 -0
- package/src/features/contracts/components/ContractSwitchOverlay/ContractSwitchOverlay.tsx +36 -0
- package/src/features/contracts/components/ContractSwitchOverlay/contract-switch-overlay.scss +7 -0
- package/src/features/contracts/components/ContractsListHeader/ContractsListHeader.tsx +58 -0
- package/src/features/contracts/components/ContractsListHeader/index.ts +2 -0
- package/src/features/contracts/components/ErrorRemoveDrawer/ErrorRemoveDrawer.tsx +69 -0
- package/src/features/contracts/components/UnableToRemoveContractsDrawer/UnableToRemoveContractsDrawer.tsx +52 -0
- package/src/features/contracts/components/UnableToRemoveContractsDrawer/unable-to-remove-contracts-drawer.scss +29 -0
- package/src/features/contracts/components/index.ts +14 -0
- package/src/features/contracts/hooks/__tests__/contractListingHelpers.test.ts +71 -0
- package/src/features/contracts/hooks/__tests__/contractSwitchChannelHelpers.test.ts +32 -0
- package/src/features/contracts/hooks/__tests__/useAddContracts.test.ts +63 -0
- package/src/features/contracts/hooks/__tests__/useContractSelection.test.ts +72 -0
- package/src/features/contracts/hooks/__tests__/useRemoveContracts.test.ts +130 -0
- package/src/features/contracts/hooks/__tests__/useSetDefaultContract.test.ts +82 -0
- package/src/features/contracts/hooks/contractListingHelpers.ts +45 -0
- package/src/features/contracts/hooks/contractSelectionHelpers.ts +28 -0
- package/src/features/contracts/hooks/contractSwitchChannelHelpers.ts +9 -0
- package/src/features/contracts/hooks/index.ts +19 -0
- package/src/features/contracts/hooks/useAddContracts.ts +27 -0
- package/src/features/contracts/hooks/useContractListing.ts +71 -0
- package/src/features/contracts/hooks/useContractSelection.ts +53 -0
- package/src/features/contracts/hooks/useContractSwitchChannel.ts +49 -0
- package/src/features/contracts/hooks/useListAvailableContracts.ts +27 -0
- package/src/features/contracts/hooks/useRemoveContracts.ts +21 -0
- package/src/features/contracts/hooks/useSetDefaultContract.ts +21 -0
- package/src/features/contracts/hooks/useSwitchContract.ts +76 -0
- package/src/features/contracts/layouts/ContractInformationLayout/ContractInformationLayout.tsx +316 -0
- package/src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx +415 -0
- package/src/features/contracts/layouts/ContractListingLayout/ContractSettingsNav.tsx +83 -0
- package/src/features/contracts/layouts/ContractListingLayout/contract-listing-layout.scss +468 -0
- package/src/features/contracts/layouts/ContractListingLayout/utils.ts +4 -0
- package/src/features/contracts/layouts/index.ts +8 -0
- package/src/features/contracts/services/__tests__/contracts-client.test.ts +148 -0
- package/src/features/contracts/services/__tests__/list-attached-contracts.service.test.ts +95 -0
- package/src/features/contracts/services/add-contracts.service.ts +27 -0
- package/src/features/contracts/services/index.ts +24 -0
- package/src/features/contracts/services/list-attached-contracts.service.ts +40 -0
- package/src/features/contracts/services/list-available-contracts.service.ts +32 -0
- package/src/features/contracts/services/remove-contract.service.ts +27 -0
- package/src/features/contracts/services/remove-contracts.service.ts +36 -0
- package/src/features/contracts/services/set-default-contract.service.ts +27 -0
- package/src/features/contracts/types/ContractData.ts +1 -0
- package/src/features/contracts/utils/__tests__/changeContractToken.test.ts +141 -0
- package/src/features/contracts/utils/changeContractToken.ts +122 -0
- package/src/features/contracts/utils/clearPersistedSessionState.ts +74 -0
- package/src/features/org-units/components/OrgUnitDetailsNavbar/OrgUnitDetailsNavbar.tsx +45 -3
- package/src/features/org-units/components/OrgUnitDetailsNavbar/org-unit-details-navbar.scss +35 -0
- package/src/features/org-units/layouts/OrgUnitsLayout/OrgUnitsLayout.tsx +54 -48
- package/src/features/roles/layout/RolesLayout/RolesLayout.tsx +32 -23
- package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +1 -0
- package/src/features/shared/components/HeaderInside/HeaderInside.tsx +8 -1
- package/src/features/shared/components/HeaderInside/header-inside.scss +9 -0
- package/src/features/shared/layouts/BaseTabsLayout/SidebarMenu.tsx +6 -97
- package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +42 -5
- package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +1 -1
- package/src/features/shared/layouts/SumaPageLayout/FullSidebarNav.tsx +124 -0
- package/src/features/shared/layouts/SumaPageLayout/SumaPageLayout.tsx +56 -0
- package/src/features/shared/layouts/SumaPageLayout/SumaSidebar.tsx +80 -0
- package/src/features/shared/layouts/SumaPageLayout/SumaSidebarDrawer.tsx +48 -0
- package/src/features/shared/layouts/SumaPageLayout/index.ts +6 -0
- package/src/features/shared/layouts/SumaPageLayout/suma-sidebar-drawer.scss +37 -0
- package/src/features/shared/layouts/index.ts +6 -0
- package/src/features/shared/services/feature-flags/__tests__/get-feature-flags.service.test.ts +71 -0
- package/src/features/shared/services/feature-flags/get-feature-flags.service.ts +1 -0
- package/src/features/shared/utils/buyerPortalRoutes.ts +9 -0
- package/src/features/shared/utils/constants.ts +1 -1
- package/src/features/shared/utils/getSumaListingSidebarLinks.ts +18 -0
- package/src/features/shared/utils/index.ts +1 -0
- package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +97 -103
- package/src/pages/contract-information.tsx +128 -0
- package/src/pages/contracts.tsx +104 -0
- package/src/pages/home.tsx +10 -6
- package/src/pages/org-unit-details.tsx +17 -13
- package/src/themes/layouts.scss +2 -0
- package/.agents/skills/create-page/SKILL.md +0 -154
- package/.agents/skills/create-page/metadata.json +0 -14
- package/.specify/memory/constitution.md +0 -83
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Tasks: SUMA Phase 2 — Contract Listing, Switching & Management
|
|
2
|
+
|
|
3
|
+
**Input**: Design documents from `specs/001-suma-phase-2/`
|
|
4
|
+
|
|
5
|
+
**Prerequisites**: plan.md ✅ | spec.md ✅ | research.md ✅ | data-model.md ✅ | contracts/ ✅ | quickstart.md ✅
|
|
6
|
+
|
|
7
|
+
**Tests**: Minimum unit + integration tests required by Constitution (Principle IV) for new hooks and API services. No TDD was explicitly requested — tests are in the Polish phase.
|
|
8
|
+
|
|
9
|
+
**Organization**: Tasks grouped by user story (US1–US7) in priority order. Each phase is independently testable.
|
|
10
|
+
|
|
11
|
+
## Format: `[ID] [P?] [Story] Description`
|
|
12
|
+
|
|
13
|
+
- **[P]**: Can run in parallel (different files, no shared dependencies)
|
|
14
|
+
- **[Story]**: Which user story this task belongs to (US1–US7)
|
|
15
|
+
- Paths are relative to repository root
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Phase 1: Setup (Shared Infrastructure)
|
|
20
|
+
|
|
21
|
+
**Purpose**: Register new routes and pages so the plugin recognizes them before any implementation begins.
|
|
22
|
+
|
|
23
|
+
- [ ] T001 Register `contracts` page (`path: /pvt/organization-account/contracts/[orgUnitId]`) and `contract-information` page (`path: /pvt/organization-account/contract-information/[orgUnitId]/[contractId]`) in `plugin.config.js`
|
|
24
|
+
- [ ] T002 Add `contracts` and `contractInformation` route builder functions to `src/features/shared/utils/buyerPortalRoutes.ts` following the existing `replaceParams` pattern
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Phase 2: Foundational (Blocking Prerequisites)
|
|
29
|
+
|
|
30
|
+
**Purpose**: Core type extensions, feature flag wiring, BFF client methods, and service wrappers that ALL user story phases depend on.
|
|
31
|
+
|
|
32
|
+
**⚠️ CRITICAL**: No user story work can begin until this phase is complete.
|
|
33
|
+
|
|
34
|
+
- [ ] T003 Extend `ContractData` type with `isDefault?: boolean` in `src/features/contracts/types/ContractData.ts` (optional field for backward compat with existing code)
|
|
35
|
+
- [ ] T004 [P] Add `suma?: boolean` to the `FeatureFlags` type in `src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx`; add `suma: normalized["suma"] ?? false` to `getBuyerPortalFeatureFlags()` in `src/features/shared/services/feature-flags/get-feature-flags.service.ts` (no snake_case conversion needed — key matches property name)
|
|
36
|
+
- [ ] T005 [P] Add four new methods to `src/features/contracts/clients/ContractsClient.ts`: `listAttachedContracts(orgUnitId, cookie, details?)`, `listAvailableContracts(orgUnitId, cookie)`, `addContracts(orgUnitId, ids, cookie)`, `removeContract(orgUnitId, contractId, cookie)` — see `specs/001-suma-phase-2/contracts/contracts-api.md` for confirmed v1.0.2 endpoint signatures. Note: `activateContract` and `setDefaultContract` endpoints are TBD (not yet in BFF v1.0.2)
|
|
37
|
+
- [ ] T006 [P] Create `src/features/contracts/services/activate-contract.service.ts` wrapping `contractsClient.activateContract` with `withClientErrorBoundary` (same pattern as `update-contract-status.service.ts`)
|
|
38
|
+
- [ ] T007 [P] Create `src/features/contracts/services/add-contracts.service.ts`, `src/features/contracts/services/remove-contracts.service.ts`, and `src/features/contracts/services/set-default-contract.service.ts` — each wrapping the corresponding client method with `withClientErrorBoundary`
|
|
39
|
+
- [ ] T008 Update `src/features/contracts/services/index.ts` to export all new services; update `src/pages/home.tsx` loader to redirect to `buyerPortalRoutes.contracts({ orgUnitId })` when `featureFlags?.suma` is true, preserving the existing `orgUnitDetails` redirect when false
|
|
40
|
+
|
|
41
|
+
**Checkpoint**: Foundation complete — all user story phases can begin in parallel.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Phase 3: User Story 1 — View and Switch Active Contract (Priority: P1) 🎯 MVP
|
|
46
|
+
|
|
47
|
+
**Goal**: A buyer opens the Contract Listing page (new account homepage when flag is on), sees all org unit contracts with the default visually highlighted, clicks a contract, and lands on the Profile page (Contract Information) operating under the selected contract. BFF switch errors show a toast on the listing.
|
|
48
|
+
|
|
49
|
+
**Independent Test**: Visit `/pvt/organization-account` with flag ON → verify redirect to `/contracts/{orgUnitId}`; click a contract → verify navigation to `/profile/{orgUnitId}/{contractId}`; simulate BFF switch failure → verify error feedback on listing page.
|
|
50
|
+
|
|
51
|
+
- [ ] T009 [US1] Create `src/features/contracts/hooks/useSwitchContract.ts` — wraps `changeContractToken`, exposes `{ switchContract, loading, error }`; on success navigates to `buyerPortalRoutes.profileDetails(...)`, on error surfaces error toast on listing
|
|
52
|
+
- [ ] T010 [P] [US1] Create `ContractListItem.tsx` — default star badge when `isDefault === true`, loading overlay during switch; plain click calls `useSwitchContract`
|
|
53
|
+
- [ ] T011 [US1] Create `ContractListingLayout.tsx` — listing page with org sidebar, add/remove/set-default admin actions, BroadcastChannel listener
|
|
54
|
+
- [ ] T012 [US1] Create `src/pages/contracts.tsx` — Contract Listing page; loader fetches attached contracts + org unit data
|
|
55
|
+
- [ ] T013 [P] [US1] *(Optional)* `ContractInformationLayout` — SUMA-specific contract detail layout at `/contract-information`; **switch destination is Profile** (`src/pages/profile.tsx`)
|
|
56
|
+
- [ ] T014 [US1] Switch flow lands on Profile via `buyerPortalRoutes.profileDetails` — no separate contract-information navigation on row click
|
|
57
|
+
|
|
58
|
+
**Checkpoint**: US1 complete — buyers can switch their active contract end-to-end. MVP deliverable.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Phase 4: User Story 2 — Multi-Select with Keyboard Shortcuts (Priority: P2)
|
|
63
|
+
|
|
64
|
+
**Goal**: A buyer on the Contract Listing page can select multiple contracts using Cmd+A (select all), Cmd+Click (toggle individual), and Shift+Click (range selection) without triggering navigation, enabling bulk admin actions in later phases.
|
|
65
|
+
|
|
66
|
+
**Independent Test**: Visit Contract Listing; press Cmd+A → all contracts highlighted; Cmd+Click toggles individual without navigating; Shift+Click selects a contiguous range.
|
|
67
|
+
|
|
68
|
+
- [ ] T015 [US2] Create `src/features/contracts/hooks/useContractSelection.ts` — manages `selectedIds: Set<string>` and `lastSelectedIndex: number`; exposes `isSelected(id)`, `handleSelectAll(allIds)`, `handleModifierClick(id)`, `handleShiftClick(id, allIds)`, `clearSelection()`; uses `event.metaKey || event.ctrlKey` for cross-platform Cmd/Ctrl support
|
|
69
|
+
- [ ] T016 [P] [US2] Create `src/features/contracts/components/ContractSelectionList/ContractSelectionList.tsx` — renders the full contracts list as a selectable list; attaches `onKeyDown` for Cmd+A; passes `handleModifierClick` and `handleShiftClick` to each `ContractListItem`; shows bulk action toolbar (contract count + action buttons) when `selectedIds.size > 0`
|
|
70
|
+
- [ ] T017 [US2] Integrate `useContractSelection` and `ContractSelectionList` into `src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx` — replace plain list with `ContractSelectionList`; bulk toolbar appears when selection is non-empty
|
|
71
|
+
|
|
72
|
+
**Checkpoint**: US2 complete — multi-select UI is functional for all subsequent admin actions.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Phase 5: User Story 3 — Admin: Add Contracts (Priority: P2)
|
|
77
|
+
|
|
78
|
+
**Goal**: An admin can open an add-contracts drawer, select one, multiple, or all available contracts (with optional child-unit propagation), confirm, and see the new contracts in the listing.
|
|
79
|
+
|
|
80
|
+
**Independent Test**: As admin, click "Add contracts" → drawer opens with available contracts; select one and confirm → contract appears in listing; toggle "Apply to children" and confirm → child units also show the contract.
|
|
81
|
+
|
|
82
|
+
- [ ] T018 [US3] Create `src/features/contracts/hooks/useAddContracts.ts` — wraps `addContractsService`; on success triggers contract list refetch (invalidate query or call `router.replace(router.asPath)`)
|
|
83
|
+
- [ ] T019 [US3] Create `src/features/contracts/components/AddContractsDrawer/AddContractsDrawer.tsx` — drawer component using `@faststore/ui` Drawer or Modal pattern; fetches available contracts not yet in the unit; supports individual/multiple selection and "Add all" shortcut; includes propagate-to-children toggle; confirm calls `useAddContracts`; cancel closes without changes
|
|
84
|
+
- [ ] T020 [US3] Wire `AddContractsDrawer` into `src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx` — show "Add contracts" button for admin users only (gate on user role from `currentUser`); open drawer on click; refresh contract list on successful addition
|
|
85
|
+
|
|
86
|
+
**Checkpoint**: US3 complete — admins can add contracts to the org unit.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Phase 6: User Story 4 — Admin: Remove Contracts (Priority: P2)
|
|
91
|
+
|
|
92
|
+
**Goal**: An admin can remove individual or multiple selected contracts (bulk). The remove option is hidden when only 1 contract remains. The action is blocked if it would leave 0 contracts.
|
|
93
|
+
|
|
94
|
+
**Independent Test**: With 2+ contracts, select one and remove → no longer in listing; with 1 contract → remove option invisible; attempt to remove all → UI blocks with message.
|
|
95
|
+
|
|
96
|
+
- [ ] T021 [US4] Create `src/features/contracts/hooks/useRemoveContracts.ts` — wraps `removeContractsService`; on success triggers list refetch and clears selection
|
|
97
|
+
- [ ] T022 [US4] Create `src/features/contracts/components/RemoveContractsDrawer/RemoveContractsDrawer.tsx` — confirmation drawer; lists contracts to be removed; shows warning if action would leave fewer than 1 (guard before calling service); includes propagate-to-children toggle; confirm calls `useRemoveContracts`
|
|
98
|
+
- [ ] T023 [US4] Wire `RemoveContractsDrawer` into `src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx` — hide "Remove" button entirely when `contracts.length === 1`; show in bulk action toolbar when `selectedIds.size > 0` and `contracts.length > 1`; guard: disable confirm if `contracts.length - selectedIds.size < 1`
|
|
99
|
+
|
|
100
|
+
**Checkpoint**: US4 complete — admins can safely remove contracts with appropriate guards.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Phase 7: User Story 5 — Admin: Set Default Contract (Priority: P2)
|
|
105
|
+
|
|
106
|
+
**Goal**: An admin can mark any contract as the default from the listing. The default contract gains a visual indicator; the previous default loses it.
|
|
107
|
+
|
|
108
|
+
**Independent Test**: As admin, trigger "Set as default" on a non-default contract → default badge moves to that contract; re-trigger on original → badge moves back.
|
|
109
|
+
|
|
110
|
+
- [ ] T024 [US5] Create `src/features/contracts/hooks/useSetDefaultContract.ts` — wraps `setDefaultContractService`; on success optimistically updates `isDefault` in local contract list state (or triggers refetch)
|
|
111
|
+
- [ ] T025 [P] [US5] Create `src/features/contracts/components/SetDefaultContractAction/SetDefaultContractAction.tsx` — a `DropdownItem` (admin-only) rendered inside each `ContractListItem`'s action dropdown; hidden on the current default contract; calls `useSetDefaultContract` on click
|
|
112
|
+
- [ ] T026 [US5] Integrate `SetDefaultContractAction` into `src/features/contracts/components/ContractListItem/ContractListItem.tsx` — add admin-gated action dropdown with `SetDefaultContractAction`; ensure `isDefault` prop drives the visual badge reactively
|
|
113
|
+
|
|
114
|
+
**Checkpoint**: US5 complete — admins can manage the default contract designation.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Phase 8: User Story 6 — Navigation: Org Unit Hierarchy (Priority: P3)
|
|
119
|
+
|
|
120
|
+
**Goal**: Users can navigate up to parent org units via the Org Unit selector in the Contract Listing, and down to child org units via the Org Units page.
|
|
121
|
+
|
|
122
|
+
**Independent Test**: In a child unit's Contract Listing, use breadcrumb/selector to navigate to parent → URL changes to parent's `/contracts/{parentOrgUnitId}`; from Org Units page, click child → navigates to child's Contract Listing.
|
|
123
|
+
|
|
124
|
+
- [ ] T027 [US6] Integrate `OrgUnitBreadcrumb` (from `src/features/org-units/components/OrgUnitBreadcrumb/`) into `src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx` — pass `orgUnit` and `currentOrgUnit` from page loader data; breadcrumb links use `buyerPortalRoutes.contracts({ orgUnitId })` for each ancestor
|
|
125
|
+
- [ ] T028 [US6] Verify `src/pages/org-units.tsx` and its layout — ensure the "navigate to child unit" action links to `buyerPortalRoutes.contracts({ orgUnitId: childId })` when `enableSumaPhase2` is active; add feature-flag-conditional navigation so flag-off behavior (linking to `orgUnitDetails`) is preserved
|
|
126
|
+
|
|
127
|
+
**Checkpoint**: US6 complete — full org unit hierarchy navigation functional.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Phase 9: User Story 7 — Cross-Tab Contract State Sync (Priority: P3)
|
|
132
|
+
|
|
133
|
+
**Goal**: When the user activates a contract in Tab A, Tab B automatically refreshes to reflect the new active contract within 2 seconds.
|
|
134
|
+
|
|
135
|
+
**Independent Test**: Open Contract Listing in two tabs; activate a contract in Tab A; within 2 seconds, Tab B reloads and shows the updated active contract without manual refresh.
|
|
136
|
+
|
|
137
|
+
- [ ] T029 [US7] Create `src/features/contracts/hooks/useCrossTabContractSync.ts` — on mount, opens a `BroadcastChannel('buyer-portal-active-contract')`; listens for `{ type: 'CONTRACT_ACTIVATED', contractId, orgUnitId }` messages and calls `router.replace(router.asPath)` to refresh; exposes `broadcastActivation(contractId, orgUnitId)` to emit after a successful activation; closes channel on unmount
|
|
138
|
+
- [ ] T030 [US7] Integrate `useCrossTabContractSync` into `src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx` — mount the hook; call `broadcastActivation` inside `useActivateContract`'s onSuccess callback
|
|
139
|
+
|
|
140
|
+
**Checkpoint**: US7 complete — cross-tab contract state is consistent.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Phase 10: Polish & Cross-Cutting Concerns
|
|
145
|
+
|
|
146
|
+
**Purpose**: Minimum test coverage required by Constitution Principle IV, final validation.
|
|
147
|
+
|
|
148
|
+
- [ ] T031 [P] Add unit test for `useActivateContract` in `src/features/contracts/hooks/__tests__/useActivateContract.test.ts` — cover: success path (calls service + navigates), error path (surfaces error without navigating), loading state during call
|
|
149
|
+
- [ ] T032 [P] Add unit test for `useContractSelection` in `src/features/contracts/hooks/__tests__/useContractSelection.test.ts` — cover: `handleSelectAll`, `handleModifierClick` toggle, `handleShiftClick` range, `clearSelection`, empty state
|
|
150
|
+
- [ ] T033 [P] Add integration test for `activate-contract.service.ts` happy path in `src/features/contracts/services/__tests__/activate-contract.service.test.ts` — mock BFF response, assert correct endpoint and headers called
|
|
151
|
+
- [ ] T034 Run `make check` (lint + typecheck + test) from repo root and fix all failures before opening PR
|
|
152
|
+
- [ ] T035 Run quickstart.md validation checklist (`specs/001-suma-phase-2/quickstart.md`) manually against `b2bfaststoredev.store` — mark all 18 items complete before requesting review
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Dependencies & Execution Order
|
|
157
|
+
|
|
158
|
+
### Phase Dependencies
|
|
159
|
+
|
|
160
|
+
- **Setup (Phase 1)**: No dependencies — start immediately
|
|
161
|
+
- **Foundational (Phase 2)**: Requires Phase 1 — **BLOCKS all user stories**
|
|
162
|
+
- **US1–US5 (Phases 3–7)**: All require Foundational; can run in parallel across developers
|
|
163
|
+
- **US6 (Phase 8)**: Requires Phase 3 (US1) — Contract Listing must exist before adding breadcrumb
|
|
164
|
+
- **US7 (Phase 9)**: Requires Phase 3 (US1) — hook integrates into Contract Listing Layout
|
|
165
|
+
- **Polish (Phase 10)**: Requires all desired user stories complete
|
|
166
|
+
|
|
167
|
+
### User Story Dependencies
|
|
168
|
+
|
|
169
|
+
| Story | Depends on | Can run in parallel with |
|
|
170
|
+
| -------- | -------------------------------------- | ------------------------ |
|
|
171
|
+
| US1 (P1) | Foundational | US2, US3, US4, US5 |
|
|
172
|
+
| US2 (P2) | Foundational | US1, US3, US4, US5 |
|
|
173
|
+
| US3 (P2) | Foundational | US1, US2, US4, US5 |
|
|
174
|
+
| US4 (P2) | Foundational | US1, US2, US3, US5 |
|
|
175
|
+
| US5 (P2) | Foundational | US1, US2, US3, US4 |
|
|
176
|
+
| US6 (P3) | US1 (ContractListingLayout must exist) | US7 |
|
|
177
|
+
| US7 (P3) | US1 (ContractListingLayout must exist) | US6 |
|
|
178
|
+
|
|
179
|
+
### Within Each User Story
|
|
180
|
+
|
|
181
|
+
- Hooks → Components → Layout integration (strict order within a story)
|
|
182
|
+
- Exception: [P]-marked tasks within a story can run in parallel
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Parallel Example: Foundational Phase (Phase 2)
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
Start T003 (type extension — no deps)
|
|
190
|
+
|
|
191
|
+
Then in parallel:
|
|
192
|
+
T004: Feature flag wiring (BuyerPortalProvider.tsx + withAuthLoader.ts)
|
|
193
|
+
T005: ContractsClient.ts new methods (4 BFF methods)
|
|
194
|
+
T006: activate-contract.service.ts
|
|
195
|
+
T007: add/remove/set-default services (3 files)
|
|
196
|
+
|
|
197
|
+
Then:
|
|
198
|
+
T008: index.ts exports + home.tsx redirect logic (depends on T005, T006, T007)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Parallel Example: User Story 1 (Phase 3)
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
Start T009 (useActivateContract hook — no deps in this phase)
|
|
205
|
+
|
|
206
|
+
In parallel with T009:
|
|
207
|
+
T010: ContractListItem component (no deps in this phase)
|
|
208
|
+
T013: ContractInformationLayout (no deps on T009–T012)
|
|
209
|
+
|
|
210
|
+
Then:
|
|
211
|
+
T011: ContractListingLayout (depends on T009, T010)
|
|
212
|
+
|
|
213
|
+
Then:
|
|
214
|
+
T012: contracts.tsx page (depends on T011)
|
|
215
|
+
T014: contract-information.tsx page (depends on T013)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Implementation Strategy
|
|
221
|
+
|
|
222
|
+
### MVP First (User Story 1 Only)
|
|
223
|
+
|
|
224
|
+
1. Complete Phase 1: Setup (T001–T002)
|
|
225
|
+
2. Complete Phase 2: Foundational (T003–T008) — **critical blocker**
|
|
226
|
+
3. Complete Phase 3: US1 (T009–T014)
|
|
227
|
+
4. **STOP and VALIDATE**: Run quickstart.md US-1 section against `b2bfaststoredev.store`
|
|
228
|
+
5. Demo to stakeholders — flag-off regression test passes
|
|
229
|
+
|
|
230
|
+
### Incremental Delivery
|
|
231
|
+
|
|
232
|
+
1. Setup + Foundational → Foundation shipped
|
|
233
|
+
2. US1 → Contract Listing + Switch (MVP — core SUMA Phase 2 value)
|
|
234
|
+
3. US2 → Multi-select (enables bulk admin workflows)
|
|
235
|
+
4. US3 + US4 + US5 → Full admin management (add/remove/default — can be one PR)
|
|
236
|
+
5. US6 + US7 → Navigation hierarchy + cross-tab sync (polish for power users)
|
|
237
|
+
|
|
238
|
+
### Parallel Team Strategy (3 developers after Foundational)
|
|
239
|
+
|
|
240
|
+
- **Dev A**: US1 (P1 core) → US6 (P3 nav hierarchy)
|
|
241
|
+
- **Dev B**: US2 (P2 multi-select) → US3 (P2 add contracts)
|
|
242
|
+
- **Dev C**: US4 (P2 remove) → US5 (P2 set default) → US7 (P3 cross-tab)
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Notes
|
|
247
|
+
|
|
248
|
+
- `[P]` tasks touch different files — safe to parallelise
|
|
249
|
+
- `[Story]` label maps each task to its user story for JIRA epic/story linking
|
|
250
|
+
- Each phase checkpoint is a valid demo/deploy milestone
|
|
251
|
+
- `make check` must exit 0 before any PR — run it at each checkpoint
|
|
252
|
+
- Flag-off regression (feature flag disabled → original navigation) must pass at every checkpoint
|
|
253
|
+
- Total: **35 tasks** across 10 phases
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# SUMA: Add Contracts to Organizational Unit
|
|
2
|
+
|
|
3
|
+
> **Status**: Done
|
|
4
|
+
> **Created**: 2026-06-22
|
|
5
|
+
|
|
6
|
+
## 1. Business Context
|
|
7
|
+
|
|
8
|
+
### Problem Statement
|
|
9
|
+
|
|
10
|
+
Admins managing a Buyer Organization need to expand the pool of contracts available to their Organizational Unit (OU). Contracts exist at the root unit scope; they are not automatically available to every sub-unit. Without an "Add Contracts" UI, admins have no self-service way to attach relevant contracts to their OU, blocking buyers from switching to contracts their unit should have access to.
|
|
11
|
+
|
|
12
|
+
### Goals
|
|
13
|
+
|
|
14
|
+
- Users can add one or more contracts from the available pool to their OU in under 5 seconds.
|
|
15
|
+
- The new contracts appear immediately in the Contract Listing without a full page reload.
|
|
16
|
+
|
|
17
|
+
### User Stories
|
|
18
|
+
|
|
19
|
+
#### US-1: Add Available Contracts to an Organizational Unit
|
|
20
|
+
|
|
21
|
+
- **Story**: As a user, I want to select contracts from the available pool and add them to my Organizational Unit, so that buyers in that unit can switch to those contracts.
|
|
22
|
+
- **Acceptance Criteria**:
|
|
23
|
+
- **Given** an admin is on the Contract Listing page, **when** they click "Add contracts", **then** a drawer opens showing all contracts available to the OU (not yet attached).
|
|
24
|
+
- **Given** the drawer is open, **when** the admin selects one or more contracts and clicks "Add", **then** the selected contracts are added to the OU and appear in the Contract Listing.
|
|
25
|
+
- **Given** no contracts are selected, **when** the drawer footer is rendered, **then** the "Add" button is disabled.
|
|
26
|
+
- **Given** the addition is in progress, **when** the BFF call is pending, **then** the drawer shows a loading state and all interactions are disabled.
|
|
27
|
+
- **Given** the BFF call fails, **when** the drawer returns from the loading state, **then** an error toast is shown on the listing page and the drawer closes without modifying the list.
|
|
28
|
+
|
|
29
|
+
#### US-2: Search and Paginate Available Contracts
|
|
30
|
+
|
|
31
|
+
- **Story**: As a user, I want to search and paginate through the available contracts list, so that I can efficiently find specific contracts when the available pool is large.
|
|
32
|
+
- **Acceptance Criteria**:
|
|
33
|
+
- **Given** the drawer is open, **when** the admin types in the search field, **then** the list filters to contracts whose names contain the query (case-insensitive) and the page resets to 1.
|
|
34
|
+
- **Given** the filtered list has more than 25 results, **when** the drawer renders, **then** pagination controls appear showing the current range and allowing navigation.
|
|
35
|
+
- **Given** the admin presses Cmd+A or Ctrl+A while the search field is not focused, **when** the drawer is open, **then** all contracts in the filtered list are selected.
|
|
36
|
+
- **Given** the admin Shift+Clicks a contract, **when** a previous contract was already clicked, **then** all contracts between the two (in displayed order) are selected as a range.
|
|
37
|
+
|
|
38
|
+
### Key Scenarios
|
|
39
|
+
|
|
40
|
+
| Scenario | Pre-conditions | Steps | Expected Result |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| Happy path — add single contract | User on Contract Listing; OU has available contracts | Open drawer → select 1 contract → click Add | Contract added via BFF; drawer closes; contract appears in listing; success toast shown |
|
|
43
|
+
| Happy path — add all available | User on Contract Listing; multiple available contracts | Open drawer → click "Select all" → click Add | All available contracts added; listing refreshes |
|
|
44
|
+
| BFF error | BFF returns 5xx | Open drawer → select contract → confirm | Drawer closes with error state; error toast shown on listing; listing unchanged |
|
|
45
|
+
| Empty available pool | All root-unit contracts already attached | Open drawer | Drawer shows empty state: "No contracts available to add" |
|
|
46
|
+
| Search with no results | Available contracts exist | Type a query matching nothing | Empty state message shown; "Select all" shows "(0)"; Add button disabled |
|
|
47
|
+
| Drawer closed mid-flow | Admin has selected contracts | Click backdrop or close button | Drawer closes; no BFF call made; listing unchanged |
|
|
48
|
+
|
|
49
|
+
### Functional Requirements
|
|
50
|
+
|
|
51
|
+
- **FR-1**: The drawer MUST load the list of available contracts (not yet attached) from the BFF when opened.
|
|
52
|
+
- **FR-2**: Available contracts MUST be displayed with their name; if a name is not available, fall back to the contract ID.
|
|
53
|
+
- **FR-3**: The user MUST be able to select any combination of available contracts via individual click, Shift+Click (range), and Cmd+A / Ctrl+A (select all in filtered view).
|
|
54
|
+
- **FR-4**: The drawer MUST include a text search that filters the list client-side by contract name.
|
|
55
|
+
- **FR-5**: The list MUST paginate client-side at 25 items per page when the filtered list exceeds 25 entries.
|
|
56
|
+
- **FR-6**: On confirmation, the selected IDs MUST be sent to `addContractsService`.
|
|
57
|
+
- **FR-7**: While the BFF call is in progress, the drawer MUST disable all interactions and show a loading indicator on the "Add" button.
|
|
58
|
+
- **FR-8**: On success, the drawer MUST close and the Contract Listing MUST refresh to include the newly added contracts.
|
|
59
|
+
- **FR-9**: On BFF failure, the drawer MUST close, and an error toast MUST appear on the Contract Listing.
|
|
60
|
+
|
|
61
|
+
### Non-Functional Requirements
|
|
62
|
+
|
|
63
|
+
- BFF call end-to-end MUST complete within 5 seconds.
|
|
64
|
+
- Loading indicator MUST appear within 500ms of clicking "Add".
|
|
65
|
+
- The drawer animation MUST follow the same slide-in-from-right pattern used elsewhere in the plugin (`cubic-bezier(0.16, 1, 0.3, 1)`, 550ms).
|
|
66
|
+
- The feature MUST be gated behind the `suma` feature flag — no entry point is rendered when the flag is off.
|
|
67
|
+
- TypeScript strict mode: zero `any` usage, zero `tsc --noEmit` errors.
|
|
68
|
+
|
|
69
|
+
### Out of Scope
|
|
70
|
+
|
|
71
|
+
- Creating new contracts (contracts originate in the root unit's pool, managed outside this plugin).
|
|
72
|
+
- Adding contracts to multiple OUs simultaneously from a single UI action (propagation covers the hierarchy case).
|
|
73
|
+
- Editing or renaming contracts.
|
|
74
|
+
- Propagating contract additions to child organizational units.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 2. Arch Decisions
|
|
79
|
+
|
|
80
|
+
### Proposed Solution
|
|
81
|
+
|
|
82
|
+
A new `AddContractsDrawer` component (controlled open/close) is added under `src/features/contracts/components/AddContractsDrawer/`. A companion `useAddContracts` hook handles the mutation. Both are wired into `ContractListingLayout`, which already owns the admin-gated toolbar. The drawer fetches available contracts when it opens (lazy load) using the existing `listAvailableContractsService`.
|
|
83
|
+
|
|
84
|
+
### Architecture Overview
|
|
85
|
+
|
|
86
|
+
```mermaid
|
|
87
|
+
sequenceDiagram
|
|
88
|
+
participant User
|
|
89
|
+
participant ContractListingLayout
|
|
90
|
+
participant AddContractsDrawer
|
|
91
|
+
participant useAddContracts
|
|
92
|
+
participant BFF
|
|
93
|
+
|
|
94
|
+
User->>ContractListingLayout: click "Add contracts"
|
|
95
|
+
ContractListingLayout->>AddContractsDrawer: isOpen=true
|
|
96
|
+
AddContractsDrawer->>BFF: GET /units/{orgUnitId}/contracts/available
|
|
97
|
+
BFF-->>AddContractsDrawer: { contracts: AvailableContract[] }
|
|
98
|
+
User->>AddContractsDrawer: select contracts, click Add
|
|
99
|
+
AddContractsDrawer->>useAddContracts: addContracts(ids)
|
|
100
|
+
useAddContracts->>BFF: POST /units/{orgUnitId}/contracts/attached
|
|
101
|
+
BFF-->>useAddContracts: 200 OK
|
|
102
|
+
useAddContracts-->>AddContractsDrawer: success
|
|
103
|
+
AddContractsDrawer->>ContractListingLayout: onSuccess()
|
|
104
|
+
ContractListingLayout->>BFF: reload contract list
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Alternatives Considered
|
|
108
|
+
|
|
109
|
+
| Alternative | Pros | Cons | Verdict |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| Modal (full-screen overlay) instead of drawer | Familiar pattern | Inconsistent with other admin flows in the plugin (ConfirmRemoveDrawer, ContractAccountDrawer) | Rejected — drawer is prototype-validated |
|
|
112
|
+
| Server-side pagination | Handles very large lists | Extra API call per page; BFF doesn't support cursor/offset on this endpoint | Rejected — client-side pagination sufficient given typical OU pool size |
|
|
113
|
+
| Fetch available contracts in page loader | Data available immediately | Loads data even when admin never opens drawer; increases page load time | Rejected — lazy fetch on drawer open preferred |
|
|
114
|
+
| Inline list on Contract Listing (no drawer) | Fewer interactions | Complex layout conflict with selection list; prototype uses drawer | Rejected |
|
|
115
|
+
|
|
116
|
+
### Risks & Mitigations
|
|
117
|
+
|
|
118
|
+
| Risk | Impact | Likelihood | Mitigation |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| BFF `listAvailableContracts` returns only IDs, not names | High — UI shows meaningless labels | High — confirmed by current API contract | Coordinate with BFF team to return `{ contracts: [{ id, name }] }` before implementation; fall back to ID display in interim |
|
|
121
|
+
| BFF `addContracts` doesn't support `propagateToChildren` flag | Med — US-2 not deliverable | Med — v1.0.2 API contract doesn't document this param | BFF team must confirm or add the param; US-2 is blocked until confirmed |
|
|
122
|
+
|
|
123
|
+
### Key Decisions
|
|
124
|
+
|
|
125
|
+
#### Decision 1: Lazy-load Available Contracts on Drawer Open
|
|
126
|
+
|
|
127
|
+
- **Status**: Accepted
|
|
128
|
+
- **Context**: Available contracts are only needed when the admin opens the drawer, which is not guaranteed on every page visit.
|
|
129
|
+
- **Decision**: Fetch `listAvailableContractsService` inside the drawer (via `useEffect` on `isOpen`) rather than in the page loader.
|
|
130
|
+
- **Consequences**: Slight delay on first open (one extra BFF call); avoids loading data for every admin visiting the listing.
|
|
131
|
+
|
|
132
|
+
#### Decision 2: Client-Side Search and Pagination
|
|
133
|
+
|
|
134
|
+
- **Status**: Accepted
|
|
135
|
+
- **Context**: `listAvailableContracts` returns the full set of available IDs at once. BFF does not support server-side filtering/pagination on this endpoint.
|
|
136
|
+
- **Decision**: Hold the full available list in component state; filter and paginate with `useMemo` (25 items per page), matching the prototype implementation.
|
|
137
|
+
- **Consequences**: Works well for typical OU pool sizes (< 100 contracts). If future pool sizes grow into the thousands, client-side filtering may need revisiting.
|
|
138
|
+
|
|
139
|
+
#### Decision 3: Contract Name Resolution Dependency on BFF Upgrade
|
|
140
|
+
|
|
141
|
+
- **Status**: Pending BFF team confirmation
|
|
142
|
+
- **Context**: The current BFF `listAvailableContracts` endpoint returns `{ contractIds: string[] }` — IDs only. The UI prototype shows contract names, which requires an upgrade.
|
|
143
|
+
- **Decision**: Block implementation of the name column until BFF confirms the endpoint will return `{ contracts: Array<{ id: string; name: string }> }`. The service layer (`listAvailableContractsService`) will be updated accordingly.
|
|
144
|
+
- **Consequences**: Implementation is blocked on this BFF change. The fallback (display ID) is acceptable only for internal testing, not for release.
|
|
145
|
+
|
|
146
|
+
### Implementation Plan
|
|
147
|
+
|
|
148
|
+
1. **BFF coordination** (blocker): Confirm `listAvailableContracts` returns names; confirm `propagateToChildren` support.
|
|
149
|
+
2. **Update `listAvailableContractsService`** to handle new response shape `{ contracts: AvailableContract[] }`.
|
|
150
|
+
3. **Create `useAddContracts` hook** — wraps `addContractsService`, exposes `{ addContracts, isLoading, hasError }`.
|
|
151
|
+
4. **Create `AddContractsDrawer` component** — implements full drawer UI from prototype spec.
|
|
152
|
+
5. **Wire into `ContractListingLayout`** — add "Add contracts" button; pass `onSuccess` to refresh listing.
|
|
153
|
+
6. **Unit test `useAddContracts`** — success, error, loading state.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 3. Technical Contract
|
|
158
|
+
|
|
159
|
+
### Data Models
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// Available contract as returned by the upgraded BFF endpoint
|
|
163
|
+
interface AvailableContract {
|
|
164
|
+
id: string
|
|
165
|
+
name: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Props for AddContractsDrawer
|
|
169
|
+
interface AddContractsDrawerProps {
|
|
170
|
+
isOpen: boolean
|
|
171
|
+
onClose: () => void
|
|
172
|
+
orgUnitId: string
|
|
173
|
+
orgUnitName: string
|
|
174
|
+
existingContractIds: string[] // IDs already attached — excluded from available list
|
|
175
|
+
onSuccess: () => void // called after successful addition (triggers listing refresh)
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Interfaces
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// useAddContracts hook
|
|
183
|
+
interface UseAddContractsReturn {
|
|
184
|
+
addContracts: (params: {
|
|
185
|
+
orgUnitId: string
|
|
186
|
+
ids: string[]
|
|
187
|
+
cookie: string
|
|
188
|
+
}) => Promise<void>
|
|
189
|
+
isLoading: boolean
|
|
190
|
+
hasError: boolean
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Updated listAvailableContractsService signature (after BFF upgrade)
|
|
194
|
+
function listAvailableContractsService(params: {
|
|
195
|
+
orgUnitId: string
|
|
196
|
+
cookie: string
|
|
197
|
+
}): Promise<AvailableContract[]>
|
|
198
|
+
|
|
199
|
+
// addContractsService
|
|
200
|
+
function addContractsService(params: {
|
|
201
|
+
orgUnitId: string
|
|
202
|
+
ids: string[]
|
|
203
|
+
cookie: string
|
|
204
|
+
}): Promise<unknown>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Integration Points
|
|
208
|
+
|
|
209
|
+
| Point | Direction | Details |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| `listAvailableContractsService` | Read | Fetches available contracts on drawer open; response shape upgrade required |
|
|
212
|
+
| `addContractsService` | Write | Called on confirm; currently at `POST units/{orgUnitId}/contracts/attached` |
|
|
213
|
+
| `ContractListingLayout` | Parent | Controls `isOpen`; calls `router.replace(router.asPath)` via `onSuccess` to refresh |
|
|
214
|
+
| `useContractSwitchChannel` | None | Not involved — adding contracts does not trigger cross-tab sync |
|
|
215
|
+
| Feature flag `suma` | Gate | `AddContractsDrawer` entry point is only rendered when `featureFlags.suma === true` |
|
|
216
|
+
|
|
217
|
+
### Invariants & Constraints
|
|
218
|
+
|
|
219
|
+
- The "Add" button MUST be disabled when `selectedIds.size === 0`.
|
|
220
|
+
- The drawer MUST disable all interactions (search, list, toggles, buttons) while `isLoading === true`.
|
|
221
|
+
- The available list MUST exclude contracts already in `existingContractIds`.
|
|
222
|
+
- Clicking the backdrop while `isLoading` is true MUST NOT close the drawer.
|
|
223
|
+
- On close (backdrop click or cancel button), no BFF call is made and the listing is unchanged.
|
|
224
|
+
- Selection state, search query, and current page reset to their defaults each time the drawer closes.
|