@vtex/faststore-plugin-buyer-portal 1.3.41 → 1.3.42

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 (24) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/package.json +1 -1
  3. package/src/features/addresses/components/CreateAddressSettingsDrawer/CreateAddressSettingsDrawer.tsx +64 -25
  4. package/src/features/addresses/components/CreateAddressSettingsDrawer/create-address-settings-drawer.scss +22 -22
  5. package/src/features/addresses/services/default-values/get-default-address.service.ts +1 -1
  6. package/src/features/addresses/types/AddressData.ts +1 -0
  7. package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +8 -6
  8. package/src/features/shared/clients/ScopeClient.ts +38 -2
  9. package/src/features/shared/components/Error/Error.tsx +15 -13
  10. package/src/features/shared/components/SettingsDrawer/SettingsDrawer.tsx +106 -0
  11. package/src/features/shared/components/SettingsDrawer/SettingsDrawerContext.tsx +17 -0
  12. package/src/features/shared/components/SettingsDrawer/SettingsDrawerListType.tsx +100 -0
  13. package/src/features/shared/components/SettingsDrawer/settings-drawer.scss +61 -0
  14. package/src/features/shared/components/index.ts +7 -0
  15. package/src/features/shared/hooks/index.ts +2 -0
  16. package/src/features/shared/hooks/useGetScopeConfig.ts +35 -0
  17. package/src/features/shared/hooks/useSetScopeConfig.ts +30 -0
  18. package/src/features/shared/services/get-scope-config.service.ts +19 -0
  19. package/src/features/shared/services/index.ts +9 -0
  20. package/src/features/shared/services/set-scope-config.service.ts +27 -0
  21. package/src/features/shared/types/index.ts +1 -0
  22. package/src/features/shared/utils/constants.ts +10 -1
  23. package/src/features/shared/utils/index.ts +6 -1
  24. package/src/pages/payment-methods.tsx +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.42] - 2025-12-11
11
+
12
+ ### Fixed
13
+
14
+ - Improve error handling in the payment methods
15
+ - Show error details on error boundary only in development environment
16
+
10
17
  ## [1.3.41] - 2025-12-09
11
18
 
12
19
  ### Changed
@@ -50,10 +57,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
50
57
 
51
58
  ## [1.3.34] - 2025-12-01
52
59
 
60
+ - Add generic `SettingsDrawer` component with `ListType` subcomponent for scope configuration
61
+ - Add Scope Config API integration (`useGetScopeConfig`, `useSetScopeConfig` hooks)
62
+ - Integrate Settings Drawer with Address Settings page
63
+
53
64
  ### Added
54
65
 
55
66
  - Add DK Docs
56
67
 
68
+ ### Fixed
69
+
70
+ - Fix address ID mapping in default address service to use correct `id` field
71
+
57
72
  ## [1.3.33] - 2025-11-25
58
73
 
59
74
  ### Changed
@@ -362,7 +377,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
362
377
  - Add CHANGELOG file
363
378
  - Add README file
364
379
 
365
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.41...HEAD
380
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/1.3.42...HEAD
366
381
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
367
382
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
368
383
  [1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
@@ -411,3 +426,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
411
426
  [1.3.37]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.36...v1.3.37
412
427
  [1.3.36]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.35...v1.3.36
413
428
  [1.3.35]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.35
429
+
430
+ [1.3.42]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.3.41",
3
+ "version": "1.3.42",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -7,11 +7,14 @@ import { useUI, Skeleton } from "@faststore/ui";
7
7
  import {
8
8
  type BasicDrawerProps,
9
9
  AutocompleteDropdown,
10
- BasicDrawer,
10
+ DEFAULT_LIST_TYPE_OPTIONS,
11
11
  Icon,
12
+ ListTypeOption,
13
+ SettingsDrawer,
12
14
  } from "../../../shared/components";
13
15
  import { OptionSelected } from "../../../shared/components/OptionSelected/OptionSelected";
14
16
  import { SearchHighlight } from "../../../shared/components/SearchHighlight/SearchHighlight";
17
+ import { useSetScopeConfig, SCOPE_KEYS } from "../../../shared/hooks";
15
18
  import { ADDRESS_MESSAGES } from "../../constants/messages";
16
19
  import { useDebouncedSearchAddressByUnitId } from "../../hooks/useDebouncedSearchAddressByUnitId";
17
20
  import { useGetDefaultAddress } from "../../hooks/useGetDefaultAddresses";
@@ -28,14 +31,27 @@ export type CreateAddressSettingsDrawerProps = Omit<
28
31
  onUpdate?: () => void;
29
32
  };
