@vtex/faststore-plugin-buyer-portal 2.0.5 → 2.0.6
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/CHANGELOG.md +8 -1
- package/package.json +1 -1
- package/specs/refactor-add-user-form.md +371 -0
- package/src/features/org-units/components/AuthSetupDrawer/AuthSetupDrawer.tsx +3 -103
- package/src/features/org-units/types/OrgUnitSettings.ts +0 -12
- package/src/features/org-units/types/index.ts +1 -1
- package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +2 -2
- package/src/features/shared/utils/constants.ts +2 -2
- package/src/features/users/clients/UsersClient.ts +10 -6
- package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +2 -2
- package/src/features/users/components/CreateUserDrawerWithUsername/CreateUserDrawerWithUsername.tsx +112 -380
- package/src/features/users/components/CreateUserDrawerWithUsername/create-user-drawer-with-username.scss +1 -0
- package/src/features/users/components/EmailTransactionalSection/EmailTransactionalSection.tsx +69 -0
- package/src/features/users/components/LoginField/LoginField.tsx +54 -0
- package/src/features/users/components/LoginField/__tests__/LoginField.test.ts +89 -0
- package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +2 -2
- package/src/features/users/components/UpdateUserDrawerWithUsername/UpdateUserDrawerWithUsername.tsx +132 -211
- package/src/features/users/components/UpdateUserDrawerWithUsername/update-user-drawer-with-username.scss +1 -0
- package/src/features/users/components/UserFormFields/UserFormFields.tsx +253 -0
- package/src/features/users/components/UserFormFields/user-form-fields.scss +13 -0
- package/src/features/users/hooks/useAddUserToOrgUnit.ts +33 -10
- package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +4 -4
- package/src/features/users/mocks/users-data.ts +4 -2
- package/src/features/users/services/add-user-to-org-unit.service.ts +51 -12
- package/src/features/users/services/get-user-by-id.service.ts +13 -3
- package/src/features/users/services/get-users-by-org-unit-id.service.ts +3 -2
- package/src/features/users/services/index.ts +1 -0
- package/src/features/users/services/update-user.service.ts +6 -3
- package/src/features/users/types/UserData.ts +4 -2
- package/src/features/users/types/UserDataService.ts +2 -0
- package/src/features/users/utils/__tests__/detectLoginType.test.ts +79 -0
- package/src/features/users/utils/detectLoginType.ts +36 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.0.6] - 2026-06-26
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Refactor Add/Edit User form (unified login): replace username-centric fields with a single `login` field that dynamically adapts UI per type (email/phone/username), remove `generateUsernameSuggestion` email→username transform, update User List to show `login` as primary column, and remove the "User identification" section from `AuthSetupDrawer`
|
|
15
|
+
|
|
10
16
|
## [2.0.5] - 2026-06-17
|
|
11
17
|
|
|
12
18
|
### Fixed
|
|
@@ -702,7 +708,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
702
708
|
- Add CHANGELOG file
|
|
703
709
|
- Add README file
|
|
704
710
|
|
|
705
|
-
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.
|
|
711
|
+
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.6...HEAD
|
|
706
712
|
[1.3.55]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.54...v1.3.55
|
|
707
713
|
[1.3.54]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.53...v1.3.54
|
|
708
714
|
[1.3.53]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.52...v1.3.53
|
|
@@ -788,6 +794,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
788
794
|
[1.3.85]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.85
|
|
789
795
|
[1.3.87]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.86...v1.3.87
|
|
790
796
|
[1.3.86]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.86
|
|
797
|
+
[2.0.6]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.5...v2.0.6
|
|
791
798
|
[2.0.5]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.4...v2.0.5
|
|
792
799
|
[2.0.4]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.3...v2.0.4
|
|
793
800
|
[2.0.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v2.0.2...v2.0.3
|
package/package.json
CHANGED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Refactor Add/Edit User Form — Unified Login
|
|
2
|
+
|
|
3
|
+
> **Status**: Approved
|
|
4
|
+
> **Created**: 2026-05-22
|
|
5
|
+
> **Implementation PR base branch**: `refactor/add-user-form`
|
|
6
|
+
> **Implementation PR labels**: `skip-changelog`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Business Context
|
|
11
|
+
|
|
12
|
+
### Problem Statement
|
|
13
|
+
|
|
14
|
+
The add/edit user form in Org Account (flow `enableAlternativeLogin = true`, components `CreateUserDrawerWithUsername` and `UpdateUserDrawerWithUsername`) treats **Username as the mandatory primary identifier**, which contradicts the current model where any credential type (email, phone, username) can be the user's login.
|
|
15
|
+
|
|
16
|
+
There is also an active bug: when an email address is typed in the "Email" field, a `useEffect` extracts the local-part (everything before `@`) and automatically fills the "Username" field with that value — with no visual indication to the admin. The persisted value is therefore different from what the administrator typed or intended to use as the identifier, causing confusion and potentially breaking access for the registered user.
|
|
17
|
+
|
|
18
|
+
Other problems stemming from the current model:
|
|
19
|
+
|
|
20
|
+
- The "User identification" section in `AuthSetupDrawer` shows toggles for Username/Email/Phone, but "Username" is always locked as required — semantically inconsistent with the new unified Login reality.
|
|
21
|
+
- The User List displays "Username" as the primary column and search operates on the `name` field in the backend, not on the user's login identifier.
|
|
22
|
+
- UI fields and behaviours (e.g. "username suggestions", async username uniqueness validation) no longer make sense with the unified Login model.
|
|
23
|
+
|
|
24
|
+
### Goals
|
|
25
|
+
|
|
26
|
+
1. Make **Login** the only required field in the add/edit user form; Full Name becomes optional.
|
|
27
|
+
2. Implement **dynamic UI behaviour** based on the detected login type (email / phone / username) without persisting that inference.
|
|
28
|
+
3. Eliminate the **automatic email → username transformation** and any other implicit transformation of the typed value.
|
|
29
|
+
4. Preserve the **exact value typed** in the Login field (no forced lowercase, aggressive trimming, or domain removal).
|
|
30
|
+
5. Update the **User List** to display Login as the primary identifier, with search and sorting operating on that field.
|
|
31
|
+
6. Clean up the **AuthSetupDrawer** by removing the "User identification" section entirely — visual, state, payload field, and type definition.
|
|
32
|
+
|
|
33
|
+
### User Stories
|
|
34
|
+
|
|
35
|
+
#### US-1: Create user with email login
|
|
36
|
+
|
|
37
|
+
- **Story**: As an organisation administrator, I want to register a new user using an email address as login, so that the user can access the portal with their email credentials.
|
|
38
|
+
- **Acceptance Criteria**:
|
|
39
|
+
- **Given** the "Add User" drawer is open, **when** the admin fills the Login field with a value containing `@` (valid email), **then** the UI displays a "Use this email for transactional notifications" checkbox checked by default.
|
|
40
|
+
- **Given** the "Use this email for transactional notifications" checkbox is checked, **when** the admin submits the form, **then** the payload sent to the API contains `login = <typed value>` and `transactionalEmail = <same value>`.
|
|
41
|
+
- **Given** the checkbox is unchecked, **when** the admin does not fill the additional "Transactional Email" field, **then** the submit button remains disabled and an error message is shown in the field.
|
|
42
|
+
- **Given** the checkbox is unchecked and the "Transactional Email" field was filled with a valid email, **when** the admin submits, **then** the payload contains `login = <typed value>`, `transactionalEmail = <transactional email>`.
|
|
43
|
+
- **Given** login is email, **when** the UI renders, **then** the "Username" field is **not** displayed.
|
|
44
|
+
- **Given** login is email, **when** the UI renders, **then** an optional phone field is displayed.
|
|
45
|
+
|
|
46
|
+
#### US-2: Create user with phone login
|
|
47
|
+
|
|
48
|
+
- **Story**: As an organisation administrator, I want to register a user using a phone number as login, so that the user can authenticate via SMS/code.
|
|
49
|
+
- **Acceptance Criteria**:
|
|
50
|
+
- **Given** the Login field contains only digits, `+`, spaces, `(`, `)` or `-` in an E.164-compatible format, **when** the UI detects the type, **then** it classifies as `phone` and displays an optional email field.
|
|
51
|
+
- **Given** login is phone and no additional email was provided, **when** the UI renders, **then** it displays the warning: _"If no email is provided, only an administrator can recover access (no SMS recovery available)"_.
|
|
52
|
+
- **Given** login is phone, **when** the UI renders, **then** the optional email field is displayed (not required by default).
|
|
53
|
+
- **Given** login is phone, **when** the UI renders, **then** the "Additional Phone" field is **not** displayed (login is already the phone).
|
|
54
|
+
|
|
55
|
+
#### US-3: Create user with username login
|
|
56
|
+
|
|
57
|
+
- **Story**: As an organisation administrator, I want to register a user with an arbitrary username as login, so that the portal supports identifiers not tied to email or phone.
|
|
58
|
+
- **Acceptance Criteria**:
|
|
59
|
+
- **Given** the Login field does not contain `@` and does not match the phone pattern, **when** the UI detects the type, **then** it classifies as `username`.
|
|
60
|
+
- **Given** login is username, **when** the UI renders, **then** it displays an optional email field, an optional phone field, and the recovery warning: _"If no email is provided, only an administrator can recover access (no SMS recovery available)"_.
|
|
61
|
+
- **Given** login is username, **when** the admin types the value, **then** the value is preserved exactly (no lowercase, no domain removal).
|
|
62
|
+
|
|
63
|
+
#### US-4: Edit existing user
|
|
64
|
+
|
|
65
|
+
- **Story**: As an administrator, I want to edit an existing user's data with the same rules as registration, so that the unified Login model is consistent between creation and editing.
|
|
66
|
+
- **Acceptance Criteria**:
|
|
67
|
+
- **Given** the "Edit User" drawer is open with an existing user's data, **when** Login is displayed, **then** the field is editable and pre-filled with the current value (`login` or legacy `userName` via defensive mapping).
|
|
68
|
+
- **Given** the admin changes the Login field to a value of a different type (e.g. from email to username), **when** the type changes, **then** the dynamic UI immediately adapts the additional fields displayed.
|
|
69
|
+
- **Given** the edit form is opened, **when** the current Login is an email, **then** the "Use this email for transactional notifications" checkbox is pre-checked if `transactionalEmail == login`, unchecked otherwise.
|
|
70
|
+
|
|
71
|
+
#### US-5: List users with Login as primary identifier
|
|
72
|
+
|
|
73
|
+
- **Story**: As an administrator, I want to see Login as the main column in the user list, so that the identifier with which the user accesses the system is immediately visible.
|
|
74
|
+
- **Acceptance Criteria**:
|
|
75
|
+
- **Given** the User List is loaded, **when** the table renders, **then** the first column displays the user's `login` field (or `userName` as fallback for legacy records).
|
|
76
|
+
- **Given** the admin types in the search field, **when** the request is sent to the backend, **then** the search parameter operates on the Login field (`login` parameter confirmed).
|
|
77
|
+
- **Given** a legacy record without a `login` field in the API response, **when** the User List renders, **then** it displays the `userName` value as fallback without error.
|
|
78
|
+
|
|
79
|
+
### Key Scenarios
|
|
80
|
+
|
|
81
|
+
| Scenario | Pre-conditions | Steps | Expected result |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| Happy path — email login with default transactional | "Add User" drawer open, flag `enableAlternativeLogin = true` | Fill Login with `joao@empresa.com`, leave checkbox checked, select role, click Add | User created with `login = joao@empresa.com`, `transactionalEmail = joao@empresa.com` |
|
|
84
|
+
| Happy path — email login with different transactional | Drawer open | Fill Login with `joao@empresa.com`, uncheck checkbox, fill Transactional Email with `notif@empresa.com`, select role, click Add | User created with `login = joao@empresa.com`, `transactionalEmail = notif@empresa.com` |
|
|
85
|
+
| Happy path — phone login without email | Drawer open | Fill Login with `+5511999990000`, select role, click Add | User created with `login = +5511999990000`; recovery warning was shown during filling |
|
|
86
|
+
| Happy path — username login | Drawer open | Fill Login with `joao.silva`, select role, click Add | User created with `login = joao.silva`; recovery warning displayed |
|
|
87
|
+
| Error — empty required Login | Drawer open | Click Add without filling Login | Button disabled while Login is empty; error message shown on field touch |
|
|
88
|
+
| Error — invalid Transactional Email | Login is email, checkbox unchecked | Fill Transactional Email with `invalido`, click Add | Invalid format error message; submit blocked |
|
|
89
|
+
| Edge case — type change while typing | Login = `joao@` (incomplete email) | Delete `@`, turning it into `joao` | Type changes to `username`; transactional checkbox disappears; recovery warning appears |
|
|
90
|
+
| Edge case — uppercase value preserved | Drawer open | Fill Login with `JOAO.SILVA` | Value sent to backend is `JOAO.SILVA` (no lowercase) |
|
|
91
|
+
| Edge case — international phone | Drawer open | Fill Login with `+44 20 7946 0958` | Type detected as `phone`; dynamic phone fields displayed |
|
|
92
|
+
| Edge case — legacy user in Edit | Existing user has only `userName` (no `login`) | Open edit drawer | Login field pre-filled with `userName` value; UI functional without error |
|
|
93
|
+
| Edge case — email login, additional Phone field | Login = detected email | UI renders | Optional Phone field visible (email login allows additional phone) |
|
|
94
|
+
| Edge case — phone login, additional Phone field | Login = detected phone | UI renders | Additional Phone field **not** visible (login is already the phone) |
|
|
95
|
+
|
|
96
|
+
### Functional Requirements
|
|
97
|
+
|
|
98
|
+
**Fixed fields (always present, in this order):**
|
|
99
|
+
- `Full Name` — free text, optional. Displayed before the Login field.
|
|
100
|
+
- `Login` — required, accepts any non-empty string; dynamic behaviour based on the inferred type.
|
|
101
|
+
|
|
102
|
+
**Dynamic behaviour — Case A (Login is email):**
|
|
103
|
+
- Display "Use this email for transactional notifications" checkbox (checked by default).
|
|
104
|
+
- If unchecked: display required "Transactional Email" field with format validation.
|
|
105
|
+
- Display optional Phone field.
|
|
106
|
+
- Do not display Username field.
|
|
107
|
+
|
|
108
|
+
**Dynamic behaviour — Case B (Login is phone):**
|
|
109
|
+
- Display optional Email field.
|
|
110
|
+
- Display warning: _"If no email is provided, only an administrator can recover access (no SMS recovery available)"_.
|
|
111
|
+
- Do not display additional Phone field.
|
|
112
|
+
- The Login field applies a phone mask during typing (e.g. `+1 (555) 123-4567`). The payload sent to the API contains the normalised number: `+` followed by digits only (e.g. `+15551234567`), with no spaces or separators.
|
|
113
|
+
- **Known limitation**: the display mask currently supports the North American format only (`+1 (NXX) NXX-XXXX`). Numbers from other countries are detected and normalised correctly for the payload, but the visual mask may format the country code incorrectly. Multi-country support (i18n) will be implemented in a future task.
|
|
114
|
+
|
|
115
|
+
**Dynamic behaviour — Case C (Login is username):**
|
|
116
|
+
- Display optional Email field.
|
|
117
|
+
- Display optional Phone field.
|
|
118
|
+
- Display warning: _"If no email is provided, only an administrator can recover access (no SMS recovery available)"_.
|
|
119
|
+
|
|
120
|
+
**Submission rules:**
|
|
121
|
+
- Submit enabled only when: Login filled + (if unchecked: Transactional Email filled and valid) + at least 1 role selected.
|
|
122
|
+
- Login value sent byte-exact (no UI-layer normalisation), **except when login is of type `phone`**: in that case the payload contains `+` followed by digits only (E.164-like format), removing spaces and separators from the display mask.
|
|
123
|
+
|
|
124
|
+
**Email → username transformation:**
|
|
125
|
+
- Remove `generateUsernameSuggestion` and the `useEffect` that calls it in `CreateUserDrawerWithUsername`.
|
|
126
|
+
|
|
127
|
+
**User List:**
|
|
128
|
+
- Primary column changes from `userName` to `login` (with fallback to `userName` for legacy records).
|
|
129
|
+
- Search operates on `login` (parameter confirmed by the backend).
|
|
130
|
+
|
|
131
|
+
**AuthSetupDrawer:**
|
|
132
|
+
- Remove the "User identification" visual section (Username/Email/Phone checkboxes) and associated state/handlers (`selectedIdentifiers`, `handleIdentifierChange`, username required validation).
|
|
133
|
+
- Remove `userIdentification` from the `OrgUnitSettings` type, from the `PATCH /units/{id}/settings` payload, and from any mock/loading skeleton that previously required the field. The backend no longer requires this field.
|
|
134
|
+
|
|
135
|
+
### Non-Functional Requirements
|
|
136
|
+
|
|
137
|
+
- **Reactivity**: the dynamic UI (conditional fields, warnings, checkbox) must re-render synchronously on every keystroke in the Login field, with no debounce.
|
|
138
|
+
- **Accessibility**: warnings and error messages must use `aria-describedby` or `role="alert"` for screen readers. Conditional fields that appear/disappear must keep focus manageable.
|
|
139
|
+
- **i18n readiness**: form strings must be declared as literal constants (no dynamic string concatenation), to ease future extraction to locale files.
|
|
140
|
+
- **Test coverage**: component tests covering the 3 dynamic flows (email / phone / username), the transactional checkbox behaviour, the recovery warnings, and the absence of the email → username transformation. Minimum coverage: Definition of Done scenarios.
|
|
141
|
+
- **No new form library dependency**: keep local `useState` consistent with the current project convention.
|
|
142
|
+
|
|
143
|
+
### Out of Scope
|
|
144
|
+
|
|
145
|
+
- Refactoring the legacy `CreateUserDrawer` / `UpdateUserDrawer` flow (flag `enableAlternativeLogin = false`).
|
|
146
|
+
- Real internationalisation of strings (only prepare the structure to ease future translation).
|
|
147
|
+
- Changes to `AuthSetupDrawer` beyond the removal of the "User identification" section.
|
|
148
|
+
- Backend contract changes — this spec documents the expected new payload; backend contract implementation is the responsibility of another team.
|
|
149
|
+
- Removal of the `enableAlternativeLogin` feature flag or the `Selector`.
|
|
150
|
+
- Implementation of async username uniqueness validation (removed along with the field).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 2. Arch Decisions
|
|
155
|
+
|
|
156
|
+
### Proposed Solution
|
|
157
|
+
|
|
158
|
+
Rebuild both `CreateUserDrawerWithUsername` and `UpdateUserDrawerWithUsername` drawers keeping local `useState` as the form state mechanism (consistent with the existing project pattern). Login type inference is extracted into a pure utility function `detectLoginType`. The dynamic UI is isolated in small sub-components reusable between create and update.
|
|
159
|
+
|
|
160
|
+
`CreateUserDrawerSelector` and `UpdateUserDrawerSelector` are not changed — they continue selecting the correct drawer based on the `enableAlternativeLogin` flag.
|
|
161
|
+
|
|
162
|
+
### Architecture Overview
|
|
163
|
+
|
|
164
|
+
```mermaid
|
|
165
|
+
flowchart TD
|
|
166
|
+
Selector[CreateUserDrawerSelector] -->|enableAlternativeLogin=true| Drawer[CreateUserDrawerWithUsername]
|
|
167
|
+
Drawer --> LoginField[LoginField component]
|
|
168
|
+
LoginField --> detectLoginType[detectLoginType pure]
|
|
169
|
+
detectLoginType -->|email| EmailUI[EmailTransactionalSection]
|
|
170
|
+
detectLoginType -->|phone| RecoveryAlert[Alert WarningCircle warning]
|
|
171
|
+
detectLoginType -->|username| RecoveryAlert
|
|
172
|
+
detectLoginType -->|phone| PhoneHidden[no additional Phone]
|
|
173
|
+
EmailUI --> PhoneOptional[optional Phone]
|
|
174
|
+
RecoveryAlert --> EmailOptional[optional Email]
|
|
175
|
+
Drawer --> Submit[Submit handler]
|
|
176
|
+
Submit --> Payload[payload: login + extras]
|
|
177
|
+
Payload --> ServiceV2[addUserToOrgUnitV2]
|
|
178
|
+
ServiceV2 --> APIV2[POST v3/units/orgUnitId/users]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The `UpdateUserDrawerWithUsername` flow is identical, with the difference that fields are pre-filled from the existing user's data (with defensive mapping `login ?? userName`).
|
|
182
|
+
|
|
183
|
+
### Alternatives Considered
|
|
184
|
+
|
|
185
|
+
| Alternative | Pros | Cons | Verdict |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| Three separate forms by login type | Internal simplicity of each form | Poor UX: admin discovers the type only after typing; type change requires reopening the drawer | Rejected |
|
|
188
|
+
| Adopt `react-hook-form` for state and validation management | Less boilerplate, declarative validation | Paradigm shift for the entire project; inflated scope | Rejected — out of scope for this refactoring |
|
|
189
|
+
| Detect login type only at submit time | Simpler implementation | Does not meet the reactive dynamic UI requirement during typing | Rejected |
|
|
190
|
+
| New separate feature flag (e.g. `unifiedLoginForm`) | Gradual rollout without touching the legacy code | Third flag for the same flow; growing complexity | Rejected — replacing `*WithUsername` is sufficient and cleaner |
|
|
191
|
+
|
|
192
|
+
### Risks & Mitigations
|
|
193
|
+
|
|
194
|
+
| Risk | Impact | Probability | Mitigation |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| ~~Backend does not yet support `login` field in the payload~~ | ~~High~~ | ~~Medium~~ | `login` field confirmed by the backend for v2 endpoints (create and update). Risk resolved. |
|
|
197
|
+
| Legacy users have no `login` field in the API response | Medium — Login column shows empty in the list | High — all users registered today are legacy | Defensive mapping: `user.login ?? user.userName ?? "-"` in the loader and User List |
|
|
198
|
+
| Removing the "User identification" section in AuthSetupDrawer confuses admins | Low — field was functional but semantically inconsistent | Low | Analytics event recorded; no additional message to the user (silent behaviour, section simply disappears). Backend confirmed to no longer require `userIdentification` in the payload — field removed from type, service and mock. |
|
|
199
|
+
| `detectLoginType` produces false positives on ambiguous strings | Low — only affects dynamic UI, not payload | Low | Conservative threshold: phone regex requires a minimum of 7 digits; `@` always indicates email. Ambiguous cases fall through to `username`. |
|
|
200
|
+
| E2E tests (Cypress) cover only the legacy flow | Medium — no automated coverage for the new flow | High — confirmed by current state | Add component tests (Vitest) for the 3 scenarios as part of this spec's DoD |
|
|
201
|
+
|
|
202
|
+
### Key Decisions
|
|
203
|
+
|
|
204
|
+
#### Decision 1: Scope — replace only the `*WithUsername` flow
|
|
205
|
+
|
|
206
|
+
- **Status**: Accepted
|
|
207
|
+
- **Context**: The repository has two parallel add/edit user flows, controlled by `enableAlternativeLogin`. The legacy flow (`CreateUserDrawer`) treats email as required and will not be changed.
|
|
208
|
+
- **Decision**: Replace the contents of `CreateUserDrawerWithUsername` and `UpdateUserDrawerWithUsername`. The `Selector` and the flag remain intact. The legacy flow is preserved as-is.
|
|
209
|
+
- **Consequences**: Lower risk of regression in the legacy flow. When the flag is eventually disabled, the legacy code can be removed in a separate task.
|
|
210
|
+
|
|
211
|
+
#### Decision 2: `detectLoginType` as an isolated pure function
|
|
212
|
+
|
|
213
|
+
- **Status**: Accepted
|
|
214
|
+
- **Context**: Login type inference is used exclusively to control the UI. Mixing it into components makes unit testing and reuse harder.
|
|
215
|
+
- **Decision**: Create `src/features/users/utils/detectLoginType.ts` exporting `detectLoginType(value: string): LoginType`. Logic: presence of `@` → `"email"`; match with E.164-like phone regex (`/^\+?[0-9][0-9\s\-()\u202f.]{5,}$/`) → `"phone"`; fallback → `"username"`. The function is **never** called outside the UI layer.
|
|
216
|
+
- **Consequences**: Testable in isolation (pure function). Does not leak into services, mutation hooks, or the payload.
|
|
217
|
+
|
|
218
|
+
#### Decision 3: Single `login` field in the API payload
|
|
219
|
+
|
|
220
|
+
- **Status**: Accepted
|
|
221
|
+
- **Context**: The current v2 backend expects separate `userName`, `email`, `phone` fields. The new model needs a `login` field representing the primary identifier regardless of type.
|
|
222
|
+
- **Decision**: Send `login` as a required field in the payload. The `email` and `phone` fields continue to be sent when filled by the user as additional contact (not derived from login). Include `transactionalEmail` as a separate field when applicable.
|
|
223
|
+
- **Consequences**: Confirmed by the backend. The `addUserToOrgUnitV2` service receives required `login` and optional `transactionalEmail`; the update endpoint accepts `login` as an optional field.
|
|
224
|
+
|
|
225
|
+
#### Decision 4: "Use this email for transactional notifications" checkbox
|
|
226
|
+
|
|
227
|
+
- **Status**: Accepted
|
|
228
|
+
- **Context**: When login is an email, it is expected that this email will be used for transactional notifications. But the admin may want to use a different email for notifications.
|
|
229
|
+
- **Decision**: Checkbox checked by default (implies `transactionalEmail = login`). When unchecked, the "Transactional Email" field appears as required. If unchecked and field is empty, submit is blocked.
|
|
230
|
+
- **Consequences**: More explicit UX for a case that was previously opaque. Payload will always include `transactionalEmail` when login is an email.
|
|
231
|
+
|
|
232
|
+
#### Decision 5: User List — column and search
|
|
233
|
+
|
|
234
|
+
- **Status**: Accepted
|
|
235
|
+
- **Context**: The User List displays "Username" as the main column. With unified Login, "Login" should be the primary identifier displayed.
|
|
236
|
+
- **Decision**: Change `getTableColumns` in `UsersLayout` to use `nameColumnLabel: "Login"` and `nameColumnKey: "login"`. Render `user.login ?? user.userName ?? "-"` for compatibility with legacy records. API search parameter uses `login` (confirmed by the backend).
|
|
237
|
+
- **Consequences**: Table displays the correct identifier. Legacy records degrade gracefully via fallback.
|
|
238
|
+
|
|
239
|
+
#### Decision 6: AuthSetupDrawer — complete removal of the "User identification" section
|
|
240
|
+
|
|
241
|
+
- **Status**: Accepted (revised)
|
|
242
|
+
- **Context**: The "User identification" section has toggles for Username (always required/disabled), Email, and Phone. With unified Login, this section has lost its semantics — any identifier type is accepted. The backend has confirmed it no longer requires the `userIdentification` field in the payload.
|
|
243
|
+
- **Decision**: Remove the section entirely: JSX, `selectedIdentifiers` and `handleIdentifierChange` state/handlers (already done in the visual pass), the `userIdentification` field from `OrgUnitSettings` type, the fixed hardcoded block from `handleConfirmClick`, and any mock/skeleton that referenced the field. The `Identifier` type (dead code left from the original section) is also removed.
|
|
244
|
+
- **Consequences**: Cleaner UI and leaner type surface. No legacy dead fields in the settings payload. Backend contract is maintained because the field is no longer expected by the backend.
|
|
245
|
+
|
|
246
|
+
#### Decision 7: Edit — Login editable with full dynamic behaviour
|
|
247
|
+
|
|
248
|
+
- **Status**: Accepted
|
|
249
|
+
- **Context**: In the current flow, Username is readonly in edit if the user already had a username. The new specification requires Login to be editable.
|
|
250
|
+
- **Decision**: Login is always editable in the edit drawer. The dynamic behaviour (conditional fields, checkbox, warnings) is the same as in the create drawer. The initial value is `user.login ?? user.userName`.
|
|
251
|
+
- **Consequences**: Admin can change an existing user's login identifier. This may have authentication implications — the decision to allow or disallow this must be validated with the backend.
|
|
252
|
+
|
|
253
|
+
### Implementation Plan
|
|
254
|
+
|
|
255
|
+
The following phases must be implemented in sequence within PRs to the `refactor/add-user-form` branch:
|
|
256
|
+
|
|
257
|
+
1. **`detectLoginType` utility** — create `src/features/users/utils/detectLoginType.ts` + unit tests covering all types and edge cases.
|
|
258
|
+
2. **Refactor `CreateUserDrawerWithUsername`** — rebuild the create drawer with the new field logic, extracting `LoginField` and `EmailTransactionalSection` as reusable sub-components. The recovery warning is rendered inline via `Alert` with `WarningCircle` icon. Remove `generateUsernameSuggestion` and the associated `useEffect`. Add component tests for the 3 flows.
|
|
259
|
+
3. **Refactor `UpdateUserDrawerWithUsername`** — reuse create sub-components. Implement defensive mapping `login ?? userName` on received data. Add component tests.
|
|
260
|
+
4. **Update User List** — change `UsersLayout` (primary column `login`) and `UsersClient` (search parameter `login` confirmed by the backend).
|
|
261
|
+
5. **Clean up `AuthSetupDrawer`** — remove "User identification" visual section and associated state; ensure fixed payload.
|
|
262
|
+
6. **Regression test** — confirm that the email → username transformation no longer exists anywhere in the code.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 3. Technical Contract
|
|
267
|
+
|
|
268
|
+
### Data Models
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// New type to represent the login type inference result (UI only)
|
|
272
|
+
export type LoginType = "email" | "phone" | "username";
|
|
273
|
+
|
|
274
|
+
// Updated UserData (src/features/users/types/UserData.ts)
|
|
275
|
+
export type UserData = {
|
|
276
|
+
id: string;
|
|
277
|
+
login: string; // primary identifier — new field
|
|
278
|
+
name: string;
|
|
279
|
+
userName?: string; // kept for compatibility with legacy records
|
|
280
|
+
email?: string;
|
|
281
|
+
phone?: string;
|
|
282
|
+
transactionalEmail?: string;
|
|
283
|
+
isActive?: boolean;
|
|
284
|
+
roles?: string[];
|
|
285
|
+
orgUnit: {
|
|
286
|
+
id?: string;
|
|
287
|
+
name: string;
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// User creation payload (src/features/users/services/add-user-to-org-unit.service.ts)
|
|
292
|
+
export type CreateUserPayload = {
|
|
293
|
+
orgUnitId: string;
|
|
294
|
+
login: string; // required — new field
|
|
295
|
+
role: number[]; // at least 1
|
|
296
|
+
name?: string;
|
|
297
|
+
email?: string; // only if filled by the admin (never derived from login)
|
|
298
|
+
phone?: string; // only if filled by the admin
|
|
299
|
+
transactionalEmail?: string; // required when login is email
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Update payload (src/features/users/services/update-user.service.ts)
|
|
303
|
+
export type UpdateUserPayload = {
|
|
304
|
+
orgUnitId: string;
|
|
305
|
+
userId: string;
|
|
306
|
+
login?: string;
|
|
307
|
+
name?: string;
|
|
308
|
+
email?: string;
|
|
309
|
+
phone?: string;
|
|
310
|
+
transactionalEmail?: string;
|
|
311
|
+
role?: string;
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Interfaces
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// src/features/users/utils/detectLoginType.ts
|
|
319
|
+
/**
|
|
320
|
+
* Infers the login type from the typed value.
|
|
321
|
+
* Result is used ONLY for UI behaviour — never persisted in the payload.
|
|
322
|
+
*
|
|
323
|
+
* Rules:
|
|
324
|
+
* - Contains "@" → "email"
|
|
325
|
+
* - Matches E.164-like phone pattern (minimum 7 chars, may have +, spaces, parentheses, hyphens) → "phone"
|
|
326
|
+
* - Any other value → "username"
|
|
327
|
+
*/
|
|
328
|
+
export function detectLoginType(value: string): LoginType;
|
|
329
|
+
|
|
330
|
+
// New components (src/features/users/components/)
|
|
331
|
+
// LoginField — login field with label, inline validation and no transformation
|
|
332
|
+
interface LoginFieldProps {
|
|
333
|
+
value: string;
|
|
334
|
+
onChange: (value: string) => void;
|
|
335
|
+
isTouched: boolean;
|
|
336
|
+
"data-testid"?: string;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// EmailTransactionalSection — displayed when loginType === "email"
|
|
340
|
+
interface EmailTransactionalSectionProps {
|
|
341
|
+
useLoginAsTransactional: boolean;
|
|
342
|
+
onCheckboxChange: (checked: boolean) => void;
|
|
343
|
+
transactionalEmail: string;
|
|
344
|
+
onTransactionalEmailChange: (value: string) => void;
|
|
345
|
+
isTouched: boolean;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Recovery warning — rendered inline via <Alert> with WarningCircle icon
|
|
349
|
+
// (no dedicated component; displayed in UserFormFields when loginType is phone or username)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Integration Points
|
|
353
|
+
|
|
354
|
+
| Integration point | Current state | Expected change |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| `usersClient.addUserToOrgUnitV2` | Receives `{ userName, email?, phone?, name?, role[] }` | Receives `CreateUserPayload` with required `login` (confirmed) |
|
|
357
|
+
| `usersClient.updateUser` | Receives `{ name?, email?, phone?, role?, userName? }` | Receives `UpdateUserPayload` with optional `login?` (confirmed) |
|
|
358
|
+
| `getUsersByOrgUnitIdService` | Returns `UserDataService[]` with `userName` field | Defensive mapping in loader: `login: user.login ?? user.userName`; `transactionalEmail` mapped directly |
|
|
359
|
+
| `UsersClient.getUsersByOrgUnitId` | Search param: `name: search` | Search param: `login: search` (confirmed) |
|
|
360
|
+
| `AuthSetupDrawer → useUpdateOrgUnitSettings` | Sends `userIdentification` based on selected checkboxes | `userIdentification` removed from payload entirely; backend no longer requires the field |
|
|
361
|
+
|
|
362
|
+
### Invariants & Constraints
|
|
363
|
+
|
|
364
|
+
1. **Login is never transformed by the UI** (except for phone): for `email` or `username` login types, the payload value is byte-exact with what the user typed — no UI layer should apply `.toLowerCase()`, aggressive `.trim()`, or part removal (e.g. email domain). For the `phone` type, `LoginField` applies a display mask during typing and the payload is normalised to `+` + digits (no spaces or separators), equivalent to E.164 format without length restriction.
|
|
365
|
+
2. **Username is not required**: at no point in the `enableAlternativeLogin = true` flow may a `userName` field be marked as required or block submit when empty.
|
|
366
|
+
3. **Email is never converted to username**: the `generateUsernameSuggestion` function and any `useEffect` that copies part of the email to `userName` must be removed and no equivalent logic may exist.
|
|
367
|
+
4. **`detectLoginType` is UI-exclusive**: the function must not be imported in services, mutation hooks, or any layer that produces API payloads.
|
|
368
|
+
5. **Additional Phone field is invalid when login is phone**: if `detectLoginType(login) === "phone"`, the "Additional Phone" field must not be rendered, and no additional phone value must be sent in the payload.
|
|
369
|
+
6. **`transactionalEmail` required when login is email and checkbox is unchecked**: submit must be blocked if `detectLoginType(login) === "email"` and `useLoginAsTransactional === false` and `transactionalEmail.trim() === ""`.
|
|
370
|
+
7. **Roles required**: at least 1 role must be selected to enable submit (invariant maintained from the current flow).
|
|
371
|
+
8. **Defensive mapping in edit**: when loading a user's data into the edit drawer, the `login` value must be `user.login ?? user.userName ?? ""`. No field may silently be `undefined`.
|
|
@@ -8,17 +8,12 @@ import {
|
|
|
8
8
|
BasicDrawer,
|
|
9
9
|
type AuthSettingsFeatureFlags,
|
|
10
10
|
type BasicDrawerProps,
|
|
11
|
-
ErrorMessage,
|
|
12
11
|
Icon,
|
|
13
12
|
} from "../../../shared/components";
|
|
14
13
|
import { useAnalytics, useBuyerPortal } from "../../../shared/hooks";
|
|
15
14
|
import { CHANGES_TIMEOUT_MESSAGE } from "../../../shared/utils/constants";
|
|
16
15
|
import { useUpdateOrgUnitSettings } from "../../hooks";
|
|
17
|
-
import {
|
|
18
|
-
AuthenticationMethod,
|
|
19
|
-
type Identifier,
|
|
20
|
-
type OrgUnitSettings,
|
|
21
|
-
} from "../../types";
|
|
16
|
+
import { AuthenticationMethod, type OrgUnitSettings } from "../../types";
|
|
22
17
|
|
|
23
18
|
export type AuthSetupDrawerProps = Omit<BasicDrawerProps, "children"> & {
|
|
24
19
|
id: string;
|
|
@@ -27,27 +22,6 @@ export type AuthSetupDrawerProps = Omit<BasicDrawerProps, "children"> & {
|
|
|
27
22
|
onSuccess?: () => void;
|
|
28
23
|
};
|
|
29
24
|
|
|
30
|
-
const IDENTIFIERS: Identifier[] = [
|
|
31
|
-
{
|
|
32
|
-
id: "userName",
|
|
33
|
-
name: "Username",
|
|
34
|
-
disabled: true,
|
|
35
|
-
defaultChecked: true,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "email",
|
|
39
|
-
name: "Email",
|
|
40
|
-
disabled: false,
|
|
41
|
-
defaultChecked: false,
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: "phone",
|
|
45
|
-
name: "Phone",
|
|
46
|
-
disabled: false,
|
|
47
|
-
defaultChecked: false,
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
|
|
51
25
|
type ActiveAuthMethods = Record<AuthenticationMethod, boolean>;
|
|
52
26
|
|
|
53
27
|
type AuthMethodOption = {
|
|
@@ -57,7 +31,7 @@ type AuthMethodOption = {
|
|
|
57
31
|
};
|
|
58
32
|
|
|
59
33
|
function getAuthMethodOptions(
|
|
60
|
-
authSettings?: AuthSettingsFeatureFlags
|
|
34
|
+
authSettings?: AuthSettingsFeatureFlags | undefined
|
|
61
35
|
): AuthMethodOption[] {
|
|
62
36
|
const password = authSettings?.password ?? true;
|
|
63
37
|
const sso = authSettings?.sso ?? true;
|
|
@@ -89,14 +63,6 @@ function getAuthMethodOptions(
|
|
|
89
63
|
return options;
|
|
90
64
|
}
|
|
91
65
|
|
|
92
|
-
const getInitialIdentifiers = (settings: OrgUnitSettings): string[] => {
|
|
93
|
-
const identifiers: string[] = [];
|
|
94
|
-
if (settings.userIdentification.userName) identifiers.push("userName");
|
|
95
|
-
if (settings.userIdentification.email) identifiers.push("email");
|
|
96
|
-
if (settings.userIdentification.phone) identifiers.push("phone");
|
|
97
|
-
return identifiers;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
66
|
const getInitialActiveAuthMethods = (
|
|
101
67
|
settings: OrgUnitSettings
|
|
102
68
|
): ActiveAuthMethods => {
|
|
@@ -144,13 +110,9 @@ export const AuthSetupDrawer = ({
|
|
|
144
110
|
shouldTrackDefaultTimer: true,
|
|
145
111
|
});
|
|
146
112
|
|
|
147
|
-
const [selectedIdentifiers, setSelectedIdentifiers] = useState<string[]>(() =>
|
|
148
|
-
getInitialIdentifiers(settings)
|
|
149
|
-
);
|
|
150
113
|
const [activeAuthMethods, setActiveAuthMethods] = useState<ActiveAuthMethods>(
|
|
151
114
|
() => getInitialActiveAuthMethods(settings)
|
|
152
115
|
);
|
|
153
|
-
const [isTouched, setIsTouched] = useState(false);
|
|
154
116
|
|
|
155
117
|
const handleSuccess = () => {
|
|
156
118
|
const authMethods = (
|
|
@@ -162,7 +124,6 @@ export const AuthSetupDrawer = ({
|
|
|
162
124
|
org_unit_id: id,
|
|
163
125
|
org_unit_name: name,
|
|
164
126
|
auth_methods: authMethods,
|
|
165
|
-
identifiers: selectedIdentifiers,
|
|
166
127
|
two_factor_enabled: false,
|
|
167
128
|
});
|
|
168
129
|
|
|
@@ -198,16 +159,6 @@ export const AuthSetupDrawer = ({
|
|
|
198
159
|
},
|
|
199
160
|
});
|
|
200
161
|
|
|
201
|
-
const handleIdentifierChange = (identifierId: string) => {
|
|
202
|
-
if (identifierId === "userName") return; // Username is always required
|
|
203
|
-
|
|
204
|
-
setSelectedIdentifiers((prev) =>
|
|
205
|
-
prev.includes(identifierId)
|
|
206
|
-
? prev.filter((id) => id !== identifierId)
|
|
207
|
-
: [...prev, identifierId]
|
|
208
|
-
);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
162
|
const handleAuthMethodChange = (method: AuthenticationMethod) => {
|
|
212
163
|
setActiveAuthMethods((prev) => {
|
|
213
164
|
const next = { ...prev, [method]: !prev[method] };
|
|
@@ -229,21 +180,7 @@ export const AuthSetupDrawer = ({
|
|
|
229
180
|
};
|
|
230
181
|
|
|
231
182
|
const handleConfirmClick = () => {
|
|
232
|
-
setIsTouched(true);
|
|
233
|
-
|
|
234
|
-
if (!selectedIdentifiers.includes("userName")) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const emailAllowed = selectedIdentifiers.includes("email");
|
|
239
|
-
const phoneAllowed = selectedIdentifiers.includes("phone");
|
|
240
|
-
|
|
241
183
|
const data: OrgUnitSettings = {
|
|
242
|
-
userIdentification: {
|
|
243
|
-
userName: true,
|
|
244
|
-
email: emailAllowed,
|
|
245
|
-
phone: phoneAllowed,
|
|
246
|
-
},
|
|
247
184
|
authenticationMethods: {
|
|
248
185
|
method: (
|
|
249
186
|
Object.values(AuthenticationMethod) as AuthenticationMethod[]
|
|
@@ -261,49 +198,12 @@ export const AuthSetupDrawer = ({
|
|
|
261
198
|
});
|
|
262
199
|
};
|
|
263
200
|
|
|
264
|
-
const isConfirmButtonEnabled =
|
|
265
|
-
!isUpdateOrgUnitSettingsLoading && selectedIdentifiers.includes("userName");
|
|
201
|
+
const isConfirmButtonEnabled = !isUpdateOrgUnitSettingsLoading;
|
|
266
202
|
|
|
267
203
|
return (
|
|
268
204
|
<BasicDrawer data-fs-bp-auth-setup-drawer close={close} {...props}>
|
|
269
205
|
<BasicDrawer.Heading title="Authentication" onClose={close} />
|
|
270
206
|
<BasicDrawer.Body>
|
|
271
|
-
<section data-fs-bp-auth-setup-section>
|
|
272
|
-
<div data-fs-bp-auth-setup-section-header>
|
|
273
|
-
<h2 data-fs-bp-auth-setup-section-title>User identification</h2>
|
|
274
|
-
</div>
|
|
275
|
-
<p data-fs-bp-auth-setup-section-description>
|
|
276
|
-
Select the identifiers allowed for sign-in
|
|
277
|
-
</p>
|
|
278
|
-
|
|
279
|
-
<div data-fs-bp-auth-setup-identifiers>
|
|
280
|
-
{IDENTIFIERS.map((identifier) => (
|
|
281
|
-
<div data-fs-bp-auth-setup-identifier-group-item>
|
|
282
|
-
<label
|
|
283
|
-
key={identifier.id}
|
|
284
|
-
data-fs-bp-auth-setup-identifier
|
|
285
|
-
data-disabled={identifier.disabled}
|
|
286
|
-
>
|
|
287
|
-
<Checkbox
|
|
288
|
-
id={identifier.id}
|
|
289
|
-
checked={selectedIdentifiers.includes(identifier.id)}
|
|
290
|
-
disabled={identifier.disabled}
|
|
291
|
-
onChange={() => handleIdentifierChange(identifier.id)}
|
|
292
|
-
/>
|
|
293
|
-
<span data-fs-bp-auth-setup-identifier-label>
|
|
294
|
-
{identifier.name}
|
|
295
|
-
</span>
|
|
296
|
-
</label>
|
|
297
|
-
</div>
|
|
298
|
-
))}
|
|
299
|
-
</div>
|
|
300
|
-
|
|
301
|
-
<ErrorMessage
|
|
302
|
-
show={isTouched && !selectedIdentifiers.includes("userName")}
|
|
303
|
-
message="Username is required"
|
|
304
|
-
/>
|
|
305
|
-
</section>
|
|
306
|
-
|
|
307
207
|
<section data-fs-bp-auth-setup-section>
|
|
308
208
|
<div data-fs-bp-auth-setup-section-header>
|
|
309
209
|
<h2 data-fs-bp-auth-setup-section-title>Authentication methods</h2>
|
|
@@ -6,11 +6,6 @@ export enum AuthenticationMethod {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export type OrgUnitSettings = {
|
|
9
|
-
userIdentification: {
|
|
10
|
-
userName: boolean;
|
|
11
|
-
email: boolean;
|
|
12
|
-
phone: boolean;
|
|
13
|
-
};
|
|
14
9
|
authenticationMethods: {
|
|
15
10
|
method: Array<{
|
|
16
11
|
method: AuthenticationMethod;
|
|
@@ -21,10 +16,3 @@ export type OrgUnitSettings = {
|
|
|
21
16
|
verificationCode: boolean;
|
|
22
17
|
};
|
|
23
18
|
};
|
|
24
|
-
|
|
25
|
-
export type Identifier = {
|
|
26
|
-
id: string;
|
|
27
|
-
name: string;
|
|
28
|
-
disabled: boolean;
|
|
29
|
-
defaultChecked?: boolean;
|
|
30
|
-
};
|
|
@@ -111,14 +111,14 @@ export const LoadingTabsLayout = ({ children }: LoadingTabsLayoutProps) => {
|
|
|
111
111
|
},
|
|
112
112
|
},
|
|
113
113
|
settings: {
|
|
114
|
-
userIdentification: { userName: true, email: true, phone: true },
|
|
115
114
|
authenticationMethods: { method: [] },
|
|
116
115
|
"2FA": { verificationCode: false },
|
|
117
116
|
},
|
|
118
117
|
user: {
|
|
118
|
+
id: "",
|
|
119
|
+
login: "",
|
|
119
120
|
name: "",
|
|
120
121
|
roles: [],
|
|
121
|
-
id: "",
|
|
122
122
|
orgUnit: {
|
|
123
123
|
id: routeParams.orgUnitId,
|
|
124
124
|
name: "",
|