@vtex/faststore-plugin-buyer-portal 1.3.5 → 1.3.7

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/cypress/constants.ts +2 -0
  3. package/cypress/integration/organizational-units.test.ts +1 -1
  4. package/cypress/integration/{collections.test.ts → product-assortment.test.ts} +54 -50
  5. package/cypress/integration/profile.test.ts +1 -1
  6. package/cypress/integration/users.test.ts +9 -0
  7. package/package.json +1 -1
  8. package/plugin.config.js +2 -2
  9. package/src/features/{collections/clients/CollectionsClient.ts → product-assortment/clients/ProductAssortmentClient.ts} +18 -20
  10. package/src/features/product-assortment/components/AddProductAssortmentDrawer/AddProductAssortmentDrawer.tsx +144 -0
  11. package/src/features/{collections/components/AddCollectionsDrawer/add-collections-drawer.scss → product-assortment/components/AddProductAssortmentDrawer/add-product-assortment-drawer.scss} +9 -10
  12. package/src/features/product-assortment/components/ProductAssortmentTable/ProductAssortmentTable.tsx +99 -0
  13. package/src/features/product-assortment/components/ProductAssortmentTable/product-assortment-table.scss +45 -0
  14. package/src/features/{collections/components/RemoveCollectionDrawer/RemoveCollectionDrawer.tsx → product-assortment/components/RemoveProductAssortmentDrawer/RemoveProductAssortmentDrawer.tsx} +21 -20
  15. package/src/features/product-assortment/components/RemoveProductAssortmentDrawer/remove-product-assortment-drawer.scss +9 -0
  16. package/src/features/product-assortment/components/index.tsx +2 -0
  17. package/src/features/{collections/components/table/AddCollectionsDrawerTable.tsx → product-assortment/components/table/AddProductAssortmentDrawerTable.tsx} +9 -9
  18. package/src/features/product-assortment/components/table/add-product-assortment-drawer-table.scss +14 -0
  19. package/src/features/product-assortment/hooks/useAddProductAssortmentToScope.ts +25 -0
  20. package/src/features/product-assortment/hooks/useRemoveProductAssortmentFromScope.ts +26 -0
  21. package/src/features/{collections/layouts/CollectionsLayout/CollectionsLayout.tsx → product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx} +43 -49
  22. package/src/features/{collections/layouts/CollectionsLayout/collections-layout.scss → product-assortment/layouts/ProductAssortmentLayout/product-assortment-layout.scss} +7 -7
  23. package/src/features/product-assortment/layouts/index.ts +1 -0
  24. package/src/features/product-assortment/services/add-product-assortment-to-scope.service.ts +16 -0
  25. package/src/features/product-assortment/services/get-product-assortment-from-contract.service.ts +13 -0
  26. package/src/features/product-assortment/services/get-product-assortment-from-scope.service.ts +28 -0
  27. package/src/features/product-assortment/services/remove-product-assortment-from-scope.ts +9 -0
  28. package/src/features/product-assortment/types/index.ts +80 -0
  29. package/src/features/profile/layouts/ProfileLayout/profile-layout.scss +8 -5
  30. package/src/features/roles/layout/RoleDetailsLayout/RoleDetailsLayout.tsx +17 -36
  31. package/src/features/roles/layout/RoleDetailsLayout/role-details-layout.scss +40 -0
  32. package/src/features/roles/layout/RolesLayout/roles-layout.scss +8 -8
  33. package/src/features/shared/utils/buyerPortalRoutes.ts +5 -2
  34. package/src/features/shared/utils/constants.ts +1 -1
  35. package/src/features/shared/utils/getContractSettingsLinks.ts +2 -2
  36. package/src/features/shared/utils/routeLayoutMapping.ts +2 -2
  37. package/src/features/users/clients/UsersClient.ts +2 -0
  38. package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +19 -2
  39. package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +31 -5
  40. package/src/features/users/components/UserDropdownMenu/UserDropdownMenu.tsx +1 -1
  41. package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +8 -0
  42. package/src/features/users/services/add-user-to-org-unit.service.ts +1 -0
  43. package/src/features/users/services/get-user-by-id.service.ts +2 -1
  44. package/src/features/users/services/update-user.service.ts +3 -0
  45. package/src/features/users/types/UserData.ts +1 -0
  46. package/src/features/users/types/UserDataService.ts +1 -0
  47. package/src/pages/{collections.tsx → productAssortment.tsx} +30 -51
  48. package/src/themes/layouts.scss +2 -2
  49. package/src/features/collections/components/AddCollectionsDrawer/AddCollectionsDrawer.tsx +0 -146
  50. package/src/features/collections/components/CollectionsTable/CollectionsTable.tsx +0 -94
  51. package/src/features/collections/components/CollectionsTable/collections-table.scss +0 -140
  52. package/src/features/collections/components/RemoveCollectionDrawer/remove-collection-drawer.scss +0 -15
  53. package/src/features/collections/components/index.tsx +0 -5
  54. package/src/features/collections/components/table/add-collections-drawer-table.scss +0 -71
  55. package/src/features/collections/hooks/useAddCollectionsToScope.ts +0 -23
  56. package/src/features/collections/hooks/useGetCollectionsFromContract.ts +0 -26
  57. package/src/features/collections/hooks/useGetCollectionsFromScope.ts +0 -27
  58. package/src/features/collections/hooks/useRemoveCollectionsFromScope.ts +0 -26
  59. package/src/features/collections/layouts/index.ts +0 -4
  60. package/src/features/collections/services/add-collections-to-scope.service.ts +0 -15
  61. package/src/features/collections/services/get-collections-from-contract.service.ts +0 -9
  62. package/src/features/collections/services/get-collections-from-scope.service.ts +0 -24
  63. package/src/features/collections/services/remove-collections-from-scope.ts +0 -7
  64. package/src/features/collections/types/index.ts +0 -20
