@vtex/faststore-plugin-buyer-portal 1.3.50 → 1.3.51

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 CHANGED
@@ -7,9 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.51] - 2026-01-05
11
+
12
+ ### Added
13
+
14
+ - Add Settings Drawers for Credit Cards, Payment Methods and Collections
15
+ - Implement `CreditCardSettingsDrawer` component for credit cards scope configuration
16
+ - Implement `PaymentMethodSettingsDrawer` component for payment methods scope configuration
17
+ - Implement `CollectionsSettingsDrawer` component for product assortment scope configuration
18
+ - Integrate settings drawers with respective layout pages
19
+
10
20
  ## [1.3.50] - 2025-12-19
11
21
 
12
22
  ### Changed
23
+
13
24
  - Introduces several improvements and refactorings to the budget notification drawer, focusing on user experience.
14
25
 
15
26
  ## [1.3.49] - 2025-12-19
@@ -28,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
39
  - Alternative Login Keys:
29
40
  - Add auth setup drawer
30
41
  - Update AddUserDrawer to support username
31
-
42
+
32
43
  ## [1.3.46] - 2025-12-19
33
44
 
34
45
  ### Fixed
@@ -38,16 +49,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
38
49
  ## [1.3.45] - 2025-12-17
39
50
 
40
51
  ### Fixed
52
+
41
53
  - Remove AwaitedType imports
42
54
 
43
55
  ## [1.3.44] - 2025-12-16
44
56
 
45
57
  ### Fixed
58
+
46
59
  - Remove AwaitedType export that was breaking the application build
47
60
 
48
61
  ## [1.3.43] - 2025-12-16
49
62
 
50
63
  ### Fixed
64
+
51
65
  - Update maskPhoneNumber to include country DDI
52
66
  - Send only phone digits to API in Users and Recipients pages
53
67
  - Show masked phone in layout and forms in Users and Recipients pages
@@ -67,6 +81,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
67
81
 
68
82
  ## [1.3.40] - 2025-12-09
69
83
 
84
+ ### Fixed
85
+
70
86
  - Fix e2e auth flow
71
87
 
72
88
  ## [1.3.39] - 2025-12-09
@@ -84,7 +100,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
84
100
 
85
101
  ## [1.3.37] - 2025-12-04
86
102
 
87
- ## Changed
103
+ ### Changed
88
104
 
89
105
  - Allow Credit Card CVV to accept 4 digits
90
106
 
@@ -102,12 +118,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
102
118
 
103
119
  ## [1.3.34] - 2025-12-01
104
120
 
121
+ ### Added
122
+
105
123
  - Add generic `SettingsDrawer` component with `ListType` subcomponent for scope configuration
106
124
  - Add Scope Config API integration (`useGetScopeConfig`, `useSetScopeConfig` hooks)
107
125
  - Integrate Settings Drawer with Address Settings page
108
-
109
- ### Added
110
-
111
126
  - Add DK Docs
112
127
 
113
128
  ### Fixed
@@ -311,6 +326,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
311
326
 
312
327
  ## [1.3.4] - 2025-10-16
313
328
 
329
+ ### Fixed
330
+
314
331
  - Error boundary:
315
332
  - Updating the authentication loader to provide detailed error context.
316
333
 
@@ -422,21 +439,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
422
439
  - Add CHANGELOG file
423
440
  - Add README file
424
441
 
