@vtex/faststore-plugin-buyer-portal 1.3.22 → 1.3.23

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
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.23] - 2025-11-25
11
+
12
+ ### Changed
13
+
14
+ - Refactor logic of fetching budgets to be handled by a new custom hook
15
+
10
16
  ## [1.3.22] - 2025-11-21
11
17
 
12
18
  ### Added
@@ -235,7 +241,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
235
241
  - Add CHANGELOG file
236
242
  - Add README file
237
243
 
238
- [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.22...HEAD
244
+ [unreleased]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.23...HEAD
239
245
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.2.2...1.2.3
240
246
  [1.2.3]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.3
241
247
  [1.2.4]: https://github.com/vtex/faststore-plugin-buyer-portal/releases/tag/1.2.4
@@ -250,6 +256,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
250
256
 
251
257
  # <<<<<<< HEAD
252
258
 
259
+ [1.3.23]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.22...v1.3.23
253
260
  [1.3.22]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.21...v1.3.22
254
261
  [1.3.21]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.20...v1.3.21
255
262
  [1.3.20]: https://github.com/vtex/faststore-plugin-buyer-portal/compare/v1.3.19...v1.3.20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "1.3.22",
3
+ "version": "1.3.23",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -7,7 +7,6 @@ import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
7
7
  import { useAddBuyingPolicy } from "../../hooks";
8
8
  import { buyingPolicyDefault } from "../../utils";
9
9
 
10
- import type { BudgetListResponse } from "../../../budgets/types";
11
10
  import type { BuyingPolicy } from "../../types";
12
11
 
13
12
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -20,7 +19,6 @@ export type AddBuyingPolicyDrawerProps = Omit<
20
19
  onCreate?: () => void;
21
20
  orgUnitId: string;
22
21
  contractId: string;
23
- budgetData?: BudgetListResponse;
24
22
  };
25
23
 
