@vtex/faststore-plugin-buyer-portal 1.3.41 → 1.3.43
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 +26 -1
- package/package.json +1 -1
- package/src/features/addresses/components/AddressRecipientsList/AddressRecipientsList.tsx +3 -2
- package/src/features/addresses/components/CreateAddressSettingsDrawer/CreateAddressSettingsDrawer.tsx +64 -25
- package/src/features/addresses/components/CreateAddressSettingsDrawer/create-address-settings-drawer.scss +22 -22
- package/src/features/addresses/components/RecipientsForm/RecipientItem/RecipientItem.tsx +6 -3
- package/src/features/addresses/data/countries.ts +3 -1
- package/src/features/addresses/services/default-values/get-default-address.service.ts +1 -1
- package/src/features/addresses/services/recipients/get-address-recipients.service.ts +8 -1
- package/src/features/addresses/types/AddressData.ts +1 -0
- package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +8 -6
- package/src/features/shared/clients/ScopeClient.ts +38 -2
- package/src/features/shared/components/Error/Error.tsx +15 -13
- package/src/features/shared/components/SettingsDrawer/SettingsDrawer.tsx +106 -0
- package/src/features/shared/components/SettingsDrawer/SettingsDrawerContext.tsx +17 -0
- package/src/features/shared/components/SettingsDrawer/SettingsDrawerListType.tsx +100 -0
- package/src/features/shared/components/SettingsDrawer/settings-drawer.scss +61 -0
- package/src/features/shared/components/index.ts +7 -0
- package/src/features/shared/hooks/index.ts +2 -0
- package/src/features/shared/hooks/useGetScopeConfig.ts +35 -0
- package/src/features/shared/hooks/useSetScopeConfig.ts +30 -0
- package/src/features/shared/services/get-scope-config.service.ts +19 -0
- package/src/features/shared/services/index.ts +9 -0
- package/src/features/shared/services/set-scope-config.service.ts +27 -0
- package/src/features/shared/types/index.ts +1 -0
- package/src/features/shared/utils/constants.ts +10 -1
- package/src/features/shared/utils/index.ts +6 -1
- package/src/features/shared/utils/phoneNumber.ts +30 -14
- package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +6 -3
- package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +7 -4
- package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +4 -1
- package/src/features/users/services/get-user-by-id.service.ts +2 -1
- package/src/pages/payment-methods.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +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.43] - 2025-12-16
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Update maskPhoneNumber to include country DDI
|
|
14
|
+
- Send only phone digits to API in Users and Recipients pages
|
|
15
|
+
- Show masked phone in layout and forms in Users and Recipients pages
|
|
16
|
+
|
|
17
|
+
## [1.3.42] - 2025-12-11
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Improve error handling in the payment methods
|
|
22
|
+
- Show error details on error boundary only in development environment
|
|
23
|
+
|
|
10
24
|
## [1.3.41] - 2025-12-09
|
|
11
25
|
|
|
12
26
|
### Changed
|
|
@@ -50,10 +64,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
50
64
|
|
|
51
65
|
## [1.3.34] - 2025-12-01
|
|
52
66
|
|
|
67
|
+
- Add generic `SettingsDrawer` component with `ListType` subcomponent for scope configuration
|
|
68
|
+
- Add Scope Config API integration (`useGetScopeConfig`, `useSetScopeConfig` hooks)
|
|
69
|
+
- Integrate Settings Drawer with Address Settings page
|
|
70
|
+
|
|
53
71
|
### Added
|
|
54
72
|
|
|
55
73
|
- Add DK Docs
|
|
56
74
|
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- Fix address ID mapping in default address service to use correct `id` field
|
|
78
|
+
|
|
57
79
|
## [1.3.33] - 2025-11-25
|
|
58
80
|
|
|
59
81
|
### Changed
|
|
@@ -362,7 +384,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
362
384
|
- Add CHANGELOG file
|
|
363
385
|
- Add README file
|
|
364
386
|
|
|
365
|
-
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.
|
|
387
|
+
[unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.43...HEAD
|
|
366
388
|
[1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
|
|
367
389
|
[1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
|
|
368
390
|
[1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
|
|
@@ -411,3 +433,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
411
433
|
[1.3.37]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.36...v1.3.37
|
|
412
434
|
[1.3.36]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.35...v1.3.36
|
|
413
435
|
[1.3.35]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.35
|
|
436
|
+
|
|
437
|
+
[1.3.43]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.42...v1.3.43
|
|
438
|
+
[1.3.42]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.3.42
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
usePageItems,
|
|
20
20
|
} from "../../../shared/hooks/usePageItems";
|
|
21
21
|
import { maskPhoneNumber } from "../../../shared/utils/phoneNumber";
|
|
22
|
+
import { type CountryCodes } from "../../data/countries";
|
|
22
23
|
import { useSearchAddressRecipients } from "../../hooks/useSearchAddressRecipients";
|
|
23
24
|
import { RecipientData } from "../../types";
|
|
24
25
|
import { DeleteRecipientAddressDrawer } from "../DeleteRecipientAddressDrawer/DeleteRecipientAddressDrawer";
|
|
@@ -33,7 +34,7 @@ export type AddressRecipientsListProps = {
|
|
|
33
34
|
total: number;
|
|
34
35
|
search: string;
|
|
35
36
|
page: number;
|
|
36
|
-
countryCode?:
|
|
37
|
+
countryCode?: CountryCodes;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export const AddressRecipientsList = forwardRef<
|
|
@@ -46,7 +47,7 @@ export const AddressRecipientsList = forwardRef<
|
|
|
46
47
|
search,
|
|
47
48
|
total,
|
|
48
49
|
page = 1,
|
|
49
|
-
countryCode
|
|
50
|
+
countryCode,
|
|
50
51
|
}: AddressRecipientsListProps,
|
|
51
52
|
ref
|
|
52
53
|
) => {
|
|
@@ -7,11 +7,14 @@ import { useUI, Skeleton } from "@faststore/ui";
|
|
|
7
7
|
import {
|
|
8
8
|
type BasicDrawerProps,
|
|
9
9
|
AutocompleteDropdown,
|
|
10
|
-
|
|
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
|
-
...
|
|
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
|
-
<
|
|
137
|
-
|
|
176
|
+
<SettingsDrawer
|
|
177
|
+
title="Address settings"
|
|
178
|
+
{...otherProps}
|
|
138
179
|
close={close}
|
|
139
|
-
{
|
|
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
|
-
<
|
|
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
|
-
<
|
|
144
|
-
<
|
|
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
|
-
</
|
|
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/
|
|
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-
|
|
16
|
+
[data-fs-bp-settings-drawer-body] {
|
|
16
17
|
padding-bottom: 90px;
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Icon, InputText } from "../../../../shared/components";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
maskPhoneNumber,
|
|
4
|
+
normalizePhoneNumber,
|
|
5
|
+
} from "../../../../shared/utils/phoneNumber";
|
|
3
6
|
|
|
4
7
|
import type { RecipientInput } from "../../../types";
|
|
5
8
|
|
|
@@ -48,7 +51,7 @@ export const RecipientItem = ({
|
|
|
48
51
|
<InputText
|
|
49
52
|
label="Phone Number"
|
|
50
53
|
className="recipients-phone"
|
|
51
|
-
value={recipient.recipientPhone}
|
|
54
|
+
value={maskPhoneNumber(recipient.recipientPhone, "USA")}
|
|
52
55
|
wrapperProps={{
|
|
53
56
|
style: {
|
|
54
57
|
borderTopLeftRadius: 0,
|
|
@@ -59,7 +62,7 @@ export const RecipientItem = ({
|
|
|
59
62
|
onChange(
|
|
60
63
|
index,
|
|
61
64
|
"recipientPhone",
|
|
62
|
-
|
|
65
|
+
normalizePhoneNumber(event.target.value)
|
|
63
66
|
)
|
|
64
67
|
}
|
|
65
68
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type CountryOption = {
|
|
2
|
-
id:
|
|
2
|
+
id: CountryCodes;
|
|
3
3
|
name: string;
|
|
4
4
|
};
|
|
5
5
|
|
|
@@ -9,3 +9,5 @@ export const CountryOptions: CountryOption[] = [
|
|
|
9
9
|
{ id: "CAN", name: "Canada" },
|
|
10
10
|
{ id: "MEX", name: "Mexico" },
|
|
11
11
|
];
|
|
12
|
+
|
|
13
|
+
export type CountryCodes = "USA" | "MEX" | "BRA" | "CAN";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizePhoneNumber } from "../../../shared/utils/phoneNumber";
|
|
1
2
|
import { recipientsClient } from "../../clients/RecipientsClient";
|
|
2
3
|
|
|
3
4
|
import type { RecipientsResponse } from "../../types/AddressData";
|
|
@@ -34,7 +35,13 @@ export const getAddressRecipientsService = async ({
|
|
|
34
35
|
page
|
|
35
36
|
);
|
|
36
37
|
|
|
37
|
-
return
|
|
38
|
+
return {
|
|
39
|
+
...recipients,
|
|
40
|
+
data: recipients.data.map((recipient) => ({
|
|
41
|
+
...recipient,
|
|
42
|
+
phone: normalizePhoneNumber(recipient.phone),
|
|
43
|
+
})),
|
|
44
|
+
};
|
|
38
45
|
} catch (err) {
|
|
39
46
|
console.error("Failed to get address recipients", err);
|
|
40
47
|
return { data: [], total: 0 };
|
|
@@ -45,7 +45,7 @@ export const PaymentMethodsLayout = ({
|
|
|
45
45
|
setSelectedMethod(undefined);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
const isLastPage = data.paging
|
|
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
|
|
174
|
+
total={data.paging?.total ?? 0}
|
|
175
175
|
itemsLength={
|
|
176
|
-
isLastPage ? data.paging
|
|
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
|
|
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
|
|
207
|
+
total={data.paging?.total ?? 0}
|
|
208
208
|
itemsLength={
|
|
209
|
-
isLastPage
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 };
|
|
@@ -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
|
|
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.43";
|
|
@@ -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 {
|
|
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,
|
|
@@ -1,18 +1,34 @@
|
|
|
1
|
+
import { type CountryCodes } from "../../addresses/data/countries";
|
|
2
|
+
|
|
1
3
|
import mask from "./mask";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
type MaskType = {
|
|
6
|
+
mask: string;
|
|
7
|
+
length: number;
|
|
8
|
+
ddi: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const MASK_MAP: Record<CountryCodes, MaskType> = {
|
|
12
|
+
USA: { mask: "(999) 999-9999", length: 14, ddi: "1" },
|
|
13
|
+
MEX: { mask: "(999) 999-9999", length: 14, ddi: "52" },
|
|
14
|
+
BRA: { mask: "(99) 99999-9999", length: 15, ddi: "55" },
|
|
15
|
+
CAN: { mask: "(999) 999-9999", length: 14, ddi: "1" },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function maskPhoneNumber(phoneNumber: string, country?: CountryCodes) {
|
|
19
|
+
if (!phoneNumber || !country || !MASK_MAP[country]) return phoneNumber;
|
|
20
|
+
|
|
21
|
+
const { mask: phoneMask, length, ddi } = MASK_MAP[country];
|
|
5
22
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
23
|
+
const normalizedPhone = normalizePhoneNumber(phoneNumber);
|
|
24
|
+
const phoneWithoutDdi = normalizedPhone.startsWith(ddi)
|
|
25
|
+
? normalizedPhone.slice(ddi.length)
|
|
26
|
+
: normalizedPhone;
|
|
27
|
+
|
|
28
|
+
return `+${ddi} ${mask(phoneWithoutDdi, phoneMask).substring(0, length)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function normalizePhoneNumber(phoneNumber: string) {
|
|
32
|
+
if (!phoneNumber) return phoneNumber;
|
|
33
|
+
return phoneNumber.replace(/\D/g, "");
|
|
18
34
|
}
|
|
@@ -15,7 +15,10 @@ import {
|
|
|
15
15
|
import { useAnalytics } from "../../../shared/hooks";
|
|
16
16
|
import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
|
|
17
17
|
import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
maskPhoneNumber,
|
|
20
|
+
normalizePhoneNumber,
|
|
21
|
+
} from "../../../shared/utils/phoneNumber";
|
|
19
22
|
import { useAddUserToOrgUnit } from "../../hooks";
|
|
20
23
|
|
|
21
24
|
export type CreateUserDrawerProps = Omit<BasicDrawerProps, "children"> & {
|
|
@@ -256,12 +259,12 @@ export const CreateUserDrawer = ({
|
|
|
256
259
|
|
|
257
260
|
<InputText
|
|
258
261
|
label="Phone number (optional)"
|
|
259
|
-
value={phone}
|
|
262
|
+
value={maskPhoneNumber(phone, "USA")}
|
|
260
263
|
onChange={(event) =>
|
|
261
264
|
// TODO: Update this when implementing i18n
|
|
262
265
|
updateField(
|
|
263
266
|
"phone",
|
|
264
|
-
|
|
267
|
+
normalizePhoneNumber(event.target.value)
|
|
265
268
|
)
|
|
266
269
|
}
|
|
267
270
|
/>
|
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
} from "../../../shared/components";
|
|
13
13
|
import { useAnalytics } from "../../../shared/hooks";
|
|
14
14
|
import { ANALYTICS_EVENTS } from "../../../shared/services/logger/analytics/constants";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
maskPhoneNumber,
|
|
17
|
+
normalizePhoneNumber,
|
|
18
|
+
} from "../../../shared/utils/phoneNumber";
|
|
16
19
|
import { useGetUserById, useUpdateUser } from "../../hooks";
|
|
17
20
|
|
|
18
21
|
export type UpdateUserDrawerProps = Omit<BasicDrawerProps, "children"> & {
|
|
@@ -174,7 +177,7 @@ export const UpdateUserDrawer = ({
|
|
|
174
177
|
updateUser({
|
|
175
178
|
userId,
|
|
176
179
|
name,
|
|
177
|
-
phone,
|
|
180
|
+
phone: phone ?? "",
|
|
178
181
|
roles,
|
|
179
182
|
orgUnitId,
|
|
180
183
|
});
|
|
@@ -245,10 +248,10 @@ export const UpdateUserDrawer = ({
|
|
|
245
248
|
) : (
|
|
246
249
|
<InputText
|
|
247
250
|
label="Phone number (optional)"
|
|
248
|
-
value={phone}
|
|
251
|
+
value={maskPhoneNumber(phone, "USA")}
|
|
249
252
|
onChange={(event) =>
|
|
250
253
|
// TODO: Update this when implementing i18n
|
|
251
|
-
updateField("phone",
|
|
254
|
+
updateField("phone", normalizePhoneNumber(event.target.value))
|
|
252
255
|
}
|
|
253
256
|
/>
|
|
254
257
|
)}
|
|
@@ -9,6 +9,7 @@ import { useBuyerPortal, useDrawerProps } from "../../../shared/hooks";
|
|
|
9
9
|
import { GlobalLayout } from "../../../shared/layouts";
|
|
10
10
|
import { OrgUnitTabsLayout } from "../../../shared/layouts/OrgUnitTabsLayout/OrgUnitTabLayout";
|
|
11
11
|
import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
|
|
12
|
+
import { maskPhoneNumber } from "../../../shared/utils/phoneNumber";
|
|
12
13
|
import { ReassignOrgUnitDrawer, UpdateUserDrawer } from "../../components";
|
|
13
14
|
import { UserDropdownMenu } from "../../components/UserDropdownMenu/UserDropdownMenu";
|
|
14
15
|
|
|
@@ -82,7 +83,9 @@ export const UserDetailsLayout = ({
|
|
|
82
83
|
|
|
83
84
|
<div data-fs-user-details-row>
|
|
84
85
|
<span data-fs-user-details-row-label>Phone number</span>
|
|
85
|
-
<span data-fs-user-details-row-value>
|
|
86
|
+
<span data-fs-user-details-row-value>
|
|
87
|
+
{maskPhoneNumber(user?.phone ?? "", "USA")}
|
|
88
|
+
</span>
|
|
86
89
|
</div>
|
|
87
90
|
|
|
88
91
|
<hr data-fs-user-details-divider />
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizePhoneNumber } from "../../shared/utils/phoneNumber";
|
|
1
2
|
import { usersClient } from "../clients/UsersClient";
|
|
2
3
|
|
|
3
4
|
import type { UserData } from "../types";
|
|
@@ -23,7 +24,7 @@ export const getUserByIdService = async ({
|
|
|
23
24
|
roles: role ? role : [],
|
|
24
25
|
id: userId,
|
|
25
26
|
email: email ?? "",
|
|
26
|
-
phone: phone ?? "",
|
|
27
|
+
phone: normalizePhoneNumber(phone ?? ""),
|
|
27
28
|
orgUnit: {
|
|
28
29
|
name: orgUnit,
|
|
29
30
|
},
|
|
@@ -82,7 +82,7 @@ const loaderFunction = async (
|
|
|
82
82
|
return {
|
|
83
83
|
data: paymentMethods,
|
|
84
84
|
search: search ?? "",
|
|
85
|
-
totalPaymentMethods: contractPaymentMethods
|
|
85
|
+
totalPaymentMethods: contractPaymentMethods?.paging?.total ?? 0,
|
|
86
86
|
context: {
|
|
87
87
|
clientContext: { cookie, userId, ...clientContext },
|
|
88
88
|
currentOrgUnit,
|