425
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.50...HEAD
426
- [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
427
- [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
428
- [1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
429
- [1.3.2]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.2
430
- [1.3.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.3
431
- [1.3.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.4
432
- [1.3.5]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.5
433
- [1.3.6]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.6
434
- [1.3.9]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.8...v1.3.9
435
- [1.3.8]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.7...v1.3.8
436
- [1.3.7]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.7
437
-
438
- # <<<<<<< HEAD
439
-
442
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.51...HEAD
443
+ [1.3.51]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.50...v1.3.51
444
+ [1.3.50]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.49...v1.3.50
445
+ [1.3.49]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.48...v1.3.49
446
+ [1.3.48]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.47...v1.3.48
447
+ [1.3.47]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.46...v1.3.47
448
+ [1.3.46]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.45...v1.3.46
449
+ [1.3.45]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.44...v1.3.45
450
+ [1.3.44]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.43...v1.3.44
451
+ [1.3.43]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.42...v1.3.43
452
+ [1.3.42]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.42
453
+ [1.3.41]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.40...v1.3.41
454
+ [1.3.40]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.39...v1.3.40
455
+ [1.3.39]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.38...v1.3.39
456
+ [1.3.38]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.37...v1.3.38
457
+ [1.3.37]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.36...v1.3.37
458
+ [1.3.36]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.35...v1.3.36
459
+ [1.3.35]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.35
440
460
  [1.3.34]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.33...v1.3.34
441
461
  [1.3.33]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.32...v1.3.33
442
462
  [1.3.32]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.31...v1.3.32
@@ -460,24 +480,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
460
480
  [1.3.14]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.13...v1.3.14
461
481
  [1.3.13]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.12...v1.3.13
462
482
  [1.3.12]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.11...v1.3.12
463
-
464
- > > > > > > > main
465
- > > > > > > > [1.3.11]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.11
466
-
467
- [1.3.41]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.40...v1.3.41
468
- [1.3.40]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.39...v1.3.40
469
- [1.3.39]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.38...v1.3.39
470
- [1.3.38]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.37...v1.3.38
471
- [1.3.37]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.36...v1.3.37
472
- [1.3.36]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.35...v1.3.36
473
- [1.3.35]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.35
474
-
475
- [1.3.50]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.49...v1.3.50
476
- [1.3.49]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.48...v1.3.49
477
- [1.3.48]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.47...v1.3.48
478
- [1.3.47]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.46...v1.3.47
479
- [1.3.46]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.45...v1.3.46
480
- [1.3.45]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.44...v1.3.45
481
- [1.3.44]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.43...v1.3.44
482
- [1.3.43]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.42...v1.3.43
483
- [1.3.42]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.42
483
+ [1.3.11]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.11
484
+ [1.3.9]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.8...v1.3.9
485
+ [1.3.8]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.7...v1.3.8
486
+ [1.3.7]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.7
487
+ [1.3.6]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.6
488
+ [1.3.5]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.5
489
+ [1.3.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.4
490
+ [1.3.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.3
491
+ [1.3.2]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.2
492
+ [1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
493
+ [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.3.50",
3
+ "version": "1.3.51",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -7,10 +7,9 @@ import { useUI, Skeleton } from "@faststore/ui";
7
7
  import {
8
8
  type BasicDrawerProps,
9
9
  AutocompleteDropdown,
10
- DEFAULT_LIST_TYPE_OPTIONS,
11
10
  Icon,
12
- ListTypeOption,
13
11
  SettingsDrawer,
12
+ createListTypeOptions,
14
13
  } from "../../../shared/components";
15
14
  import { OptionSelected } from "../../../shared/components/OptionSelected/OptionSelected";
16
15
  import { SearchHighlight } from "../../../shared/components/SearchHighlight/SearchHighlight";
@@ -31,17 +30,7 @@ export type CreateAddressSettingsDrawerProps = Omit<
31
30
  onUpdate?: () => void;
32
31
  };
33
32
 
34
- export const ADDRESS_LIST_TYPE_OPTIONS: ListTypeOption[] = [
35
- {
36
- ...DEFAULT_LIST_TYPE_OPTIONS[0],
37
- description: "Manage a unique list of addresses for this organization.",
38
- },
39
- {
40
- ...DEFAULT_LIST_TYPE_OPTIONS[1],
41
- description:
42
- "Use the shared list of addresses defined by the contract. Updates are automatic.",
43
- },
44
- ];
33
+ export const ADDRESS_LIST_TYPE_OPTIONS = createListTypeOptions("addresses");
45
34
 
46
35
  export const CreateAddressSettingsDrawer = ({
47
36
  close,
@@ -6,11 +6,10 @@ import { useUI } from "@faststore/ui";
6
6
 
7
7
  import {
8
8
  type BasicDrawerProps,
9
- BasicDrawer,
10
- InputText,
9
+ SettingsDrawer,
10
+ createListTypeOptions,
11
11
  } from "../../../shared/components";
12
-
13
- import type { CreditCard } from "../../types";
12
+ import { useSetScopeConfig, SCOPE_KEYS } from "../../../shared/hooks";
14
13
 
15
14
  export type CreditCardSettingsDrawerProps = Omit<
16
15
  BasicDrawerProps,
@@ -19,70 +18,60 @@ export type CreditCardSettingsDrawerProps = Omit<
19
18
  readonly?: boolean;
20
19
  };
21
20
 
21
+ const CREDIT_CARD_LIST_TYPE_OPTIONS = createListTypeOptions("credit cards");
22
+
22
23
  export const CreditCardSettingsDrawer = ({
23
24
  close,
24
- ...props
25
+ ...otherProps
25
26
  }: CreditCardSettingsDrawerProps) => {
26
27
  const { pushToast } = useUI();
27
-
28
28
  const router = useRouter();
29
- const [newCard, setNewCard] = useState<CreditCard>({} as CreditCard);
29
+
30
+ const [listType, setListType] = useState<"sync" | "custom">("custom");
31
+
32
+ const { setScopeConfig, isSetScopeConfigLoading } = useSetScopeConfig({
33
+ onSuccess: () => {
34
+ pushToast({
35
+ message: "Scope configuration updated successfully",
36
+ status: "INFO",
37
+ });
38
+ close();
39
+ },
40
+ onError: () => {
41
+ pushToast({
42
+ message: "Failed to update scope configuration",
43
+ status: "ERROR",
44
+ });
45
+ },
46
+ });
30
47
 
31
48
  const handleConfirmClick = () => {
32
- pushToast({
33
- message: "Credit card added successfully",
34
- status: "INFO",
49
+ setScopeConfig({
50
+ customerId: router.query.contractId as string,
51
+ unitId: router.query.orgUnitId as string,
52
+ scopeName: SCOPE_KEYS.CREDIT_CARDS,
53
+ type: listType,
35
54
  });
36
- router.reload();
37
- close();
38
55
  };
39
56
 
40
- const hasCardInformation = () => {
41
- return (
42
- newCard.cardholder &&
43
- newCard.cardLabel &&
44
- newCard.cvv &&
45
- newCard.expirationDate &&
46
- newCard.number
47
- );
48
- };
49
-
50
- const isConfirmButtonEnabled = hasCardInformation();
51
-
52
57
  return (
53
- <BasicDrawer
54
- data-fs-bp-edit-credit-card-settings-drawer
58
+ <SettingsDrawer
59
+ title="Credit cards settings"
60
+ {...otherProps}
55
61
  close={close}
56
- {...props}
62
+ onPrimaryAction={handleConfirmClick}
63
+ isPrimaryButtonLoading={isSetScopeConfigLoading}
64
+ scopeName={SCOPE_KEYS.CREDIT_CARDS}
65
+ onDismiss={close}
66
+ data-fs-bp-edit-credit-card-settings-drawer
57
67
  >
58
- <BasicDrawer.Heading title="Credit card settings" onClose={close} />
59
- <BasicDrawer.Body>
60
- <div data-fs-bp-edit-credit-card-settings-drawer-section-label>
61
- <span>Default credit card</span>
62
- </div>
63
- <InputText
64
- label="Search Card nickname"
65
- value={newCard.cardLabel}
66
- wrapperProps={{ style: { marginTop: 16 } }}
67
- onChange={(event) =>
68
- setNewCard({ ...newCard, cardLabel: event.target.value })
69
- }
70
- />
71
- </BasicDrawer.Body>
72
-
73
- <BasicDrawer.Footer>
74
- <BasicDrawer.Button variant="ghost" onClick={close}>
75
- Cancel
76
- </BasicDrawer.Button>
77
- <BasicDrawer.Button
78
- variant="confirm"
79
- disabled={!isConfirmButtonEnabled}
80
- onClick={handleConfirmClick}
81
- isLoading={false}
82
- >
83
- Save
84
- </BasicDrawer.Button>
85
- </BasicDrawer.Footer>
86
- </BasicDrawer>
68
+ <SettingsDrawer.ListType
69
+ title="List type"
70
+ name="credit-card-list-type"
71
+ value={listType}
72
+ onChange={setListType}
73
+ options={CREDIT_CARD_LIST_TYPE_OPTIONS}
74
+ />
75
+ </SettingsDrawer>
87
76
  );
88
77
  };
@@ -19,6 +19,7 @@ import {
19
19
  CreateCreditCardDrawer,
20
20
  CreditCardDropdownMenu,
21
21
  } from "../../components";
22
+ import { CreditCardSettingsDrawer } from "../../components/CreditCardSettingsDrawer/CreditCardSettingsDrawer";
22
23
  import { useChangeCardScope } from "../../hooks";
23
24
  import { useGetCreditCards } from "../../hooks/useGetCreditCards";
24
25
 
@@ -35,6 +36,12 @@ export const CreditCardLayout = ({ data }: CreditCardsLayoutProps) => {
35
36
  ...createDrawerProps
36
37
  } = useDrawerProps();
37
38
 
39
+ const {
40
+ open: openCreateCreditCardSettingsDrawer,
41
+ isOpen: isCreateCreditCardSettingsDrawerOpen,
42
+ ...createCreditCardSettingsDrawerProps
43
+ } = useDrawerProps();
44
+
38
45
  const {
39
46
  currentOrgUnit: orgUnit,
40
47
  currentUser: user,
@@ -137,6 +144,11 @@ export const CreditCardLayout = ({ data }: CreditCardsLayoutProps) => {
137
144
  >
138
145
  <section data-fs-credit-card-section>
139
146
  <HeaderInside title="Credit cards">
147
+ <HeaderInside.Button
148
+ iconName="EditSettings"
149
+ style={{ backgroundColor: "transparent", color: "#1f1f1f" }}
150
+ onClick={openCreateCreditCardSettingsDrawer}
151
+ />
140
152
  <HeaderInside.Button onClick={openCreateDrawer} />
141
153
  </HeaderInside>
142
154
  {data.length === 0 ? (
@@ -173,6 +185,12 @@ export const CreditCardLayout = ({ data }: CreditCardsLayoutProps) => {
173
185
  isOpen={isCreateCreditCardDrawerOpen}
174
186
  />
175
187
  )}
188
+ {isCreateCreditCardSettingsDrawerOpen && (
189
+ <CreditCardSettingsDrawer
190
+ isOpen={isCreateCreditCardSettingsDrawerOpen}
191
+ {...createCreditCardSettingsDrawerProps}
192
+ />
193
+ )}
176
194
  </section>
177
195
  </ContractTabsLayout>
178
196
  </GlobalLayout>
@@ -1,10 +1,19 @@
1
- import { useState } from "react";
1
+ import { useEffect, useMemo, useState } from "react";
2
2
 
3
- import { useUI } from "@faststore/ui";
3
+ import { RadioGroup, RadioOption, useUI } from "@faststore/ui";
4
4
 
5
- import { BasicDrawerProps } from "../../../shared/components";
6
- import { CustomFieldSettingsDrawer as CustomFieldSettingsDrawerComponent } from "../../../shared/components/CustomField";
7
- import { useBuyerPortal, useDebounce } from "../../../shared/hooks";
5
+ import {
6
+ AutocompleteDropdown,
7
+ SettingsDrawer,
8
+ type BasicDrawerProps,
9
+ createListTypeOptions,
10
+ } from "../../../shared/components";
11
+ import { CustomFieldSelectedCardItem } from "../../../shared/components/CustomField/selected-card-item/CustomFieldSelectedCardItem";
12
+ import {
13
+ useBuyerPortal,
14
+ useDebounce,
15
+ useSetScopeConfig,
16
+ } from "../../../shared/hooks";
8
17
  import {
9
18
  useAddDefaultValue,
10
19
  useCustomFieldValues,
@@ -15,6 +24,12 @@ import {
15
24
  import { useCustomFieldSettings } from "../../../shared/hooks/custom-field/useCustomFieldSettings";
16
25
  import { CustomFieldData } from "../../types";
17
26
 
27
+ type FormState = {
28
+ level: string;
29
+ required: boolean;
30
+ defaultValue: CustomFieldData | null;
31
+ };
32
+
18
33
  export interface CustomFieldSettingsDrawerProps
19
34
  extends Omit<BasicDrawerProps, "children"> {
20
35
  contractId: string;
@@ -30,6 +45,8 @@ export function CustomFieldSettingsDrawer({
30
45
  unitId,
31
46
  close,
32
47
  refetch,
48
+ loading = false,
49
+ onDismiss,
33
50
  ...props
34
51
  }: CustomFieldSettingsDrawerProps) {
35
52
  const {
@@ -40,8 +57,23 @@ export function CustomFieldSettingsDrawer({
40
57
  CustomFieldData[]
41
58
  >([]);
42
59
  const [search, setSearch] = useState<string>("");
60
+ const [formData, setFormData] = useState<FormState>({
61
+ level: "",
62
+ required: false,
63
+ defaultValue: null,
64
+ });
65
+ const [autoCompleteValue, setAutocompleteValue] = useState<string>("");
66
+ const [listType, setListType] = useState<"sync" | "custom">("custom");
43
67
 
44
68
  const debouncedSearchValue = useDebounce(search, 200);
69
+ const customFieldScopeName = useMemo(
70
+ () => `custom-fields/${customField}`,
71
+ [customField]
72
+ );
73
+ const customFieldPlural = useMemo(
74
+ () => `${customField.toLowerCase()}s`,
75
+ [customField]
76
+ );
45
77
 
46
78
  const {
47
79
  data: customFieldSettings,
@@ -72,7 +104,7 @@ export function CustomFieldSettingsDrawer({
72
104
 
73
105
  const {
74
106
  mutate: updateCustomFieldSettings,
75
- isLoading: isLoadginUpdateCustomFieldSettings,
107
+ isLoading: isLoadingUpdateCustomFieldSettings,
76
108
  } = useUpdateCustomFieldSettings({
77
109
  options: {
78
110
  onSuccess: () =>
@@ -114,6 +146,14 @@ export function CustomFieldSettingsDrawer({
114
146
  },
115
147
  });
116
148
 
149
+ const { setScopeConfig, isSetScopeConfigLoading } = useSetScopeConfig({
150
+ onError: () =>
151
+ pushToast({
152
+ status: "ERROR",
153
+ message: "Failed to update scope configuration",
154
+ }),
155
+ });
156
+
117
157
  useCustomFieldValues({
118
158
  keys: `default-values/options/${debouncedSearchValue}`,
119
159
  data: {
@@ -140,63 +180,199 @@ export function CustomFieldSettingsDrawer({
140
180
  },
141
181
  });
142
182
 
143
- async function onSave(
144
- level: string,
145
- required: boolean,
146
- defaulValue: CustomFieldData | null
147
- ) {
148
- if (defaulValue) {
183
+ const listTypeOptions = useMemo(
184
+ () => createListTypeOptions(customFieldPlural),
185
+ [customFieldPlural]
186
+ );
187
+
188
+ useEffect(() => {
189
+ setFormData({
190
+ level: customFieldSettings?.level ?? "",
191
+ required: !!customFieldSettings?.required,
192
+ defaultValue: defaultValueData
193
+ ? { ...defaultValueData, isDefault: true }
194
+ : null,
195
+ });
196
+ }, [customFieldSettings, defaultValueData]);
197
+
198
+ const handleClose = () => {
199
+ onDismiss?.();
200
+ close();
201
+ setSearch("");
202
+ setAutocompleteValue("");
203
+ };
204
+
205
+ const handleSave = async () => {
206
+ if (formData.defaultValue) {
149
207
  await updateDefaultValue({
150
208
  contractId,
151
209
  customField,
152
- customFieldValueId: defaulValue.id,
210
+ customFieldValueId: formData.defaultValue.id,
153
211
  unitId,
154
212
  });
155
213
  } else {
156
214
  await removeDefaultValue({ contractId, customField, unitId });
157
215
  }
158
216
 
159
- updateCustomFieldSettings({
160
- cookie,
161
- data: {
162
- contractId,
217
+ await Promise.all([
218
+ updateCustomFieldSettings({
219
+ cookie,
220
+ data: {
221
+ contractId,
222
+ unitId,
223
+ customField,
224
+ level: formData.level,
225
+ required: formData.required,
226
+ },
227
+ }),
228
+ setScopeConfig({
229
+ customerId: contractId,
163
230
  unitId,
164
- customField,
165
- level,
166
- required,
167
- },
168
- });
169
- }
231
+ scopeName: customFieldScopeName,
232
+ type: listType,
233
+ }),
234
+ ]);
235
+
236
+ handleClose();
237
+ refetch();
238
+ };
239
+
240
+ const isSavingSettings =
241
+ isLoadingUpdateDefaultValue ||
242
+ isLoadingRemoveDefaultValue ||
243
+ isLoadingUpdateCustomFieldSettings ||
244
+ isSetScopeConfigLoading ||
245
+ loading;
246
+
247
+ const isPrimaryButtonDisabled =
248
+ (!formData.defaultValue?.id && !!autoCompleteValue) || isSavingSettings;
170
249
 
171
250
  if (isLoading || isLoadingDefaultValue || error) return null;
172
251
 
173
252
  return (
174
- <CustomFieldSettingsDrawerComponent
175
- initialValue={{
176
- level: customFieldSettings?.level ?? "",
177
- required: !!customFieldSettings?.required,
178
- defaultValue: defaultValueData
179
- ? { ...defaultValueData, isDefault: true }
180
- : null,
181
- }}
182
- close={() => {
183
- close();
184
- setSearch("");
185
- }}
186
- onSave={async ({ level, required, defaultValue }) => {
187
- await onSave(level, required, defaultValue);
188
- close();
189
- refetch();
190
- }}
191
- loading={
192
- isLoadingUpdateDefaultValue ||
193
- isLoadingRemoveDefaultValue ||
194
- isLoadginUpdateCustomFieldSettings
195
- }
196
- onSearch={setSearch}
197
- customFieldOptions={customFieldOptions}
198
- customFieldLabel={customField}
253
+ <SettingsDrawer
254
+ title={`${customField} settings`}
255
+ close={close}
256
+ onPrimaryAction={handleSave}
257
+ onSecondaryAction={handleClose}
258
+ onDismiss={handleClose}
259
+ isPrimaryButtonLoading={isSavingSettings}
260
+ isPrimaryButtonDisabled={isPrimaryButtonDisabled}
261
+ scopeName={customFieldScopeName}
262
+ customerId={contractId}
263
+ unitId={unitId}
264
+ data-bp-custom-fields-settings-drawer
199
265
  {...props}
200
- />
266
+ >
267
+ <div data-bp-settings-drawer-body>
268
+ <p>
269
+ Set how buyers in this unit should enter {customField}s during
270
+ checkout
271
+ </p>
272
+
273
+ <SettingsDrawer.ListType
274
+ title="List type"
275
+ name={`${customField}-list-type`}
276
+ value={listType}
277
+ onChange={setListType}
278
+ options={listTypeOptions}
279
+ />
280
+
281
+ <form data-bp-custom-fields-settings-drawer-form>
282
+ <div>
283
+ <h4>Input level</h4>
284
+ <RadioGroup
285
+ name="inputLevel"
286
+ selectedValue={formData.level}
287
+ onChange={(e) => {
288
+ setFormData((curr) => ({
289
+ ...curr,
290
+ level: e.target.value,
291
+ }));
292
+ }}
293
+ >
294
+ <RadioOption value="order" label="Order level" name="inputLevel">
295
+ Order level
296
+ </RadioOption>
297
+
298
+ <RadioOption value="item" label="Item level" name="inputLevel">
299
+ Item level
300
+ </RadioOption>
301
+ </RadioGroup>
302
+ </div>
303
+
304
+ <div>
305
+ <h4>Input requirement</h4>
306
+ <RadioGroup
307
+ name="requirement"
308
+ selectedValue={formData.required.toString()}
309
+ onChange={(e) => {
310
+ setFormData((curr) => ({
311
+ ...curr,
312
+ required: e.target.value === "true",
313
+ }));
314
+ }}
315
+ >
316
+ <RadioOption value="true" label="Required" name="requirement">
317
+ Required
318
+ </RadioOption>
319
+
320
+ <RadioOption value="false" label="Optional" name="requirement">
321
+ Optional
322
+ </RadioOption>
323
+ </RadioGroup>
324
+ </div>
325
+
326
+ <div data-bp-settings-drawer-autocomplete-dropdown-container>
327
+ <h4>Default {customField}</h4>
328
+ <span>Select the default {customField} for this unit.</span>
329
+
330
+ {formData.defaultValue?.id ? (
331
+ <CustomFieldSelectedCardItem
332
+ text={formData.defaultValue.value}
333
+ onCancel={() => {
334
+ setFormData((curr) => ({
335
+ ...curr,
336
+ defaultValue: null,
337
+ }));
338
+ setAutocompleteValue("");
339
+ setSearch("");
340
+ }}
341
+ />
342
+ ) : (
343
+ <AutocompleteDropdown
344
+ label=""
345
+ value={autoCompleteValue}
346
+ options={customFieldOptions}
347
+ shouldOpenOnFocus={false}
348
+ autoComplete="off"
349
+ shouldShowArrowDown={false}
350
+ onChange={(v) => {
351
+ setAutocompleteValue(v.target.value);
352
+ setSearch(v.target.value);
353
+ }}
354
+ placeholder={`Search by ${customField} name`}
355
+ renderOption={(option, index) => (
356
+ <AutocompleteDropdown.Item
357
+ key={option.id}
358
+ closeOnClick
359
+ index={index}
360
+ isSelected={formData.defaultValue?.id === option?.id}
361
+ onClick={() => {
362
+ setFormData((curr) => ({
363
+ ...curr,
364
+ defaultValue: option,
365
+ }));
366
+ }}
367
+ >
368
+ {option?.value}
369
+ </AutocompleteDropdown.Item>
370
+ )}
371
+ />
372
+ )}
373
+ </div>
374
+ </form>
375
+ </div>
376
+ </SettingsDrawer>
201
377
  );
202
378
  }
@@ -0,0 +1,78 @@
1
+ import { useState } from "react";
2
+
3
+ import { useRouter } from "next/router";
4
+
5
+ import { useUI } from "@faststore/ui";
6
+
7
+ import {
8
+ type BasicDrawerProps,
9
+ SettingsDrawer,
10
+ createListTypeOptions,
11
+ } from "../../../shared/components";
12
+ import { useSetScopeConfig, SCOPE_KEYS } from "../../../shared/hooks";
13
+
14
+ export type PaymentMethodSettingsDrawerProps = Omit<
15
+ BasicDrawerProps,
16
+ "children"
17
+ > & {
18
+ readonly?: boolean;
19
+ };
20
+
21
+ const PAYMENT_METHOD_LIST_TYPE_OPTIONS =
22
+ createListTypeOptions("payment methods");
23
+
24
+ export const PaymentMethodSettingsDrawer = ({
25
+ close,
26
+ ...otherProps
27
+ }: PaymentMethodSettingsDrawerProps) => {
28
+ const { pushToast } = useUI();
29
+ const router = useRouter();
30
+
31
+ const [listType, setListType] = useState<"sync" | "custom">("custom");
32
+
33
+ const { setScopeConfig, isSetScopeConfigLoading } = useSetScopeConfig({
34
+ onSuccess: () => {
35
+ pushToast({
36
+ message: "Scope configuration updated successfully",
37
+ status: "INFO",
38
+ });
39
+ close();
40
+ },
41
+ onError: () => {
42
+ pushToast({
43
+ message: "Failed to update scope configuration",
44
+ status: "ERROR",
45
+ });
46
+ },
47
+ });
48
+
49
+ const handleConfirmClick = () => {
50
+ setScopeConfig({
51
+ customerId: router.query.contractId as string,
52
+ unitId: router.query.orgUnitId as string,
53
+ scopeName: SCOPE_KEYS.PAYMENT_SYSTEMS,
54
+ type: listType,
55
+ });
56
+ };
57
+
58
+ return (
59
+ <SettingsDrawer
60
+ title="Payment method settings"
61
+ {...otherProps}
62
+ close={close}
63
+ onPrimaryAction={handleConfirmClick}
64
+ isPrimaryButtonLoading={isSetScopeConfigLoading}
65
+ scopeName={SCOPE_KEYS.PAYMENT_SYSTEMS}
66
+ onDismiss={close}
67
+ data-fs-bp-payment-method-settings-drawer
68
+ >
69
+ <SettingsDrawer.ListType
70
+ title="List type"
71
+ name="payment-method-list-type"
72
+ value={listType}
73
+ onChange={setListType}
74
+ options={PAYMENT_METHOD_LIST_TYPE_OPTIONS}
75
+ />
76
+ </SettingsDrawer>
77
+ );
78
+ };
@@ -2,3 +2,7 @@ export { AddPaymentMethodsDrawer } from "./AddPaymentMethodsDrawer/AddPaymentMet
2
2
  export { RemovePaymentMethodsDrawer } from "./RemovePaymentMethodsDrawer/RemovePaymentMethodsDrawer";
3
3
  export { RemoveMethodButton } from "./RemoveMethodButton/RemoveMethodButton";
4
4
  export { SearchPaymentMethods } from "./SearchPaymentMethods/SearchPaymentMethods";
5
+ export {
6
+ PaymentMethodSettingsDrawer,
7
+ type PaymentMethodSettingsDrawerProps,
8
+ } from "./PaymentMethodSettingsDrawer/PaymentMethodSettingsDrawer";
@@ -19,6 +19,7 @@ import {
19
19
  AddPaymentMethodsDrawer,
20
20
  RemoveMethodButton,
21
21
  RemovePaymentMethodsDrawer,
22
+ PaymentMethodSettingsDrawer,
22
23
  } from "../../components";
23
24
  import { useRemovePaymentMethod } from "../../hooks/useRemovePaymentMethodSubmit";
24
25
 
@@ -66,6 +67,12 @@ export const PaymentMethodsLayout = ({
66
67
  ...addDrawerProps
67
68
  } = useDrawerProps();
68
69
 
70
+ const {
71
+ open: openPaymentMethodSettingsDrawer,
72
+ isOpen: isPaymentMethodSettingsDrawerOpen,
73
+ ...paymentMethodSettingsDrawerProps
74
+ } = useDrawerProps();
75
+
69
76
  const {
70
77
  open: openRemoveDrawer,
71
78
  isOpen: isRemovePaymentMethodDrawerOpen,
@@ -153,6 +160,11 @@ export const PaymentMethodsLayout = ({
153
160
  >
154
161
  <section data-fs-payment-methods-section>
155
162
  <HeaderInside title="Payment methods">
163
+ <HeaderInside.Button
164
+ iconName="EditSettings"
165
+ style={{ backgroundColor: "transparent", color: "#1f1f1f" }}
166
+ onClick={openPaymentMethodSettingsDrawer}
167
+ />
156
168
  <ConditionalTooltip
157
169
  condition={allPaymentMethodsSelected}
158
170
  placement="left-center"
@@ -227,6 +239,13 @@ export const PaymentMethodsLayout = ({
227
239
  />
228
240
  )}
229
241
 
242
+ {isPaymentMethodSettingsDrawerOpen && (
243
+ <PaymentMethodSettingsDrawer
244
+ isOpen={isPaymentMethodSettingsDrawerOpen}
245
+ {...paymentMethodSettingsDrawerProps}
246
+ />
247
+ )}
248
+
230
249
  {isRemovePaymentMethodDrawerOpen && selectedMethod && (
231
250
  <RemovePaymentMethodsDrawer
232
251
  {...removeDrawerProps}
@@ -0,0 +1,77 @@
1
+ import { useState } from "react";
2
+
3
+ import { useRouter } from "next/router";
4
+
5
+ import { useUI } from "@faststore/ui";
6
+
7
+ import {
8
+ type BasicDrawerProps,
9
+ SettingsDrawer,
10
+ createListTypeOptions,
11
+ } from "../../../shared/components";
12
+ import { useSetScopeConfig, SCOPE_KEYS } from "../../../shared/hooks";
13
+
14
+ export type CollectionsSettingsDrawerProps = Omit<
15
+ BasicDrawerProps,
16
+ "children"
17
+ > & {
18
+ readonly?: boolean;
19
+ };
20
+
21
+ const COLLECTIONS_LIST_TYPE_OPTIONS = createListTypeOptions("collections");
22
+
23
+ export const CollectionsSettingsDrawer = ({
24
+ close,
25
+ ...otherProps
26
+ }: CollectionsSettingsDrawerProps) => {
27
+ const { pushToast } = useUI();
28
+ const router = useRouter();
29
+
30
+ const [listType, setListType] = useState<"sync" | "custom">("custom");
31
+
32
+ const { setScopeConfig, isSetScopeConfigLoading } = useSetScopeConfig({
33
+ onSuccess: () => {
34
+ pushToast({
35
+ message: "Scope configuration updated successfully",
36
+ status: "INFO",
37
+ });
38
+ close();
39
+ },
40
+ onError: () => {
41
+ pushToast({
42
+ message: "Failed to update scope configuration",
43
+ status: "ERROR",
44
+ });
45
+ },
46
+ });
47
+
48
+ const handleConfirmClick = () => {
49
+ setScopeConfig({
50
+ customerId: router.query.contractId as string,
51
+ unitId: router.query.orgUnitId as string,
52
+ scopeName: SCOPE_KEYS.COLLECTIONS,
53
+ type: listType,
54
+ });
55
+ };
56
+
57
+ return (
58
+ <SettingsDrawer
59
+ title="Collections settings"
60
+ {...otherProps}
61
+ close={close}
62
+ onPrimaryAction={handleConfirmClick}
63
+ isPrimaryButtonLoading={isSetScopeConfigLoading}
64
+ scopeName={SCOPE_KEYS.COLLECTIONS}
65
+ onDismiss={close}
66
+ data-fs-bp-collections-settings-drawer
67
+ >
68
+ <SettingsDrawer.ListType
69
+ title="List type"
70
+ name="collections-list-type"
71
+ value={listType}
72
+ onChange={setListType}
73
+ options={COLLECTIONS_LIST_TYPE_OPTIONS}
74
+ />
75
+ </SettingsDrawer>
76
+ );
77
+ };
@@ -1,2 +1,6 @@
1
1
  export { AddProductAssortmentDrawer } from "./AddProductAssortmentDrawer/AddProductAssortmentDrawer";
2
2
  export { AddProductAssortmentDrawerTable } from "./table/AddProductAssortmentDrawerTable";
3
+ export {
4
+ CollectionsSettingsDrawer,
5
+ type CollectionsSettingsDrawerProps,
6
+ } from "./CollectionsSettingsDrawer/CollectionsSettingsDrawer";
@@ -14,7 +14,10 @@ import {
14
14
  usePageItems,
15
15
  } from "../../../shared/hooks";
16
16
  import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
17
- import { AddProductAssortmentDrawer } from "../../components";
17
+ import {
18
+ AddProductAssortmentDrawer,
19
+ CollectionsSettingsDrawer,
20
+ } from "../../components";
18
21
  import { ProductAssortmentTable } from "../../components/ProductAssortmentTable/ProductAssortmentTable";
19
22
  import { useGetProductAssortment } from "../../hooks/useGetProductAssortment";
20
23
  import { useGetProductAssortmentFromContract } from "../../hooks/useGetProductAssortmentFromContract";
@@ -92,6 +95,12 @@ export const ProductAssortmentLayout = ({
92
95
  ...addProductAssortmentDrawerProps
93
96
  } = useDrawerProps();
94
97
 
98
+ const {
99
+ open: openCollectionsSettingsDrawer,
100
+ isOpen: isCollectionsSettingsDrawerOpen,
101
+ ...collectionsSettingsDrawerProps
102
+ } = useDrawerProps();
103
+
95
104
  const hasAllAssortment = drawerProductAssortment.items.length === 0;
96
105
  const isEmpty = productAssortment.length === 0;
97
106
 
@@ -123,6 +132,11 @@ export const ProductAssortmentLayout = ({
123
132
  >
124
133
  <section data-fs-bp-product-assortment-container>
125
134
  <HeaderInside title="Product assortment">
135
+ <HeaderInside.Button
136
+ iconName="EditSettings"
137
+ style={{ backgroundColor: "transparent", color: "#1f1f1f" }}
138
+ onClick={openCollectionsSettingsDrawer}
139
+ />
126
140
  {!isContractEmpty &&
127
141
  (hasAllAssortment ? (
128
142
  <Tooltip
@@ -222,6 +236,13 @@ export const ProductAssortmentLayout = ({
222
236
  {...addProductAssortmentDrawerProps}
223
237
  />
224
238
  )}
239
+
240
+ {isCollectionsSettingsDrawerOpen && (
241
+ <CollectionsSettingsDrawer
242
+ isOpen={isCollectionsSettingsDrawerOpen}
243
+ {...collectionsSettingsDrawerProps}
244
+ />
245
+ )}
225
246
  </ContractTabsLayout>
226
247
  </GlobalLayout>
227
248
  );
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from "react";
2
2
 
3
- import { RadioGroup, RadioOption } from "@faststore/ui";
3
+ import { RadioGroup, RadioOption, Skeleton } from "@faststore/ui";
4
4
 
5
5
  import { useGetScopeConfig } from "../../hooks";
6
6
 
@@ -80,21 +80,33 @@ export const SettingsDrawerListType = ({
80
80
  return (
81
81
  <div data-fs-bp-settings-drawer-list-type>
82
82
  <h4 data-fs-bp-settings-drawer-list-type-title>{title}</h4>
83
- <RadioGroup name={name} selectedValue={value} onChange={handleChange}>
84
- {options.map((option) => (
85
- <div key={option.value} data-fs-bp-settings-drawer-list-type-option>
86
- <RadioOption
87
- value={option.value}
88
- label={option.label}
89
- name={name}
90
- disabled={disabled || isLoading}
91
- />
92
- <p data-fs-bp-settings-drawer-list-type-description>
93
- {option.description}
94
- </p>
95
- </div>
96
- ))}
97
- </RadioGroup>
83
+ {isLoading ? (
84
+ <div data-fs-bp-settings-drawer-list-type-skeleton>
85
+ {options.map((option) => (
86
+ <div key={option.value} data-fs-bp-settings-drawer-list-type-option>
87
+ <Skeleton size={{ width: "100%", height: "3.5rem" }} />
88
+ </div>
89
+ ))}
90
+ </div>
91
+ ) : (
92
+ <RadioGroup name={name} selectedValue={value} onChange={handleChange}>
93
+ {options.map((option) => (
94
+ <div key={option.value} data-fs-bp-settings-drawer-list-type-option>
95
+ <RadioOption
96
+ value={option.value}
97
+ label={option.label}
98
+ name={name}
99
+ disabled={disabled || isLoading}
100
+ >
101
+ {option.label}
102
+ </RadioOption>
103
+ <p data-fs-bp-settings-drawer-list-type-description>
104
+ {option.description}
105
+ </p>
106
+ </div>
107
+ ))}
108
+ </RadioGroup>
109
+ )}
98
110
  </div>
99
111
  );
100
112
  };
@@ -0,0 +1,23 @@
1
+ import {
2
+ DEFAULT_LIST_TYPE_OPTIONS,
3
+ type ListTypeOption,
4
+ } from "./SettingsDrawerListType";
5
+
6
+ export const getCustomListDescription = (entityPlural: string) =>
7
+ `Manage a unique list of ${entityPlural} for this organization.`;
8
+
9
+ export const getSharedListDescription = (entityPlural: string) =>
10
+ `Use the shared list of ${entityPlural} defined by the contract. Updates are automatic.`;
11
+
12
+ export const createListTypeOptions = (
13
+ entityPlural: string
14
+ ): ListTypeOption[] => [
15
+ {
16
+ ...DEFAULT_LIST_TYPE_OPTIONS[0],
17
+ description: getCustomListDescription(entityPlural),
18
+ },
19
+ {
20
+ ...DEFAULT_LIST_TYPE_OPTIONS[1],
21
+ description: getSharedListDescription(entityPlural),
22
+ },
23
+ ];
@@ -14,6 +14,11 @@ export {
14
14
  type SettingsDrawerListTypeProps,
15
15
  DEFAULT_LIST_TYPE_OPTIONS,
16
16
  } from "./SettingsDrawer/SettingsDrawer";
17
+ export {
18
+ createListTypeOptions,
19
+ getCustomListDescription,
20
+ getSharedListDescription,
21
+ } from "./SettingsDrawer/listTypeOptions";
17
22
  export {
18
23
  BasicDropdownMenu,
19
24
  type BasicDropdownMenuProps,
@@ -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.50";
25
+ export const CURRENT_VERSION = "1.3.51";