@vtex/faststore-plugin-buyer-portal 1.0.28 → 1.0.29

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 (29) hide show
  1. package/package.json +1 -1
  2. package/public/buyer-portal-icons.svg +4 -10
  3. package/src/clients/BuyerPortalClient.ts +16 -11
  4. package/src/clients/Client.ts +41 -25
  5. package/src/features/org-units/components/CreateOrgUnitDrawer/CreateOrgUnitDrawer.tsx +13 -1
  6. package/src/features/org-units/components/DeleteOrgUnitDrawer/DeleteOrgUnitDrawer.tsx +66 -67
  7. package/src/features/org-units/components/DeleteOrgUnitDrawer/delete-org-unit-drawer.scss +17 -1
  8. package/src/features/org-units/components/OrgUnitsDropdownMenu/OrgUnitsDropdownMenu.tsx +21 -21
  9. package/src/features/org-units/components/OrgUnitsHierarchyTree/OrgUnitsHierarchyTree.tsx +2 -9
  10. package/src/features/org-units/components/OrganizationalUnitsCard/OrganizationalUnitsCard.tsx +0 -1
  11. package/src/features/org-units/components/UpdateOrgUnitDrawer/UpdateOrgUnitDrawer.tsx +12 -32
  12. package/src/features/org-units/hooks/useCreateNewOrgUnit.ts +1 -1
  13. package/src/features/org-units/hooks/useUpdateOrgUnit.ts +1 -1
  14. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +19 -2
  15. package/src/features/org-units/layouts/OrgUnitsLayout/OrgUnitsLayout.tsx +17 -1
  16. package/src/features/org-units/layouts/OrgUnitsLayout/org-units-layout.scss +7 -1
  17. package/src/features/org-units/types/OrgUnitsData.ts +1 -1
  18. package/src/features/shared/components/BasicDropdownMenu/BasicDropdownMenu.tsx +4 -0
  19. package/src/features/shared/components/BasicDropdownMenu/basic-dropdown-menu.scss +27 -12
  20. package/src/features/shared/components/InputText/InputText.tsx +2 -2
  21. package/src/features/shared/components/Toast/Toast.tsx +1 -3
  22. package/src/features/shared/components/Toast/toast.scss +8 -0
  23. package/src/features/shared/components/index.ts +2 -0
  24. package/src/features/shared/hooks/useBuyerPortal.ts +1 -4
  25. package/src/features/shared/layouts/GlobalLayout/GlobalLayout.tsx +1 -2
  26. package/src/features/shared/layouts/HomeLayout/HomeLayout.tsx +19 -2
  27. package/src/features/shared/layouts/HomeLayout/home-layout.scss +10 -4
  28. package/src/features/shared/utils/getClientContext.ts +0 -2
  29. package/src/features/shared/utils/getTreeDepth.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -211,17 +211,11 @@
211
211
  d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" />
212
212
  </symbol>
213
213
 
214
-
215
- <symbol
216
- id="Profile"
217
- xmlns="http://www.w3.org/2000/svg"
218
- viewBox="0 0 14 14"
219
- fill="none"
220
- >
214
+ <symbol id="Profile" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"
215
+ fill="none">
221
216
  <path
222
- d="M7.00065 7.00114C6.08398 7.00114 5.29926 6.67475 4.64648 6.02197C3.99371 5.3692 3.66732 4.58447 3.66732 3.66781C3.66732 2.75114 3.99371 1.96642 4.64648 1.31364C5.29926 0.660862 6.08398 0.334473 7.00065 0.334473C7.91732 0.334473 8.70204 0.660862 9.35482 1.31364C10.0076 1.96642 10.334 2.75114 10.334 3.66781C10.334 4.58447 10.0076 5.3692 9.35482 6.02197C8.70204 6.67475 7.91732 7.00114 7.00065 7.00114ZM0.333984 13.6678V11.3345C0.333984 10.8623 0.455512 10.4282 0.698568 10.0324C0.941623 9.63656 1.26454 9.33447 1.66732 9.12614C2.52843 8.69559 3.40343 8.37267 4.29232 8.15739C5.18121 7.94211 6.08398 7.83447 7.00065 7.83447C7.91732 7.83447 8.8201 7.94211 9.70899 8.15739C10.5979 8.37267 11.4729 8.69559 12.334 9.12614C12.7368 9.33447 13.0597 9.63656 13.3027 10.0324C13.5458 10.4282 13.6673 10.8623 13.6673 11.3345V13.6678H0.333984Z"
223
- fill="currentColor"
224
- />
217
+ d="M6 6C5.16667 6 4.45833 5.70833 3.875 5.125C3.29167 4.54167 3 3.83333 3 3C3 2.16667 3.29167 1.45833 3.875 0.875C4.45833 0.291667 5.16667 0 6 0C6.83333 0 7.54167 0.291667 8.125 0.875C8.70833 1.45833 9 2.16667 9 3C9 3.83333 8.70833 4.54167 8.125 5.125C7.54167 5.70833 6.83333 6 6 6ZM0 12V10C0 9.68056 0.0868056 9.37847 0.260417 9.09375C0.434028 8.80903 0.673611 8.56944 0.979167 8.375C1.74306 7.93056 2.55063 7.59028 3.40188 7.35417C4.25313 7.11806 5.11771 7 5.99563 7C6.87354 7 7.73958 7.11806 8.59375 7.35417C9.44792 7.59028 10.2569 7.93056 11.0208 8.375C11.3264 8.55556 11.566 8.79167 11.7396 9.08333C11.9132 9.375 12 9.68056 12 10V12H0ZM1.5 10.5H10.5V10C10.5 9.92806 10.479 9.86271 10.4369 9.80396C10.3949 9.74507 10.3396 9.69931 10.2708 9.66667C9.63194 9.27778 8.95139 8.98611 8.22917 8.79167C7.50694 8.59722 6.76389 8.5 6 8.5C5.23611 8.5 4.49306 8.59722 3.77083 8.79167C3.04861 8.98611 2.36806 9.27778 1.72917 9.66667C1.65972 9.72222 1.60417 9.77583 1.5625 9.8275C1.52083 9.87931 1.5 9.93681 1.5 10V10.5ZM6.00437 4.5C6.41813 4.5 6.77083 4.35271 7.0625 4.05812C7.35417 3.76354 7.5 3.40937 7.5 2.99562C7.5 2.58187 7.35271 2.22917 7.05812 1.9375C6.76354 1.64583 6.40938 1.5 5.99563 1.5C5.58188 1.5 5.22917 1.64729 4.9375 1.94188C4.64583 2.23646 4.5 2.59063 4.5 3.00438C4.5 3.41813 4.64729 3.77083 4.94187 4.0625C5.23646 4.35417 5.59062 4.5 6.00437 4.5Z"
218
+ fill="currentColor" />
225
219
  </symbol>