30
33
 
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
+ ];
45
+
31
46
  export const CreateAddressSettingsDrawer = ({
32
47
  close,
33
48
  onUpdate,
34
- ...props
49
+ ...otherProps
35
50
  }: CreateAddressSettingsDrawerProps) => {
36
51
  const { pushToast } = useUI();
37
52
  const router = useRouter();
38
53
 
54
+ const [listType, setListType] = useState<"sync" | "custom">("custom");
39
55
  const [searchValue, setSearchValue] = useState("");
40
56
  const [activeField, setActiveField] = useState<"Shipping" | "Billing" | null>(
41
57
  "Shipping"
@@ -103,6 +119,21 @@ export const CreateAddressSettingsDrawer = ({
103
119
  },
104
120
  });
105
121
 
122
+ const { setScopeConfig, isSetScopeConfigLoading } = useSetScopeConfig({
123
+ onSuccess: () => {
124
+ pushToast({
125
+ message: "Scope configuration updated successfully",
126
+ status: "INFO",
127
+ });
128
+ },
129
+ onError: () => {
130
+ pushToast({
131
+ message: "Failed to update scope configuration",
132
+ status: "ERROR",
133
+ });
134
+ },
135
+ });
136
+
106
137
  const isConfirmButtonEnabled = Boolean(
107
138
  shippingAddress?.id || billingAddress?.id
108
139
  );
@@ -125,6 +156,15 @@ export const CreateAddressSettingsDrawer = ({
125
156
  }
126
157
 
127
158
  const handleConfirmClick = () => {
159
+ // Update scope config if listType changed
160
+ setScopeConfig({
161
+ customerId: router.query.contractId as string,
162
+ unitId: router.query.orgUnitId as string,
163
+ scopeName: SCOPE_KEYS.ADDRESSES,
164
+ type: listType,
165
+ });
166
+
167
+ // Set default addresses
128
168
  setDefaultAddresses({
129
169
  orgUnitId: router.query.orgUnitId as string,
130
170
  customerId: router.query.contractId as string,
@@ -133,16 +173,29 @@ export const CreateAddressSettingsDrawer = ({
133
173
  };
134
174
 
135
175
  return (
136
- <BasicDrawer
137
- data-fs-bp-create-address-settings-drawer
176
+ <SettingsDrawer
177
+ title="Address settings"
178
+ {...otherProps}
138
179
  close={close}
139
- {...props}
180
+ onPrimaryAction={handleConfirmClick}
181
+ isPrimaryButtonLoading={
182
+ isSetDefaultAddressesLoading || isSetScopeConfigLoading
183
+ }
184
+ isPrimaryButtonDisabled={!isConfirmButtonEnabled}
185
+ scopeName={SCOPE_KEYS.ADDRESSES}
186
+ onDismiss={close}
187
+ data-fs-bp-create-address-settings-drawer
140
188
  >
141
- <BasicDrawer.Heading title="Address settings" onClose={close} />
189
+ <SettingsDrawer.ListType
190
+ title="List type"
191
+ name="listType"
192
+ value={listType}
193
+ onChange={setListType}
194
+ options={ADDRESS_LIST_TYPE_OPTIONS}
195
+ />
142
196
 
143
- <BasicDrawer.Body>
144
- <h3>Default addresses</h3>
145
- <h4>Select the default addresses for this unit</h4>
197
+ <div data-fs-bp-default-addresses-section>
198
+ <h4 data-fs-bp-default-addresses-title>Default addresses</h4>
146
199
 
147
200
  <p data-fs-bp-default-address-label>
148
201
  Default shipping address (optional)
@@ -248,21 +301,7 @@ export const CreateAddressSettingsDrawer = ({
248
301
  )}
249
302
  />
250
303
  )}
251
- </BasicDrawer.Body>
252
-
253
- <BasicDrawer.Footer>
254
- <BasicDrawer.Button variant="ghost" onClick={close}>
255
- Cancel
256
- </BasicDrawer.Button>
257
- <BasicDrawer.Button
258
- variant="confirm"
259
- disabled={!isConfirmButtonEnabled}
260
- onClick={handleConfirmClick}
261
- isLoading={isSetDefaultAddressesLoading}
262
- >
263
- Save
264
- </BasicDrawer.Button>
265
- </BasicDrawer.Footer>
266
- </BasicDrawer>
304
+ </div>
305
+ </SettingsDrawer>
267
306
  );
268
307
  };
@@ -1,9 +1,10 @@
1
- @import "../../../shared/components/BasicDrawer/basic-drawer.scss";
1
+ @import "../../../shared/components/SettingsDrawer/settings-drawer.scss";
2
2
 
3
3
  [data-fs-bp-create-address-settings-drawer] {
4
4
  @import "../../../shared/components/InputText/input-text.scss";
5
5
  @import "../../../shared/components/ErrorMessage/error-message.scss";
6
6
  @import "../../../shared/components/SearchHighlight/search-highlight.scss";
7
+
7
8
  @import "../ExistingAddress/existing-address.scss";
8
9
  @import "../LocationForm/location-form.scss";
9
10
  @import "../RecipientsForm/recipients-form.scss";
@@ -12,33 +13,32 @@
12
13
  @import "@faststore/ui/src/components/atoms/Button/styles.scss";
13
14
  @import "@faststore/ui/src/components/molecules/Alert/styles.scss";
14
15
 
15
- [data-fs-bp-basic-drawer-body] {
16
+ [data-fs-bp-settings-drawer-body] {
16
17
  padding-bottom: 90px;
17
18
 
18
- h3 {
19
- font-weight: var(--fs-text-weight-semibold);
20
- font-size: var(--fs-text-size-2);
21
- line-height: var(--fs-spacing-4);
22
- margin-top: var(--fs-spacing-6);
23
-
24
- &:first-of-type {
19
+ [data-fs-bp-default-addresses-section] {
20
+ [data-fs-bp-default-addresses-title] {
21
+ font-weight: var(--fs-text-weight-semibold);
22
+ font-size: var(--fs-text-size-1);
23
+ line-height: 1.25rem;
24
+ letter-spacing: -0.01em;
25
+ margin-bottom: var(--fs-spacing-4);
25
26
  margin-top: 0;
27
+ color: #000000;
26
28
  }
27
- }
28
29
 
29
- h4 {
30
- font-weight: var(--fs-text-weight-regular);
31
- font-size: var(--fs-text-size-1);
32
- line-height: calc(var(--fs-spacing-0) + var(--fs-spacing-3));
33
- margin-bottom: calc(var(--fs-spacing-0) + var(--fs-spacing-3));
34
- }
30
+ [data-fs-bp-default-address-label] {
31
+ font-weight: var(--fs-text-weight-regular);
32
+ font-size: var(--fs-text-size-1);
33
+ line-height: 1.5;
34
+ color: #1f1f1f;
35
+ margin-bottom: var(--fs-spacing-1);
36
+ margin-top: var(--fs-spacing-4);
35
37
 
36
- [data-fs-bp-default-address-label] {
37
- font-weight: var(--fs-text-weight-regular);
38
- font-size: var(--fs-text-size-0);
39
- line-height: calc(var(--fs-spacing-0) + var(--fs-spacing-3));
40
- color: #1f1f1f;
41
- margin-bottom: var(--fs-spacing-0);
38
+ &:first-of-type {
39
+ margin-top: 0;
40
+ }
41
+ }
42
42
  }
43
43
  }
44
44
 
@@ -13,7 +13,7 @@ const mapDefaultAddressToAddressData = (
13
13
  response: DefaultAddressResponse
14
14
  ): AddressData => {
15
15
  return {
16
- id: response.userId || "",
16
+ id: response.id,
17
17
  name: response.addressLabel,
18
18
  types: [response.addressType],
19
19
  isActive: response.isActive,
@@ -184,4 +184,5 @@ export type DefaultAddressResponse = Pick<
184
184
  geoCoordinate: string | null;
185
185
  isActive: boolean;
186
186
  userId?: string;
187
+ id: string;
187
188
  };
@@ -45,7 +45,7 @@ export const PaymentMethodsLayout = ({
45
45
  setSelectedMethod(undefined);
46
46
  });
47
47
 
48
- const isLastPage = data.paging.pages === page || data.paging.pages === 0;
48
+ const isLastPage = data.paging?.pages === page || data.paging?.pages === 0;
49
49
 
50
50
  const {
51
51
  isLoading,
@@ -171,9 +171,9 @@ export const PaymentMethodsLayout = ({
171
171
  textSearch={setSearchTerm}
172
172
  />
173
173
  <Paginator.Counter
174
- total={data.paging.total}
174
+ total={data.paging?.total ?? 0}
175
175
  itemsLength={
176
- isLastPage ? data.paging.total : page * data.paging.perPage
176
+ isLastPage ? data.paging?.total : page * data.paging?.perPage
177
177
  }
178
178
  />
179
179
  </div>
@@ -182,7 +182,7 @@ export const PaymentMethodsLayout = ({
182
182
 
183
183
  {!isLoading && paymentMethods.length > 0 && (
184
184
  <div data-fs-bp-payment-methods-paginator>
185
- {data.paging.page > 1 ? (
185
+ {(data.paging?.page ?? 1) > 1 ? (
186
186
  <Paginator.NextPageButton
187
187
  onClick={decreasePage}
188
188
  disabled={isLoading}
@@ -204,9 +204,11 @@ export const PaymentMethodsLayout = ({
204
204
  )}
205
205
 
206
206
  <Paginator.Counter
207
- total={data.paging.total}
207
+ total={data.paging?.total ?? 0}
208
208
  itemsLength={
209
- isLastPage ? data.paging.total : page * data.paging.perPage
209
+ isLastPage
210
+ ? data.paging?.total ?? 0
211
+ : page * (data.paging?.perPage ?? 0)
210
212
  }
211
213
  />
212
214
  </div>
@@ -1,4 +1,4 @@
1
- import { getApiUrl } from "../../shared/utils";
1
+ import { getApiUrl, SCOPE_KEYS } from "../../shared/utils";
2
2
 
3
3
  import { Client } from "./Client";
4
4
 
@@ -32,8 +32,44 @@ export default class ScopeClient extends Client {
32
32
  }
33
33
  );
34
34
  }
35
+
36
+ getScopeConfig(
37
+ customerId: string,
38
+ unitId: string,
39
+ scopeName: string,
40
+ cookie: string
41
+ ) {
42
+ return this.get<{ type: "sync" | "custom" }>(
43
+ `customers/${customerId}/units/${unitId}/scopes/configs`,
44
+ {
45
+ params: { scopeName },
46
+ headers: {
47
+ Cookie: cookie,
48
+ },
49
+ }
50
+ );
51
+ }
52
+
53
+ setScopeConfig(
54
+ customerId: string,
55
+ unitId: string,
56
+ scopeName: string,
57
+ type: "sync" | "custom",
58
+ cookie: string
59
+ ) {
60
+ return this.post<{ message: string }, { type: "sync" | "custom" }>(
61
+ `customers/${customerId}/units/${unitId}/scopes/configs`,
62
+ { type },
63
+ {
64
+ params: { scopeName },
65
+ headers: {
66
+ Cookie: cookie,
67
+ },
68
+ }
69
+ );
70
+ }
35
71
  }
36
72
 
37
73
  const scopesClient = new ScopeClient();
38
74
 
39
- export { scopesClient };
75
+ export { scopesClient, SCOPE_KEYS };
@@ -1,4 +1,4 @@
1
- // import { isDevelopment } from "../../utils/environment";
1
+ import { isDevelopment } from "../../utils/environment";
2
2
  import { Icon } from "../Icon";
3
3
 
4
4
  export type ErrorProps = {
@@ -20,18 +20,20 @@ export default function Error({ error }: ErrorProps) {
20
20
  <button data-fs-bp-error-button onClick={() => window.location.reload()}>
21
21
  Try again
22
22
  </button>
23
- <div data-fs-bp-error-details>
24
- <span data-fs-bp-error-details-type>{error?.tags?.errorType}</span>
25
- <h2 data-fs-bp-error-details-title>Error Details</h2>
26
- <p data-fs-bp-error-details-message>{error?.error.message}</p>
27
- <p data-fs-bp-error-details-stack>Stack: {error?.error.stack}</p>
28
- <p data-fs-bp-error-details-component>
29
- Component: {error?.tags?.component}
30
- </p>
31
- <p data-fs-bp-error-details-query>
32
- Query: {JSON.stringify(error?.query)}
33
- </p>
34
- </div>
23
+ {isDevelopment() && (
24
+ <div data-fs-bp-error-details>
25
+ <span data-fs-bp-error-details-type>{error?.tags?.errorType}</span>
26
+ <h2 data-fs-bp-error-details-title>Error Details</h2>
27
+ <p data-fs-bp-error-details-message>{error?.error.message}</p>
28
+ <p data-fs-bp-error-details-stack>Stack: {error?.error.stack}</p>
29
+ <p data-fs-bp-error-details-component>
30
+ Component: {error?.tags?.component}
31
+ </p>
32
+ <p data-fs-bp-error-details-query>
33
+ Query: {JSON.stringify(error?.query)}
34
+ </p>
35
+ </div>
36
+ )}
35
37
  </div>
36
38
  );
37
39
  }
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+
3
+ import { useBuyerPortal } from "../../hooks";
4
+ import { BasicDrawer, type BasicDrawerProps } from "../BasicDrawer/BasicDrawer";
5
+
6
+ import {
7
+ SettingsDrawerContext,
8
+ type SettingsDrawerContextType,
9
+ } from "./SettingsDrawerContext";
10
+ import {
11
+ SettingsDrawerListType,
12
+ type ListTypeOption,
13
+ type SettingsDrawerListTypeProps,
14
+ DEFAULT_LIST_TYPE_OPTIONS,
15
+ } from "./SettingsDrawerListType";
16
+
17
+ export type { ListTypeOption, SettingsDrawerListTypeProps };
18
+ export { DEFAULT_LIST_TYPE_OPTIONS };
19
+
20
+ export type SettingsDrawerProps = Omit<BasicDrawerProps, "children"> & {
21
+ title: string;
22
+ subtitle?: string;
23
+ children: React.ReactNode;
24
+ primaryButtonLabel?: string;
25
+ secondaryButtonLabel?: string;
26
+ onPrimaryAction?: () => void;
27
+ onSecondaryAction?: () => void;
28
+ isPrimaryButtonLoading?: boolean;
29
+ isPrimaryButtonDisabled?: boolean;
30
+ isSecondaryButtonDisabled?: boolean;
31
+ scopeName?: string;
32
+ customerId?: string;
33
+ unitId?: string;
34
+ onScopeConfigChange?: (type: "sync" | "custom") => void;
35
+ };
36
+
37
+ export const SettingsDrawer = ({
38
+ title,
39
+ subtitle,
40
+ children,
41
+ primaryButtonLabel = "Save",
42
+ secondaryButtonLabel = "Cancel",
43
+ onPrimaryAction,
44
+ onSecondaryAction,
45
+ isPrimaryButtonLoading = false,
46
+ isPrimaryButtonDisabled = false,
47
+ isSecondaryButtonDisabled = false,
48
+ scopeName,
49
+ customerId,
50
+ unitId,
51
+ onScopeConfigChange,
52
+ onDismiss,
53
+ ...otherProps
54
+ }: SettingsDrawerProps) => {
55
+ const { clientContext, currentOrgUnit, currentContract } = useBuyerPortal();
56
+
57
+ const resolvedCustomerId =
58
+ customerId || currentContract?.id || clientContext.customerId;
59
+ const resolvedUnitId = unitId || currentOrgUnit?.id || "";
60
+
61
+ const handleClose = () => {
62
+ onDismiss?.();
63
+ };
64
+
65
+ const contextValue: SettingsDrawerContextType = {
66
+ scopeName,
67
+ customerId: resolvedCustomerId,
68
+ unitId: resolvedUnitId,
69
+ onScopeConfigChange,
70
+ };
71
+
72
+ return (
73
+ <SettingsDrawerContext.Provider value={contextValue}>
74
+ <BasicDrawer
75
+ data-fs-bp-settings-drawer
76
+ onDismiss={handleClose}
77
+ {...otherProps}
78
+ >
79
+ <BasicDrawer.Heading title={title} onClose={handleClose} />
80
+ <BasicDrawer.Body data-fs-bp-settings-drawer-body>
81
+ {subtitle && <p data-fs-bp-settings-drawer-subtitle>{subtitle}</p>}
82
+ {children}
83
+ </BasicDrawer.Body>
84
+ <BasicDrawer.Footer>
85
+ <BasicDrawer.Button
86
+ variant="ghost"
87
+ onClick={onSecondaryAction || handleClose}
88
+ disabled={isSecondaryButtonDisabled}
89
+ >
90
+ {secondaryButtonLabel}
91
+ </BasicDrawer.Button>
92
+ <BasicDrawer.Button
93
+ variant="confirm"
94
+ onClick={onPrimaryAction}
95
+ isLoading={isPrimaryButtonLoading}
96
+ disabled={isPrimaryButtonDisabled}
97
+ >
98
+ {primaryButtonLabel}
99
+ </BasicDrawer.Button>
100
+ </BasicDrawer.Footer>
101
+ </BasicDrawer>
102
+ </SettingsDrawerContext.Provider>
103
+ );
104
+ };
105
+
106
+ SettingsDrawer.ListType = SettingsDrawerListType;
@@ -0,0 +1,17 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ export type SettingsDrawerContextType = {
4
+ scopeName?: string;
5
+ customerId?: string;
6
+ unitId?: string;
7
+ onScopeConfigChange?: (type: "sync" | "custom") => void;
8
+ };
9
+
10
+ export const SettingsDrawerContext = createContext<
11
+ SettingsDrawerContextType | undefined
12
+ >(undefined);
13
+
14
+ export const useSettingsDrawerContext = (): SettingsDrawerContextType => {
15
+ const context = useContext(SettingsDrawerContext);
16
+ return context || {};
17
+ };
@@ -0,0 +1,100 @@
1
+ import React, { useState } from "react";
2
+
3
+ import { RadioGroup, RadioOption } from "@faststore/ui";
4
+
5
+ import { useGetScopeConfig } from "../../hooks";
6
+
7
+ import { useSettingsDrawerContext } from "./SettingsDrawerContext";
8
+
9
+ export type ListTypeOption = {
10
+ value: string;
11
+ label: string;
12
+ description: string;
13
+ };
14
+
15
+ export const DEFAULT_LIST_TYPE_OPTIONS: ListTypeOption[] = [
16
+ {
17
+ value: "custom",
18
+ label: "Custom list",
19
+ description: "Manage a unique for this organization.",
20
+ },
21
+ {
22
+ value: "sync",
23
+ label: "Synchronized List",
24
+ description:
25
+ "Use the shared list defined by the contract. Updates are automatic.",
26
+ },
27
+ ];
28
+
29
+ export type SettingsDrawerListTypeProps = {
30
+ title: string;
31
+ name: string;
32
+ value?: "sync" | "custom";
33
+ options?: ListTypeOption[];
34
+ onChange?: (value: "sync" | "custom") => void;
35
+ disabled?: boolean;
36
+ };
37
+
38
+ export const SettingsDrawerListType = ({
39
+ title,
40
+ name,
41
+ value: controlledValue,
42
+ options = DEFAULT_LIST_TYPE_OPTIONS,
43
+ onChange: controlledOnChange,
44
+ disabled = false,
45
+ }: SettingsDrawerListTypeProps) => {
46
+ const { scopeName, customerId, unitId } = useSettingsDrawerContext();
47
+ const [internalValue, setInternalValue] = useState<"sync" | "custom">(
48
+ "custom"
49
+ );
50
+
51
+ const { isScopeConfigLoading } = useGetScopeConfig(
52
+ {
53
+ customerId: customerId ?? "",
54
+ unitId: unitId ?? "",
55
+ scopeName: scopeName ?? "",
56
+ },
57
+ {
58
+ lazy: !scopeName || !customerId || !unitId,
59
+ onSuccess: (data) => {
60
+ if (data?.type) {
61
+ setInternalValue(data.type);
62
+ controlledOnChange?.(data.type);
63
+ }
64
+ },
65
+ }
66
+ );
67
+
68
+ const value = controlledValue ?? internalValue;
69
+ const isLoading = isScopeConfigLoading;
70
+
71
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
72
+ const newValue = e.target.value as "sync" | "custom";
73
+ setInternalValue(newValue);
74
+
75
+ if (controlledOnChange) {
76
+ controlledOnChange(newValue);
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div data-fs-bp-settings-drawer-list-type>
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>
98
+ </div>
99
+ );
100
+ };
@@ -0,0 +1,61 @@
1
+ [data-fs-bp-settings-drawer] {
2
+ @import "@faststore/ui/src/components/atoms/Radio/styles.scss";
3
+
4
+ [data-fs-bp-settings-drawer-body] {
5
+ color: #1f1f1f;
6
+ font-size: var(--fs-text-size-1);
7
+
8
+ [data-fs-bp-settings-drawer-subtitle] {
9
+ margin-bottom: var(--fs-spacing-5);
10
+ color: #1f1f1f;
11
+ }
12
+
13
+ [data-fs-bp-settings-drawer-list-type] {
14
+ display: flex;
15
+ flex-direction: column;
16
+ margin-bottom: var(--fs-spacing-4);
17
+
18
+ h4[data-fs-bp-settings-drawer-list-type-title] {
19
+ color: #000000;
20
+ font-weight: var(--fs-text-weight-semibold);
21
+ font-size: var(--fs-text-size-1);
22
+ line-height: 1.25rem;
23
+ letter-spacing: -0.01em;
24
+ margin-bottom: var(--fs-spacing-4);
25
+ margin-top: 0;
26
+ }
27
+
28
+ [data-fs-bp-settings-drawer-list-type-option] {
29
+ display: flex;
30
+ flex-direction: column;
31
+ margin-bottom: var(--fs-spacing-4);
32
+
33
+ &:last-child {
34
+ margin-bottom: 0;
35
+ }
36
+
37
+ [data-fs-radio-group-option] {
38
+ display: flex;
39
+ align-items: flex-start;
40
+ gap: var(--fs-spacing-2);
41
+
42
+ label {
43
+ font-weight: var(--fs-text-weight-regular);
44
+ font-size: var(--fs-text-size-1);
45
+ color: #1f1f1f;
46
+ }
47
+ }
48
+
49
+ [data-fs-bp-settings-drawer-list-type-description] {
50
+ margin-left: var(--fs-spacing-5);
51
+ margin-bottom: 0;
52
+ color: #707070;
53
+ font-weight: var(--fs-text-weight-regular);
54
+ font-size: var(--fs-bp-text-size-0);
55
+ line-height: var(--fs-bp-text-size-2);
56
+ letter-spacing: 0;
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
@@ -7,6 +7,13 @@ export {
7
7
  export { useAutocompletePosition } from "./AutocompleteDropdown/useAutocompletePosition";
8
8
  export { BasicCard, type BasicCardProps } from "./BasicCard/BasicCard";
9
9
  export { BasicDrawer, type BasicDrawerProps } from "./BasicDrawer/BasicDrawer";
10
+ export {
11
+ SettingsDrawer,
12
+ type SettingsDrawerProps,
13
+ type ListTypeOption,
14
+ type SettingsDrawerListTypeProps,
15
+ DEFAULT_LIST_TYPE_OPTIONS,
16
+ } from "./SettingsDrawer/SettingsDrawer";
10
17
  export {
11
18
  BasicDropdownMenu,
12
19
  type BasicDropdownMenuProps,
@@ -15,3 +15,5 @@ export { useRouterLoading } from "./useRouterLoading";
15
15
  export { useLogger } from "./useLogger";
16
16
  export { useGetDependenciesVersion } from "./useGetDependenciesVersion";
17
17
  export { useAnalytics } from "./analytics/useAnalytics";
18
+ export { useGetScopeConfig, SCOPE_KEYS } from "./useGetScopeConfig";
19
+ export { useSetScopeConfig } from "./useSetScopeConfig";
@@ -0,0 +1,35 @@
1
+ import {
2
+ getScopeConfigService,
3
+ type GetScopeConfigServiceProps,
4
+ SCOPE_KEYS,
5
+ } from "../services";
6
+
7
+ import { useQuery, type QueryOptions } from "./useQuery";
8
+
9
+ import type { AwaitedType } from "../types";
10
+
11
+ export const useGetScopeConfig = (
12
+ props: Omit<GetScopeConfigServiceProps, "cookie">,
13
+ options?: QueryOptions<AwaitedType<typeof getScopeConfigService>>
14
+ ) => {
15
+ const { data, error, isLoading, refetch } = useQuery<
16
+ AwaitedType<typeof getScopeConfigService>
17
+ >(
18
+ `scope-config-${props.customerId}-${props.unitId}-${props.scopeName}`,
19
+ (clientContext) =>
20
+ getScopeConfigService({
21
+ ...props,
22
+ cookie: clientContext.cookie,
23
+ }),
24
+ options
25
+ );
26
+
27
+ return {
28
+ scopeConfig: data,
29
+ isScopeConfigLoading: isLoading,
30
+ hasScopeConfigError: error,
31
+ refetchScopeConfig: refetch,
32
+ };
33
+ };
34
+
35
+ export { SCOPE_KEYS };
@@ -0,0 +1,30 @@
1
+ import {
2
+ setScopeConfigService,
3
+ type SetScopeConfigServiceProps,
4
+ SCOPE_KEYS,
5
+ } from "../services";
6
+
7
+ import { type MutationOptions, useMutation } from "./useMutation";
8
+
9
+ import type { AwaitedType } from "../types";
10
+
11
+ export const useSetScopeConfig = (
12
+ options?: MutationOptions<AwaitedType<typeof setScopeConfigService>>
13
+ ) => {
14
+ const { mutate, isLoading, error } = useMutation<
15
+ AwaitedType<typeof setScopeConfigService>,
16
+ Omit<SetScopeConfigServiceProps, "cookie">
17
+ >(
18
+ (variables, clientContext) =>
19
+ setScopeConfigService({ ...variables, cookie: clientContext.cookie }),
20
+ options
21
+ );
22
+
23
+ return {
24
+ setScopeConfig: mutate,
25
+ isSetScopeConfigLoading: isLoading,
26
+ hasSetScopeConfigError: error,
27
+ };
28
+ };
29
+
30
+ export { SCOPE_KEYS };
@@ -0,0 +1,19 @@
1
+ import { scopesClient, SCOPE_KEYS } from "../clients/ScopeClient";
2
+
3
+ export type GetScopeConfigServiceProps = {
4
+ customerId: string;
5
+ unitId: string;
6
+ scopeName: string;
7
+ cookie: string;
8
+ };
9
+
10
+ export const getScopeConfigService = async ({
11
+ customerId,
12
+ unitId,
13
+ scopeName,
14
+ cookie,
15
+ }: GetScopeConfigServiceProps) => {
16
+ return scopesClient.getScopeConfig(customerId, unitId, scopeName, cookie);
17
+ };
18
+
19
+ export { SCOPE_KEYS };
@@ -15,3 +15,12 @@ export {
15
15
  getDependenciesVersionService,
16
16
  type GetDependenciesVersionProps,
17
17
  } from "./get-dependencies-version.service";
18
+ export {
19
+ getScopeConfigService,
20
+ type GetScopeConfigServiceProps,
21
+ SCOPE_KEYS,
22
+ } from "./get-scope-config.service";
23
+ export {
24
+ setScopeConfigService,
25
+ type SetScopeConfigServiceProps,
26
+ } from "./set-scope-config.service";
@@ -0,0 +1,27 @@
1
+ import { scopesClient, SCOPE_KEYS } from "../clients/ScopeClient";
2
+
3
+ export type SetScopeConfigServiceProps = {
4
+ customerId: string;
5
+ unitId: string;
6
+ scopeName: string;
7
+ type: "sync" | "custom";
8
+ cookie: string;
9
+ };
10
+
11
+ export const setScopeConfigService = async ({
12
+ customerId,
13
+ unitId,
14
+ scopeName,
15
+ type,
16
+ cookie,
17
+ }: SetScopeConfigServiceProps) => {
18
+ return scopesClient.setScopeConfig(
19
+ customerId,
20
+ unitId,
21
+ scopeName,
22
+ type,
23
+ cookie
24
+ );
25
+ };
26
+
27
+ export { SCOPE_KEYS };
@@ -9,3 +9,4 @@ export type {
9
9
  PaymentMethodsReqCommonParams,
10
10
  } from "./PaymentMethodsClientTypes";
11
11
  export type { ScopeInput } from "./ScopeInput";
12
+ export type { AwaitedType } from "./AwaitedType";
@@ -13,4 +13,13 @@ export const LOCAL_STORAGE_LOCATION_EDIT_KEY = "bp_hide_edit_location_confirm";
13
13
  export const LOCAL_STORAGE_RECIPIENT_EDIT_KEY =
14
14
  "bp_hide_edit_recipient_confirm";
15
15
 
16
- export const CURRENT_VERSION = "1.3.41";
16
+ export const SCOPE_KEYS = {
17
+ CONTRACTS: "contractIds",
18
+ ADDRESSES: "addresses",
19
+ CUSTOM_FIELDS: "customFields",
20
+ COLLECTIONS: "collectionIds",
21
+ PAYMENT_SYSTEMS: "paymentSystemIds",
22
+ CREDIT_CARDS: "creditCards",
23
+ } as const;
24
+
25
+ export const CURRENT_VERSION = "1.3.42";
@@ -1,7 +1,12 @@
1
1
  export { addressLabelToPropMapping } from "./addresLabelToPropMapping";
2
2
  export { getApiUrl, getPostalCodeApiUrl, getTokenizationUrl } from "./api";
3
3
  export { compareItems } from "./compareItems";
4
- export { API_URL, AUT_COOKIE_KEY, DEBOUNCE_TIMEOUT } from "./constants";
4
+ export {
5
+ API_URL,
6
+ AUT_COOKIE_KEY,
7
+ DEBOUNCE_TIMEOUT,
8
+ SCOPE_KEYS,
9
+ } from "./constants";
5
10
  export {
6
11
  getAuthCookie,
7
12
  getCookieAsString,
@@ -82,7 +82,7 @@ const loaderFunction = async (
82
82
  return {
83
83
  data: paymentMethods,
84
84
  search: search ?? "",
85
- totalPaymentMethods: contractPaymentMethods.paging.total,
85
+ totalPaymentMethods: contractPaymentMethods?.paging?.total ?? 0,
86
86
  context: {
87
87
  clientContext: { cookie, userId, ...clientContext },
88
88
  currentOrgUnit,