26
24
  export const AddBuyingPolicyDrawer = ({
@@ -28,7 +26,6 @@ export const AddBuyingPolicyDrawer = ({
28
26
  onCreate,
29
27
  orgUnitId,
30
28
  contractId,
31
- budgetData = { data: [], total: 0 },
32
29
  ...props
33
30
  }: AddBuyingPolicyDrawerProps) => {
34
31
  const { pushToast } = useUI();
@@ -88,7 +85,6 @@ export const AddBuyingPolicyDrawer = ({
88
85
  orgUnitId={orgUnitId}
89
86
  contractId={contractId}
90
87
  initialValues={defaultBuyingPolicyValues}
91
- budgetData={budgetData}
92
88
  {...props}
93
89
  />
94
90
  );
@@ -31,8 +31,6 @@ import { BUYING_POLICIES_WORKFLOW_LABELS } from "../../utils/buyingPoliciesWorkf
31
31
  import { getHighlightedText } from "../../utils/criteriaHighlightSyntax";
32
32
  import { BudgetCriteriaSelector } from "../BudgetCriteriaSelector";
33
33
 
34
- import type { BudgetListResponse } from "../../../budgets/types";
35
-
36
34
  const LIMIT_OF_LEVELS = 5;
37
35
 
38
36
  export type BuyingPolicyForm = Omit<BuyingPolicy, "id"> & {
@@ -54,7 +52,6 @@ export type BasicBuyingPolicyDrawerProps = Omit<
54
52
  drawerTitle?: string;
55
53
  saveButtonLabel?: string;
56
54
  isDrawerLoading?: boolean;
57
- budgetData?: BudgetListResponse;
58
55
  };
59
56
 
60
57
  export const BasicBuyingPolicyDrawer = ({
@@ -67,7 +64,6 @@ export const BasicBuyingPolicyDrawer = ({
67
64
  orgUnitId,
68
65
  contractId,
69
66
  initialValues = defaultBuyingPolicyValues,
70
- budgetData = { data: [], total: 0 },
71
67
  ...props
72
68
  }: BasicBuyingPolicyDrawerProps) => {
73
69
  const [form, setForm] = useState<BuyingPolicyForm>(initialValues);
@@ -181,7 +177,6 @@ export const BasicBuyingPolicyDrawer = ({
181
177
  if (isBudgetCriteria) {
182
178
  return (
183
179
  <BudgetCriteriaSelector
184
- budgetData={budgetData}
185
180
  currentCriteria={criteria}
186
181
  onCriteriaChange={(newCriteria) =>
187
182
  updateField("criteria", newCriteria)
@@ -1,22 +1,32 @@
1
1
  import { useState, useEffect } from "react";
2
2
 
3
3
  import { CustomDropdown } from "../../../shared/components/CustomDropdown/CustomDropdown";
4
-
5
- import type { BudgetListResponse } from "../../../budgets/types";
4
+ import { useBuyerPortal } from "../../../shared/hooks";
5
+ import { useGetBudgets } from "../../hooks/useGetBudgets";
6
6
 
7
7
  export type BudgetCriteriaSelectorProps = {
8
8
  onCriteriaChange: (criteria: string) => void;
9
9
  currentCriteria: string;
10
10
  onEditablePartClick?: () => void;
11
- budgetData?: BudgetListResponse;
12
11
  };
13
12
 
14
13
  export const BudgetCriteriaSelector = ({
15
14
  onCriteriaChange,
16
15
  currentCriteria,
17
16
  onEditablePartClick,
18
- budgetData = { data: [], total: 0 },
19
17
  }: BudgetCriteriaSelectorProps) => {
18
+ const {
19
+ clientContext: { customerId },
20
+ currentOrgUnit: orgUnit,
21
+ } = useBuyerPortal();
22
+
23
+ const { budgets, isBudgetsLoading, refetchBudgets } = useGetBudgets({
24
+ data: { customerId, orgUnitId: orgUnit?.id || "" },
25
+ options: { lazy: true },
26
+ });
27
+
28
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
29
+
20
30
  const extractBudgetValueFromCriteria = (criteria: string): string => {
21
31
  const match = criteria.match(/id="([^"]+)"/);
22
32
  return match ? match[1] : "budget-001";
@@ -31,31 +41,28 @@ export const BudgetCriteriaSelector = ({
31
41
  setCurrentBudgetValue(extractedValue);
32
42
  }, [currentCriteria]);
33
43
 
34
- const getMatchingBudget = () => {
35
- return budgetData.data.find(
36
- (budget) =>
37
- budget.id === currentBudgetValue || budget.id === currentBudgetValue
38
- );
39
- };
40
-
41
44
  const generateCriteriaString = (value: string) => {
42
45
  return `$exists(budgetData.budgets[id="${value}"])`;
43
46
  };
44
47
 
45
48
  const handleBudgetSelect = (budgetId: string) => {
46
- const selectedBudget = budgetData.data.find(
47
- (budget) => budget.id === budgetId
48
- );
49
- if (selectedBudget) {
50
- const newValue = selectedBudget.id;
51
- setCurrentBudgetValue(newValue);
52
- const newCriteria = generateCriteriaString(newValue);
53
- onCriteriaChange(newCriteria);
54
- }
49
+ if (budgetId === "loading") return;
50
+
51
+ const selectedBudget = budgets.data.find((b) => b.id === budgetId);
52
+ if (!selectedBudget) return;
53
+
54
+ setCurrentBudgetValue(selectedBudget.id);
55
+ onCriteriaChange(generateCriteriaString(selectedBudget.id));
56
+ setIsDropdownOpen(false);
55
57
  };
56
58
 
57
- const handleBudgetSelectorClick = (e: React.MouseEvent) => {
59
+ const handleBudgetValueClick = (e: React.MouseEvent) => {
58
60
  e.stopPropagation();
61
+ setIsDropdownOpen(true);
62
+
63
+ if (budgets.data.length === 0 && !isBudgetsLoading) {
64
+ refetchBudgets();
65
+ }
59
66
  };
60
67
 
61
68
  const handleEditablePartClick = (e: React.MouseEvent) => {
@@ -63,47 +70,44 @@ export const BudgetCriteriaSelector = ({
63
70
  onEditablePartClick?.();
64
71
  };
65
72
 
66
- const matchingBudget = getMatchingBudget();
67
- const displayValue = matchingBudget ? matchingBudget.id : currentBudgetValue;
68
- const hasBudgetData = budgetData.data.length > 0;
73
+ const loadingOptions = [{ label: "Loading budgets...", value: "loading" }];
74
+
75
+ const budgetOptions = budgets.data.map((b) => ({
76
+ label: b.name,
77
+ value: b.id,
78
+ }));
79
+
80
+ const options =
81
+ isBudgetsLoading || budgets.data.length === 0
82
+ ? loadingOptions
83
+ : budgetOptions;
69
84
 
70
85
  return (
71
- <div
72
- data-fs-bp-budget-criteria-selector
73
- onClick={handleBudgetSelectorClick}
74
- >
86
+ <div data-fs-bp-budget-criteria-selector>
75
87
  <div data-fs-bp-budget-criteria-string>
76
88
  <span onClick={handleEditablePartClick} data-fs-bp-editable-part>
77
89
  $exists(budgetData.budgets[id="
78
90
  </span>
79
- {hasBudgetData ? (
80
- <div onClick={handleBudgetSelectorClick}>
81
- <CustomDropdown
82
- options={budgetData.data.map((budget) => ({
83
- label: budget.name,
84
- value: budget.id,
85
- }))}
86
- onSelect={handleBudgetSelect}
87
- triggerLabel={displayValue}
88
- highlightText={(text) => (
89
- <div data-fs-bp-dropdown-option>
90
- <div data-fs-bp-dropdown-option-label>{text}</div>
91
+
92
+ <div onClick={handleBudgetValueClick}>
93
+ <CustomDropdown
94
+ options={options}
95
+ onSelect={handleBudgetSelect}
96
+ triggerLabel={isBudgetsLoading ? "Loading..." : currentBudgetValue}
97
+ isOpen={isDropdownOpen}
98
+ highlightText={(text) => (
99
+ <div data-fs-bp-dropdown-option>
100
+ <div data-fs-bp-dropdown-option-label>{text}</div>
101
+ {!isBudgetsLoading && (
91
102
  <div data-fs-bp-dropdown-option-uuid>
92
- {budgetData.data.find((b) => b.name === text)?.id}
103
+ {budgets.data.find((b) => b.name === text)?.id}
93
104
  </div>
94
- </div>
95
- )}
96
- />
97
- </div>
98
- ) : (
99
- <span
100
- onClick={handleEditablePartClick}
101
- data-fs-bp-editable-part
102
- data-fs-bp-budget-value
103
- >
104
- {displayValue}
105
- </span>
106
- )}
105
+ )}
106
+ </div>
107
+ )}
108
+ />
109
+ </div>
110
+
107
111
  <span onClick={handleEditablePartClick} data-fs-bp-editable-part>
108
112
  "])
109
113
  </span>
@@ -7,14 +7,11 @@ import { BasicDropdownMenu, Icon } from "../../../shared/components";
7
7
  import { useBuyerPortal, useDrawerProps } from "../../../shared/hooks";
8
8
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
9
9
 
10
- import type { BudgetListResponse } from "../../../budgets/types";
11
-
12
10
  export type BuyingPolicyDropdownMenuProps = {
13
11
  id: string;
14
12
  name: string;
15
13
  onDelete?: () => void;
16
14
  onUpdate?: () => void;
17
- budgetData?: BudgetListResponse;
18
15
  };
19
16
 
20
17
  export const BuyingPolicyDropdownMenu = ({
@@ -22,7 +19,6 @@ export const BuyingPolicyDropdownMenu = ({
22
19
  name,
23
20
  onDelete,
24
21
  onUpdate,
25
- budgetData = { data: [], total: 0 },
26
22
  }: BuyingPolicyDropdownMenuProps) => {
27
23
  const { currentContract, currentOrgUnit } = useBuyerPortal();
28
24
 
@@ -84,7 +80,6 @@ export const BuyingPolicyDropdownMenu = ({
84
80
  contractId={currentContract?.id ?? ""}
85
81
  buyingPolicyId={id}
86
82
  onUpdate={onUpdate}
87
- budgetData={budgetData}
88
83
  {...updateBuyingPolicyDrawerProps}
89
84
  isOpen={isUpdateBuyingPolicyDrawerOpen}
90
85
  />
@@ -7,8 +7,6 @@ import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
7
7
  import { useGetBuyingPolicy, useUpdateBuyingPolicy } from "../../hooks";
8
8
  import { buyingPolicyDefault } from "../../utils";
9
9
 
10
- import type { BudgetListResponse } from "../../../budgets/types";
11
-
12
10
  export type UpdateBuyingPolicyDrawerProps = Omit<
13
11
  BasicBuyingPolicyDrawerProps,
14
12
  "children" | "initialValues"
@@ -17,7 +15,6 @@ export type UpdateBuyingPolicyDrawerProps = Omit<
17
15
  orgUnitId: string;
18
16
  contractId: string;
19
17
  buyingPolicyId: string;
20
- budgetData?: BudgetListResponse;
21
18
  };
22
19
 
23
20
  export const UpdateBuyingPolicyDrawer = ({
@@ -26,7 +23,6 @@ export const UpdateBuyingPolicyDrawer = ({
26
23
  orgUnitId,
27
24
  contractId,
28
25
  buyingPolicyId,
29
- budgetData = { data: [], total: 0 },
30
26
  ...props
31
27
  }: UpdateBuyingPolicyDrawerProps) => {
32
28
  const { buyingPolicy, isBuyingPolicyLoading } = useGetBuyingPolicy(
@@ -97,7 +93,6 @@ export const UpdateBuyingPolicyDrawer = ({
97
93
  initialValues={{ ...buyingPolicyDefault, ...(buyingPolicy ?? {}) }}
98
94
  orgUnitId={orgUnitId}
99
95
  contractId={contractId}
100
- budgetData={budgetData}
101
96
  {...props}
102
97
  />
103
98
  );
@@ -0,0 +1,35 @@
1
+ import { listBudgetsService } from "../../budgets/services";
2
+ import { QueryOptions, useQuery } from "../../shared/hooks";
3
+
4
+ import type { BudgetListResponse } from "../../budgets/types";
5
+
6
+ interface UseGetBudgetsProps {
7
+ data: {
8
+ customerId: string;
9
+ orgUnitId: string;
10
+ };
11
+ options?: QueryOptions<BudgetListResponse>;
12
+ }
13
+
14
+ export const useGetBudgets = ({
15
+ data: { customerId, orgUnitId },
16
+ options,
17
+ }: UseGetBudgetsProps) => {
18
+ const { data, error, isLoading, refetch } = useQuery(
19
+ `api/budgets/${customerId}/${orgUnitId}`,
20
+ ({ cookie }) =>
21
+ listBudgetsService({
22
+ customerId,
23
+ unitId: orgUnitId,
24
+ cookie,
25
+ }),
26
+ options
27
+ );
28
+
29
+ return {
30
+ budgets: data ?? { data: [], total: 0 },
31
+ hasBudgetsError: error,
32
+ isBudgetsLoading: isLoading,
33
+ refetchBudgets: refetch,
34
+ };
35
+ };
@@ -21,7 +21,6 @@ import { BuyingPolicyDropdownMenu } from "../../components";
21
21
  import { AddBuyingPolicyDrawer } from "../../components/AddBuyingPolicyDrawer/AddBuyingPolicyDrawer";
22
22
  import { useGetBuyingPolicies } from "../../hooks/useGetBuyingPolicies";
23
23
 
24
- import type { BudgetListResponse } from "../../../budgets/types";
25
24
  import type { BuyingPolicy } from "../../types";
26
25
 
27
26
  export type BuyingPoliciesLayoutProps = {
@@ -29,14 +28,12 @@ export type BuyingPoliciesLayoutProps = {
29
28
  total: number;
30
29
  search: string;
31
30
  page: number;
32
- budgetData?: BudgetListResponse;
33
31
  };
34
32
 
35
33
  export const BuyingPoliciesLayout = ({
36
34
  buyingPolicies: initialBuyingPolicies,
37
35
  search,
38
36
  page,
39
- budgetData = { data: [], total: 0 },
40
37
  }: BuyingPoliciesLayoutProps) => {
41
38
  const { currentContract, currentOrgUnit } = useBuyerPortal();
42
39
 
@@ -136,7 +133,6 @@ export const BuyingPoliciesLayout = ({
136
133
  name={buyingPolicy.name}
137
134
  onUpdate={handleRefetchBuyingPolicies}
138
135
  onDelete={handleRefetchBuyingPolicies}
139
- budgetData={budgetData}
140
136
  />
141
137
  }
142
138
  />
@@ -191,7 +187,6 @@ export const BuyingPoliciesLayout = ({
191
187
  contractId={currentContract?.id ?? ""}
192
188
  isOpen={isAddBuyingPolicyDrawerOpen}
193
189
  onCreate={handleRefetchBuyingPolicies}
194
- budgetData={budgetData}
195
190
  {...addBuyingPolicyDrawerProps}
196
191
  />
197
192
  )}
@@ -10,6 +10,7 @@ interface CustomDropdownProps<T> {
10
10
  onSelect: (value: T) => void;
11
11
  triggerLabel: string;
12
12
  highlightText?: (text: string) => React.ReactNode;
13
+ isOpen?: boolean;
13
14
  }
14
15
 
15
16
  export function CustomDropdown<T>({
@@ -17,11 +18,18 @@ export function CustomDropdown<T>({
17
18
  onSelect,
18
19
  triggerLabel,
19
20
  highlightText = (text) => text,
21
+ isOpen: controlledIsOpen,
20
22
  }: CustomDropdownProps<T>) {
21
- const [isOpen, setIsOpen] = useState(false);
23
+ const [isOpen, setIsOpen] = useState(controlledIsOpen ?? false);
22
24
  const [focusedIndex, setFocusedIndex] = useState(-1);
23
25
  const dropdownRef = useRef<HTMLDivElement>(null);
24
26
 
27
+ useEffect(() => {
28
+ if (controlledIsOpen !== undefined) {
29
+ setIsOpen(controlledIsOpen);
30
+ }
31
+ }, [controlledIsOpen]);
32
+
25
33
  const handleOutsideClick = (e: MouseEvent) => {
26
34
  if (
27
35
  dropdownRef.current &&
@@ -24,7 +24,9 @@ export const useQuery = <TData>(
24
24
  ): QueryResult<TData> => {
25
25
  const [data, setData] = useState<TData | null>(null);
26
26
  const [error, setError] = useState<Error | null>(null);
27
- const [isLoading, setIsLoading] = useState<boolean>(true);
27
+ const [isLoading, setIsLoading] = useState<boolean>(
28
+ options?.lazy ? false : true
29
+ );
28
30
  const { clientContext } = useBuyerPortal();
29
31
 
30
32
  const fetchData = useCallback(async () => {
@@ -13,4 +13,4 @@ export const LOCAL_STORAGE_LOCATION_EDIT_KEY = "bp_hide_edit_location_confirm";
13
13
  export const LOCAL_STORAGE_RECIPIENT_EDIT_KEY =
14
14
  "bp_hide_edit_recipient_confirm";
15
15
 
16
- export const CURRENT_VERSION = "1.3.22";
16
+ export const CURRENT_VERSION = "1.3.23";
@@ -1,4 +1,3 @@
1
- import { listBudgetsService } from "../features/budgets/services";
2
1
  import { BuyingPoliciesLayout } from "../features/buying-policies/layouts";
3
2
  import { getBuyingPoliciesService } from "../features/buying-policies/services";
4
3
  import { getContractDetailsService } from "../features/contracts/services";
@@ -16,7 +15,6 @@ import {
16
15
  import { getUserByIdService } from "../features/users/services";
17
16
 
18
17
  import type { GetAddressesServiceProps } from "../features/addresses/services";
19
- import type { BudgetListResponse } from "../features/budgets/types";
20
18
  import type { BuyingPolicy } from "../features/buying-policies/types";
21
19
  import type { ContractData } from "../features/contracts/types";
22
20
  import type { OrgUnitBasicData } from "../features/org-units/types";
@@ -34,7 +32,6 @@ export type BuyingPoliciesPageData = {
34
32
  currentOrgUnit: OrgUnitBasicData | null;
35
33
  currentUser: UserData | null;
36
34
  };
37
- budgetData?: BudgetListResponse;
38
35
  hasError?: boolean;
39
36
  error?: ErrorBoundaryProps;
40
37
  };
@@ -68,40 +65,29 @@ const loaderFunction = async (
68
65
  currentContract: null,
69
66
  currentUser: null,
70
67
  },
71
- budgetData: { data: [], total: 0 },
72
68
  };
73
69
  }
74
70
 
75
- const [
76
- currentOrgUnit,
77
- user,
78
- contract,
79
- budgetData,
80
- buyingPoliciesResponse,
81
- ] = await Promise.all([
82
- getOrgUnitBasicDataService({
83
- id: orgUnitId,
84
- cookie,
85
- }),
86
- getUserByIdService({ orgUnitId, userId, cookie }),
87
- getContractDetailsService({
88
- contractId,
89
- cookie,
90
- unitId: orgUnitId,
91
- }),
92
- listBudgetsService({
93
- customerId,
94
- cookie,
95
- unitId: orgUnitId,
96
- }),
97
- getBuyingPoliciesService({
98
- orgUnitId,
99
- contractId,
100
- cookie,
101
- page,
102
- search,
103
- }),
104
- ]);
71
+ const [currentOrgUnit, user, contract, buyingPoliciesResponse] =
72
+ await Promise.all([
73
+ getOrgUnitBasicDataService({
74
+ id: orgUnitId,
75
+ cookie,
76
+ }),
77
+ getUserByIdService({ orgUnitId, userId, cookie }),
78
+ getContractDetailsService({
79
+ contractId,
80
+ cookie,
81
+ unitId: orgUnitId,
82
+ }),
83
+ getBuyingPoliciesService({
84
+ orgUnitId,
85
+ contractId,
86
+ cookie,
87
+ page,
88
+ search,
89
+ }),
90
+ ]);
105
91
 
106
92
  const { data: buyingPolicies = [], total = 0 } = buyingPoliciesResponse;
107
93
 
@@ -122,7 +108,6 @@ const loaderFunction = async (
122
108
  currentUser: user,
123
109
  currentContract: contract,
124
110
  },
125
- budgetData: budgetData ?? { data: [], total: 0 },
126
111
  };
127
112
  }
128
113
  );
@@ -140,7 +125,6 @@ const BuyingPoliciesPage = ({
140
125
  total,
141
126
  hasError,
142
127
  error,
143
- budgetData = { data: [], total: 0 },
144
128
  }: BuyingPoliciesPageData) => (
145
129
  <>
146
130
  {hasError ? (
@@ -151,7 +135,6 @@ const BuyingPoliciesPage = ({
151
135
  search={search}
152
136
  page={page}
153
137
  total={total}
154
- budgetData={budgetData}
155
138
  />
156
139
  )}
157
140
  </>