@vtex/faststore-plugin-buyer-portal 1.3.64 → 1.3.66

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 (20) hide show
  1. package/CHANGELOG.md +23 -3
  2. package/package.json +1 -1
  3. package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +6 -1
  4. package/src/features/addresses/components/CreateAddressSettingsDrawer/CreateAddressSettingsDrawer.tsx +13 -11
  5. package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +19 -1
  6. package/src/features/custom-fields/layouts/CustomFieldsLayout/CustomFieldsLayout.tsx +15 -0
  7. package/src/features/payment-methods/components/PaymentMethodSettingsDrawer/PaymentMethodSettingsDrawer.tsx +3 -0
  8. package/src/features/payment-methods/components/RemoveMethodButton/RemoveMethodButton.tsx +4 -1
  9. package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +30 -1
  10. package/src/features/payment-methods/layouts/PaymentMethodsLayout/payment-methods-layout.scss +1 -0
  11. package/src/features/product-assortment/components/CollectionsSettingsDrawer/CollectionsSettingsDrawer.tsx +3 -0
  12. package/src/features/product-assortment/components/ProductAssortmentTable/ProductAssortmentTable.tsx +8 -2
  13. package/src/features/product-assortment/components/ProductAssortmentTable/product-assortment-table.scss +43 -37
  14. package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx +36 -9
  15. package/src/features/shared/components/BasicDropdownMenu/basic-dropdown-menu.scss +10 -3
  16. package/src/features/shared/components/CustomField/table/CustomFieldTable.tsx +4 -0
  17. package/src/features/shared/hooks/useGetScopeConfig.ts +3 -0
  18. package/src/features/shared/layouts/CustomFieldLayout/CustomFieldLayout.tsx +3 -0
  19. package/src/features/shared/utils/constants.ts +1 -1
  20. package/src/features/shared/utils/cookie.ts +7 -9
package/CHANGELOG.md CHANGED
@@ -7,17 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.66] - 2026-02-13
11
+
12
+ ### Fixed
13
+
14
+ - Disable "Remove from Unit" actions in sync mode instead of hiding them across Addresses, Payment Methods, Product Assortment, and Custom Fields
15
+ - Fix address settings drawer to allow saving when only changing list type without requiring default address selection
16
+ - Fix automatic UI refresh after switching between Synchronized List and Custom List modes (no F5 needed)
17
+ - Fix stale sync mode state after scope config changes by refetching scope config on updates
18
+ - Fix Product Assortment remove button sizing to remain square when disabled
19
+
20
+ ## [1.3.65] - 2026-02-11
21
+
22
+ ### Fixed
23
+
24
+ - Fix cookie encoding on string transformation
25
+
10
26
  ## [1.3.64] - 2026-02-11
11
27
 
12
28
  ### Fixed
13
- - Fix build error on import invalid type in `OrgUnitBreadcrumbDropdown`
29
+
30
+ - Fix build error on import invalid type in `OrgUnitBreadcrumbDropdown`
14
31
 
15
32
  ## [1.3.63] - 2026-02-10
16
33
 
17
34
  ### Added
35
+
18
36
  - Include `Super Buyer Admin` role validation on mount breadcrumb path
19
37
 
20
38
  ### Changed
39
+
21
40
  - Remove unused `person` property from Layout components
22
41
 
23
42
  ## [1.3.62] - 2026-02-06
@@ -468,7 +487,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
468
487
  - Add CHANGELOG file
469
488
  - Add README file
470
489
 
471
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/1.3.64...HEAD
490
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.66...HEAD
472
491
  [1.3.55]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.54...v1.3.55
473
492
  [1.3.54]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.53...v1.3.54
474
493
  [1.3.53]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.52...v1.3.53
@@ -531,5 +550,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
531
550
  [1.3.60]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.59...v1.3.60
532
551
  [1.3.59]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.58...v1.3.59
533
552
  [1.3.58]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.58