@@ -0,0 +1,13 @@
1
+ import { productAssortmentClient } from "../clients/ProductAssortmentClient";
2
+
3
+ export const getProductAssortmentFromContractService = async (
4
+ params: Parameters<
5
+ typeof productAssortmentClient.getProductAssortmentFromContract
6
+ >[0]
7
+ ) => {
8
+ const data = await productAssortmentClient.getProductAssortmentFromContract(
9
+ params
10
+ );
11
+
12
+ return data;
13
+ };
@@ -0,0 +1,28 @@
1
+ import { isRightFormat } from "../../shared/utils/isRightFormat";
2
+ import { productAssortmentClient } from "../clients/ProductAssortmentClient";
3
+
4
+ export const getProductAssortmentFromScopeService = async (
5
+ args: Parameters<
6
+ typeof productAssortmentClient.getProductAssortmentListInScope
7
+ >[0]
8
+ ) => {
9
+ try {
10
+ const data = await productAssortmentClient.getProductAssortmentListInScope(
11
+ args
12
+ );
13
+
14
+ return data;
15
+ } catch (error) {
16
+ if (error instanceof Error) {
17
+ const err = JSON.parse(error.message) as unknown;
18
+ if (
19
+ isRightFormat(err) &&
20
+ err.message.startsWith("No Product assortment found for the name")
21
+ ) {
22
+ return [];
23
+ }
24
+ }
25
+
26
+ throw error;
27
+ }
28
+ };
@@ -0,0 +1,9 @@
1
+ import { productAssortmentClient } from "../clients/ProductAssortmentClient";
2
+
3
+ export const removeProductAssortmentFromScopeService = async (
4
+ params: Parameters<
5
+ typeof productAssortmentClient.removeProductAssortmentFromScope
6
+ >[0]
7
+ ) => {
8
+ await productAssortmentClient.removeProductAssortmentFromScope(params);
9
+ };
@@ -0,0 +1,80 @@
1
+ import type { ContractData } from "../../contracts/types";
2
+ import type { OrgUnitBasicData } from "../../org-units/types";
3
+ import type { BasicDrawerProps } from "../../shared/components";
4
+ import type { ErrorBoundaryProps } from "../../shared/components/ErrorBoundary/types";
5
+ import type { ClientContext } from "../../shared/utils";
6
+
7
+ export type ProductAssortmentSummary = {
8
+ id: string;
9
+ name: string;
10
+ };
11
+
12
+ export type ContractProductAssortment = ProductAssortmentSummary & {
13
+ isEnabled: boolean;
14
+ };
15
+ export type ScopeProductAssortment = ProductAssortmentSummary & {
16
+ isEnabled: boolean;
17
+ };
18
+
19
+ export type GetProductAssortmentFromContractResponse = {
20
+ collections: Array<ContractProductAssortment>;
21
+ };
22
+
23
+ export type GetProductAssortmentFromScopeResponse = {
24
+ collections: Array<ScopeProductAssortment>;
25
+ };
26
+
27
+ export type AddProductAssortmentResponse = {
28
+ message: string;
29
+ addedCollections: string[];
30
+ };
31
+
32
+ export type AddProductAssortmentPayload = Array<{ name: string }>;
33
+
34
+ export type AddProductAssortmentDrawerProps = Omit<
35
+ BasicDrawerProps,
36
+ "children"
37
+ > & {
38
+ productAssortment: ScopeProductAssortment[];
39
+ };
40
+
41
+ export type RemoveProductAssortmentDrawerProps = Omit<
42
+ BasicDrawerProps,
43
+ "children"
44
+ > & {
45
+ productAssortment: ProductAssortmentSelectedProps;
46
+ };
47
+ export interface ProductAssortmentSelectedProps {
48
+ id: string;
49
+ name: string;
50
+ }
51
+
52
+ export type ProductAssortmentLayoutProps = {
53
+ initialProductAssortment: ScopeProductAssortment[];
54
+ drawerProductAssortment: ScopeProductAssortment[];
55
+ page: number;
56
+ search: string;
57
+ isContractEmpty: boolean;
58
+ };
59
+
60
+ export type ProductAssortmentData = {
61
+ productAssortment: ScopeProductAssortment[];
62
+ drawerProductAssortment: ScopeProductAssortment[];
63
+ isContractEmpty: boolean;
64
+ search: string;
65
+ page: number;
66
+ context: {
67
+ clientContext: ClientContext;
68
+ currentOrgUnit: OrgUnitBasicData;
69
+ currentContract: ContractData | null;
70
+ };
71
+ hasError?: boolean;
72
+ error?: ErrorBoundaryProps;
73
+ };
74
+
75
+ export type ProductAssortmentQuery = {
76
+ orgUnitId: string;
77
+ contractId: string;
78
+ search: string;
79
+ page: string;
80
+ };
@@ -14,7 +14,7 @@
14
14
  font-weight: var(--fs-text-weight-semibold);
