@vtex/faststore-plugin-buyer-portal 1.0.41 → 1.0.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.
Files changed (47) hide show
  1. package/package.json +1 -1
  2. package/src/features/addresses/clients/AddressesClient.ts +10 -0
  3. package/src/features/addresses/components/AddressLine/AddressLine.tsx +1 -1
  4. package/src/features/addresses/components/AddressLine/address-line.scss +0 -8
  5. package/src/features/addresses/hooks/useDebouncedFilterAddress.ts +27 -0
  6. package/src/features/addresses/hooks/useDebouncedSearchAddressByUnitId.ts +26 -0
  7. package/src/features/addresses/hooks/useSearchAddressByUnitId.ts +20 -0
  8. package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +101 -71
  9. package/src/features/addresses/layouts/AddressesLayout/addresses-layout.scss +5 -2
  10. package/src/features/addresses/services/get-addresses-by-unit-id.service.ts +47 -0
  11. package/src/features/addresses/services/index.ts +8 -0
  12. package/src/features/addresses/services/search-address-by-unit-id.service.ts +30 -0
  13. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +22 -19
  14. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/buying-policies-layout.scss +1 -1
  15. package/src/features/shared/components/EmptyState/EmptyState.tsx +28 -0
  16. package/src/features/shared/components/EmptyState/empty-state.scss +19 -0
  17. package/src/features/shared/components/InternalSearch/InternalSearch.tsx +49 -9
  18. package/src/features/shared/components/InternalSearch/internal-search.scss +29 -2
  19. package/src/features/shared/components/SearchHighlight/SearchHighlight.tsx +25 -0
  20. package/src/features/shared/components/SearchHighlight/search-highlight.scss +5 -0
  21. package/src/features/shared/components/Table/Table.tsx +17 -0
  22. package/src/features/shared/components/Table/TableBody/TableBody.tsx +3 -0
  23. package/src/features/shared/components/Table/TableHead/TableHead.tsx +36 -0
  24. package/src/features/shared/components/Table/TableHead/table-head.scss +41 -0
  25. package/src/features/shared/components/Table/TableLoading/TableLoading.tsx +15 -0
  26. package/src/features/shared/components/Table/TableLoading/table-loading.scss +11 -0
  27. package/src/features/shared/components/Table/TableRow/TableRow.tsx +71 -0
  28. package/src/features/shared/components/Table/TableRow/table-row.scss +64 -0
  29. package/src/features/shared/components/Table/table.scss +13 -0
  30. package/src/features/shared/components/Table/utils/tableColumns.ts +40 -0
  31. package/src/features/shared/components/index.ts +1 -1
  32. package/src/features/shared/utils/search.tsx +0 -12
  33. package/src/features/users/clients/UsersClient.ts +8 -10
  34. package/src/features/users/services/get-user-by-id.service.ts +5 -3
  35. package/src/features/users/services/get-users-by-org-unit-id.service.ts +1 -1
  36. package/src/pages/address-details.tsx +1 -1
  37. package/src/pages/addresses.tsx +14 -14
  38. package/src/pages/buying-policies.tsx +1 -1
  39. package/src/pages/buying-policy-details.tsx +1 -1
  40. package/src/pages/credit-cards.tsx +1 -1
  41. package/src/pages/org-unit-details.tsx +1 -1
  42. package/src/pages/org-units.tsx +3 -2
  43. package/src/pages/profile.tsx +1 -1
  44. package/src/pages/user-details.tsx +1 -1
  45. package/src/pages/users.tsx +1 -1
  46. package/src/features/shared/components/ListLine/ListLine.tsx +0 -52
  47. package/src/features/shared/components/ListLine/list-line.scss +0 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -19,6 +19,16 @@ export default class AddressesClient extends Client {
19
19
  });
20
20
  }
21
21
 