534
-
553
+ [1.3.66]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.65...v1.3.66
554
+ [1.3.65]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.64...v1.3.65
535
555
  [1.3.64]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.64
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.3.64",
3
+ "version": "1.3.66",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -23,12 +23,14 @@ export type AddressDropdownMenuProps = {
23
23
  onOpen?: () => void;
24
24
  onCreate?: () => void;
25
25
  isComplete?: boolean;
26
+ isSyncMode?: boolean;
26
27
  };
27
28
 
28
29
  export const AddressDropdownMenu = ({
29
30
  currentAddress,
30
31
  onUpdate,
31
32
  onOpen,
33
+ isSyncMode = false,
32
34
  }: AddressDropdownMenuProps) => {
33
35
  const router = useRouter();
34
36
  const { pushToast } = useUI();
@@ -179,7 +181,10 @@ export const AddressDropdownMenu = ({
179
181
  )}
180
182
 
181
183
  <BasicDropdownMenu.Separator />
182
- <DropdownItem onClick={openRemoveAddressDrawer}>
184
+ <DropdownItem
185
+ onClick={openRemoveAddressDrawer}
186
+ data-fs-bp-dropdown-menu-item-disabled={isSyncMode}
187
+ >
183
188
  <UIIcon name="MinusCircle" {...sizeProps} />
184
189
  Remove from Unit
185
190
  </DropdownItem>
@@ -123,10 +123,6 @@ export const CreateAddressSettingsDrawer = ({
123
123
  },
124
124
  });
125
125
 
