@vtex/faststore-plugin-buyer-portal 1.0.47 → 1.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
  - Add Buying Policy Drawers
20
20
  - Add Buying Policy Integrations
21
21
  - Add Pagination Tools
22
+ - Add Address Pagination
22
23
 
23
24
  ### Added
24
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.0.47",
3
+ "version": "1.0.48",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -2,8 +2,9 @@ import { Client } from "../../shared/clients/Client";
2
2
  import { getApiUrl } from "../../shared/utils";
3
3
  import type { AddressData, AddressInput } from "../types";
4
4
 
5
- type AddressList = {
5
+ type AddressResponse = {
6
6
  addresses: AddressData[];
7
+ total: number;
7
8
  };
8
9
 
9
10
  export default class AddressesClient extends Client {
@@ -12,17 +13,27 @@ export default class AddressesClient extends Client {
12
13
  }
13
14
 
14
15
  getAddressesByCustomerId(customerId: string, cookie: string) {
15
- return this.get<AddressList>(`addresses/${customerId}`, {
16
+ return this.get<AddressResponse>(`addresses/${customerId}`, {
16
17
  headers: {
17
18
  Cookie: cookie,
18
19
  },
19
20
  });
20
21
  }
21
22
 
23
+ getAddressesByUnitId(
24
+ orgUnitId: string,
25
+ cookie: string,
26
+ name?: string,
27
+ page = 1
28
+ ) {
29
+ const params = new URLSearchParams();
30
+ if (name) params.append("name", name);
31
+ if (page && page > 1) params.append("page", String(page));
32
+ const queryString = params.toString();
22
33
 
23
- getAddressesByUnitId(orgUnitId: string, cookie: string, name?: string) {
24
- const url = `unit/addresses/${orgUnitId}` + (name ? `?name=${name}` : "");
25
- return this.get<AddressList>(url, {
34
+ const url = `unit/addresses/${orgUnitId}?${queryString}`;
35
+
36
+ return this.get<AddressResponse>(url, {
26
37
  headers: {
27
38
  Cookie: cookie,
28
39
  },
@@ -38,7 +49,7 @@ export default class AddressesClient extends Client {
38
49
  }
39
50
 
40
51
  searchAddressesByName(customerId: string, name: string, cookie: string) {
41
- return this.get<AddressList>(
52
+ return this.get<AddressResponse>(
42
53
  `search/addresses/${customerId}?name=${name}`,
43
54
  {
44
55
  headers: {
@@ -1,28 +1,42 @@
1
- import { use, useEffect, useState } from "react";
2
- import { InternalSearch, HeaderInside } from "../../../shared/components";
3
1
  import {
4
- useQueryParams,
5
- useDrawerProps,
6
- useBuyerPortal,
7
- } from "../../../shared/hooks";
2
+ InternalSearch,
3
+ HeaderInside,
4
+ Paginator,
5
+ } from "../../../shared/components";
6
+ import { useDrawerProps, useBuyerPortal } from "../../../shared/hooks";
8
7
  import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
9
8
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
10
9
  import { AddressDropdownMenu, CreateAddressDrawer } from "../../components";
11
10
  import type { AddressData } from "../../types";
12
- import { useDebouncedSearchAddressByUnitId } from "../../hooks/useDebouncedSearchAddressByUnitId";
13
11
  import { Table } from "../../../shared/components/Table/Table";
14
12
  import { EmptyState } from "../../../shared/components/EmptyState/EmptyState";
15
13
  import { getTableColumns } from "../../../shared/components/Table/utils/tableColumns";
14
+ import { usePageItems } from "../../../shared/hooks/usePageItems";
16
15
 
17
16
  export type AddressLayoutProps = {
18
- data: AddressData[];
19
- search?: string;
17
+ addresses: AddressData[];
18
+ total: number;
19
+ search: string;
20
+ page: number;
20
21
  };
21
22
 
22
- export const AddressLayout = ({ data, search }: AddressLayoutProps) => {
23
- const [addressesData, setAddressesData] = useState<AddressData[]>(data);
24
- const [querySearch, setQuerySearch] = useState(search ?? "");
25
- const { setQueryString, removeQueryString } = useQueryParams();
23
+ export const AddressLayout = ({
24
+ addresses: initialAddresses,
25
+ search,
26
+ total,
27
+ page,
28
+ }: AddressLayoutProps) => {
29
+ const {
30
+ isLoading,
31
+ items: addresses,
32
+ searchTerm,
33
+ setSearchTerm,
34
+ increasePage,
35
+ } = usePageItems<AddressData>({
36
+ initialItems: initialAddresses,
37
+ search,
38
+ page,
39
+ });
26
40
 
27
41
  const {
28
42
  open: openCreateDrawer,
@@ -36,28 +50,6 @@ export const AddressLayout = ({ data, search }: AddressLayoutProps) => {
36
50
  currentContract: contract,
37
51
  } = useBuyerPortal();
38
52
 
39
- const { searchedAddresses, isLoading } = useDebouncedSearchAddressByUnitId(
40
- querySearch,
41
- orgUnit?.id ?? ""
42
- );
43
-
44
- useEffect(() => {
45
- if (!isLoading && querySearch.length > 0) {
46
- setAddressesData(searchedAddresses);
47
- }
48
- }, [searchedAddresses, querySearch, isLoading]);
49
-
50
- const handleSearch = (searchTerm: string) => {
51
- setQuerySearch(searchTerm);
52
-
53
- if (searchTerm) {
54
- setQueryString("search", searchTerm);
55
- } else {
56
- removeQueryString("search");
57
- setAddressesData(data);
58
- }
59
- };
60
-
61
53
  return (
62
54
  <GlobalLayout>
63
55
  <ContractTabsLayout
@@ -77,16 +69,22 @@ export const AddressLayout = ({ data, search }: AddressLayoutProps) => {
77
69
  <HeaderInside.Button onClick={openCreateDrawer} />
78
70
  </HeaderInside>
79
71
 
80
- {data.length === 0 ? (
72
+ {addresses.length === 0 && !isLoading && searchTerm.length === 0 ? (
81
73
  <EmptyState title="No addresses yet" iconName="LocalPostOffice" />
82
74
  ) : (
83
75
  <>
84
76
  <div data-fs-buyer-portal-address-filter>
85
- <InternalSearch defaultValue={querySearch} textSearch={handleSearch} />
77
+ <InternalSearch
78
+ defaultValue={searchTerm}
79
+ textSearch={setSearchTerm}
80
+ />
81
+
82
+ <Paginator.Counter
83
+ total={total}
84
+ itemsLength={addresses.length}
85
+ />
86
86
  </div>
87
- {!isLoading &&
88
- addressesData.length === 0 &&
89
- querySearch.length > 0 ? (
87
+ {!isLoading && addresses.length === 0 && searchTerm.length > 0 ? (
90
88
  <EmptyState
91
89
  title="No results found"
92
90
  description="Try using different terms or filters"
@@ -95,46 +93,57 @@ export const AddressLayout = ({ data, search }: AddressLayoutProps) => {
95
93
  <div data-fs-addresses-table>
96
94
  <Table>
97
95
  <Table.Head columns={getTableColumns({ withType: true })} />
98
- {isLoading ? (
99
- <Table.Body>
100
- <Table.Loading />
101
- </Table.Body>
102
- ) : (
103
- <Table.Body>
104
- {addressesData.map(({ id, ...address }) => (
105
- <Table.Row
106
- key={id}
107
- title={address.name}
108
- searchTerm={querySearch}
109
- iconName="LocalPostOffice"
110
- iconSize={20}
111
- children={
112
- Array.isArray(address.types) &&
113
- address.types.length > 0
114
- ? address.types.map((type) => (
115
- <span key={type}>{type}</span>
116
- ))
117
- : null
118
- }
119
- href={buyerPortalRoutes.addressDetails({
120
- orgUnitId: orgUnit?.id ?? "",
121
- contractId: contract?.id ?? "",
122
- addressId: id,
123
- })}
124
- dropdownMenu={
125
- <AddressDropdownMenu
126
- currentAddress={{
127
- id,
128
- ...address,
129
- }}
130
- />
131
- }
132
- />
133
- ))}
134
- </Table.Body>
135
- )}
96
+
97
+ <Table.Body>
98
+ {addresses.map(({ id, ...address }) => (
99
+ <Table.Row
100
+ key={id}
101
+ title={address.name}
102
+ searchTerm={search}
103
+ iconName="LocalPostOffice"
104
+ iconSize={20}
105
+ children={
106
+ Array.isArray(address.types) &&
107
+ address.types.length > 0
108
+ ? address.types.map((type) => (
109
+ <span key={type}>{type}</span>
110
+ ))
111
+ : null
112
+ }
113
+ href={buyerPortalRoutes.addressDetails({
114
+ orgUnitId: orgUnit?.id ?? "",
115
+ contractId: contract?.id ?? "",
116
+ addressId: id,
117
+ })}
118
+ dropdownMenu={
119
+ <AddressDropdownMenu
120
+ currentAddress={{
121
+ id,
122
+ ...address,
123
+ }}
124
+ />
125
+ }
126
+ />
127
+ ))}
128
+ </Table.Body>
136
129
  </Table>
137
130
 
131
+ <div data-fs-bp-addresses-paginator>
132
+ {total > addresses.length && (
133
+ <Paginator.NextPageButton
134
+ onClick={increasePage}
135
+ disabled={isLoading}
136
+ >
137
+ {isLoading ? "Loading" : "Load More"}
138
+ </Paginator.NextPageButton>
139
+ )}
140
+
141
+ <Paginator.Counter
142
+ total={total}
143
+ itemsLength={addresses.length}
144
+ />
145
+ </div>
146
+
138
147
  {isCreateAddressDrawerOpen && (
139
148
  <CreateAddressDrawer
140
149
  readonly
@@ -12,8 +12,9 @@
12
12
  @import "../../../shared/components/HeaderInside/header-inside.scss";
13
13
  @import "../../../shared/components/Table/table.scss";
14
14
  @import "../../../shared/components/EmptyState/empty-state.scss";
15
+ @import "../../../shared/components/Paginator/paginator.scss";
15
16
 
16
- padding: 0 calc(var(--fs-spacing-9) - var(--fs-spacing-0));
17
+ padding: calc(var(--fs-spacing-9) - var(--fs-spacing-0));
17
18
  display: flex;
18
19
  flex-direction: column;
19
20
  height: 100%;
@@ -62,7 +63,7 @@
62
63
  [data-fs-buyer-portal-address-filter] {
63
64
  display: flex;
64
65
  justify-content: space-between;
65
- padding-bottom: var(--fs-spacing-4);
66
+ padding: 0 0 var(--fs-spacing-4);
66
67
  }
67
68
 
68
69
  [data-fs-buyer-portal-address-filter-search-container] {
@@ -70,9 +71,15 @@
70
71
  gap: var(--fs-spacing-1);
71
72
  }
72
73
 
73
- [data-fs-addresses-list] {
74
- width: 100%;
74
+ [data-fs-addresses-table] {
75
+ padding-bottom: var(--fs-spacing-9);
76
+ }
77
+
78
+ [data-fs-bp-addresses-paginator] {
75
79
  display: flex;
76
- flex-direction: column;
80
+ align-items: center;
81
+ justify-content: space-between;
82
+ margin-top: var(--fs-spacing-0);
83
+ padding: var(--fs-spacing-2) 0;
77
84
  }
78
85
  }
@@ -8,6 +8,7 @@ export type GetAddressesByUnitIdServiceProps = Partial<{
8
8
  status: string;
9
9
  type: string;
10
10
  sort: string;
11
+ page?: number;
11
12
  }> & {
12
13
  cookie: string;
13
14
  };
@@ -18,24 +19,20 @@ export const getAddressesByUnitIdService = async ({
18
19
  type,
19
20
  sort,
20
21
  cookie,
21
- }: GetAddressesByUnitIdServiceProps): Promise<AddressData[]> => {
22
+ search = "",
23
+ page = 1,
24
+ }: GetAddressesByUnitIdServiceProps) => {
22
25
  if (!orgUnitId) {
23
- return [];
26
+ return { data: [], total: 0 };
24
27
  }
28
+ const { addresses, total } = await addressesClient.getAddressesByUnitId(
29
+ orgUnitId,
30
+ cookie,
31
+ search,
32
+ page
33
+ );
25
34
 
26
- const addressesData: AddressData[] = [];
27
-
28
- try {
29
- const { addresses = [] } = await addressesClient.getAddressesByUnitId(
30
- orgUnitId,
31
- cookie
32
- );
33
- addressesData.push(...addresses);
34
- } catch (err) {
35
- throw new Error(JSON.stringify(err));
36
- }
37
-
38
- return addressesData
35
+ const formattedAddress = addresses
39
36
  ?.filter((address) => {
40
37
  const matchesStatus =
41
38
  !status || address.isActive === statusFilters[status];
@@ -44,4 +41,6 @@ export const getAddressesByUnitIdService = async ({
44
41
  return matchesStatus && matchesType;
45
42
  })
46
43
  .sort((a, b) => compareItems(a, b, sort));
44
+
45
+ return { data: formattedAddress, total };
47
46
  };
@@ -19,10 +19,6 @@ export {
19
19
  searchAddressByNameService,
20
20
  type SearchAddressByNameProps,
21
21
  } from "./search-address-by-name.service";
22
- export {
23
- searchAddressByUnitIdService,
24
- type SearchAddressByUnitIdProps,
25
- } from "./search-address-by-unit-id.service";
26
22
  export {
27
23
  editAddressService,
28
24
  type EditAddressServiceProps,
@@ -1,4 +1,6 @@
1
1
  [data-fs-bp-table-head] {
2
+ border-bottom: var(--fs-border-width) solid #e0e0e0;
3
+
2
4
  &[data-fs-bp-table-cell-align="left"] {
3
5
  justify-content: flex-start;
4
6
  }
@@ -2,8 +2,7 @@
2
2
 
3
3
  [data-fs-bp-table-row] {
4
4
  @import "../../SearchHighlight/search-highlight.scss";
5
-
6
- border-top: var(--fs-border-width) solid #e0e0e0;
5
+ border-bottom: var(--fs-border-width) solid #e0e0e0;
7
6
  cursor: pointer;
8
7
  position: relative;
9
8
 
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  getAddressesByUnitIdService,
3
- searchAddressByUnitIdService,
4
3
  type GetAddressesServiceProps,
5
4
  } from "../features/addresses/services";
6
5
 
7
- import { type ClientContext, getClientContext } from "../features/shared/utils";
6
+ import {
7
+ type ClientContext,
8
+ getClientContext,
9
+ getValidPage,
10
+ } from "../features/shared/utils";
8
11
  import type { AddressData } from "../features/addresses/types";
9
12
  import { AddressLayout } from "../features/addresses/layouts";
10
13
  import { BuyerPortalProvider } from "../features/shared/components";
@@ -19,6 +22,8 @@ import { getContractDetailsService } from "../features/contracts/services";
19
22
  export type AddressesPageData = {
20
23
  data: AddressData[];
21
24
  search: string;
25
+ total: number;
26
+ page: number;
22
27
  context: {
23
28
  currentContract: ContractData | null;
24
29
  clientContext: ClientContext;
@@ -30,12 +35,15 @@ export type AddressesPageData = {
30
35
  export type AddressesPageQuery = GetAddressesServiceProps & {
31
36
  orgUnitId: string;
32
37
  contractId: string;
38
+ page?: string;
33
39
  };
34
40
 
35
41
  export async function loader(
36
42
  data: LoaderData<AddressesPageQuery>
37
43
  ): Promise<AddressesPageData> {
38
- const { contractId, orgUnitId, search } = data.query;
44
+ const { contractId, orgUnitId, search, page: pageString } = data.query;
45
+
46
+ const page = getValidPage(pageString);
39
47
 
40
48
  const { cookie, userId, customerId, ...clientContext } =
41
49
  await getClientContext(data);
@@ -52,11 +60,19 @@ export async function loader(
52
60
  cookie,
53
61
  });
54
62
 
55
- return {
56
- data: await getAddressesByUnitIdService({
63
+ const { data: addresses = [], total = 0 } = await getAddressesByUnitIdService(
64
+ {
57
65
  orgUnitId: currentOrgUnit.id,
66
+ search,
67
+ page,
58
68
  cookie,
59
- }),
69
+ }
70
+ );
71
+
72
+ return {
73
+ data: addresses,
74
+ total,
75
+ page,
60
76
  search: search ?? "",
61
77
  context: {
62
78
  clientContext: { cookie, userId, customerId, ...clientContext },
@@ -67,9 +83,15 @@ export async function loader(
67
83
  };
68
84
  }
69
85
 
70
- const AddressPage = ({ data, search, context }: AddressesPageData) => (
86
+ const AddressPage = ({
87
+ data,
88
+ search,
89
+ page,
90
+ total,
91
+ context,
92
+ }: AddressesPageData) => (
71
93
  <BuyerPortalProvider {...context}>
72
- <AddressLayout data={data} search={search} />
94
+ <AddressLayout addresses={data} search={search} page={page} total={total} />
73
95
  </BuyerPortalProvider>
74
96
  );
75
97
 
@@ -1,26 +0,0 @@
1
- import { useDebounce } from "../../shared/hooks";
2
- import { AddressData } from "../types";
3
- import { useSearchAddressByUnitId } from "./useSearchAddressByUnitId";
4
-
5
- export const useDebouncedSearchAddressByUnitId = (
6
- searchTerm = "",
7
- unitId: string
8
- ) => {
9
- const debouncedSearchTerm = useDebounce(searchTerm, 500);
10
- const { searchedAddresses, isLoading } = useSearchAddressByUnitId(
11
- debouncedSearchTerm,
12
- unitId
13
- );
14
-
15
- if (searchTerm === "") {
16
- return {
17
- searchedAddresses: [] as AddressData[],
18
- isLoading: false,
19
- };
20
- }
21
-
22
- return {
23
- searchedAddresses: searchedAddresses?.addresses ?? [],
24
- isLoading,
25
- };
26
- };
@@ -1,20 +0,0 @@
1
- import { type QueryOptions, useQuery } from "../../shared/hooks";
2
- import { searchAddressByUnitIdService } from "../services";
3
-
4
- export const useSearchAddressByUnitId = (
5
- name: string,
6
- unitId: string,
7
- options?: QueryOptions<AwaitedType<typeof searchAddressByUnitIdService>>
8
- ) => {
9
- const { data, error, isLoading, refetch } = useQuery(
10
- `api/search/addresses/${name}`,
11
- ({ cookie }) => searchAddressByUnitIdService({ name, unitId }, cookie),
12
- options
13
- );
14
- return {
15
- searchedAddresses: data,
16
- hasError: error,
17
- isLoading,
18
- refetchSearchedAddresses: refetch,
19
- };
20
- };
@@ -1,30 +0,0 @@
1
- import { addressesClient } from "../clients/AddressesClient";
2
- import type { AddressData } from "../types";
3
-
4
- export type SearchAddressByUnitIdProps = {
5
- name: string;
6
- unitId: string;
7
- };
8
-
9
- export const searchAddressByUnitIdService = async (
10
- { name, unitId }: SearchAddressByUnitIdProps,
11
- cookie: string
12
- ): Promise<{ addresses: AddressData[]; total: number }> => {
13
- if (!name || !unitId) {
14
- return {
15
- addresses: [],
16
- total: 0,
17
- };
18
- }
19
-
20
- const { addresses } = await addressesClient.getAddressesByUnitId(
21
- unitId,
22
- cookie,
23
- name
24
- );
25
-
26
- return {
27
- addresses: addresses,
28
- total: addresses?.length ?? 0,
29
- };
30
- };