@vtex/faststore-plugin-buyer-portal 2.0.27 → 2.0.28

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 (81) hide show
  1. package/package.json +1 -1
  2. package/plugin.config.js +4 -0
  3. package/src/features/accounting-fields/layouts/AccountingFieldLayout/AccountingFieldLayout.tsx +1 -1
  4. package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +48 -11
  5. package/src/features/addresses/components/CreateAddressDrawer/CreateAddressDrawer.tsx +19 -11
  6. package/src/features/addresses/components/CreateAddressDrawer/create-address-drawer.scss +1 -1
  7. package/src/features/addresses/components/CreateAddressSettingsDrawer/create-address-settings-drawer.scss +1 -1
  8. package/src/features/addresses/components/RemoveAddressDrawer/remove-address-drawer.scss +1 -1
  9. package/src/features/addresses/components/index.ts +0 -2
  10. package/src/features/addresses/layouts/AddressDetailsLayout/AddressDetailsLayout.tsx +265 -52
  11. package/src/features/addresses/layouts/AddressDetailsLayout/address-details-layout.scss +3 -1
  12. package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +1 -2
  13. package/src/features/addresses/layouts/AddressesLayout/addresses-layout.scss +1 -1
  14. package/src/features/addresses/utils/index.ts +1 -0
  15. package/src/features/addresses/utils/resolveAddressDetailsTab.test.ts +40 -0
  16. package/src/features/addresses/utils/resolveAddressDetailsTab.ts +27 -0
  17. package/src/features/contracts/components/AddContractsDrawer/AddContractsDrawer.tsx +1 -2
  18. package/src/features/contracts/hooks/useContractListing.ts +2 -2
  19. package/src/features/contracts/layouts/ContractInformationLayout/ContractInformationLayout.tsx +11 -0
  20. package/src/features/contracts/layouts/ContractListingLayout/ContractListingLayout.tsx +1 -0
  21. package/src/features/contracts/layouts/ContractListingLayout/ContractSettingsNav.tsx +18 -3
  22. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +7 -4
  23. package/src/features/recipients/clients/ContractRecipientsClient.ts +129 -0
  24. package/src/features/recipients/clients/__tests__/ContractRecipientsClient.test.ts +366 -0
  25. package/src/features/recipients/clients/index.ts +2 -0
  26. package/src/features/recipients/components/AddRecipientsDrawer/AddRecipientsDrawer.tsx +131 -0
  27. package/src/features/recipients/components/AddRecipientsDrawer/add-recipients-drawer.scss +14 -0
  28. package/src/features/recipients/components/DeleteRecipientDrawer/DeleteRecipientDrawer.tsx +138 -0
  29. package/src/features/recipients/components/DeleteRecipientDrawer/delete-recipient-drawer.scss +9 -0
  30. package/src/features/{addresses/components/EditRecipientAddressDrawer/EditRecipientAddressDrawer.tsx → recipients/components/EditRecipientDrawer/EditRecipientDrawer.tsx} +68 -75
  31. package/src/features/{addresses/components/EditRecipientAddressDrawer/edit-recipient-address-drawer.scss → recipients/components/EditRecipientDrawer/edit-recipient-drawer.scss} +3 -6
  32. package/src/features/recipients/components/RecipientsList/RecipientsList.tsx +98 -0
  33. package/src/features/recipients/components/RecipientsList/recipients-list.scss +12 -0
  34. package/src/features/recipients/components/index.ts +21 -0
  35. package/src/features/recipients/hooks/__tests__/useListContractRecipients.test.ts +134 -0
  36. package/src/features/recipients/hooks/index.ts +7 -0
  37. package/src/features/recipients/hooks/useAddContractRecipients.ts +27 -0
  38. package/src/features/recipients/hooks/useDeleteContractRecipient.ts +27 -0
  39. package/src/features/recipients/hooks/useEditContractRecipient.ts +27 -0
  40. package/src/features/recipients/hooks/useListContractRecipients.ts +38 -0
  41. package/src/features/recipients/index.ts +6 -0
  42. package/src/features/recipients/layouts/RecipientsLayout/RecipientsLayout.tsx +271 -0
  43. package/src/features/recipients/layouts/RecipientsLayout/recipients-layout.scss +66 -0
  44. package/src/features/recipients/layouts/index.ts +1 -0
  45. package/src/features/recipients/services/__tests__/add-contract-recipients.service.test.ts +141 -0
  46. package/src/features/recipients/services/__tests__/delete-contract-recipient.service.test.ts +92 -0
  47. package/src/features/recipients/services/__tests__/edit-contract-recipient.service.test.ts +132 -0
  48. package/src/features/recipients/services/__tests__/get-contract-recipients.service.test.ts +165 -0
  49. package/src/features/recipients/services/add-contract-recipients.service.ts +40 -0
  50. package/src/features/recipients/services/delete-contract-recipient.service.ts +38 -0
  51. package/src/features/recipients/services/edit-contract-recipient.service.ts +43 -0
  52. package/src/features/recipients/services/get-contract-recipients.service.ts +55 -0
  53. package/src/features/recipients/services/index.ts +16 -0
  54. package/src/features/recipients/types/RecipientData.ts +42 -0
  55. package/src/features/recipients/types/index.ts +1 -0
  56. package/src/features/recipients/utils/__tests__/formatRecipientName.test.ts +25 -0
  57. package/src/features/recipients/utils/formatRecipientName.ts +7 -0
  58. package/src/features/recipients/utils/index.ts +1 -0
  59. package/src/features/shared/clients/Client.ts +13 -2
  60. package/src/features/shared/clients/__tests__/Client.test.ts +68 -0
  61. package/src/features/shared/components/BuyerPortalProvider/BuyerPortalProvider.tsx +1 -0
  62. package/src/features/shared/components/Paginator/Interval.tsx +1 -2
  63. package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +2 -1
  64. package/src/features/shared/layouts/SumaPageLayout/FullSidebarNav.tsx +6 -4
  65. package/src/features/shared/services/feature-flags/get-feature-flags.service.ts +2 -0
  66. package/src/features/shared/utils/__tests__/getContractSettingsLinks.test.ts +87 -0
  67. package/src/features/shared/utils/buyerPortalRoutes.ts +2 -0
  68. package/src/features/shared/utils/constants.ts +4 -1
  69. package/src/features/shared/utils/getContractSettingsLinks.ts +39 -23
  70. package/src/features/shared/utils/index.ts +1 -0
  71. package/src/features/shared/utils/phoneNumber.ts +4 -0
  72. package/src/pages/address-details.tsx +33 -16
  73. package/src/pages/recipients.tsx +140 -0
  74. package/src/themes/layouts.scss +3 -0
  75. package/src/features/addresses/components/AddRecipientsDrawer/AddRecipientsDrawer.tsx +0 -140
  76. package/src/features/addresses/components/AddressRecipientsList/AddressRecipientsList.tsx +0 -250
  77. package/src/features/addresses/components/DeleteRecipientAddressDrawer/DeleteRecipientAddressDrawer.tsx +0 -142
  78. /package/src/features/{addresses → recipients}/components/RecipientsForm/RecipientItem/RecipientItem.tsx +0 -0
  79. /package/src/features/{addresses → recipients}/components/RecipientsForm/RecipientItem/recipient-item.scss +0 -0
  80. /package/src/features/{addresses → recipients}/components/RecipientsForm/RecipientsForm.tsx +0 -0
  81. /package/src/features/{addresses → recipients}/components/RecipientsForm/recipients-form.scss +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "2.0.27",