15
15
  size: var(--fs-spacing-3);
16
16
  line-height: var(--fs-spacing-4);
17
-
17
+ color: #000;
18
18
  display: flex;
19
19
  justify-content: space-between;
20
20
  padding: var(--fs-spacing-4) 0;
@@ -22,7 +22,7 @@
22
22
  }
23
23
 
24
24
  [data-fs-bp-profile-divider] {
25
- border: calc(var(--fs-border-width) / 2) solid #e0e0e0;
25
+ border: calc(var(--fs-border-width) / 1) solid #e0e0e0;
26
26
  }
27
27
 
28
28
  [data-fs-button][data-fs-button-variant="primary"] [data-fs-button-wrapper] {
@@ -31,22 +31,25 @@
31
31
 
32
32
  [data-fs-bp-profile-details-row] {
33
33
  display: flex;
34
+ flex-direction: row;
34
35
  padding: 1.375rem 0;
35
- font-weight: var(--fs-text-weight-medium);
36
36
  size: var(--fs-text-size-1);
37
37
  line-height: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
38
+ font-weight: var(--fs-text-weight-regular);
38
39
 
39
40
  [data-fs-bp-profile-details-row-label] {
40
41
  width: 12.5rem;
41
42
  color: #707070;
42
43
  font-size: var(--fs-text-size-1);
43
- font-weight: 500;
44
44
  }
45
45
 
46
46
  [data-fs-bp-profile-details-row-value] {
47
47
  color: #000;
48
48
  font-size: var(--fs-text-size-1);
49
- font-weight: 500;
49
+ }
50
+
51
+ @include media("<=tablet") {
52
+ flex-direction: column;
50
53
  }
51
54
  }
52
55
 
@@ -21,7 +21,7 @@ export const RoleDetailsLayout = ({
21
21
  const { currentOrgUnit } = useBuyerPortal();
22
22
 
23
23
  const columns = getTableColumns({
24
- nameColumnLabel: "Permissions",
24
+ nameColumnLabel: "",
25
25
  nameColumnSize: "18rem",
26
26
  actionsLength: 1,
27
27
  extraColumns: [
@@ -34,10 +34,12 @@ export const RoleDetailsLayout = ({
34
34
  ],
35
35
  });
36
36
 
37
+ const count = roles.reduce((acc, item) => acc + (item.inRole ? 1 : 0), 0);
38
+
37
39
  return (
38
40
  <GlobalLayout>
39
41
  <OrgUnitTabsLayout pageName="Organization">
40
- <section data-fs-bp-roles-section>
42
+ <section data-fs-bp-roles-details-section>
41
43
  <HeaderInside
42
44
  title={roleName}
43
45
  backLink={buyerPortalRoutes.roles({
@@ -46,12 +48,20 @@ export const RoleDetailsLayout = ({
46
48
  />
47
49
 
48
50
  <div data-fs-role-details-page>
51
+ <div data-fs-role-header>
52
+ <span>Permissions</span>
53
+ <div data-fs-role-status>
54
+ <span data-fs-role-active>{count}</span>/
55
+ <span data-fs-role-not-active>{roles.length}</span>
56
+ </div>
57
+ </div>
49
58
  <Table layoutFixed>
50
59
  <Table.Head columns={columns} />
51
60
  <Table.Body>
52
61
  {roles.map((role) => (
53
62
  <Table.Row
54
63
  data-fs-bp-role-details-row
64
+ data-fs-bp-role-details-row-disabled={role.inRole}
55
65
  key={role.label}
56
66
  title={
57
67
  <>
@@ -80,46 +90,17 @@ export const RoleDetailsLayout = ({
80
90
  )
81
91
  }
82
92
  >
83
- <Table.Cell align="left" hideOnScreenSize="tablet">
93
+ <Table.Cell
94
+ align="left"
95
+ data-fs-bp-role-details-description
96
+ hideOnScreenSize="tablet"
97
+ >
84
98
  {role.description}
85
99
  </Table.Cell>
86
100
  </Table.Row>
87
101
  ))}
88
102
  </Table.Body>
89
103
  </Table>
90
- {/* <Table>
91
- <Table.Head columns={configTable} />
92
- <Table.Body>
93
- {roles.map((role) => (
94
- <Table.Row
95
- key={role.label}
96
- title={role.label}
97
- enabled={role.inRole}
98
- >
99
- <Table.Cell data-fs-bp-role-description>
100
- {role.description}
101
- </Table.Cell>
102
- <Table.Cell>
103
- {role.inRole ? (
104
- <Icon
105
- name="CheckCircle"
106
- width={20}
107
- height={20}
108
- data-fs-bp-check-icon
109
- />
110
- ) : (
111
- <Icon
112
- name="Block"
113
- width={20}
114
- height={20}
115
- data-fs-bp-block-icon
116
- />
117
- )}
118
- </Table.Cell>
119
- </Table.Row>
120
- ))}
121
- </Table.Body>
122
- </Table> */}
123
104
  </div>
124
105
  </section>
125
106
  </OrgUnitTabsLayout>
@@ -1,6 +1,46 @@
1
1
  [data-fs-role-details-page] {
2
2
  @import "../../../shared/components/Table/table.scss";
3
3
 
4
+ [data-fs-bp-table-head] {
5
+ @include media("<=tablet") {
6
+ border: none;
7
+ }
8
+
9
+ [data-fs-bp-table-head-column] {
10
+ padding: 0;
11
+ }
12
+ }
13
+
14
+ [data-fs-role-header] {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ margin-bottom: var(--fs-spacing-3);
19
+
20
+ @include media(">=tablet") {
21
+ margin-bottom: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
22
+ }
23
+
24
+ [data-fs-role-status] {
25
+ margin-right: var(--fs-spacing-1);
26
+ }
27
+ span:first-child {
28
+ color: #000000;
29
+ font-weight: var(--fs-text-weight-semibold);
30
+ }
31
+ [data-fs-role-not-active],
32
+ [data-fs-role-active] {
33
+ font-size: var(--fs-text-size-1);
34
+ font-weight: var(--fs-text-weight-semibold);
35
+ }
36
+ [data-fs-role-active] {
37
+ color: #000000;
38
+ }
39
+ [data-fs-role-not-active] {
40
+ color: #5c5c5c;
41
+ }
42
+ }
43
+
4
44
  [data-fs-bp-role-details-row] {
5
45
  [data-fs-bp-table-row-title-cell] {
6
46
  padding-left: var(--fs-spacing-0);
@@ -1,13 +1,13 @@
1
1
  [data-fs-bp-roles-section] {
2
- @import "../../../shared/components/Table/table.scss";
2
+ @import "../../../shared/components/Table/table.scss";
3
3
 
4
- @import "../../../shared/components/HeaderInside/header-inside.scss";
4
+ @import "../../../shared/components/HeaderInside/header-inside.scss";
5
5
 
6
- @import "../../../shared/components/InternalSearch/internal-search.scss";
7
- @import "../../../shared/components/Tag/tag.scss";
8
- @import "../../../shared/components/InternalTopbar/internal-top-bar.scss";
6
+ @import "../../../shared/components/InternalSearch/internal-search.scss";
7
+ @import "../../../shared/components/Tag/tag.scss";
8
+ @import "../../../shared/components/InternalTopbar/internal-top-bar.scss";
9
9
 
10
- [data-fs-bp-table-row-icon] {
11
- color: #0366dd;
12
- }
10
+ [data-fs-bp-table-row-icon] {
11
+ color: #0366dd;
12
+ }
13
13
  }
@@ -45,8 +45,11 @@ export const buyerPortalRoutes = {
45
45
  creditCards: (params: { orgUnitId: string; contractId: string }) =>
46
46
  replaceParams(`${base}/credit-cards/[orgUnitId]/[contractId]`, params),
47
47
 
48
- collections: (params: { orgUnitId: string; contractId: string }) =>
49
- replaceParams(`${base}/collections/[orgUnitId]/[contractId]`, params),
48
+ productAssortment: (params: { orgUnitId: string; contractId: string }) =>
49
+ replaceParams(
50
+ `${base}/product-assortment/[orgUnitId]/[contractId]`,
51
+ params
52
+ ),
50
53
 
51
54
  poNumbers: (params: { orgUnitId: string; contractId: string }) =>
52
55
  replaceParams(`${base}/po-numbers/[orgUnitId]/[contractId]`, params),
@@ -13,4 +13,4 @@ export const LOCAL_STORAGE_LOCATION_EDIT_KEY = "bp_hide_edit_location_confirm";
13
13
  export const LOCAL_STORAGE_RECIPIENT_EDIT_KEY =
14
14
  "bp_hide_edit_recipient_confirm";
15
15
 
16
- export const CURRENT_VERSION = "1.3.5";
16
+ export const CURRENT_VERSION = "1.3.7";
@@ -24,8 +24,8 @@ export const getContractSettingsLinks = ({
24
24
  link: buyerPortalRoutes.creditCards({ orgUnitId, contractId }),
25
25
  },
26
26
  {
27
- name: "Collections",
28
- link: buyerPortalRoutes.collections({ orgUnitId, contractId }),
27
+ name: "Product assortment",
28
+ link: buyerPortalRoutes.productAssortment({ orgUnitId, contractId }),
29
29
  },
30
30
  {
31
31
  name: "PO numbers",
@@ -37,10 +37,10 @@ export const ROUTE_TABS_LAYOUT_MAPPING: Record<string, RouteTabsLayoutConfig> =
37
37
  pageName: "Contract",
38
38
  pageTitle: "Credit Cards",
39
39
  },
40
- "/pvt/organization-account/collections/[orgUnitId]/[contractId]": {
40
+ "/pvt/organization-account/product-assortment/[orgUnitId]/[contractId]": {
41
41
  layout: "ContractTabsLayout",
42
42
  pageName: "Contract",
43
- pageTitle: "Collections",
43
+ pageTitle: "Product assortment",
44
44
  },
45
45
  "/pvt/organization-account/po-numbers/[orgUnitId]/[contractId]": {
46
46
  layout: "ContractTabsLayout",
@@ -142,6 +142,7 @@ class UsersClient extends Client {
142
142
  props: {
143
143
  orgUnitId: string;
144
144
  userId: string;
145
+ phone?: string;
145
146
  name?: string;
146
147
  role?: string;
147
148
  },
@@ -153,6 +154,7 @@ class UsersClient extends Client {
153
154
  unknown,
154
155
  {
155
156
  name?: string;
157
+ phone?: string;
156
158
  role?: string;
157
159
  }
158
160
  >(
@@ -13,6 +13,7 @@ import {
13
13
  InputText,
14
14
  } from "../../../shared/components";
15
15
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
16
+ import { maskPhoneNumber } from "../../../shared/utils/phoneNumber";
16
17
  import { useAddUserToOrgUnit } from "../../hooks";
17
18
 
18
19
  export type CreateUserDrawerProps = Omit<BasicDrawerProps, "children"> & {
@@ -49,16 +50,18 @@ export const CreateUserDrawer = ({
49
50
  const [form, setForm] = useState<{
50
51
  name: string;
51
52
  email: string;
53
+ phone: string;
52
54
  roles: number[];
53
55
  }>({
54
56
  name: "",
55
57
  email: "",
58
+ phone: "",
56
59
  roles: [],
57
60
  });
58
61
 
59
62
  const [isTouched, setIsTouched] = useState(false);
60
63
 
61
- const { name, email, roles } = form;
64
+ const { name, email, roles, phone } = form;
62
65
 
63
66
  const updateField = (
64
67
  field: keyof typeof form,
@@ -122,7 +125,7 @@ export const CreateUserDrawer = ({
122
125
  });
123
126
 
124
127
  const isAllFieldsFilled = Object.keys(form).every((key) => {
125
- if (key === "roles") {
128
+ if (key === "roles" || key === "phone") {
126
129
  return true; // allow empty roles
127
130
  }
128
131
 
@@ -140,6 +143,7 @@ export const CreateUserDrawer = ({
140
143
  addUserToOrgUnit({
141
144
  name,
142
145
  email,
146
+ phone: phone ?? "",
143
147
  roles,
144
148
  orgUnitId,
145
149
  });
@@ -154,6 +158,7 @@ export const CreateUserDrawer = ({
154
158
  setForm({
155
159
  name: "",
156
160
  email: "",
161
+ phone: "",
157
162
  roles: [],
158
163
  });
159
164
  };
@@ -225,6 +230,18 @@ export const CreateUserDrawer = ({
225
230
  message="Email is required"
226
231
  />
227
232
 
233
+ <InputText
234
+ label="Phone number (optional)"
235
+ value={phone}
236
+ onChange={(event) =>
237
+ // TODO: Update this when implementing i18n
238
+ updateField(
239
+ "phone",
240
+ maskPhoneNumber(event.target.value, "USA")
241
+ )
242
+ }
243
+ />
244
+
228
245
  <div data-fs-bp-create-user-roles>
229
246
  <span data-fs-bp-create-user-roles-label>Roles</span>
230
247
 
@@ -10,10 +10,11 @@ import {
10
10
  ErrorMessage,
11
11
  InputText,
12
12
  } from "../../../shared/components";
13
+ import { maskPhoneNumber } from "../../../shared/utils/phoneNumber";
13
14
  import { useGetUserById, useUpdateUser } from "../../hooks";
14
15
 
15
16
  export type UpdateUserDrawerProps = Omit<BasicDrawerProps, "children"> & {
16
- onCreate?: () => void;
17
+ onUpdate?: () => void;
17
18
  orgUnitId: string;
18
19
  userId: string;
19
20
  rolesOptions: RoleData[] | null;
@@ -21,7 +22,7 @@ export type UpdateUserDrawerProps = Omit<BasicDrawerProps, "children"> & {
21
22
 
22
23
  export const UpdateUserDrawer = ({
23
24
  close,
24
- onCreate,
25
+ onUpdate,
25
26
  userId,
26
27
  orgUnitId,
27
28
  rolesOptions,
@@ -36,6 +37,7 @@ export const UpdateUserDrawer = ({
36
37
  setForm({
37
38
  name: data?.name ?? "",
38
39
  email: data?.email ?? "",
40
+ phone: data?.phone ?? "",
39
41
  roles: data?.roles ? data.roles : [],
40
42
  });
41
43
  },
@@ -51,16 +53,18 @@ export const UpdateUserDrawer = ({
51
53
  const [form, setForm] = useState<{
52
54
  name: string;
53
55
  email: string;
56
+ phone: string;
54
57
  roles: string[];
55
58
  }>({
56
59
  name: "",
57
60
  email: "",
61
+ phone: "",
58
62
  roles: [],
59
63
  });
60
64
 
61
65
  const [isTouched, setIsTouched] = useState(false);
62
66
 
63
- const { name, email, roles } = form;
67
+ const { name, email, roles, phone } = form;
64
68
 
65
69
  const updateField = (field: keyof typeof form, value: string | string[]) => {
66
70
  setForm((prev) => ({
@@ -74,7 +78,7 @@ export const UpdateUserDrawer = ({
74
78
  message: "User successfully updated",
75
79
  status: "INFO",
76
80
  });
77
- onCreate?.();
81
+ onUpdate?.();
78
82
  close();
79
83
  };
80
84
 
@@ -99,7 +103,7 @@ export const UpdateUserDrawer = ({
99
103
  });
100
104
 
101
105
  const isAllFieldsFilled = Object.keys(form).every((key) => {
102
- if (key === "roles") {
106
+ if (key === "roles" || key === "phone") {
103
107
  return true; // allow empty roles
104
108
  }
105
109
 
@@ -129,6 +133,7 @@ export const UpdateUserDrawer = ({
129
133
  updateUser({
130
134
  userId,
131
135
  name,
136
+ phone,
132
137
  roles,
133
138
  orgUnitId,
134
139
  });
@@ -186,6 +191,27 @@ export const UpdateUserDrawer = ({
186
191
  message="Email is required"
187
192
  />
188
193
 
194
+ {isUserLoading ? (
195
+ <Skeleton
196
+ size={{ width: "100%", height: "3.5rem" }}
197
+ style={{
198
+ marginTop: "1rem",
199
+ marginBottom: "1rem",
200
+ width: "100%",
201
+ height: "3.5rem",
202
+ }}
203
+ />
204
+ ) : (
205
+ <InputText
206
+ label="Phone number (optional)"
207
+ value={phone}
208
+ onChange={(event) =>
209
+ // TODO: Update this when implementing i18n
210
+ updateField("phone", maskPhoneNumber(event.target.value, "USA"))
211
+ }
212
+ />
213
+ )}
214
+
189
215
  <div data-fs-bp-update-user-roles>
190
216
  <span data-fs-bp-update-user-roles-label>Roles</span>
191
217
  {rolesOptions &&
@@ -104,7 +104,7 @@ export const UserDropdownMenu = ({
104
104
  userId={user.id}
105
105
  orgUnitId={currentOrgUnit?.id ?? ""}
106
106
  isOpen={isUpdateUserDrawerOpen}
107
- onCreate={handleUpdate}
107
+ onUpdate={handleUpdate}
108
108
  {...updateUserDrawerProps}
109
109
  />
110
110
  )}
@@ -80,6 +80,13 @@ export const UserDetailsLayout = ({
80
80
 
81
81
  <hr data-fs-user-details-divider />
82
82
 
83
+ <div data-fs-user-details-row>
84
+ <span data-fs-user-details-row-label>Phone number</span>
85
+ <span data-fs-user-details-row-value>{user?.phone}</span>
86
+ </div>
87
+
88
+ <hr data-fs-user-details-divider />
89
+
83
90
  <div data-fs-user-details-row>
84
91
  <span data-fs-user-details-row-label>Email</span>
85
92
  <a href={`mailto:${user?.email}`} data-fs-user-details-row-value>
@@ -133,6 +140,7 @@ export const UserDetailsLayout = ({
133
140
  userId={user?.id ?? ""}
134
141
  orgUnitId={currentOrgUnit?.id ?? ""}
135
142
  isOpen={isUpdateUserDrawerOpen}
143
+ onUpdate={() => location.reload()}
136
144
  {...updateUserDrawerProps}
137
145
  />
138
146
  )}
@@ -5,6 +5,7 @@ export type AddUserToOrgUnitServiceProps = {
5
5
  roles: number[];
6
6
  email: string;
7
7
  name: string;
8
+ phone?: string;
8
9
  cookie: string;
9
10
  };
10
11
 
@@ -12,7 +12,7 @@ export const getUserByIdService = async ({
12
12
  cookie: string;
13
13
  }): Promise<UserData | null> => {
14
14
  try {
15
- const { email, name, orgUnit, role } = await usersClient.getUserById(
15
+ const { email, name, phone, orgUnit, role } = await usersClient.getUserById(
16
16
  orgUnitId,
17
17
  userId,
18
18
  cookie
@@ -23,6 +23,7 @@ export const getUserByIdService = async ({
23
23
  roles: role ? role : [],
24
24
  id: userId,
25
25
  email: email ?? "",
26
+ phone: phone ?? "",
26
27
  orgUnit: {
27
28
  name: orgUnit,
28
29
  },
@@ -4,6 +4,7 @@ export type UpdateUserServiceProps = {
4
4
  orgUnitId: string;
5
5
  userId: string;
6
6
  name?: string;
7
+ phone?: string;
7
8
  roles?: string[];
8
9
  cookie: string;
9
10
  };
@@ -13,6 +14,7 @@ export const updateUserService = async ({
13
14
  userId,
14
15
  cookie,
15
16
  name,
17
+ phone = "",
16
18
  roles,
17
19
  }: UpdateUserServiceProps) => {
18
20
  return usersClient.updateUser(
@@ -20,6 +22,7 @@ export const updateUserService = async ({
20
22
  orgUnitId,
21
23
  userId,
22
24
  name,
25
+ phone,
23
26
  role: roles?.join(",") || undefined,
24
27
  },
25
28
  cookie
@@ -1,5 +1,6 @@
1
1
  export type UserData = {
2
2
  name: string;
3
+ phone?: string;
3
4
  isActive?: boolean;
4
5
  id: string;
5
6
  email?: string;
@@ -2,6 +2,7 @@ export type UserDataService = {
2
2
  userId: string;
3
3
  name: string;
4
4
  email: string;
5
+ phone?: string;
5
6
  role: string[];
6
7
  orgUnit: string;
7
8
  };