126
- const isConfirmButtonEnabled = Boolean(
127
- shippingAddress?.id || billingAddress?.id
128
- );
129
-
130
126
  function getFormData() {
131
127
  const data = [];
132
128
  if (shippingAddress?.id) {
@@ -153,12 +149,19 @@ export const CreateAddressSettingsDrawer = ({
153
149
  type: listType,
154
150
  });
155
151
 
156
- // Set default addresses
157
- setDefaultAddresses({
158
- orgUnitId: router.query.orgUnitId as string,
159
- customerId: router.query.contractId as string,
160
- data: getFormData(),
161
- });
152
+ // Set default addresses only if at least one is selected
153
+ const formData = getFormData();
154
+ if (formData.length > 0) {
155
+ setDefaultAddresses({
156
+ orgUnitId: router.query.orgUnitId as string,
157
+ customerId: router.query.contractId as string,
158
+ data: formData,
159
+ });
160
+ } else {
161
+ // Close drawer and trigger update when only list type changed
162
+ onUpdate?.();
163
+ close();
164
+ }
162
165
  };
163
166
 
164
167
  return (
@@ -170,7 +173,6 @@ export const CreateAddressSettingsDrawer = ({
170
173
  isPrimaryButtonLoading={
171
174
  isSetDefaultAddressesLoading || isSetScopeConfigLoading
172
175
  }
173
- isPrimaryButtonDisabled={!isConfirmButtonEnabled}
174
176
  scopeName={SCOPE_KEYS.ADDRESSES}
175
177
  onDismiss={close}
176
178
  data-fs-bp-create-address-settings-drawer
@@ -11,7 +11,12 @@ import {
11
11
  import { EmptyState } from "../../../shared/components/EmptyState/EmptyState";
12
12
  import { Table } from "../../../shared/components/Table/Table";
13
13
  import { getTableColumns } from "../../../shared/components/Table/utils/tableColumns";
14
- import { useBuyerPortal, useDrawerProps } from "../../../shared/hooks";
14
+ import {
15
+ useBuyerPortal,
16
+ useDrawerProps,
17
+ useGetScopeConfig,
18
+ SCOPE_KEYS,
19
+ } from "../../../shared/hooks";
15
20
  import { usePageItems } from "../../../shared/hooks/usePageItems";
16
21
  import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
17
22
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
@@ -74,6 +79,7 @@ export const AddressLayout = ({
74
79
 
75
80
  const handleRefetchAddresses = () => {
76
81
  setIsLoading(true);
82
+ refetchScopeConfig();
77
83
  refetchSearchedAddresses();
78
84
  };
79
85
 
@@ -92,6 +98,17 @@ export const AddressLayout = ({
92
98
  const { currentOrgUnit: orgUnit, currentContract: contract } =
93
99
  useBuyerPortal();
94
100
 
101
+ const { isSyncMode, refetchScopeConfig } = useGetScopeConfig(
102
+ {
103
+ customerId: router.query.contractId as string,
104
+ unitId: router.query.orgUnitId as string,
105
+ scopeName: SCOPE_KEYS.ADDRESSES,
106
+ },
107
+ {
108
+ lazy: !router.query.contractId || !router.query.orgUnitId,
109
+ }
110
+ );
111
+
95
112
  const columns = getTableColumns({
96
113
  nameColumnSize: "18.125rem",
97
114
  actionsLength: 2,
@@ -184,6 +201,7 @@ export const AddressLayout = ({
184
201
  id,
185
202
  ...address,
186
203
  }}
204
+ isSyncMode={isSyncMode}
187
205
  onUpdate={handleRefetchAddresses}
188
206
  onOpen={() => {
189
207
  const url = buyerPortalRoutes.addressDetails({
@@ -10,6 +10,7 @@ import {
10
10
  useBuyerPortal,
11
11
  useDebounce,
12
12
  useDrawerProps,
13
+ useGetScopeConfig,
13
14
  } from "../../../shared/hooks";
14
15
  import {
15
16
  useAddDefaultValue,
@@ -63,6 +64,18 @@ export const CustomFieldsLayout = ({
63
64
  const unitId = orgUnit?.id ?? "";
64
65
  const cookie = clientContext?.cookie ?? "";
65
66
  const debouncedSearchValue = useDebounce(filter.search, 500);
67
+ const customFieldScopeName = `custom-fields/${customField}`;
68
+
69
+ const { isSyncMode, refetchScopeConfig } = useGetScopeConfig(
70
+ {
71
+ customerId: contractId,
72
+ unitId: unitId,
73
+ scopeName: customFieldScopeName,
74
+ },
75
+ {
76
+ lazy: !contractId || !unitId,
77
+ }
78
+ );
66
79
 
67
80
  useEffect(() => {
68
81
  const isNewSearchValue = lastSearchValue.current !== filter.search;
@@ -188,6 +201,7 @@ export const CustomFieldsLayout = ({
188
201
  function restartRequest() {
189
202
  setIsRefetchingAfterMutation(true);
190
203
  setCustomFieldValuesOnUnitScope([]);
204
+ refetchScopeConfig();
191
205
 
192
206
  if (filter.page !== 1) {
193
207
  return setFilter((curr) => ({ ...curr, page: 1 }));
@@ -224,6 +238,7 @@ export const CustomFieldsLayout = ({
224
238
  data={customFieldValuesOnUnitScope ?? []}
225
239
  customFieldlabel={customField}
226
240
  isLoading={loadingTable || isRefetchingAfterMutation}
241
+ isSyncMode={isSyncMode}
227
242
  onSearch={(value) =>
228
243
  setFilter((curr) => ({ ...curr, search: value }))
229
244
  }
@@ -16,6 +16,7 @@ export type PaymentMethodSettingsDrawerProps = Omit<
16
16
  "children"
17
17
  > & {
18
18
  readonly?: boolean;
19
+ onUpdate?: () => void;
19
20
  };
20
21
 
21
22
  const PAYMENT_METHOD_LIST_TYPE_OPTIONS =
@@ -23,6 +24,7 @@ const PAYMENT_METHOD_LIST_TYPE_OPTIONS =
23
24
 
24
25
  export const PaymentMethodSettingsDrawer = ({
25
26
  close,
27
+ onUpdate,
26
28
  ...otherProps
27
29
  }: PaymentMethodSettingsDrawerProps) => {
28
30
  const { pushToast } = useUI();
@@ -36,6 +38,7 @@ export const PaymentMethodSettingsDrawer = ({
36
38
  message: "Scope configuration updated successfully",
37
39
  status: "INFO",
38
40
  });
41
+ onUpdate?.();
39
42
  close();
40
43
  },
41
44
  onError: () => {
@@ -7,12 +7,14 @@ import { Icon } from "../../../shared/components";
7
7
  export type RemoveMethodButtonProps = {
8
8
  isLoading: boolean;
9
9
  disabled: boolean;
10
+ disabledTooltipContent?: string;
10
11
  onClick?: () => void;
11
12
  } & ComponentProps<"button">;
12
13
 
13
14
  export const RemoveMethodButton = ({
14
15
  isLoading,
15
16
  disabled,
17
+ disabledTooltipContent,
16
18
  onClick,
17
19
  ...props
18
20
  }: RemoveMethodButtonProps) => {
@@ -22,7 +24,8 @@ export const RemoveMethodButton = ({
22
24
  placement={disabled ? "left-center" : "top-center"}
23
25
  content={
24
26
  disabled
25
- ? "This payment method can't be removed. Each unit must have at least one active payment method. Add another one to proceed with the removal."
27
+ ? disabledTooltipContent ??
28
+ "This payment method can't be removed. Each unit must have at least one active payment method. Add another one to proceed with the removal."
26
29
  : "Remove from unit"
27
30
  }
28
31
  >
@@ -1,5 +1,7 @@
1
1
  import { useState } from "react";
2
2
 
3
+ import { useRouter } from "next/router";
4
+
3
5
  import {
4
6
  HeaderInside,
5
7
  InternalSearch,
@@ -13,6 +15,8 @@ import {
13
15
  useBuyerPortal,
14
16
  useDrawerProps,
15
17
  usePageItems,
18
+ useGetScopeConfig,
19
+ SCOPE_KEYS,
16
20
  } from "../../../shared/hooks";
17
21
  import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
18
22
  import {
@@ -38,6 +42,7 @@ export const PaymentMethodsLayout = ({
38
42
  totalPaymentMethods,
39
43
  page = 1,
40
44
  }: PaymentMethodsLayoutProps) => {
45
+ const router = useRouter();
41
46
  const [selectedMethod, setSelectedMethod] = useState<
42
47
  PaymentMethodData | undefined
43
48
  >(undefined);
@@ -61,6 +66,17 @@ export const PaymentMethodsLayout = ({
61
66
  page,
62
67
  });
63
68
 
69
+ const { isSyncMode, refetchScopeConfig } = useGetScopeConfig(
70
+ {
71
+ customerId: router.query.contractId as string,
72
+ unitId: router.query.orgUnitId as string,
73
+ scopeName: SCOPE_KEYS.PAYMENT_SYSTEMS,
74
+ },
75
+ {
76
+ lazy: !router.query.contractId || !router.query.orgUnitId,
77
+ }
78
+ );
79
+
64
80
  const {
65
81
  open: openAddDrawer,
66
82
  isOpen: isAddPaymentMethodDrawerOpen,
@@ -83,6 +99,8 @@ export const PaymentMethodsLayout = ({
83
99
  useBuyerPortal();
84
100
 
85
101
  const isSinglePaymentMethod = paymentMethods.length <= 1;
102
+ const isRemoveDisabled =
103
+ isSyncMode || isSinglePaymentMethod || isRemovingPaymentMethod;
86
104
  const allPaymentMethodsSelected =
87
105
  paymentMethods.length === totalPaymentMethods;
88
106
 
@@ -105,6 +123,11 @@ export const PaymentMethodsLayout = ({
105
123
  />
106
124
  );
107
125
 
126
+ const handleScopeConfigUpdate = () => {
127
+ refetchScopeConfig();
128
+ router.replace(router.asPath);
129
+ };
130
+
108
131
  const renderTableRows = () =>
109
132
  paymentMethods.map((method) => (
110
133
  <Table.Row
@@ -117,7 +140,12 @@ export const PaymentMethodsLayout = ({
117
140
  <RemoveMethodButton
118
141
  isLoading={isRemovingPaymentMethod}
119
142
  onClick={() => onRemoveClick(method)}
120
- disabled={isSinglePaymentMethod || isRemovingPaymentMethod}
143
+ disabled={isRemoveDisabled}
144
+ disabledTooltipContent={
145
+ isSyncMode
146
+ ? "Cannot remove items while using synchronized list"
147
+ : undefined
148
+ }
121
149
  />
122
150
  }
123
151
  />
@@ -234,6 +262,7 @@ export const PaymentMethodsLayout = ({
234
262
  {isPaymentMethodSettingsDrawerOpen && (
235
263
  <PaymentMethodSettingsDrawer
236
264
  isOpen={isPaymentMethodSettingsDrawerOpen}
265
+ onUpdate={handleScopeConfigUpdate}
237
266
  {...paymentMethodSettingsDrawerProps}
238
267
  />
239
268
  )}
@@ -106,6 +106,7 @@
106
106
  justify-content: space-between;
107
107
  align-items: center;
108
108
  height: 2.5rem;
109
+ margin-bottom: var(--fs-spacing-3);
109
110
 
110
111
  @include media("<=tablet") {
111
112
  [data-fs-buyer-portal-internal-search] {
@@ -16,12 +16,14 @@ export type CollectionsSettingsDrawerProps = Omit<
16
16
  "children"
17
17
  > & {
18
18
  readonly?: boolean;
19
+ onUpdate?: () => void;
19
20
  };
20
21
 
21
22
  const COLLECTIONS_LIST_TYPE_OPTIONS = createListTypeOptions("collections");
22
23
 
23
24
  export const CollectionsSettingsDrawer = ({
24
25
  close,
26
+ onUpdate,
25
27
  ...otherProps
26
28
  }: CollectionsSettingsDrawerProps) => {
27
29
  const { pushToast } = useUI();
@@ -35,6 +37,7 @@ export const CollectionsSettingsDrawer = ({
35
37
  message: "Scope configuration updated successfully",
36
38
  status: "INFO",
37
39
  });
40
+ onUpdate?.();
38
41
  close();
39
42
  },
40
43
  onError: () => {
@@ -16,8 +16,10 @@ import type {
16
16
 
17
17
  export function ProductAssortmentTable({
18
18
  productAssortment,
19
+ isSyncMode = false,
19
20
  }: {
20
21
  productAssortment: ProductAssortmentWithAdditionalInformation[];
22
+ isSyncMode?: boolean;
21
23
  }) {
22
24
  const [assortmentSelected, setAssortmentSelected] =
23
25
  useState<ProductAssortmentSelectedProps | null>();
@@ -66,14 +68,18 @@ export function ProductAssortmentTable({
66
68
  searchTerm={searchTerm ?? undefined}
67
69
  actionIcons={
68
70
  <Tooltip
69
- content={removeTooltipContent}
71
+ content={
72
+ isSyncMode
73
+ ? "Cannot remove items while using synchronized list"
74
+ : removeTooltipContent
75
+ }
70
76
  placement={"left-center"}
71
77
  >
72
78
  <IconButton
73
79
  data-fs-product-assortment-row-action-button
74
80
  icon={<Icon name={"MinusCircle"} width={20} height={20} />}
75
81
  aria-label="Remove product assortment"
76
- disabled={removeDisabled}
82
+ disabled={isSyncMode || removeDisabled}
77
83
  onClick={() =>
78
84
  handleRemoveSelectedAssortment({
79
85
  id: item.id,
@@ -1,45 +1,51 @@
1
1
  [data-fs-bp-product-assortment-table] {
2
- @import "@faststore/ui/src/components/molecules/Tooltip/styles.scss";
3
- @import "../AddProductAssortmentDrawer/add-product-assortment-drawer.scss";
4
- @import "../../../shared/components/Table/table.scss";
2
+ @import "@faststore/ui/src/components/molecules/Tooltip/styles.scss";
3
+ @import "../AddProductAssortmentDrawer/add-product-assortment-drawer.scss";
4
+ @import "../../../shared/components/Table/table.scss";
5
5
 
6
- width: 100%;
6
+ width: 100%;
7
7
 
8
- [data-fs-tooltip] {
9
- --fs-tooltip-background: #1f1f1f;
10
- border-bottom: none;
11
- text-wrap: balance;
12
- text-align: justify;
8
+ [data-fs-tooltip] {
9
+ --fs-tooltip-background: #1f1f1f;
10
+ border-bottom: none;
11
+ text-wrap: balance;
12
+ text-align: justify;
13
13
 
14
- [data-fs-tooltip-placement="left-center"] {
15
- [data-fs-tooltip-indicator="true"] {
16
- margin-left: -0.1%;
17
- }
18
- }
19
- [data-fs-tooltip-placement="top-center"] {
20
- [data-fs-tooltip-indicator="true"] {
21
- margin-top: -1%;
22
- }
23
- }
24
- }
14
+ [data-fs-tooltip-placement="left-center"] {
15
+ [data-fs-tooltip-indicator="true"] {
16
+ margin-left: -0.1%;
17
+ }
18
+ }
19
+ [data-fs-tooltip-placement="top-center"] {
20
+ [data-fs-tooltip-indicator="true"] {
21
+ margin-top: -1%;
22
+ }
23
+ }
24
+ }
25
25
 
26
- [data-fs-product-assortment-row-action-button] {
27
- width: var(--fs-spacing-6);
28
- height: var(--fs-spacing-6);
29
- display: inline-flex;
30
- border-radius: var(--fs-border-radius-pill);
31
- justify-content: center;
32
- align-items: center;
26
+ [data-fs-product-assortment-row-action-button] {
27
+ width: var(--fs-spacing-6);
28
+ height: var(--fs-spacing-6);
29
+ padding: 0;
30
+ display: inline-flex;
31
+ border-radius: var(--fs-border-radius-pill);
32
+ justify-content: center;
33
+ align-items: center;
33
34
 
34
- &:hover:not(:disabled) {
35
- background-color: #ebebeb;
36
- cursor: pointer;
37
- }
35
+ &:hover:not(:disabled) {
36
+ background-color: #ebebeb;
37
+ cursor: pointer;
38
+ }
38
39
 
39
- [data-fs-button-icon] {
40
- display: block;
41
- width: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
42
- height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
43
- }
44
- }
40
+ &:disabled {
41
+ opacity: 0.4;
42
+ cursor: not-allowed;
43
+ }
44
+
45
+ [data-fs-button-icon] {
46
+ display: block;
47
+ width: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
48
+ height: calc(var(--fs-spacing-4) - var(--fs-spacing-0));
49
+ }
50
+ }
45
51
  }
@@ -12,6 +12,8 @@ import {
12
12
  useBuyerPortal,
13
13
  useDrawerProps,
14
14
  usePageItems,
15
+ useGetScopeConfig,
16
+ SCOPE_KEYS,
15
17
  } from "../../../shared/hooks";
16
18
  import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
17
19
  import {
@@ -36,7 +38,21 @@ export const ProductAssortmentLayout = ({
36
38
  const { currentOrgUnit: orgUnit, currentContract: contract } =
37
39
  useBuyerPortal();
38
40
 
39
- const { data: drawerProductAssortment } = useGetProductAssortment({
41
+ const { isSyncMode, refetchScopeConfig } = useGetScopeConfig(
42
+ {
43
+ customerId: contractId ?? "",
44
+ unitId: orgUnitId ?? "",
45
+ scopeName: SCOPE_KEYS.COLLECTIONS,
46
+ },
47
+ {
48
+ lazy: !contractId || !orgUnitId,
49
+ }
50
+ );
51
+
52
+ const {
53
+ data: drawerProductAssortment,
54
+ refetchProductAssortment: refetchDrawerAssortment,
55
+ } = useGetProductAssortment({
40
56
  contractId,
41
57
  orgUnitId,
42
58
  });
@@ -44,14 +60,23 @@ export const ProductAssortmentLayout = ({
44
60
  contractId,
45
61
  orgUnitId,
46
62
  });
47
- const { data: initialProductAssortment, isProductAssortmentLoading } =
48
- useGetProductAssortment({
49
- filterByScope: true,
50
- contractId,
51
- orgUnitId,
52
- search,
53
- page,
54
- });
63
+ const {
64
+ data: initialProductAssortment,
65
+ isProductAssortmentLoading,
66
+ refetchProductAssortment,
67
+ } = useGetProductAssortment({
68
+ filterByScope: true,
69
+ contractId,
70
+ orgUnitId,
71
+ search,
72
+ page,
73
+ });
74
+
75
+ const handleScopeConfigUpdate = () => {
76
+ refetchScopeConfig();
77
+ refetchProductAssortment();
78
+ refetchDrawerAssortment();
79
+ };
55
80
 
56
81
  const isContractEmpty = !contractAssortment || contractAssortment.total === 0;
57
82
 
@@ -178,6 +203,7 @@ export const ProductAssortmentLayout = ({
178
203
  <section>
179
204
  <ProductAssortmentTable
180
205
  productAssortment={productAssortment}
206
+ isSyncMode={isSyncMode}
181
207
  />
182
208
 
183
209
  <div data-fs-bp-product-assortment-paginator>
@@ -231,6 +257,7 @@ export const ProductAssortmentLayout = ({
231
257
  {isCollectionsSettingsDrawerOpen && (
232
258
  <CollectionsSettingsDrawer
233
259
  isOpen={isCollectionsSettingsDrawerOpen}
260
+ onUpdate={handleScopeConfigUpdate}
234
261
  {...collectionsSettingsDrawerProps}
235
262
  />
236
263
  )}
@@ -11,15 +11,16 @@
11
11
  &:hover {
12
12
  background-color: var(--fs-bp-color-transparent-0);
13
13
  }
14
-
15
- &:active, &[aria-expanded="true"] {
14
+
15
+ &:active,
16
+ &[aria-expanded="true"] {
16
17
  background-color: var(--fs-bp-color-transparent-1);
17
18
  }
18
19
  }
19
20
 
20
21
  [data-fs-dropdown-menu] {
21
22
  &[data-fs-bp-basic-dropdown-menu] {
22
- --fs-dropdown-menu-box-shadow: var(--fs-bp-elevation-dropdown);
23
+ --fs-dropdown-menu-box-shadow: var(--fs-bp-elevation-dropdown);
23
24
  width: auto;
24
25
  background-color: #fff;
25
26
  padding: var(--fs-spacing-1) 0;
@@ -53,6 +54,12 @@
53
54
  }
54
55
  }
55
56
 
57
+ &[data-fs-bp-dropdown-menu-item-disabled="true"] {
58
+ opacity: 0.4;
59
+ cursor: not-allowed;
60
+ pointer-events: none;
61
+ }
62
+
56
63
  &[data-fs-bp-dropdown-menu-item-mode="danger"] {
57
64
  color: #d31a15;
58
65
 
@@ -32,6 +32,7 @@ interface CustomFieldTableProps {
32
32
  data: CustomFieldData[];
33
33
  actions: TableActions;
34
34
  searchTerm?: string;
35
+ isSyncMode?: boolean;
35
36
  }
36
37
 
37
38
  export const CustomFieldTable = ({
@@ -40,6 +41,7 @@ export const CustomFieldTable = ({
40
41
  loading,
41
42
  actions,
42
43
  searchTerm,
44
+ isSyncMode = false,
43
45
  }: CustomFieldTableProps) => {
44
46
  const { pushToast } = useUI();
45
47
 
@@ -122,9 +124,11 @@ export const CustomFieldTable = ({
122
124
  actions.itemToSelect(option);
123
125
  actions.isOnlyDeleting(false);
124
126
  }}
127
+ data-fs-bp-dropdown-menu-item-disabled={isSyncMode}
125
128
  >
126
129
  <Icon name="CircleRemove" /> <span>Remove from unit</span>
127
130
  </DropdownItem>
131
+ <BasicDropdownMenu.Separator />
128
132
  <DropdownItem
129
133
  data-fs-bp-dropdown-menu-item-mode="danger"
130
134
  onClick={() => {
@@ -22,8 +22,11 @@ export const useGetScopeConfig = (
22
22
  options
23
23
  );
24
24
 
25
+ const isSyncMode = data?.type === "sync";
26
+
25
27
  return {
26
28
  scopeConfig: data,
29
+ isSyncMode,
27
30
  isScopeConfigLoading: isLoading,
28
31
  hasScopeConfigError: error,
29
32
  refetchScopeConfig: refetch,
@@ -13,6 +13,7 @@ import {
13
13
  export interface CustomFieldLayoutProps {
14
14
  data: CustomFieldData[];
15
15
  isLoading?: boolean;
16
+ isSyncMode?: boolean;
16
17
  tableActions: TableActions;
17
18
  customFieldlabel: string;
18
19
  pagination: {
@@ -27,6 +28,7 @@ export interface CustomFieldLayoutProps {
27
28
  export const CustomFieldLayout = ({
28
29
  data,
29
30
  isLoading,
31
+ isSyncMode = false,
30
32
  tableActions,
31
33
  customFieldlabel,
32
34
  pagination,
@@ -88,6 +90,7 @@ export const CustomFieldLayout = ({
88
90
  actions={tableActions}
89
91
  loading={isLoading}
90
92
  searchTerm={searchValue}
93
+ isSyncMode={isSyncMode}
91
94
  />
92
95
  <Pagination
93
96
  data-fs-bp-custom-field-bottom-pagination
@@ -22,4 +22,4 @@ export const SCOPE_KEYS = {
22
22
  CREDIT_CARDS: "creditCards",
23
23
  } as const;
24
24
 
25
- export const CURRENT_VERSION = "1.3.64";
25
+ export const CURRENT_VERSION = "1.3.66";
@@ -36,7 +36,7 @@ export function getCookieWithoutSessionObjects(cookie: string) {
36
36
  const newCookie: Record<string, string> = {};
37
37
 
38
38
  for (const key in mappedCookie) {
39
- if (!valuesToRemove.includes(key)) {
39
+ if (!valuesToRemove.includes(key.toLocaleLowerCase())) {
40
40
  newCookie[key] = mappedCookie[key];
41
41
  }
42
42
  }
@@ -80,14 +80,12 @@ export function getUserIdFromCookieServerSide(data: LoaderData): string {
80
80
  }
81
81
 
82
82
  export function getCookieAsString(cookie: Record<string, string>): string {
83
- return (
84
- JSON.stringify(cookie)
85
- .replace(/[{}]/g, "")
86
- .replace(/[{}]/g, "")
87
- .replace(/"/g, "") // Remove all quotes
88
- .replace(/:/g, "=") // Replace all colons with equals
89
- .replace(/,/g, ";") ?? ""
90
- );
83
+ let cookieString = "";
84
+ Object.keys(cookie).forEach((cookieKey) => {
85
+ cookieString += `${cookieKey}=${encodeURIComponent(cookie[cookieKey])};`;
86
+ });
87
+
88
+ return cookieString.slice(0, cookieString.length - 1);
91
89
  }
92
90
 
93
91
  function parseJwt(token: string): ParsedCookie {