3
+ "version": "2.0.28",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "files": [
package/plugin.config.js CHANGED
@@ -20,6 +20,10 @@ module.exports = {
20
20
  path: "/pvt/organization-account/addresses/[orgUnitId]/[contractId]",
21
21
  appLayout: false,
22
22
  },
23
+ recipients: {
24
+ path: "/pvt/organization-account/recipients/[orgUnitId]/[contractId]",
25
+ appLayout: false,
26
+ },
23
27
  budgets: {
24
28
  path: "/pvt/organization-account/budgets/[orgUnitId]/[contractId]",
25
29
  appLayout: false,
@@ -25,6 +25,7 @@ import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
25
25
  import { useLocalization } from "../../../shared/localization/LocalizationContext";
26
26
  import { getValidPage } from "../../../shared/utils";
27
27
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
28
+ import { DEFAULT_PAGE_SIZE as PAGE_SIZE } from "../../../shared/utils/constants";
28
29
  import {
29
30
  AccountingFieldEmptyAlert,
30
31
  AccountingValueDropdown,
@@ -44,7 +45,6 @@ type AccountingFieldLayoutProps = {
44
45
  const HEADER_ICON_SIZE = 24;
45
46
  const PAGE_PARAM = "page";
46
47
  const SEARCH_PARAM = "search";
47
- const PAGE_SIZE = 25;
48
48
 
49
49
  export const AccountingFieldLayout = ({
50
50
  accountingField,
@@ -1,3 +1,5 @@
1
+ import { useRef } from "react";
2
+
1
3
  import { useRouter } from "next/router";
2
4
 
3
5
  import { DropdownItem, Icon as UIIcon, useUI } from "@faststore/ui";
@@ -5,16 +7,19 @@ import { DropdownItem, Icon as UIIcon, useUI } from "@faststore/ui";
5
7
  import {
6
8
  EditAddressDrawer,
7
9
  LocationDrawer,
8
- AddRecipientsDrawer,
9
10
  RemoveAddressDrawer,
10
11
  DeleteAddressDrawer,
11
12
  } from "..";
13
+ import { AddRecipientsDrawer } from "../../../recipients/components";
12
14
  import { BasicDropdownMenu, Icon } from "../../../shared/components";
13
- import { useDrawerProps } from "../../../shared/hooks";
15
+ import { useBuyerPortal, useDrawerProps } from "../../../shared/hooks";
14
16
  import { useLocalization } from "../../../shared/localization/LocalizationContext";
17
+ import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
18
+ import { useAddRecipientsToAddress } from "../../hooks/useAddRecipientsToAddress";
15
19
  import { useSetDefaultAddress } from "../../hooks/useSetDefaultAddress";
16
20
  import { useUnsetDefaultAddress } from "../../hooks/useUnsetDefaultAddress";
17
21
 
22
+ import type { RecipientInput } from "../../../recipients/types";
18
23
  import type { AddressData } from "../../types";
19
24
 
20
25
  export type AddressDropdownMenuProps = {
@@ -37,6 +42,10 @@ export const AddressDropdownMenu = ({
37
42
  const router = useRouter();
38
43
  const { pushToast } = useUI();
39
44
  const { t } = useLocalization();
45
+ const { featureFlags } = useBuyerPortal();
46
+ const isRecipientsContractLevelEnabled = Boolean(
47
+ featureFlags?.enableRecipientsContractLevel
48
+ );
40
49
 
41
50
  const resolvedLocationsLabel =
42
51
  locationsLabel ?? t("addresses.labels.locations");
@@ -122,6 +131,25 @@ ${t("shared.messages.changesTimeout")}`,
122
131
  onOpen?.();
123
132
  };
124
133
 
134
+ const addRecipientsErrorRef = useRef<Error | null>(null);
135
+ const { addRecipientsToAddress, isAddRecipientToAddressLoading } =
136
+ useAddRecipientsToAddress({
137
+ onError: (error) => {
138
+ addRecipientsErrorRef.current = error;
139
+ },
140
+ });
141
+
142
+ const handleAddRecipientsSubmit = async (recipients: RecipientInput[]) => {
143
+ addRecipientsErrorRef.current = null;
144
+ await addRecipientsToAddress({
145
+ unitId: orgUnitId,
146
+ contractId: customerId,
147
+ addressId: currentAddress.id,
148
+ recipients,
149
+ });
150
+ if (addRecipientsErrorRef.current) throw addRecipientsErrorRef.current;
151
+ };
152
+
125
153
  const {
126
154
  open: openEditDrawerProps,
127
155
  isOpen: isEditDrawerOpen,
@@ -172,13 +200,15 @@ ${t("shared.messages.changesTimeout")}`,
172
200
  <UIIcon name="PlusCircle" {...sizeProps} />
173
201
  {t("addresses.actions.addLocations", [resolvedLocationsLabel])}
174
202
  </DropdownItem>
175
- <DropdownItem
176
- data-fs-bp-testid="dropdown-item-add-recipient"
177
- onClick={openRecipientsDrawer}
178
- >
179
- <UIIcon name="PlusCircle" {...sizeProps} />
180
- {t("addresses.actions.addRecipients")}
181
- </DropdownItem>
203
+ {!isRecipientsContractLevelEnabled && (
204
+ <DropdownItem
205
+ data-fs-bp-testid="dropdown-item-add-recipient"
206
+ onClick={openRecipientsDrawer}
207
+ >
208
+ <UIIcon name="PlusCircle" {...sizeProps} />
209
+ {t("addresses.actions.addRecipients")}
210
+ </DropdownItem>
211
+ )}
182
212
  {currentAddress.isDefault ? (
183
213
  <DropdownItem onClick={handleUnsetAsDefault}>
184
214
  <Icon name="BookmarkRemove" {...sizeProps} />
@@ -227,13 +257,20 @@ ${t("shared.messages.changesTimeout")}`,
227
257
  locationsLabel={resolvedLocationsLabel}
228
258
  />
229
259
  )}
230
- {isOpenRecipientsDrawer && (
260
+ {!isRecipientsContractLevelEnabled && isOpenRecipientsDrawer && (
231
261
  <AddRecipientsDrawer
232
262
  readonly
233
263
  {...recipientDrawerProps}
234
264
  isOpen={isOpenRecipientsDrawer}
235
- refetchRecipients={() => router.reload()}
265
+ isSubmitting={isAddRecipientToAddressLoading}
266
+ onSubmit={handleAddRecipientsSubmit}
267
+ onSubmitted={() => router.reload()}
236
268
  addressId={currentAddress.id}
269
+ viewLink={`${buyerPortalRoutes.addressDetails({
270
+ orgUnitId,
271
+ contractId: customerId,
272
+ addressId: currentAddress.id,
273
+ })}?tab=recipient-tab`}
237
274
  />
238
275
  )}
239
276
  {isOpenRemoveAddressDrawer && (
@@ -2,7 +2,8 @@ import { useState } from "react";
2
2
 
3
3
  import { useUI, Alert } from "@faststore/ui";
4
4
 
5
- import { AddressForm, LocationForm, RecipientsForm } from "../";
5
+ import { AddressForm, LocationForm } from "../";
6
+ import { RecipientsForm } from "../../../recipients/components";
6
7
  import {
7
8
  type BasicDrawerProps,
8
9
  BasicDrawer,
@@ -40,7 +41,10 @@ export const CreateAddressDrawer = ({
40
41
  const { t } = useLocalization();
41
42
  const [isTouched, setIsTouched] = useState(false);
42
43
  const [useExistingAddress, setUseExistingAddress] = useState(false);
43
- const { currentOrgUnit } = useBuyerPortal();
44
+ const { currentOrgUnit, featureFlags } = useBuyerPortal();
45
+ const isRecipientsContractLevelEnabled = Boolean(
46
+ featureFlags?.enableRecipientsContractLevel
47
+ );
44
48
  const { trackEvent, trackEntityCreated, trackEntityCreateError } =
45
49
  useAnalytics({
46
50
  entityType: "address",
@@ -186,9 +190,11 @@ export const CreateAddressDrawer = ({
186
190
  <TabBar>
187
191
  <TabOption id="address-tab">{t("addresses.tabs.details")}</TabOption>
188
192
  <TabOption id="location-tab">{resolvedLocationsLabel}</TabOption>
189
- <TabOption id="recipient-tab">
190
- {t("addresses.tabs.recipients")}
191
- </TabOption>
193
+ {!isRecipientsContractLevelEnabled && (
194
+ <TabOption id="recipient-tab">
195
+ {t("addresses.tabs.recipients")}
196
+ </TabOption>
197
+ )}
192
198
  </TabBar>
193
199
 
194
200
  {invalidAddress && (
@@ -233,12 +239,14 @@ export const CreateAddressDrawer = ({
233
239
  />
234
240
  </TabContent>
235
241
 
236
- <TabContent id="recipient-tab">
237
- <RecipientsForm
238
- recipients={recipients}
239
- setRecipients={setRecipients}
240
- />
241
- </TabContent>
242
+ {!isRecipientsContractLevelEnabled && (
243
+ <TabContent id="recipient-tab">
244
+ <RecipientsForm
245
+ recipients={recipients}
246
+ setRecipients={setRecipients}
247
+ />
248
+ </TabContent>
249
+ )}
242
250
  </BasicDrawer.Body>
243
251
  </Tab>
244
252
 
@@ -6,7 +6,7 @@
6
6
  @import "../../../shared/components/ErrorMessage/error-message.scss";
7
7
  @import "../ExistingAddress/existing-address.scss";
8
8
  @import "../LocationForm/location-form.scss";
9
- @import "../RecipientsForm/recipients-form.scss";
9
+ @import "../../../recipients/components/RecipientsForm/recipients-form.scss";
10
10
 
11
11
  @import "@faststore/ui/src/components/molecules/Alert/styles.scss";
12
12
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  @import "../ExistingAddress/existing-address.scss";
9
9
  @import "../LocationForm/location-form.scss";
10
- @import "../RecipientsForm/recipients-form.scss";
10
+ @import "../../../recipients/components/RecipientsForm/recipients-form.scss";
11
11
 
12
12
  @import "@faststore/ui/src/components/molecules/Alert/styles.scss";
13
13
 
@@ -5,7 +5,7 @@
5
5
  @import "../../../shared/components/ErrorMessage/error-message.scss";
6
6
  @import "../ExistingAddress/existing-address.scss";
7
7
  @import "../LocationForm/location-form.scss";
8
- @import "../RecipientsForm/recipients-form.scss";
8
+ @import "../../../recipients/components/RecipientsForm/recipients-form.scss";
9
9
 
10
10
 
11
11
  [data-fs-bp-drawer-label] {
@@ -2,13 +2,11 @@ export { AddressLine, type AddressLineProps } from "./AddressLine/AddressLine";
2
2
  export { default as AddressesCard } from "./AddressesCard/AddressesCard";
3
3
  export { CreateAddressDrawer } from "./CreateAddressDrawer/CreateAddressDrawer";
4
4
  export { LocationForm } from "./LocationForm/LocationForm";
5
- export { RecipientsForm } from "./RecipientsForm/RecipientsForm";
6
5
  export { AddressForm } from "./AddressForm/AddressForm";
7
6
  export { ExistingAddress } from "./ExistingAddress/ExistingAddress";
8
7
  export { EditAddressDrawer } from "./EditAddressDrawer/EditAddressDrawer";
9
8
  export { AddressDropdownMenu } from "./AddressDropdownMenu/AddressDropdownMenu";
10
9
  export { LocationDrawer } from "./LocationsDrawer/LocationsDrawer";
11
- export { AddRecipientsDrawer } from "./AddRecipientsDrawer/AddRecipientsDrawer";
12
10
  export { RemoveAddressDrawer } from "./RemoveAddressDrawer/RemoveAddressDrawer";
13
11
  export { DeleteAddressDrawer } from "./DeleteAddressDrawer/DeleteAddressDrawer";
14
12
  export { AddressLocationsList } from "./AddressLocationsList/AddressLocationsList";
@@ -4,15 +4,29 @@ import { useRouter } from "next/router";
4
4
 
5
5
  import { Dropdown, Skeleton, useUI } from "@faststore/ui";
6
6
 
7
+ import {
8
+ AddRecipientsDrawer,
9
+ DeleteRecipientDrawer,
10
+ EditRecipientDrawer,
11
+ RecipientsList,
12
+ } from "../../../recipients/components";
7
13
  import {
8
14
  BasicDropdownMenu,
15
+ EmptyState,
9
16
  HeaderInside,
17
+ InternalSearch,
18
+ Paginator,
10
19
  Tab,
11
20
  } from "../../../shared/components";
12
21
  import { TabBar } from "../../../shared/components/Tab/TabBar";
13
22
  import { TabContent } from "../../../shared/components/Tab/TabContent";
14
23
  import { TabOption } from "../../../shared/components/Tab/TabOption";
15
24
  import { useBuyerPortal, useDrawerProps } from "../../../shared/hooks";
25
+ import {
26
+ PAGE_PARAMS,
27
+ SEARCH_PARAMS,
28
+ usePageItems,
29
+ } from "../../../shared/hooks/usePageItems";
16
30
  import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
17
31
  import { useLocalization } from "../../../shared/localization/LocalizationContext";
18
32
  import { addressLabelToPropMapping } from "../../../shared/utils";
@@ -20,21 +34,27 @@ import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
20
34
  import {
21
35
  AddressDropdownMenu,
22
36
  EditAddressDrawer,
23
- AddRecipientsDrawer,
24
37
  LocationDrawer,
25
38
  } from "../../components";
26
39
  import { AddressLocationsList } from "../../components/AddressLocationsList/AddressLocationsList";
27
- import { AddressRecipientsList } from "../../components/AddressRecipientsList/AddressRecipientsList";
28
40
  import { useGetAddressLocationSettings } from "../../hooks";
29
- import { getStateDisplayFromCode } from "../../utils";
41
+ import { useAddRecipientsToAddress } from "../../hooks/useAddRecipientsToAddress";
42
+ import { useDeleteRecipientAddress } from "../../hooks/useDeleteRecipientAddress";
43
+ import { useEditRecipientsToAddress } from "../../hooks/useEditRecipientsToAddress";
44
+ import { useSearchAddressRecipients } from "../../hooks/useSearchAddressRecipients";
45
+ import { getStateDisplayFromCode, resolveTab } from "../../utils";
30
46
 
47
+ import type {
48
+ RecipientEditInput,
49
+ RecipientInput,
50
+ } from "../../../recipients/types";
31
51
  import type { AddressLocationsListRef } from "../../components/AddressLocationsList/AddressLocationsList";
32
- import type { AddressRecipientsListRef } from "../../components/AddressRecipientsList/AddressRecipientsList";
33
- import type { AddressData } from "../../types";
52
+ import type { AddressData, RecipientData } from "../../types";
34
53
  import type {
35
54
  LocationsPageData,
36
55
  RecipientsPageData,
37
56
  } from "../../types/AddressData";
57
+ import type { AddressDetailTabId } from "../../utils";
38
58
 
39
59
  export type AddressDetailsLayoutProps = {
40
60
  addressDetails: AddressData | null;
@@ -42,8 +62,6 @@ export type AddressDetailsLayoutProps = {
42
62
  locationsData: LocationsPageData;
43
63
  };
44
64
 
45
- type AddressDetailTabId = "address-tab" | "location-tab" | "recipient-tab";
46
-
47
65
  export const AddressDetailsLayout = ({
48
66
  addressDetails,
49
67
  recipientsData,
@@ -58,35 +76,31 @@ export const AddressDetailsLayout = ({
58
76
  return null;
59
77
  }
60
78
 
61
- const { currentOrgUnit: orgUnit, currentContract: contract } =
62
- useBuyerPortal();
63
-
64
- const [activeTabId, setActiveTabId] = useState<AddressDetailTabId>(() => {
65
- const tabFromQuery = router.query.tab as AddressDetailTabId;
66
- const validTabs: AddressDetailTabId[] = [
67
- "address-tab",
68
- "location-tab",
69
- "recipient-tab",
70
- ];
79
+ const {
80
+ currentOrgUnit: orgUnit,
81
+ currentContract: contract,
82
+ featureFlags,
83
+ } = useBuyerPortal();
84
+ const isRecipientsContractLevelEnabled = Boolean(
85
+ featureFlags?.enableRecipientsContractLevel
86
+ );
71
87
 
72
- return validTabs.includes(tabFromQuery) ? tabFromQuery : "address-tab";
73
- });
88
+ const [activeTabId, setActiveTabId] = useState<AddressDetailTabId>(() =>
89
+ resolveTab(router.query.tab, isRecipientsContractLevelEnabled)
90
+ );
74
91
  const { open: openEditDrawer, ...createDrawerProps } = useDrawerProps();
75
- const addressRecipientsListRef = useRef<AddressRecipientsListRef>(null);
76
92
  const addressLocationsListRef = useRef<AddressLocationsListRef>(null);
77
93
 
78
94
  useEffect(() => {
79
- const tabFromQuery = router.query.tab as AddressDetailTabId;
80
- const validTabs: AddressDetailTabId[] = [
81
- "address-tab",
82
- "location-tab",
83
- "recipient-tab",
84
- ];
85
-
86
- if (validTabs.includes(tabFromQuery) && tabFromQuery !== activeTabId) {
87
- setActiveTabId(tabFromQuery);
95
+ const resolvedTab = resolveTab(
96
+ router.query.tab,
97
+ isRecipientsContractLevelEnabled
98
+ );
99
+
100
+ if (resolvedTab !== activeTabId) {
101
+ setActiveTabId(resolvedTab);
88
102
  }
89
- }, [router.query.tab, activeTabId]);
103
+ }, [router.query.tab, activeTabId, isRecipientsContractLevelEnabled]);
90
104
 
91
105
  const {
92
106
  open: openLocationsDrawer,
@@ -100,6 +114,18 @@ export const AddressDetailsLayout = ({
100
114
  ...recipientDrawerProps
101
115
  } = useDrawerProps();
102
116
 
117
+ const {
118
+ open: openEditRecipientDrawer,
119
+ isOpen: isOpenEditRecipientDrawer,
120
+ ...editRecipientDrawerProps
121
+ } = useDrawerProps();
122
+
123
+ const {
124
+ open: openDeleteRecipientDrawer,
125
+ isOpen: isOpenDeleteRecipientDrawer,
126
+ ...deleteRecipientDrawerProps
127
+ } = useDrawerProps();
128
+
103
129
  const { isLocationSettingsLoading, locationSettingsData } =
104
130
  useGetAddressLocationSettings(
105
131
  {
@@ -138,8 +164,113 @@ export const AddressDetailsLayout = ({
138
164
  setActiveTabId(newTabId);
139
165
  };
140
166
 
167
+ const [currentRecipient, setCurrentRecipient] = useState<RecipientData>();
168
+ const [totalRecipients, setTotalRecipients] = useState(recipientsData.total);
169
+
170
+ const {
171
+ isLoading: isRecipientsLoading,
172
+ items: recipients,
173
+ searchTerm: recipientsSearchTerm,
174
+ setSearchTerm: setRecipientsSearchTerm,
175
+ increasePage: increaseRecipientsPage,
176
+ setIsLoading: setIsRecipientsLoading,
177
+ setItems: setRecipientItems,
178
+ } = usePageItems<RecipientData>({
179
+ initialItems: recipientsData.data,
180
+ search: recipientsData.search,
181
+ page: recipientsData.page ?? 1,
182
+ pageParam: PAGE_PARAMS.RECIPIENTS,
183
+ searchParam: SEARCH_PARAMS.RECIPIENTS,
184
+ });
185
+
186
+ const { searchAddressRecipients, refetchSearchAddressRecipients } =
187
+ useSearchAddressRecipients(
188
+ {
189
+ unitId: orgUnit?.id ?? "",
190
+ addressId: router.query.addressId as string,
191
+ customerId: router.query.contractId as string,
192
+ page: 1,
193
+ search: recipientsData.search ?? "",
194
+ },
195
+ { lazy: isRecipientsContractLevelEnabled }
196
+ );
197
+
198
+ useEffect(() => {
199
+ if (searchAddressRecipients?.data) {
200
+ setRecipientItems(searchAddressRecipients.data);
201
+ setTotalRecipients(searchAddressRecipients.total);
202
+ setIsRecipientsLoading(false);
203
+ }
204
+ }, [searchAddressRecipients]);
205
+
141
206
  const handleRefetchRecipients = () => {
142
- addressRecipientsListRef.current?.refetchRecipients();
207
+ setIsRecipientsLoading(true);
208
+ refetchSearchAddressRecipients();
209
+ };
210
+
211
+ const addRecipientsErrorRef = useRef<Error | null>(null);
212
+ const { addRecipientsToAddress, isAddRecipientToAddressLoading } =
213
+ useAddRecipientsToAddress({
214
+ onError: (error) => {
215
+ addRecipientsErrorRef.current = error;
216
+ },
217
+ });
218
+
219
+ const handleAddRecipientsSubmit = async (recipients: RecipientInput[]) => {
220
+ addRecipientsErrorRef.current = null;
221
+ await addRecipientsToAddress({
222
+ unitId: orgUnit?.id ?? "",
223
+ contractId: router.query.contractId as string,
224
+ addressId: addressDetails.id,
225
+ recipients,
226
+ });
227
+ if (addRecipientsErrorRef.current) throw addRecipientsErrorRef.current;
228
+ };
229
+
230
+ const editRecipientErrorRef = useRef<Error | null>(null);
231
+ const { editRecipientsToAddress, isEditRecipientToAddressLoading } =
232
+ useEditRecipientsToAddress({
233
+ onError: (error) => {
234
+ editRecipientErrorRef.current = error;
235
+ },
236
+ });
237
+
238
+ const handleEditRecipientSubmit = async (input: RecipientEditInput) => {
239
+ if (!currentRecipient) return;
240
+
241
+ editRecipientErrorRef.current = null;
242
+ await editRecipientsToAddress({
243
+ unitId: orgUnit?.id ?? "",
244
+ contractId: router.query.contractId as string,
245
+ addressId: router.query.addressId as string,
246
+ recipientId: currentRecipient.id,
247
+ recipientData: {
248
+ recipientName: input.recipientName,
249
+ recipientPhone: input.recipientPhone,
250
+ },
251
+ });
252
+ if (editRecipientErrorRef.current) throw editRecipientErrorRef.current;
253
+ };
254
+
255
+ const deleteRecipientErrorRef = useRef<Error | null>(null);
256
+ const { deleteRecipientAddress, isDeleteRecipientAddressLoading } =
257
+ useDeleteRecipientAddress({
258
+ onError: (error) => {
259
+ deleteRecipientErrorRef.current = error;
260
+ },
261
+ });
262
+
263
+ const handleDeleteRecipientSubmit = async () => {
264
+ if (!currentRecipient) return;
265
+
266
+ deleteRecipientErrorRef.current = null;
267
+ await deleteRecipientAddress({
268
+ unitId: orgUnit?.id ?? "",
269
+ recipientId: currentRecipient.id,
270
+ addressId: router.query.addressId as string,
271
+ customerId: router.query.contractId as string,
272
+ });
273
+ if (deleteRecipientErrorRef.current) throw deleteRecipientErrorRef.current;
143
274
  };
144
275
 
145
276
  const renderActionButton = () =>
@@ -219,9 +350,11 @@ export const AddressDetailsLayout = ({
219
350
  {locationSettingsData?.name ??
220
351
  t("addresses.labels.locations")}
221
352
  </TabOption>
222
- <TabOption id="recipient-tab">
223
- {t("addresses.tabs.recipients")}
224
- </TabOption>
353
+ {!isRecipientsContractLevelEnabled && (
354
+ <TabOption id="recipient-tab">
355
+ {t("addresses.tabs.recipients")}
356
+ </TabOption>
357
+ )}
225
358
  </TabBar>
226
359
  )}
227
360
 
@@ -284,25 +417,105 @@ export const AddressDetailsLayout = ({
284
417
  />
285
418
  </TabContent>
286
419
 
287
- <TabContent id="recipient-tab">
288
- <AddressRecipientsList
289
- recipients={recipientsData.data}
290
- total={recipientsData?.total}
291
- search={recipientsData.search ?? ""}
292
- page={recipientsData.page ?? 1}
293
- countryCode={"USA"}
294
- ref={addressRecipientsListRef}
295
- />
420
+ {!isRecipientsContractLevelEnabled && (
421
+ <TabContent id="recipient-tab">
422
+ <div data-fs-bp-address-tabs-list>
423
+ <div data-fs-bp-address-tabs-filter>
424
+ <InternalSearch
425
+ defaultValue={recipientsSearchTerm}
426
+ textSearch={setRecipientsSearchTerm}
427
+ />
296
428
 
297
- {isOpenRecipientsDrawer && (
298
- <AddRecipientsDrawer
299
- readonly
300
- {...recipientDrawerProps}
301
- isOpen={isOpenRecipientsDrawer}
302
- refetchRecipients={handleRefetchRecipients}
303
- />
304
- )}
305
- </TabContent>
429
+ <Paginator.Counter
430
+ total={totalRecipients}
431
+ itemsLength={recipients.length}
432
+ />
433
+ </div>
434
+
435
+ {!isRecipientsLoading &&
436
+ recipients.length === 0 &&
437
+ recipientsSearchTerm.length > 0 ? (
438
+ <EmptyState
439
+ title={t("addresses.empty.noResults")}
440
+ description={t("addresses.empty.tryDifferentTerms")}
441
+ />
442
+ ) : (
443
+ <div data-fs-bp-address-tabs-table>
444
+ <RecipientsList
445
+ recipients={recipients}
446
+ isLoading={isRecipientsLoading}
447
+ searchTerm={recipientsSearchTerm}
448
+ countryCode="USA"
449
+ onEditRecipient={(recipient) => {
450
+ setCurrentRecipient(recipient);
451
+ openEditRecipientDrawer();
452
+ }}
453
+ onDeleteRecipient={(recipient) => {
454
+ setCurrentRecipient(recipient);
455
+ openDeleteRecipientDrawer();
456
+ }}
457
+ />
458
+
459
+ <div data-fs-bp-address-tabs-paginator-last>
460
+ {totalRecipients > recipients.length && (
461
+ <Paginator.NextPageButton
462
+ onClick={increaseRecipientsPage}
463
+ disabled={isRecipientsLoading}
464
+ >
465
+ {isRecipientsLoading
466
+ ? t("shared.labels.loading")
467
+ : t("shared.buttons.loadMore")}
468
+ </Paginator.NextPageButton>
469
+ )}
470
+
471
+ <Paginator.Counter
472
+ total={totalRecipients}
473
+ itemsLength={recipients.length}
474
+ />
475
+ </div>
476
+ </div>
477
+ )}
478
+ </div>
479
+
480
+ {isOpenRecipientsDrawer && (
481
+ <AddRecipientsDrawer
482
+ readonly
483
+ {...recipientDrawerProps}
484
+ isOpen={isOpenRecipientsDrawer}
485
+ isSubmitting={isAddRecipientToAddressLoading}
486
+ onSubmit={handleAddRecipientsSubmit}
487
+ onSubmitted={handleRefetchRecipients}
488
+ addressId={addressDetails.id}
489
+ />
490
+ )}
491
+
492
+ {isOpenEditRecipientDrawer && currentRecipient && (
493
+ <EditRecipientDrawer
494
+ readonly
495
+ recipient={currentRecipient}
496
+ {...editRecipientDrawerProps}
497
+ isOpen={isOpenEditRecipientDrawer}
498
+ isSubmitting={isEditRecipientToAddressLoading}
499
+ onSubmit={handleEditRecipientSubmit}
500
+ onSubmitted={handleRefetchRecipients}
501
+ addressId={addressDetails.id}
502
+ />
503
+ )}
504
+
505
+ {isOpenDeleteRecipientDrawer && currentRecipient && (
506
+ <DeleteRecipientDrawer
507
+ readonly
508
+ recipient={currentRecipient}
509
+ {...deleteRecipientDrawerProps}
510
+ isOpen={isOpenDeleteRecipientDrawer}
511
+ isSubmitting={isDeleteRecipientAddressLoading}
512
+ onSubmit={handleDeleteRecipientSubmit}
513
+ onSubmitted={handleRefetchRecipients}
514
+ addressId={addressDetails.id}
515
+ />
516
+ )}
517
+ </TabContent>
518
+ )}
306
519
  </Tab>
307
520
  </div>
308
521
  </section>
@@ -9,6 +9,8 @@
9
9
  @import "../../../shared/components/EmptyState/empty-state.scss";
10
10
  @import "../../../shared/components/Paginator/paginator.scss";
11
11
  @import "../../components//AddressForm/address-form.scss";
12
+ @import "../../../recipients/components/EditRecipientDrawer/edit-recipient-drawer.scss";
13
+ @import "../../../recipients/components/DeleteRecipientDrawer/delete-recipient-drawer.scss";
12
14
 
13
15
  height: 100%;
14
16
 
@@ -202,7 +204,7 @@
202
204
 
203
205
  [data-fs-bp-address-tabs-list] {
204
206
  @import "@faststore/ui/src/components/atoms/Icon/styles.scss";
205
- @import "../../components/EditRecipientAddressDrawer/edit-recipient-address-drawer.scss";
207
+ @import "../../../recipients/components/RecipientsList/recipients-list.scss";
206
208
 
207
209
  height: 100%;
208
210
  display: flex;