22
+
23
+ getAddressesByUnitId(orgUnitId: string, cookie: string, name?: string) {
24
+ const url = `unit/addresses/${orgUnitId}` + (name ? `?name=${name}` : "");
25
+ return this.get<AddressList>(url, {
26
+ headers: {
27
+ Cookie: cookie,
28
+ },
29
+ });
30
+ }
31
+
22
32
  getAddressById(addressId: string, cookie: string) {
23
33
  return this.get<AddressData>(`address/${addressId}`, {
24
34
  headers: {
@@ -1,4 +1,4 @@
1
- import { Dropdown } from "@faststore/ui";
1
+ import { Toggle, Dropdown, DropdownButton } from "@faststore/ui";
2
2
  import Link from "next/link";
3
3
  import { BasicDropdownMenu, Icon, Tag } from "../../../shared/components";
4
4
  import type { AddressData } from "../../types";
@@ -1,15 +1,8 @@
1
1
  [data-fs-addresses-line] {
2
2
  width: 100%;
3
- display: flex;
4
- align-items: center;
5
- justify-content: space-between;
6
-
7
3
  padding: var(--fs-spacing-2) 0;
8
4
  border-top: var(--fs-border-width) solid #e5e5e5;
9
5
 
10
- display: flex;
11
- align-items: center;
12
-
13
6
  &:hover {
14
7
  background-color: #f5f5f5;
15
8
  }
@@ -32,7 +25,6 @@
32
25
  }
33
26
 
34
27
  [data-fs-addresses-line-name] {
35
- width: 25%;
36
28
  margin-right: var(--fs-spacing-3);
37
29
  font-weight: var(--fs-text-weight-medium);
38
30
  font-size: var(--fs-text-size-1);
@@ -0,0 +1,27 @@
1
+ import { useMemo } from "react";
2
+ import { useDebounce } from "../../shared/hooks";
3
+ import { AddressData } from "../types";
4
+
5
+ export const useDebouncedFilterAddress = (
6
+ searchTerm = "",
7
+ addressData: AddressData[]
8
+ ) => {
9
+ const debouncedSearchTerm = useDebounce(searchTerm, 200);
10
+
11
+ const filteredAddresses = useMemo(() => {
12
+ if (debouncedSearchTerm === "") {
13
+ return addressData;
14
+ }
15
+
16
+ return addressData.filter((address) => {
17
+ const addressName = address.name.toLowerCase() || "";
18
+
19
+ return addressName
20
+ .includes(debouncedSearchTerm.toLowerCase());
21
+ });
22
+ }, [debouncedSearchTerm, addressData]);
23
+
24
+ return {
25
+ filteredAddresses,
26
+ };
27
+ };
@@ -0,0 +1,26 @@
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
+ };
@@ -0,0 +1,20 @@
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,45 +1,63 @@
1
- import {
2
- InternalTopBar,
3
- MainLinksDropdownMenu,
4
- InternalSearch,
5
- DropdownFilter,
6
- SortFilter,
7
- Icon,
8
- HeaderInside,
9
- } from "../../../shared/components";
1
+ import { use, useEffect, useState } from "react";
2
+ import { InternalSearch, HeaderInside } from "../../../shared/components";
10
3
  import {
11
4
  useQueryParams,
12
5
  useDrawerProps,
13
6
  useBuyerPortal,
14
7
  } from "../../../shared/hooks";
15
8
  import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
16
- import { statusFilters } from "../../../shared/utils";
17
9
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
18
- import { AddressLine, CreateAddressDrawer } from "../../components";
10
+ import { AddressDropdownMenu, CreateAddressDrawer } from "../../components";
19
11
  import type { AddressData } from "../../types";
12
+ import { useDebouncedSearchAddressByUnitId } from "../../hooks/useDebouncedSearchAddressByUnitId";
13
+ import { Table } from "../../../shared/components/Table/Table";
14
+ import { EmptyState } from "../../../shared/components/EmptyState/EmptyState";
15
+ import { getTableColumns } from "../../../shared/components/Table/utils/tableColumns";
20
16
 
21
17
  export type AddressLayoutProps = {
22
18
  data: AddressData[];
19
+ search?: string;
23
20
  };
24
21
 
25
- export const AddressLayout = ({ data }: AddressLayoutProps) => {
22
+ export const AddressLayout = ({ data, search }: AddressLayoutProps) => {
23
+ const [addressesData, setAddressesData] = useState<AddressData[]>(data);
24
+ const [querySearch, setQuerySearch] = useState(search ?? "");
26
25
  const { setQueryString, removeQueryString } = useQueryParams();
26
+
27
27
  const {
28
28
  open: openCreateDrawer,
29
29
  isOpen: isCreateAddressDrawerOpen,
30
30
  ...createDrawerProps
31
31
  } = useDrawerProps();
32
32
 
33
- const types = data
34
- .flatMap((address) => address.types)
35
- .filter((tag, index, self) => self.indexOf(tag) === index);
36
-
37
33
  const {
38
34
  currentOrgUnit: orgUnit,
39
35
  currentUser: user,
40
36
  currentContract: contract,
41
37
  } = useBuyerPortal();
42
38
 
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
+
43
61
  return (
44
62
  <GlobalLayout>
45
63
  <ContractTabsLayout
@@ -58,63 +76,75 @@ export const AddressLayout = ({ data }: AddressLayoutProps) => {
58
76
  <HeaderInside title="Address">
59
77
  <HeaderInside.Button onClick={openCreateDrawer} />
60
78
  </HeaderInside>
61
- <div data-fs-buyer-portal-address-filter>
62
- <div data-fs-buyer-portal-address-filter-search-container>
63
- <InternalSearch
64
- textSearch={(searchTerm) => {
65
- searchTerm
66
- ? setQueryString("search", searchTerm)
67
- : removeQueryString("search");
68
- }}
69
- />
70
-
71
- <DropdownFilter
72
- label="Type"
73
- filterOptions={types}
74
- updateFilter={(inputFilter) => {
75
- setQueryString("type", inputFilter);
76
- }}
77
- clearFilter={() => {
78
- removeQueryString("type");
79
- }}
80
- />
81
-
82
- <DropdownFilter
83
- label="Status"
84
- filterOptions={Object.keys(statusFilters)}
85
- updateFilter={(inputFilter) => {
86
- setQueryString("status", inputFilter);
87
- }}
88
- clearFilter={() => {
89
- removeQueryString("status");
90
- }}
91
- />
92
- </div>
93
79
 
94
- <SortFilter updateSort={(sort) => setQueryString("sort", sort)} />
95
- </div>
80
+ {data.length === 0 ? (
81
+ <EmptyState title="No addresses yet" iconName="LocalPostOffice" />
82
+ ) : (
83
+ <>
84
+ <div data-fs-buyer-portal-address-filter>
85
+ <InternalSearch defaultValue={querySearch} textSearch={handleSearch} />
86
+ </div>
87
+ {!isLoading &&
88
+ addressesData.length === 0 &&
89
+ querySearch.length > 0 ? (
90
+ <EmptyState
91
+ title="No results found"
92
+ description="Try using different terms or filters"
93
+ />
94
+ ) : (
95
+ <div data-fs-addresses-table>
96
+ <Table>
97
+ <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
+ )}
136
+ </Table>
96
137
 
97
- <ul data-fs-addresses-list>
98
- {data.map(({ id, ...address }) => (
99
- <AddressLine
100
- currentAddress={{ id, ...address }}
101
- href={buyerPortalRoutes.addressDetails({
102
- orgUnitId: orgUnit?.id ?? "",
103
- contractId: contract?.id ?? "",
104
- addressId: id,
105
- })}
106
- id={`${id}`}
107
- key={id}
108
- {...address}
109
- />
110
- ))}
111
- </ul>
112
- {isCreateAddressDrawerOpen && (
113
- <CreateAddressDrawer
114
- readonly
115
- isOpen={isCreateAddressDrawerOpen}
116
- {...createDrawerProps}
117
- />
138
+ {isCreateAddressDrawerOpen && (
139
+ <CreateAddressDrawer
140
+ readonly
141
+ isOpen={isCreateAddressDrawerOpen}
142
+ {...createDrawerProps}
143
+ />
144
+ )}
145
+ </div>
146
+ )}
147
+ </>
118
148
  )}
119
149
  </section>
120
150
  </ContractTabsLayout>
@@ -4,16 +4,19 @@
4
4
 
5
5
  [data-fs-addresses-section] {
6
6
  @import "@faststore/ui/src/components/molecules/Toggle/styles.scss";
7
-
8
7
  @import "../../components/AddressLine/address-line.scss";
9
-
10
8
  @import "../../../shared/components/InternalSearch/internal-search.scss";
11
9
  @import "../../../shared/components/DropdownFilter/dropdown-filter.scss";
12
10
  @import "../../../shared/components/Tag/tag.scss";
13
11
  @import "../../../shared/components/SortFilter/sort-filter.scss";
14
12
  @import "../../../shared/components/HeaderInside/header-inside.scss";
13
+ @import "../../../shared/components/Table/table.scss";
14
+ @import "../../../shared/components/EmptyState/empty-state.scss";
15
15
 
16
16
  padding: 0 calc(var(--fs-spacing-9) - var(--fs-spacing-0));
17
+ display: flex;
18
+ flex-direction: column;
19
+ height: 100%;
17
20
 
18
21
  [data-fs-topbar-actions-wrapper] {
19
22
  display: flex;
@@ -0,0 +1,47 @@
1
+ import type { AddressData } from "../types/AddressData";
2
+ import { compareItems, statusFilters } from "../../shared/utils";
3
+ import { addressesClient } from "../clients/AddressesClient";
4
+
5
+ export type GetAddressesByUnitIdServiceProps = Partial<{
6
+ search: string;
7
+ orgUnitId: string;
8
+ status: string;
9
+ type: string;
10
+ sort: string;
11
+ }> & {
12
+ cookie: string;
13
+ };
14
+
15
+ export const getAddressesByUnitIdService = async ({
16
+ orgUnitId,
17
+ status,
18
+ type,
19
+ sort,
20
+ cookie,
21
+ }: GetAddressesByUnitIdServiceProps): Promise<AddressData[]> => {
22
+ if (!orgUnitId) {
23
+ return [];
24
+ }
25
+
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
39
+ ?.filter((address) => {
40
+ const matchesStatus =
41
+ !status || address.isActive === statusFilters[status];
42
+ const matchesType = !type || address.types.includes(type);
43
+
44
+ return matchesStatus && matchesType;
45
+ })
46
+ .sort((a, b) => compareItems(a, b, sort));
47
+ };
@@ -3,6 +3,10 @@ export {
3
3
  getAddressesService,
4
4
  type GetAddressesServiceProps,
5
5
  } from "./get-addresses.service";
6
+ export {
7
+ getAddressesByUnitIdService,
8
+ type GetAddressesByUnitIdServiceProps,
9
+ } from "./get-addresses-by-unit-id.service";
6
10
  export {
7
11
  createNewAddressService,
8
12
  type CreateNewAddressServiceProps,
@@ -15,6 +19,10 @@ export {
15
19
  searchAddressByNameService,
16
20
  type SearchAddressByNameProps,
17
21
  } from "./search-address-by-name.service";
22
+ export {
23
+ searchAddressByUnitIdService,
24
+ type SearchAddressByUnitIdProps,
25
+ } from "./search-address-by-unit-id.service";
18
26
  export {
19
27
  editAddressService,
20
28
  type EditAddressServiceProps,
@@ -0,0 +1,30 @@
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
+ };
@@ -1,13 +1,11 @@
1
1
  import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
2
- import {
3
- HeaderInside,
4
- InternalSearch,
5
- ListLine,
6
- } from "../../../shared/components";
2
+ import { HeaderInside, InternalSearch } from "../../../shared/components";
7
3
  import { useBuyerPortal, useQueryParams } from "../../../shared/hooks";
8
4
  import type { BuyingPolicy } from "../../types";
9
5
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
10
6
  import { BuyingPolicyDropdownMenu } from "../../components";
7
+ import { Table } from "../../../shared/components/Table/Table";
8
+ import { getTableColumns } from "../../../shared/components/Table/utils/tableColumns";
11
9
 
12
10
  export type BuyingPoliciesLayoutProps = {
13
11
  data: { buyingPolicies: BuyingPolicy[] } | null;
@@ -41,20 +39,25 @@ export const BuyingPoliciesLayout = ({
41
39
  />
42
40
  </div>
43
41
 
44
- <span data-fs-buying-policies-heading>Name</span>
45
- {data?.buyingPolicies.map((buyingPolicy) => (
46
- <ListLine
47
- key={buyingPolicy.id}
48
- title={buyingPolicy.name}
49
- iconName="Rebase"
50
- href={buyerPortalRoutes.buyingPolicyDetails({
51
- contractId: currentContract?.id ?? "",
52
- orgUnitId: currentOrgUnit?.id ?? "",
53
- buyingPolicyId: buyingPolicy.id,
54
- })}
55
- dropdownMenu={<BuyingPolicyDropdownMenu />}
56
- />
57
- ))}
42
+ <Table>
43
+ <Table.Head columns={getTableColumns({ withType: false })} />
44
+ <Table.Body>
45
+ {data?.buyingPolicies.map((buyingPolicy) => (
46
+ <Table.Row
47
+ key={buyingPolicy.id}
48
+ title={buyingPolicy.name}
49
+ iconName="Rebase"
50
+ iconSize={20}
51
+ href={buyerPortalRoutes.buyingPolicyDetails({
52
+ contractId: currentContract?.id ?? "",
53
+ orgUnitId: currentOrgUnit?.id ?? "",
54
+ buyingPolicyId: buyingPolicy.id,
55
+ })}
56
+ dropdownMenu={<BuyingPolicyDropdownMenu />}
57
+ />
58
+ ))}
59
+ </Table.Body>
60
+ </Table>
58
61
  </section>
59
62
  </FinanceTabsLayout>
60
63
  </GlobalLayout>
@@ -5,7 +5,7 @@
5
5
  [data-fs-buying-policies-section] {
6
6
  @import "../../../shared/components/HeaderInside/header-inside.scss";
7
7
  @import "../../../shared/components/InternalSearch/internal-search.scss";
8
- @import "../../../shared/components/ListLine/list-line.scss";
8
+ @import "../../../shared/components/Table/table.scss";
9
9
 
10
10
  [data-fs-buying-policies-filter] {
11
11
  display: flex;
@@ -0,0 +1,28 @@
1
+ import { Icon } from "../Icon";
2
+
3
+ type EmptyStateProps = {
4
+ title: string;
5
+ description?: string;
6
+ iconName?: string;
7
+ iconSize?: number;
8
+ };
9
+
10
+ export const EmptyState = ({
11
+ title,
12
+ iconSize = 40,
13
+ iconName,
14
+ description,
15
+ }: EmptyStateProps) => (
16
+ <section data-fs-empty-state-section>
17
+ {iconName ? (
18
+ <Icon
19
+ data-fs-bp-line-icon
20
+ name={iconName}
21
+ width={iconSize}
22
+ height={iconSize}
23
+ />
24
+ ) : null}
25
+ <h2 data-fs-empty-state-title>{title}</h2>
26
+ {description ? <p data-fs-empty-state-description>{description}</p> : null}
27
+ </section>
28
+ );
@@ -0,0 +1,19 @@
1
+ [data-fs-empty-state-section] {
2
+ display: flex;
3
+ flex-direction: column;
4
+ align-items: center;
5
+ justify-content: center;
6
+ flex: 1;
7
+ gap: var(--fs-spacing-1);
8
+ color: #5c5c5c;
9
+
10
+ [data-fs-empty-state-title] {
11
+ font-size: var(--fs-text-size-3);
12
+ font-weight: var(--fs-text-weight-semibold);
13
+ line-height: calc(var(--fs-text-size-4) + var(--fs-scale));
14
+ }
15
+
16
+ [data-fs-empty-state-description] {
17
+ font-size: var(--fs-text-size-1);
18
+ }
19
+ }
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import { useState } from "react";
2
2
  import { Icon as UIIcon } from "@faststore/ui";
3
3
 
4
4
  type InternalSearchProps = {
@@ -7,20 +7,60 @@ type InternalSearchProps = {
7
7
  };
8
8
 
9
9
  const InternalSearch = ({ textSearch, defaultValue }: InternalSearchProps) => {
10
+ const [searchTerm, setSearchTerm] = useState(defaultValue || "");
11
+ const [isFocused, setIsFocused] = useState(false);
12
+
13
+ const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
14
+ const value = event.target.value;
15
+ setSearchTerm(value);
16
+ textSearch(value);
17
+ };
18
+
10
19
  return (
11
- <div data-fs-buyer-portal-internal-search>
12
- <UIIcon
13
- name="MagnifyingGlass"
14
- height={20}
15
- width={20}
16
- data-fs-buyer-portal-search-icon
17
- />
20
+ <div
21
+ data-fs-buyer-portal-internal-search
22
+ data-fs-search-input-focused={isFocused}
23
+ >
18
24
  <input
19
25
  data-fs-buyer-portal-internal-search-input
26
+ value={searchTerm}
20
27
  placeholder="Search"
21
28
  defaultValue={defaultValue}
22
- onChange={(event) => textSearch(event.target.value)}
29
+ onChange={handleInputChange}
30
+ onFocus={() => setIsFocused(true)}
31
+ onBlur={() => setIsFocused(false)}
23
32
  />
33
+
34
+ <div data-fs-buyer-portal-search-icon>
35
+ {searchTerm.length > 0 ? (
36
+ <>
37
+ <div data-fs-icon-button>
38
+ <UIIcon
39
+ name="X"
40
+ width={20}
41
+ height={20}
42
+ onClick={() => {
43
+ setSearchTerm("");
44
+ textSearch("");
45
+ }}
46
+ />
47
+ </div>
48
+ <div data-fs-divider />
49
+ </>
50
+ ) : null}
51
+
52
+ <div data-fs-icon-button>
53
+ <UIIcon
54
+ name="MagnifyingGlass"
55
+ width={20}
56
+ height={20}
57
+ onClick={() => {
58
+ setSearchTerm("");
59
+ textSearch("");
60
+ }}
61
+ />
62
+ </div>
63
+ </div>
24
64
  </div>
25
65
  );
26
66
  };