226
220
 
227
221
  <symbol
@@ -89,15 +89,22 @@ class BuyerPortalClient extends Client {
89
89
  }
90
90
 
91
91
  createOrgUnit(
92
- {
93
- parentId,
94
- ...data
95
- }: { name: string; parentId: string | null; customerId: string[] },
92
+ data: { name: string; parentId?: string | null; customerId: string[] },
96
93
  cookie: string
97
94
  ) {
98
- return this.post(
95
+ const { parentId, ...input } = data;
96
+ return this.post<
97
+ {
98
+ customerIds: string[];
99
+ id: string;
100
+ isActive: boolean;
101
+ name: string;
102
+ parentId: string;
103
+ },
104
+ typeof data
105
+ >(
99
106
  "units",
100
- { ...data, ...(parentId && { parentId }) },
107
+ { ...input, ...(parentId && { parentId }) },
101
108
  {
102
109
  headers: {
103
110
  Cookie: cookie,
@@ -106,11 +113,9 @@ class BuyerPortalClient extends Client {
106
113
  );
107
114
  }
108
115
 
109
- updateOrgUnit(
110
- { id, ...data }: { name?: string; id: string },
111
- cookie: string
112
- ) {
113
- return this.patch(`units/${id}`, data, {
116
+ updateOrgUnit(data: { name?: string; id: string }, cookie: string) {
117
+ const { id, ...input } = data;
118
+ return this.patch<null, typeof input>(`units/${id}`, input, {
114
119
  headers: {
115
120
  Cookie: cookie,
116
121
  },
@@ -1,9 +1,9 @@
1
1
  import { toQueryParams } from "../features/shared/utils";
2
2
 
3
- interface RequestConfig<T> {
3
+ interface RequestConfig<Input = never> {
4
4
  url: string;
5
5
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
6
- data?: T;
6
+ data?: Input;
7
7
  headers?: Record<string, string>;
8
8
  params?: Record<string, string | number | boolean>;
9
9
  }
@@ -15,13 +15,13 @@ class Client {
15
15
  this.baseURL = baseURL;
16
16
  }
17
17
 
18
- async request<T>({
18
+ async request<Return, Input = never>({
19
19
  url,
20
20
  method,
21
21
  data,
22
22
  headers = {},
23
23
  params = {},
24
- }: RequestConfig<T>): Promise<T> {
24
+ }: RequestConfig<Input>): Promise<Return> {
25
25
  const config: RequestInit = {
26
26
  method,
27
27
  headers: {
@@ -38,7 +38,7 @@ class Client {
38
38
 
39
39
  try {
40
40
  const response = await fetch(this.baseURL + url + paramsString, config);
41
- let responseData: T | null = null;
41
+ let responseData: Return | null = null;
42
42
 
43
43
  const contentType = response.headers.get("Content-Type");
44
44
  if (contentType?.includes("application/json")) {
@@ -52,42 +52,58 @@ class Client {
52
52
  );
53
53
  }
54
54
 
55
- return responseData as T;
55
+ return responseData as Return;
56
56
  } catch (error) {
57
57
  return Promise.reject(error);
58
58
  }
59
59
  }
60
60
 
61
- get<T>(url: string, config: Partial<RequestConfig<T>> = {}): Promise<T> {
62
- return this.request<T>({ url, method: "GET", ...config });
61
+ get<Return>(
62
+ url: string,
63
+ config: Partial<RequestConfig> = {}
64
+ ): Promise<Return> {
65
+ return this.request<Return>({ url, method: "GET", ...config });
63
66
  }
64
67
 
65
- post<T>(
68
+ post<Return, Input>(
66
69
  url: string,
67
- data: T,
68
- config: Partial<RequestConfig<T>> = {}
69
- ): Promise<T> {
70
- return this.request<T>({ url, method: "POST", data, ...config });
70
+ data: Input,
71
+ config: Partial<RequestConfig<Input>> = {}
72
+ ): Promise<Return> {
73
+ return this.request<Return, Input>({
74
+ url,
75
+ method: "POST",
76
+ data,
77
+ ...config,
78
+ });
71
79
  }
72
80
 
73
- put<T>(
81
+ put<Return, Input>(
74
82
  url: string,
75
- data: T,
76
- config: Partial<RequestConfig<T>> = {}
77
- ): Promise<T> {
78
- return this.request<T>({ url, method: "PUT", data, ...config });
83
+ data: Input,
84
+ config: Partial<RequestConfig<Input>> = {}
85
+ ): Promise<Return> {
86
+ return this.request<Return, Input>({ url, method: "PUT", data, ...config });
79
87
  }
80
88
 
81
- patch<T>(
89
+ patch<Return, Input>(
82
90
  url: string,
83
- data: T,
84
- config: Partial<RequestConfig<T>> = {}
85
- ): Promise<T> {
86
- return this.request<T>({ url, method: "PATCH", data, ...config });
91
+ data: Input,
92
+ config: Partial<RequestConfig<Input>> = {}
93
+ ): Promise<Return> {
94
+ return this.request<Return, Input>({
95
+ url,
96
+ method: "PATCH",
97
+ data,
98
+ ...config,
99
+ });
87
100
  }
88
101
 
89
- delete<T>(url: string, config: Partial<RequestConfig<T>> = {}): Promise<T> {
90
- return this.request<T>({ url, method: "DELETE", ...config });
102
+ delete<Return>(
103
+ url: string,
104
+ config: Partial<RequestConfig<never>> = {}
105
+ ): Promise<Return> {
106
+ return this.request<Return>({ url, method: "DELETE", ...config });
91
107
  }
92
108
  }
93
109
 
@@ -11,6 +11,7 @@ import {
11
11
  Icon,
12
12
  } from "../../../shared/components";
13
13
  import { useCreateNewOrgUnit } from "../../hooks";
14
+ import type { createNewOrgUnitService } from "../../services";
14
15
 
15
16
  export type CreateOrgUnitDrawerProps = Omit<BasicDrawerProps, "children"> & {
16
17
  options?: { id: string; name: string }[];
@@ -35,10 +36,21 @@ export const CreateOrgUnitDrawer = ({
35
36
 
36
37
  const [isTouched, setIsTouched] = useState(false);
37
38
 
38
- const handleCreateNewOrgUnitSuccess = () => {
39
+ const handleCreateNewOrgUnitSuccess = (
40
+ data: AwaitedType<typeof createNewOrgUnitService>
41
+ ) => {
39
42
  pushToast({
40
43
  message: "Organizational unit added successfully",
41
44
  status: "INFO",
45
+ icon: (
46
+ <button
47
+ data-fs-bp-toast-view-button
48
+ type="button"
49
+ onClick={() => router.push(`/org-unit/${data.id}`)}
50
+ >
51
+ View
52
+ </button>
53
+ ),
42
54
  });
43
55
  close();
44
56
  router.replace(router.asPath);
@@ -8,10 +8,13 @@ import { useChildrenOrgUnits } from "../../hooks";
8
8
  import { useUsersByOrgUnit } from "../../../users/hooks";
9
9
  import {
10
10
  BasicDrawer,
11
+ ErrorMessage,
11
12
  Icon,
13
+ InputText,
12
14
  Tab,
13
15
  type BasicDrawerProps,
14
16
  } from "../../../shared/components";
17
+ import { useState } from "react";
15
18
 
16
19
  export type DeleteOrgUnitDrawerProps = Omit<BasicDrawerProps, "children"> & {
17
20
  id: string;
@@ -26,14 +29,12 @@ export const DeleteOrgUnitDrawer = ({
26
29
  }: DeleteOrgUnitDrawerProps) => {
27
30
  const router = useRouter();
28
31
  const { childrenOrgUnits } = useChildrenOrgUnits(id);
29
- const { users } = useUsersByOrgUnit(id);
30
32
  const { pushToast } = useUI();
31
33
 
32
- const hasChildren = Boolean(childrenOrgUnits?.nodes?.length);
33
-
34
- const hasUsers = Boolean(users?.length);
34
+ const [confirmName, setConfirmName] = useState("");
35
+ const [isTouched, setIsTouched] = useState(false);
35
36
 
36
- const hasContent = hasChildren || hasUsers;
37
+ const hasChildren = Boolean(childrenOrgUnits?.nodes?.length);
37
38
 
38
39
  const handleDeleteOrgUnitSuccess = () => {
39
40
  pushToast({
@@ -41,6 +42,11 @@ export const DeleteOrgUnitDrawer = ({
41
42
  status: "INFO",
42
43
  });
43
44
  close();
45
+
46
+ if (router.asPath === `/org-unit/${id}`) {
47
+ router.replace("/org-units/${id}");
48
+ return;
49
+ }
44
50
  router.replace(router.asPath);
45
51
  };
46
52
 
@@ -49,87 +55,80 @@ export const DeleteOrgUnitDrawer = ({
49
55
  });
50
56
 
51
57
  const handleDelete = () => {
52
- deleteOrgUnit({ id });
58
+ if (hasChildren) {
59
+ close();
60
+ }
61
+
62
+ setIsTouched(true);
63
+ if (confirmName === name) {
64
+ deleteOrgUnit({ id });
65
+ }
53
66
  };
54
67
 
55
68
  return (
56
69
  <BasicDrawer
57
70
  data-fs-bp-delete-org-unit-drawer
58
- data-fs-bp-delete-org-unit-drawer-has-content={hasContent}
71
+ data-fs-bp-delete-org-unit-drawer-has-content={hasChildren}
59
72
  close={close}
60
73
  {...props}
61
74
  >
62
75
  <BasicDrawer.Heading title="Delete organizational unit" onClose={close} />
63
76
  <BasicDrawer.Body>
64
- <p data-fs-bp-delete-org-unit-drawer-message>
65
- Are you sure you want to permanently remove the
66
- <span
67
- data-fs-bp-delete-org-unit-drawer-message-name
68
- >{` ${name} `}</span>
69
- organizational unit?
70
- </p>
71
-
72
- {!hasContent && (
77
+ {!hasChildren && (
73
78
  <>
74
- <br />
75
- <br />
79
+ <p data-fs-bp-delete-org-unit-drawer-message>
80
+ Are you sure you want to permanently delete the
81
+ <span
82
+ data-fs-bp-delete-org-unit-drawer-message-name
83
+ >{` ${name}`}</span>
84
+ ?
85
+ </p>
76
86
  <p data-fs-bp-delete-org-unit-drawer-message>
77
87
  This action will delete all data from this unit.
88
+ <br />
89
+ To confirm this action, please enter the organizational unit name
90
+ bellow:
78
91
  </p>
92
+
93
+ <InputText
94
+ label="Name"
95
+ value={confirmName}
96
+ onChange={(e) => setConfirmName(e.target.value)}
97
+ hasError={isTouched && confirmName !== name}
98
+ />
99
+
100
+ <ErrorMessage
101
+ show={isTouched && confirmName !== name}
102
+ message="The entered name does not match the organizational unit's name. Please, check and try again."
103
+ />
79
104
  </>
80
105
  )}
81
106
 
82
- {hasContent && (
107
+ {hasChildren && (
83
108
  <>
84
109
  <p data-fs-bp-delete-org-unit-drawer-message>
85
- This action will delete all data from this unit, including the
86
- child organizational units and users below.
110
+ Before deleting
111
+ <span data-fs-bp-delete-org-unit-drawer-message-name>
112
+ {` ${name} `}
113
+ </span>
114
+ , you must first remove all child organizational units.
115
+ <br />
116
+ Please delete them individually, starting from the lowest level.
117
+ <br />
118
+ <br />
119
+ Once all child units bellow are removed, you’ll be able to delete{" "}
120
+ {name}.
87
121
  </p>
88
122
 
89
- <Tab defaultTabId={hasChildren ? "child" : "users"}>
90
- <Tab.Bar>
91
- {hasChildren && (
92
- <Tab.Option id="child">
93
- Child Organizational Units ({users?.length ?? 0})
94
- </Tab.Option>
95
- )}
96
- {hasUsers && (
97
- <Tab.Option id="users">
98
- Users ({users?.length ?? 0})
99
- </Tab.Option>
100
- )}
101
- </Tab.Bar>
102
- <Tab.Content id="child">
103
- {childrenOrgUnits && (
104
- <OrgUnitsHierarchyTree
105
- readonly
106
- orgUnitHierarchyData={childrenOrgUnits}
107
- />
108
- )}
109
- </Tab.Content>
110
- <Tab.Content id="users">
111
- {users?.map((user) => (
112
- <div
113
- data-fs-bp-delete-org-unit-drawer-user-line
114
- key={user.userId}
115
- >
116
- <span data-fs-bp-delete-org-unit-drawer-user-icon>
117
- <Icon name="Profile" width={16} height={16} />
118
- </span>
119
-
120
- <span data-fs-bp-delete-org-unit-drawer-user-name>
121
- {user.name}
122
- </span>
123
- <span data-fs-bp-delete-org-unit-drawer-user-org>
124
- {user.orgUnit.name}
125
- </span>
126
- <span data-fs-bp-delete-org-unit-drawer-user-type>
127
- {user.userType}
128
- </span>
129
- </div>
130
- ))}
131
- </Tab.Content>
132
- </Tab>
123
+ <span data-fs-bp-delete-org-unit-drawer-subtitle>
124
+ Organizational Units
125
+ </span>
126
+ {childrenOrgUnits && (
127
+ <OrgUnitsHierarchyTree
128
+ readonly
129
+ orgUnitHierarchyData={childrenOrgUnits}
130
+ />
131
+ )}
133
132
  </>
134
133
  )}
135
134
  </BasicDrawer.Body>
@@ -139,10 +138,10 @@ export const DeleteOrgUnitDrawer = ({
139
138
  </BasicDrawer.Button>
140
139
  <BasicDrawer.Button
141
140
  onClick={handleDelete}
142
- variant="danger"
141
+ variant={hasChildren ? "confirm" : "danger"}
143
142
  isLoading={isDeleteOrgUnitLoading}
144
143
  >
145
- Delete
144
+ {hasChildren ? "Ok" : "Delete"}
146
145
  </BasicDrawer.Button>
147
146
  </BasicDrawer.Footer>
148
147
  </BasicDrawer>
@@ -2,6 +2,8 @@
2
2
 
3
3
  [data-fs-bp-basic-drawer][data-fs-bp-delete-org-unit-drawer] {
4
4
  @import "../../../shared/components/Tab/tab.scss";
5
+ @import "../../../shared/components/InputText/input-text.scss";
6
+ @import "../../../shared/components/ErrorMessage/error-message.scss";
5
7
  @import "../OrgUnitsHierarchyTree/org-units-hierarchy-tree.scss";
6
8
 
7
9
  &[data-fs-bp-delete-org-unit-drawer-has-content="true"] {
@@ -13,16 +15,30 @@
13
15
  }
14
16
 
15
17
  [data-fs-bp-delete-org-unit-drawer-message] {
16
- font-weight: 400;
18
+ font-weight: var(--fs-text-weight-regular);
17
19
  font-size: var(--fs-text-size-1);
18
20
  line-height: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
19
21
  color: #1f1f1f;
22
+ margin-bottom: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
20
23
 
21
24
  [data-fs-bp-delete-org-unit-drawer-message-name] {
22
25
  color: #0068d7;
23
26
  }
24
27
  }
25
28
 
29
+ [data-fs-bp-delete-org-unit-drawer-subtitle] {
30
+ font-weight: var(--fs-text-weight-medium);
31
+ font-size: var(--fs-text-size-1);
32
+ line-height: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
33
+ padding: var(--fs-spacing-2) 0;
34
+ color: #707070;
35
+ display: block;
36
+ }
37
+
38
+ [data-fs-bp-input-text] {
39
+ margin-top: var(--fs-spacing-5);
40
+ }
41
+
26
42
  [data-fs-bp-tab-bar] {
27
43
  margin-top: var(--fs-spacing-4);
28
44
  margin-bottom: var(--fs-spacing-3);
@@ -12,17 +12,16 @@ import { BasicDropdownMenu, Icon } from "../../../shared/components";
12
12
  export type OrgUnitsDropdownMenuProps = {
13
13
  id: string;
14
14
  name: string;
15
- parentOrgUnit: { id: string | null; name: string };
16
15
  onUpdate?: () => void;
16
+ isComplete?: boolean;
17
17
  };
18
18
 
19
19
  export const OrgUnitsDropdownMenu = ({
20
20
  id,
21
21
  name,
22
- parentOrgUnit,
23
22
  onUpdate,
23
+ isComplete = true,
24
24
  }: OrgUnitsDropdownMenuProps) => {
25
- const { push } = useRouter();
26
25
  const { open: openCreateDrawerProps, ...createDrawerProps } =
27
26
  useDrawerProps();
28
27
 
@@ -37,31 +36,33 @@ export const OrgUnitsDropdownMenu = ({
37
36
  return (
38
37
  <>
39
38
  <BasicDropdownMenu>
40
- <DropdownItem onClick={() => push(`/org-unit/${id}`)}>
41
- <Icon name="ArrowSquareOut" {...sizeProps} />
42
- Open
43
- </DropdownItem>
44
39
  <DropdownItem onClick={openUpdateDrawerProps}>
45
40
  <Icon name="Edit" {...sizeProps} />
46
41
  Edit
47
42
  </DropdownItem>
48
- <DropdownItem>
49
- <Icon name="Profile" {...sizeProps} />
50
- Add User
51
- </DropdownItem>
52
- <DropdownItem onClick={openCreateDrawerProps}>
53
- <Icon name="Folder" {...sizeProps} />
54
- Add Organizational Unit
55
- </DropdownItem>
43
+ <BasicDropdownMenu.Separator />
44
+ {isComplete && (
45
+ <>
46
+ <DropdownItem>
47
+ <Icon name="Profile" {...sizeProps} />
48
+ Add User
49
+ </DropdownItem>
50
+ <DropdownItem onClick={openCreateDrawerProps}>
51
+ <Icon name="Folder" {...sizeProps} />
52
+ Add Organizational Unit
53
+ </DropdownItem>
54
+ <BasicDropdownMenu.Separator />
55
+ <DropdownItem>
56
+ <Icon name="LocalPostOffice" {...sizeProps} />
57
+ Add Address
58
+ </DropdownItem>
59
+ <BasicDropdownMenu.Separator />
60
+ </>
61
+ )}
56
62
  <DropdownItem onClick={openDeleteDrawerProps}>
57
63
  <Icon name="Delete" {...sizeProps} />
58
64
  Delete
59
65
  </DropdownItem>
60
- <DropdownItem dismissOnClick={false}>
61
- <Icon name="Active" {...sizeProps} />
62
- Active
63
- <Toggle id={id} />
64
- </DropdownItem>
65
66
  </BasicDropdownMenu>
66
67
  <CreateOrgUnitDrawer
67
68
  readonly
@@ -71,7 +72,6 @@ export const OrgUnitsDropdownMenu = ({
71
72
  <DeleteOrgUnitDrawer id={id} name={name} {...deleteDrawerProps} />
72
73
  <UpdateOrgUnitDrawer
73
74
  readonly
74
- parentOrgUnit={parentOrgUnit}
75
75
  id={id}
76
76
  name={name}
77
77
  onSuccess={onUpdate}
@@ -14,8 +14,7 @@ export const OrgUnitsHierarchyTree = ({
14
14
  orgUnitHierarchyData,
15
15
  readonly,
16
16
  }: OrgUnitsHierarchyTreeProps) => {
17
- const { structure, setNode, getParentNode } =
18
- useOrgUnitStructure(orgUnitHierarchyData);
17
+ const { structure, setNode } = useOrgUnitStructure(orgUnitHierarchyData);
19
18
 
20
19
  return (
21
20
  <HierarchyTree
@@ -31,8 +30,6 @@ export const OrgUnitsHierarchyTree = ({
31
30
  },
32
31
  });
33
32
 
34
- const parentOrgUnit = getParentNode(id) ?? { name: "Root", id: null };
35
-
36
33
  return (
37
34
  <>
38
35
  <span data-fs-org-unit-item>
@@ -77,11 +74,7 @@ export const OrgUnitsHierarchyTree = ({
77
74
  <Icon name="MoreVert" width={24} height={24} />
78
75
  </button>
79
76
  </DropdownButton>
80
- <OrgUnitsDropdownMenu
81
- parentOrgUnit={parentOrgUnit}
82
- id={id}
83
- name={name}
84
- />
77
+ <OrgUnitsDropdownMenu id={id} name={name} />
85
78
  </Dropdown>
86
79
  </div>
87
80
  )}
@@ -60,7 +60,6 @@ export default function OrganizationalUnitsCard({
60
60
  <Icon name="MoreVert" data-fs-menu-action-button />
61
61
  </DropdownButton>
62
62
  <OrgUnitsDropdownMenu
63
- parentOrgUnit={orgUnitData}
64
63
  id={orgUnit.id}
65
64
  name={orgUnit.name}
66
65
  />
@@ -18,7 +18,6 @@ export type UpdateOrgUnitDrawerProps = Omit<BasicDrawerProps, "children"> & {
18
18
  id: string;
19
19
  name: string;
20
20
  readonly?: boolean;
21
- parentOrgUnit: { id: string | null; name: string };
22
21
  onSuccess?: () => void;
23
22
  };
24
23
 
@@ -28,7 +27,6 @@ export const UpdateOrgUnitDrawer = ({
28
27
  id,
29
28
  name: initialName,
30
29
  readonly,
31
- parentOrgUnit,
32
30
  onSuccess,
33
31
  ...props
34
32
  }: UpdateOrgUnitDrawerProps) => {
@@ -40,8 +38,17 @@ export const UpdateOrgUnitDrawer = ({
40
38
 
41
39
  const handleSuccess = () => {
42
40
  pushToast({
43
- message: "Child organizational unit edited successfully",
41
+ message: "Organizational unit successfully edited",
44
42
  status: "INFO",
43
+ icon: (
44
+ <button
45
+ data-fs-bp-toast-view-button
46
+ type="button"
47
+ onClick={() => router.push(`/org-unit/${id}`)}
48
+ >
49
+ View
50
+ </button>
51
+ ),
45
52
  });
46
53
  close();
47
54
  onSuccess?.() ?? router.reload();
@@ -54,7 +61,7 @@ export const UpdateOrgUnitDrawer = ({
54
61
  const handleConfirmClick = () => {
55
62
  setIsTouched(true);
56
63
 
57
- if (!name?.trim() || !parentOrgUnit.name?.trim()) {
64
+ if (!name?.trim()) {
58
65
  return;
59
66
  }
60
67
 
@@ -64,39 +71,12 @@ export const UpdateOrgUnitDrawer = ({
64
71
  });
65
72
  };
66
73
 
67
- const isConfirmButtonEnabled =
68
- !isUpdateOrgUnitLoading && name && parentOrgUnit.name;
74
+ const isConfirmButtonEnabled = !isUpdateOrgUnitLoading && name;
69
75
 
70
76
  return (
71
77
  <BasicDrawer data-fs-bp-update-org-unit-drawer close={close} {...props}>
72
78
  <BasicDrawer.Heading title="Edit organization unit" onClose={close} />
73
79
  <BasicDrawer.Body>
74
- <AutocompleteDropdown
75
- label="Parent"
76
- value={parentOrgUnit.name}
77
- options={options}
78
- disabled={readonly}
79
- hasError={isTouched && !parentOrgUnit.name?.trim()}
80
- renderOption={(option, index) => (
81
- <AutocompleteDropdown.Item
82
- key={option.id}
83
- closeOnClick
84
- index={index}
85
- isSelected={parentOrgUnit?.id === option?.id}
86
- >
87
- {option?.name}
88
- {parentOrgUnit?.id === option?.id && (
89
- <Icon name="Check" width={12} height={12} />
90
- )}
91
- </AutocompleteDropdown.Item>
92
- )}
93
- />
94
-
95
- <ErrorMessage
96
- show={isTouched && !parentOrgUnit.name?.trim()}
97
- message="Parent is required"
98
- />
99
-
100
80
  <InputText
101
81
  label="Name"
102
82
  value={name}
@@ -8,7 +8,7 @@ export const useCreateNewOrgUnit = (
8
8
  options?: MutationOptions<AwaitedType<typeof createNewOrgUnitService>>
9
9
  ) => {
10
10
  const { mutate, isLoading, error } = useMutation<
11
- { parentId?: string; name: string; customerId: string[] },
11
+ AwaitedType<typeof createNewOrgUnitService>,
12
12
  Omit<CreateNewOrgUnitServiceProps, "cookie" | "customerId">
13
13
  >(
14
14
  (variables, clientContext) =>
@@ -8,7 +8,7 @@ export const useUpdateOrgUnit = (
8
8
  options?: MutationOptions<AwaitedType<typeof updateOrgUnitService>>
9
9
  ) => {
10
10
  const { mutate, isLoading, error } = useMutation<
11
- { name?: string | undefined },
11
+ null,
12
12
  Omit<UpdateOrgUnitServiceProps, "cookie">
13
13
  >(
14
14
  (variables, clientContext) =>
@@ -1,11 +1,16 @@
1
+ import { Dropdown, DropdownButton } from "@faststore/ui";
1
2
  import { AddressesCard } from "../../../addresses/components";
2
3
  import { ContractsCard } from "../../../contracts/components";
3
4
  import { ProfileCard } from "../../../profile/components";
4
5
  import { GlobalLayout } from "../../../shared/layouts";
5
6
  import { UsersCard } from "../../../users/components";
6
7
  import type { UserData } from "../../../users/types";
7
- import { OrganizationalUnitsCard } from "../../components";
8
+ import {
9
+ OrganizationalUnitsCard,
10
+ OrgUnitsDropdownMenu,
11
+ } from "../../components";
8
12
  import type { OrgUnitData, OrgUnitSummaryData } from "../../types";
13
+ import { Icon } from "../../../shared/components";
9
14
 
10
15
  export type OrgUnitsDetailsLayoutProps = {
11
16
  data: OrgUnitSummaryData;
@@ -19,7 +24,19 @@ export const OrgUnitsDetailsLayout = ({ data }: OrgUnitsDetailsLayoutProps) => {
19
24
  return (
20
25
  <GlobalLayout>
21
26
  <section data-fs-home-section>
22
- <h1 data-fs-home-title>{data.name}</h1>
27
+ <div data-fs-home-title-wrapper>
28
+ <h1 data-fs-home-title>{data.name}</h1>
29
+ <Dropdown>
30
+ <DropdownButton data-fs-card-row-dropdown-button>
31
+ <Icon name="MoreVert" data-fs-menu-action-button />
32
+ </DropdownButton>
33
+ <OrgUnitsDropdownMenu
34
+ isComplete={false}
35
+ id={data.id}
36
+ name={data.name}
37
+ />
38
+ </Dropdown>
39
+ </div>
23
40
  <div data-fs-home-grid>
24
41
  {hasContracts ? (
25
42
  <ProfileCard orgUnitData={data} {...data.contracts[0]} />
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  Icon,
3
+ InternalSearch,
3
4
  InternalTopBar,
4
5
  MainLinksDropdownMenu,
5
6
  } from "../../../shared/components";
6
- import { useDrawerProps } from "../../../shared/hooks";
7
+ import { useDrawerProps, useQueryParams } from "../../../shared/hooks";
7
8
  import { GlobalLayout } from "../../../shared/layouts";
8
9
  import { CreateOrgUnitDrawer, OrgUnitsHierarchyTree } from "../../components";
9
10
  import type { OrgUnitHierarchyData } from "../../types";
@@ -13,6 +14,8 @@ export type OrgUnitsLayoutProps = {
13
14
  };
14
15
 
15
16
  export const OrgUnitsLayout = ({ data }: OrgUnitsLayoutProps) => {
17
+ const { setQueryString, removeQueryString } = useQueryParams();
18
+
16
19
  const { open, ...drawerProps } = useDrawerProps();
17
20
 
18
21
  return (
@@ -29,6 +32,19 @@ export const OrgUnitsLayout = ({ data }: OrgUnitsLayoutProps) => {
29
32
  Add New
30
33
  </button>
31
34
  </InternalTopBar>
35
+
36
+ <div data-fs-buyer-portal-org-units-filter>
37
+ <InternalSearch
38
+ textSearch={(searchTerm) => {
39
+ searchTerm
40
+ ? setQueryString("search", searchTerm)
41
+ : removeQueryString("search");
42
+ }}
43
+ />
44
+
45
+ <hr data-fs-org-units-divider />
46
+ </div>
47
+
32
48
  <section data-fs-hierarchy-tree-wrapper>
33
49
  {data?.organizationalUnits?.map((orgUnit) => (
34
50
  <OrgUnitsHierarchyTree
@@ -11,11 +11,17 @@
11
11
 
12
12
  @import "../../../shared/components/HierarchyTree/hierarchy-tree.scss";
13
13
  @import "../../../shared/components/InternalTopbar/internal-top-bar.scss";
14
+ @import "../../../shared/components/InternalSearch/internal-search.scss";
14
15
 
15
16
  [data-fs-buyer-portal-org-units-filter] {
16
17
  display: flex;
17
18
  justify-content: space-between;
18
- padding: var(--fs-spacing-4);
19
+ padding: var(--fs-spacing-6) calc(var(--fs-spacing-8) + var(--fs-spacing-0))
20
+ var(--fs-spacing-4);
21
+ }
22
+
23
+ [data-fs-org-units-divider] {
24
+ border: calc(var(--fs-border-width) / 2) solid #e0e0e0;
19
25
  }
20
26
 
21
27
  [data-fs-buyer-portal-org-units-filter-search-container] {
@@ -1,4 +1,4 @@
1
- import type { Structure } from "../../shared/components/HierarchyTree/HierarchyTree";
1
+ import type { Structure } from "../../shared/components";
2
2
 
3
3
  export type OrgUnitData = {
4
4
  name: string;
@@ -11,3 +11,7 @@ export const BasicDropdownMenu = ({ children }: BasicDropdownMenuProps) => {
11
11
  </DropdownMenu>
12
12
  );
13
13
  };
14
+
15
+ const Separator = () => <div data-fs-bp-basic-dropdown-menu-separator />;
16
+
17
+ BasicDropdownMenu.Separator = Separator;
@@ -1,19 +1,34 @@
1
- [data-fs-bp-basic-dropdown-menu] {
2
- width: auto;
3
- [data-fs-dropdown-item] {
4
- display: flex;
5
- padding: var(--fs-spacing-1) calc(var(--fs-spacing-3) + var(--fs-spacing-0));
1
+ [data-fs-dropdown-menu] {
2
+ &[data-fs-bp-basic-dropdown-menu] {
3
+ width: auto;
4
+ background-color: #fff;
5
+ padding: var(--fs-spacing-1) 0;
6
6
 
7
- font-weight: 500;
8
- font-size: var(--fs-text-size-1);
9
- line-height: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
7
+ [data-fs-dropdown-item] {
8
+ display: flex;
9
+ padding: var(--fs-spacing-1)
10
+ calc(var(--fs-spacing-4) + var(--fs-spacing-0));
10
11
 
11
- svg {
12
- margin-right: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
12
+ font-weight: 500;
13
+ font-size: var(--fs-text-size-1);
14
+ line-height: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
15
+ color: #5c5c5c;
16
+
17
+ svg {
18
+ margin-right: calc(var(--fs-spacing-3) + var(--fs-spacing-0));
19
+ color: #5c5c5c;
20
+ }
21
+
22
+ [data-fs-toggle] {
23
+ margin-left: auto;
24
+ }
13
25
  }
14
26
 
15
- [data-fs-toggle] {
16
- margin-left: auto;
27
+ [data-fs-bp-basic-dropdown-menu-separator] {
28
+ border-top: var(--fs-border-width) solid #e0e0e0;
29
+ height: 0;
30
+ width: 100%;
31
+ margin: var(--fs-spacing-1) 0;
17
32
  }
18
33
  }
19
34
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
- ComponentProps,
3
- FocusEvent,
2
+ type ComponentProps,
3
+ type FocusEvent,
4
4
  forwardRef,
5
5
  useEffect,
6
6
  useId,
@@ -35,13 +35,11 @@ function Toast() {
35
35
  data-fs-bp-toast-visible={visible}
36
36
  onTransitionEnd={() => !visible && popToast()}
37
37
  >
38
- {toast.icon && (
39
- <div data-fs-bp-toast-icon-container>{!!toast.icon && toast.icon}</div>
40
- )}
41
38
  <div data-fs-bp-toast-content>
42
39
  {toast.title && <p data-fs-bp-toast-title>{toast.title}</p>}
43
40
  <p data-fs-bp-toast-message>{toast.message}</p>
44
41
  </div>
42
+ {!!toast.icon && toast.icon}
45
43
  <button type="button" data-fs-bp-toast-dismiss onClick={popToast}>
46
44
  <Icon name="Close" width={10} height={10} />
47
45
  </button>
@@ -95,6 +95,14 @@
95
95
  overflow: hidden;
96
96
  }
97
97
 
98
+ [data-fs-bp-toast-view-button] {
99
+ color: #ffffff;
100
+ font-weight: var(--fs-text-weight-semibold);
101
+ font-size: var(--fs-text-size-1);
102
+ line-height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
103
+ cursor: pointer;
104
+ }
105
+
98
106
  [data-fs-bp-toast-title] {
99
107
  margin-left: var(--fs-toast-title-margin-left);
100
108
  overflow: hidden;
@@ -14,6 +14,8 @@ export {
14
14
  export {
15
15
  BuyerPortalProvider,
16
16
  type BuyerPortalProviderProps,
17
+ BuyerPortalContext,
18
+ type BuyerPortalContextType,
17
19
  } from "./BuyerPortalProvider/BuyerPortalProvider";
18
20
  export { Card, CardBody, CardFooter, CardHeader } from "./Card";
19
21
  export {
@@ -1,8 +1,5 @@
1
1
  import { useContext } from "react";
2
- import {
3
- type BuyerPortalContextType,
4
- BuyerPortalContext,
5
- } from "../components/BuyerPortalProvider/BuyerPortalProvider";
2
+ import { type BuyerPortalContextType, BuyerPortalContext } from "../components";
6
3
 
7
4
  export const useBuyerPortal = (): BuyerPortalContextType["clientContext"] => {
8
5
  const context = useContext(BuyerPortalContext);
@@ -1,6 +1,5 @@
1
1
  import { useUI } from "@faststore/ui";
2
- import { Navbar } from "../../components/Navbar/Navbar";
3
- import Toast from "../../components/Toast/Toast";
2
+ import { Navbar, Toast } from "../../components";
4
3
 
5
4
  export type GlobalLayoutProps = { children: React.ReactNode };
6
5
 
@@ -1,10 +1,15 @@
1
+ import { Dropdown, DropdownButton } from "@faststore/ui";
1
2
  import { AddressesCard } from "../../../addresses/components";
2
3
  import { ContractsCard } from "../../../contracts/components";
3
- import { OrganizationalUnitsCard } from "../../../org-units/components";
4
+ import {
5
+ OrganizationalUnitsCard,
6
+ OrgUnitsDropdownMenu,
7
+ } from "../../../org-units/components";
4
8
  import type { OrganizationData } from "../../../org-units/types";
5
9
  import { ProfileCard } from "../../../profile/components";
6
10
  import { UsersCard } from "../../../users/components";
7
11
  import { GlobalLayout } from "../GlobalLayout/GlobalLayout";
12
+ import { Icon } from "../../components";
8
13
 
9
14
  export type HomeLayoutProps = {
10
15
  data: OrganizationData;
@@ -14,7 +19,19 @@ export const HomeLayout = ({ data }: HomeLayoutProps) => {
14
19
  return (
15
20
  <GlobalLayout>
16
21
  <section data-fs-home-section>
17
- <h1 data-fs-home-title>{data.displayName}</h1>
22
+ <div data-fs-home-title-wrapper>
23
+ <h1 data-fs-home-title>{data.displayName}</h1>
24
+ <Dropdown>
25
+ <DropdownButton data-fs-card-row-dropdown-button>
26
+ <Icon name="MoreVert" data-fs-menu-action-button />
27
+ </DropdownButton>
28
+ <OrgUnitsDropdownMenu
29
+ isComplete={false}
30
+ id={data.id}
31
+ name={data.displayName}
32
+ />
33
+ </Dropdown>
34
+ </div>
18
35
  <div data-fs-home-grid>
19
36
  {data.contracts.length === 1 ? (
20
37
  <ProfileCard {...data.contracts[0]} />
@@ -12,10 +12,16 @@
12
12
 
13
13
  padding: 2rem calc(var(--fs-spacing-8) + var(--fs-spacing-0));
14
14
 
15
- [data-fs-home-title] {
16
- text-transform: capitalize;
17
- font-size: var(--fs-text-size-4);
18
- font-weight: 600;
15
+ [data-fs-home-title-wrapper] {
16
+ width: 100%;
17
+ display: flex;
18
+ justify-content: space-between;
19
+ align-items: center;
20
+ [data-fs-home-title] {
21
+ text-transform: capitalize;
22
+ font-size: var(--fs-text-size-4);
23
+ font-weight: 600;
24
+ }
19
25
  }
20
26
 
21
27
  [data-fs-home-grid] {
@@ -20,8 +20,6 @@ export const getClientContext = async ({
20
20
  }: LoaderData): Promise<ClientContext> => {
21
21
  const cookie = getCookieAsString(req.cookies);
22
22
 
23
- console.log(query?.orgUnitId);
24
-
25
23
  const orgUnitId: string = query?.orgUnitId
26
24
  ? Array.isArray(query?.orgUnitId)
27
25
  ? query.orgUnitId[0]
@@ -1,4 +1,4 @@
1
- import { Structure } from "../components/HierarchyTree/HierarchyTree";
1
+ import type { Structure } from "../components";
2
2
 
3
3
  export const getTreeDepth = (node: Structure<{}, "nodes">): number => {
4
4
  return (