@vtex/faststore-plugin-buyer-portal 1.3.16 → 1.3.18

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 (34) hide show
  1. package/.github/workflows/betarelease.yaml +69 -0
  2. package/CHANGELOG.md +24 -6
  3. package/beta-release.sh +23 -0
  4. package/package.json +3 -2
  5. package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +1 -1
  6. package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +139 -0
  7. package/src/features/budgets/components/BudgetEditNotificationDrawer/budget-edit-notification-drawer.scss +34 -0
  8. package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +361 -0
  9. package/src/features/budgets/components/BudgetNotificationForm/budget-notification-form.scss +219 -0
  10. package/src/features/budgets/components/BudgetNotificationsInfo/BudgetNotificationsInfo.tsx +116 -0
  11. package/src/features/budgets/components/BudgetNotificationsInfo/budget-notifications-info.scss +97 -0
  12. package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +118 -0
  13. package/src/features/budgets/components/BudgetUsersTable/budget-users-table.scss +65 -0
  14. package/src/features/budgets/components/BudgetsTable/BudgetsTable.tsx +10 -0
  15. package/src/features/budgets/components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer.tsx +1 -1
  16. package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +86 -25
  17. package/src/features/budgets/components/CreateBudgetDrawer/create-budget-drawer.scss +6 -0
  18. package/src/features/budgets/components/DeleteBudgetAllocationDrawer/DeleteBudgetAllocationDrawer.tsx +1 -1
  19. package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +40 -1
  20. package/src/features/budgets/components/EditBudgetDrawer/edit-budget-drawer.scss +5 -0
  21. package/src/features/budgets/hooks/useDebouncedSearchBudgetNotification.ts +37 -0
  22. package/src/features/budgets/hooks/useListUsers.ts +1 -1
  23. package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +9 -1
  24. package/src/features/budgets/layouts/BudgetsDetailsLayout/budget-details-layout.scss +14 -1
  25. package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +39 -0
  26. package/src/features/budgets/layouts/BudgetsLayout/budgets-layout.scss +1 -1
  27. package/src/features/budgets/types/index.ts +17 -0
  28. package/src/features/shared/components/AutocompleteDropdown/AutocompleteDropdownItem.tsx +4 -0
  29. package/src/features/shared/components/QuantitySelectorWithPercentage/QuantitySelectorWithPercentage.tsx +150 -0
  30. package/src/features/shared/components/index.ts +24 -23
  31. package/src/features/shared/types/CurrencyType.d.ts +4 -0
  32. package/src/features/shared/types/index.ts +4 -3
  33. package/src/features/shared/utils/budgetAmountParse.ts +24 -0
  34. package/src/features/shared/utils/constants.ts +1 -1
@@ -0,0 +1,37 @@
1
+ import { useDebounce } from "../../shared/hooks";
2
+ import { DEBOUNCE_TIMEOUT } from "../../shared/utils";
3
+
4
+ import { useListUsers } from "./useListUsers";
5
+
6
+ type Params = {
7
+ customerId: string;
8
+ unitId: string;
9
+ search?: string;
10
+ };
11
+
12
+ export const useDebouncedSearchBudgetNotification = ({
13
+ customerId,
14
+ unitId,
15
+ search = "",
16
+ }: Params) => {
17
+ const debouncedSearchTerm = useDebounce(search, DEBOUNCE_TIMEOUT);
18
+ const isDebouncing = search !== debouncedSearchTerm;
19
+
20
+ const { listUsers, isListUsersLoading } = useListUsers({
21
+ keys: `${debouncedSearchTerm}`,
22
+ data: {
23
+ customerId,
24
+ unitId,
25
+ params: {
26
+ search: debouncedSearchTerm,
27
+ page: 1,
28
+ },
29
+ },
30
+ });
31
+
32
+ return {
33
+ usersResponse: listUsers,
34
+ isLoadingUsers: isListUsersLoading,
35
+ isDebouncing,
36
+ };
37
+ };
@@ -29,7 +29,7 @@ export const useListUsers = ({
29
29
  listUsersService({
30
30
  unitId,
31
31
  params: {
32
- budgetId,
32
+ ...(budgetId && { budgetId }),
33
33
  name: search,
34
34
  page,
35
35
  },
@@ -16,11 +16,12 @@ import { ContractTabsLayout, GlobalLayout } from "../../../shared/layouts";
16
16
  import { buyerPortalRoutes } from "../../../shared/utils/buyerPortalRoutes";
17
17
  import { BudgetAllocationsTable } from "../../components/BudgetAllocationsTable/BudgetAllocationsTable";
18
18
  import { BudgetDeleteDrawer } from "../../components/BudgetDeleteDrawer/BudgetDeleteDrawer";
19
+ import { BudgetNotificationsInfo } from "../../components/BudgetNotificationsInfo/BudgetNotificationsInfo";
19
20
  import { BudgetRemainingBalance } from "../../components/BudgetRemainingBalance/BudgetRemainingBalance";
20
21
  import { BudgetSettingsInfoBalance } from "../../components/BudgetSettingsInfo/BudgetSettingsInfo";
21
22
  import { CreateBudgetAllocationDrawer } from "../../components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer";
22
23
  import { EditBudgetDrawer } from "../../components/EditBudgetDrawer/EditBudgetDrawer";
23
- import { useListAllocations } from "../../hooks";
24
+ import { useListAllocations } from "../../hooks/useListAllocations";
24
25
 
25
26
  import type { Budget, BudgetAllocationListResponse } from "../../types";
26
27
 
@@ -203,6 +204,13 @@ export const BudgetsDetailsLayout = ({ budget }: BudgetsDetailsLayoutProps) => {
203
204
  contractId={contract?.id ?? ""}
204
205
  />
205
206
 
207
+ <BudgetNotificationsInfo
208
+ initialBudget={budget}
209
+ budgetId={budget.id}
210
+ orgUnitId={orgUnit?.id ?? ""}
211
+ contractId={contract?.id ?? ""}
212
+ />
213
+
206
214
  {hasMounted && (
207
215
  <BudgetAllocationsTable
208
216
  loading={isAllocationsLoading && !loadingLoadMore}
@@ -4,12 +4,14 @@
4
4
  @import "../../components/BudgetAllocationsSelection/budget-allocations-selection.scss";
5
5
  @import "../../../budgets/components/DeleteBudgetAllocationDrawer/delete-budget-allocations.scss";
6
6
  @import "../../components/CreateBudgetAllocationDrawer/create-budget-allocation-drawer.scss";
7
+ @import "../../components/BudgetEditNotificationDrawer/budget-edit-notification-drawer.scss";
7
8
 
8
9
  [data-fs-bp-budgets-details-layout] {
9
10
  @import "../../components/CreateBudgetDrawer/create-budget-drawer.scss";
10
11
  @import "../../components/BudgetAddForm/budget-add-form.scss";
11
12
  @import "../../components/BudgetRemainingBalance/budget-remaining-balance.scss";
12
-
13
+ @import "../../components/BudgetNotificationForm/budget-notification-form.scss";
14
+ @import "../../components/BudgetNotificationsInfo/budget-notifications-info.scss";
13
15
  @import "../../components/BudgetSettingsInfo/budget-settings-info.scss";
14
16
  @import "../../../shared/components/InternalSearch/internal-search.scss";
15
17
  @import "../../components/BudgetsTable/budgets-table.scss";
@@ -37,6 +39,17 @@
37
39
  cursor: pointer;
38
40
  }
39
41
  }
42
+ [data-fs-bp-budget-notifications-empty] {
43
+ [data-fs-empty-state-section] {
44
+ padding: 4rem 17rem;
45
+ text-align: center;
46
+ color: #858585;
47
+
48
+ @include media("<=tablet") {
49
+ padding: 4rem clamp(1rem, 5vw, 17rem);
50
+ }
51
+ }
52
+ }
40
53
 
41
54
  [data-fs-bp-budgets-details-section] {
42
55
  min-height: calc(100vh - calc(var(--fs-spacing-9) + var(--fs-spacing-0)));
@@ -10,6 +10,7 @@ import {
10
10
  } from "../../../shared/hooks";
11
11
  import { FinanceTabsLayout, GlobalLayout } from "../../../shared/layouts";
12
12
  import { BudgetDeleteDrawer } from "../../components/BudgetDeleteDrawer/BudgetDeleteDrawer";
13
+ import { BudgetEditNotificationDrawer } from "../../components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer";
13
14
  import { BudgetsTable } from "../../components/BudgetsTable/BudgetsTable";
14
15
  import { CreateBudgetAllocationDrawer } from "../../components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer";
15
16
  import { CreateBudgetDrawer } from "../../components/CreateBudgetDrawer/CreateBudgetDrawer";
@@ -73,6 +74,12 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
73
74
  ...createBudgetDrawerProps
74
75
  } = useDrawerProps();
75
76
 
77
+ const {
78
+ open: openNotificationDrawer,
79
+ isOpen: isNotificationDrawerOpen,
80
+ ...notificationDrawerProps
81
+ } = useDrawerProps();
82
+
76
83
  async function loadMore() {
77
84
  setPaginationLoading(true);
78
85
  setLoading(true);
@@ -134,6 +141,26 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
134
141
  [page, contract?.id, orgUnit?.id, cookie]
135
142
  );
136
143
 
144
+ const handleCreateNotification = useCallback(
145
+ async (budgetId: string) => {
146
+ setSelectedBudgetId(budgetId);
147
+ try {
148
+ const budgetsData = await getBudgetByIdService({
149
+ budgetId,
150
+ customerId: contract?.id ?? "",
151
+ unitId: orgUnit?.id ?? "",
152
+ cookie,
153
+ });
154
+
155
+ setBudgetToEdit({ ...budgetsData, amount: String(budgetsData.amount) });
156
+ openNotificationDrawer();
157
+ } catch (error) {
158
+ console.error("Failed to load budget:", error);
159
+ }
160
+ },
161
+ [page, contract?.id, orgUnit?.id, cookie]
162
+ );
163
+
137
164
  return (
138
165
  <GlobalLayout>
139
166
  <FinanceTabsLayout pageName="Finance and Compliance">
@@ -164,6 +191,7 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
164
191
  total={Number(total ?? 0)}
165
192
  onClickAllocationPage={handleAddAllocation}
166
193
  onClickEditBudget={handleBudgetEditPage}
194
+ onClickCreateNotification={handleCreateNotification}
167
195
  openDeleteDrawer={(budgetId) => {
168
196
  setSelectedBudgetId(budgetId);
169
197
  openDeleteDrawer();
@@ -232,6 +260,17 @@ export const BudgetsLayout = ({ data }: BudgetsLayoutProps) => {
232
260
  initialBudget={budgetsToEdit}
233
261
  />
234
262
  )}
263
+
264
+ {isNotificationDrawerOpen && budgetsToEdit && (
265
+ <BudgetEditNotificationDrawer
266
+ {...notificationDrawerProps}
267
+ isOpen={isNotificationDrawerOpen}
268
+ budget={budgetsToEdit}
269
+ budgetId={selectedBudgetId}
270
+ orgUnitId={orgUnit?.id ?? ""}
271
+ contractId={contract?.id ?? ""}
272
+ />
273
+ )}
235
274
  </section>
236
275
  </FinanceTabsLayout>
237
276
  </GlobalLayout>
@@ -4,7 +4,7 @@
4
4
  @import "@faststore/ui/src/components/molecules/Tooltip/styles.scss";
5
5
  @import "@faststore/ui/src/components/molecules/Table/styles.scss";
6
6
  @import "@faststore/ui/src/components/atoms/Button/styles.scss";
7
-
7
+ @import "../../components/BudgetNotificationForm/budget-notification-form.scss";
8
8
  @import "../../../shared/components/InternalSearch/internal-search.scss";
9
9
  @import "../../../shared/components/HeaderInside/header-inside.scss";
10
10
  @import "../../components/BudgetsTable/budgets-table.scss";
@@ -25,6 +25,22 @@ export type BudgetAllocationListResponse = {
25
25
  data: BudgetAllocation[];
26
26
  total: number;
27
27
  };
28
+
29
+ export type NotificationThresholds = {
30
+ value: number;
31
+ };
32
+ export type NotificationUsers = {
33
+ userId: string;
34
+ userName: string;
35
+ userEmail: string;
36
+ };
37
+
38
+ export type BudgetNotification = {
39
+ hasNotification: boolean;
40
+ thresholds: NotificationThresholds[];
41
+ users: NotificationUsers[];
42
+ };
43
+
28
44
  export type Budget = {
29
45
  id: string;
30
46
  name: string;
@@ -39,6 +55,7 @@ export type Budget = {
39
55
  preventCheckoutBudgetExceeded?: boolean;
40
56
  preventCheckoutBudgetExpired?: boolean;
41
57
  allocations?: BudgetAllocation[];
58
+ notifications?: BudgetNotification;
42
59
  };
43
60
 
44
61
  export type BudgetInputForm = Omit<BudgetInput, "amount"> & {
@@ -29,7 +29,11 @@ export const AutocompleteDropdownItem = ({
29
29
  }
30
30
  data-fs-bp-autocomplete-dropdown-option-selected={isSelected}
31
31
  onMouseEnter={() => setFocusedItemIndex(index)}
32
+ onMouseDown={(e) => {
33
+ e.preventDefault();
34
+ }}
32
35
  onClick={(e) => {
36
+ e.stopPropagation();
33
37
  if (closeOnClick) {
34
38
  close();
35
39
  }
@@ -0,0 +1,150 @@
1
+ import React, { useEffect, useMemo, useState } from "react";
2
+
3
+ import { Icon, IconButton, Input } from "@faststore/ui";
4
+
5
+ interface QuantitySelectorWithPercentageParams {
6
+ testId?: string;
7
+ max?: number;
8
+ min?: number;
9
+ initial?: number;
10
+ unitMultiplier?: number;
11
+ useUnitMultiplier?: boolean;
12
+ disabled?: boolean;
13
+ onChange?: (value: number) => void;
14
+ onValidateBlur?: (min: number, maxValue: number, quantity: number) => void;
15
+
16
+ formatAsPercent?: boolean;
17
+ allowPercentToggle?: boolean;
18
+ onFormatToggle?: (formatAsPercent: boolean) => void;
19
+ }
20
+
21
+ const QuantitySelectorWithPercentage = ({
22
+ max,
23
+ min = 1,
24
+ unitMultiplier = 1,
25
+ useUnitMultiplier,
26
+ initial,
27
+ disabled = false,
28
+ onChange,
29
+ onValidateBlur,
30
+ testId = "fs-quantity-selector",
31
+ formatAsPercent = true,
32
+ ...otherProps
33
+ }: QuantitySelectorWithPercentageParams) => {
34
+ const [quantity, setQuantity] = useState<number>(initial ?? min);
35
+ const [multipliedUnit, setMultipliedUnit] = useState<number>(
36
+ quantity * unitMultiplier
37
+ );
38
+
39
+ const [showPercent, setShowPercent] = useState<boolean>(formatAsPercent);
40
+
41
+ useEffect(() => {
42
+ if (initial) setQuantity(initial);
43
+ }, [initial]);
44
+
45
+ useEffect(() => {
46
+ setShowPercent(formatAsPercent);
47
+ }, [formatAsPercent]);
48
+
49
+ const roundUpQuantityIfNeeded = (q: number) => {
50
+ if (!useUnitMultiplier) return q;
51
+ return Math.ceil(q / unitMultiplier) * unitMultiplier;
52
+ };
53
+
54
+ const isLeftDisabled = quantity === min;
55
+ const isRightDisabled = quantity === max;
56
+
57
+ const validateQuantityBounds = (n: number): number => {
58
+ const maxValue = min ? Math.max(n, min) : n;
59
+ return max
60
+ ? Math.min(maxValue, useUnitMultiplier ? max * unitMultiplier : max)
61
+ : maxValue;
62
+ };
63
+
64
+ const increase = () => changeQuantity(1);
65
+ const decrease = () => changeQuantity(-1);
66
+
67
+ const changeQuantity = (delta: number) => {
68
+ const next = validateQuantityBounds(quantity + delta);
69
+ onChange?.(next);
70
+ setQuantity(next);
71
+ setMultipliedUnit(next * unitMultiplier);
72
+ };
73
+
74
+ const validateBlur = () => {
75
+ const q = validateQuantityBounds(quantity);
76
+ const rounded = roundUpQuantityIfNeeded(q);
77
+
78
+ const maxValue = max ?? (min ? Math.max(quantity, min) : quantity);
79
+ const isOut = quantity > maxValue || quantity < min;
80
+ if (isOut) {
81
+ onValidateBlur?.(min, maxValue, rounded);
82
+ }
83
+
84
+ setQuantity(() => {
85
+ setMultipliedUnit(rounded);
86
+ onChange?.(rounded / unitMultiplier);
87
+ return rounded / unitMultiplier;
88
+ });
89
+ };
90
+
91
+ const changeInputValue = (e: React.ChangeEvent<HTMLInputElement>) => {
92
+ const raw = e.target.value.replace(/\s+/g, "").replace(/[^\d]/g, "");
93
+
94
+ const numeric = Number(raw || "0");
95
+ setQuantity(numeric);
96
+ };
97
+
98
+ const displayValue = useMemo(() => {
99
+ const base = useUnitMultiplier ? multipliedUnit : quantity;
100
+ if (showPercent) return `${base}%`;
101
+ return String(base);
102
+ }, [showPercent, useUnitMultiplier, multipliedUnit, quantity]);
103
+
104
+ return (
105
+ <div
106
+ data-fs-quantity-selector={disabled ? "disabled" : "true"}
107
+ data-testid={testId}
108
+ {...otherProps}
109
+ style={{ display: "inline-flex", alignItems: "center", gap: 8 }}
110
+ >
111
+ <IconButton
112
+ data-quantity-selector-button="left"
113
+ icon={<Icon name="Minus" width={16} height={16} weight="bold" />}
114
+ aria-label="Decrement Quantity"
115
+ aria-controls="quantity-selector-input"
116
+ disabled={isLeftDisabled || disabled}
117
+ onClick={decrease}
118
+ testId={`${testId}-left-button`}
119
+ size="small"
120
+ />
121
+
122
+ <Input
123
+ data-quantity-selector-input
124
+ id="quantity-selector-input"
125
+ aria-label={showPercent ? "Quantity (percent)" : "Quantity"}
126
+ value={displayValue}
127
+ onChange={changeInputValue}
128
+ onBlur={validateBlur}
129
+ onInput={(event: React.FormEvent<HTMLInputElement>) => {
130
+ const input = event.currentTarget;
131
+ input.value = input.value.replace(/[^0-9%]/g, "");
132
+ }}
133
+ disabled={disabled}
134
+ />
135
+
136
+ <IconButton
137
+ data-quantity-selector-button="right"
138
+ aria-controls="quantity-selector-input"
139
+ aria-label="Increment Quantity"
140
+ disabled={isRightDisabled || disabled}
141
+ icon={<Icon name="Plus" width={16} height={16} weight="bold" />}
142
+ onClick={increase}
143
+ testId={`${testId}-right-button`}
144
+ size="small"
145
+ />
146
+ </div>
147
+ );
148
+ };
149
+
150
+ export default QuantitySelectorWithPercentage;
@@ -1,8 +1,8 @@
1
1
  export {
2
2
  AutocompleteDropdown,
3
+ useAutocompleteDropdownContext,
3
4
  type AutocompleteDropdownContextProps,
4
5
  type AutocompleteDropdownProps,
5
- useAutocompleteDropdownContext,
6
6
  } from "./AutocompleteDropdown/AutocompleteDropdown";
7
7
  export { useAutocompletePosition } from "./AutocompleteDropdown/useAutocompletePosition";
8
8
  export { BasicCard, type BasicCardProps } from "./BasicCard/BasicCard";
@@ -12,19 +12,19 @@ export {
12
12
  type BasicDropdownMenuProps,
13
13
  } from "./BasicDropdownMenu/BasicDropdownMenu";
14
14
  export {
15
- BuyerPortalProvider,
16
- type BuyerPortalProviderProps,
17
15
  BuyerPortalContext,
16
+ BuyerPortalProvider,
18
17
  type BuyerPortalContextType,
18
+ type BuyerPortalProviderProps,
19
19
  } from "./BuyerPortalProvider/BuyerPortalProvider";
20
20
  export { Card, CardBody, CardFooter, CardHeader } from "./Card";
21
21
  export {
22
22
  CustomerSwitchDrawer,
23
23
  CustomerSwitchOption,
24
- type CustomerSwitchOptionProps,
25
24
  CustomerSwitchOptionsList,
26
- type CustomerSwitchOptionsListProps,
27
25
  CustomerSwitchSearch,
26
+ type CustomerSwitchOptionProps,
27
+ type CustomerSwitchOptionsListProps,
28
28
  type CustomerSwitchSearchProps,
29
29
  } from "./CustomerSwitch";
30
30
  export { default as DropdownFilter } from "./DropdownFilter/DropdownFilter";
@@ -32,6 +32,10 @@ export {
32
32
  ErrorMessage,
33
33
  type ErrorMessageProps,
34
34
  } from "./ErrorMessage/ErrorMessage";
35
+ export {
36
+ HeaderInside,
37
+ type HeaderInsideProps,
38
+ } from "./HeaderInside/HeaderInside";
35
39
  export {
36
40
  HierarchyTree,
37
41
  type HierarchyTreeLevelProps,
@@ -42,37 +46,34 @@ export { Icon } from "./Icon";
42
46
  export { InputText, type InputTextProps } from "./InputText/InputText";
43
47
  export { default as InternalSearch } from "./InternalSearch/InternalSearch";
44
48
  export { InternalTopBar } from "./InternalTopbar/InternalTopbar";
49
+ export {
50
+ LetterHighlight,
51
+ type LetterHighlightProps,
52
+ } from "./LetterHighlight/LetterHighlight";
53
+ export {
54
+ LevelDivider,
55
+ type LevelDividerProps,
56
+ } from "./LevelDivider/LevelDivider";
45
57
  export { Logo } from "./Logo/Logo";
46
58
  export { MainLinksDropdownMenu } from "./MainLinksDropdownMenu/MainLinksDropdownMenu";
59
+ export { Pagination } from "./Pagination/Pagination";
47
60
  export { default as SortFilter } from "./SortFilter/SortFilter";
48
- export { Tab, type TabProps, useTab } from "./Tab/Tab";
61
+ export { Tab, useTab, type TabProps } from "./Tab/Tab";
62
+ export { Table, type TableProps } from "./Table/Table";
49
63
  export { Tag, type TagProps } from "./Tag/Tag";
50
64
  export { default as Toast } from "./Toast/Toast";
51
65
  export {
52
66
  VerticalNav,
53
67
  type VerticalNavMenuProps,
54
68
  } from "./VerticalNav/VerticalNavMenu";
55
- export {
56
- LetterHighlight,
57
- type LetterHighlightProps,
58
- } from "./LetterHighlight/LetterHighlight";
59
- export {
60
- HeaderInside,
61
- type HeaderInsideProps,
62
- } from "./HeaderInside/HeaderInside";
63
- export { Pagination } from "./Pagination/Pagination";
64
- export { Table, type TableProps } from "./Table/Table";
65
- export {
66
- LevelDivider,
67
- type LevelDividerProps,
68
- } from "./LevelDivider/LevelDivider";
69
69
 
70
- export { Paginator } from "./Paginator/Paginator";
71
70
  export type { CounterProps as PaginatorCounterProps } from "./Paginator/Counter";
72
71
  export type { NextPageButtonProps as PaginatorNextPageButtonProps } from "./Paginator/NextPageButton";
72
+ export { Paginator } from "./Paginator/Paginator";
73
73
 
74
74
  export { EmptyState, type EmptyStateProps } from "./EmptyState/EmptyState";
75
- export { ErrorBoundary } from "./ErrorBoundary/ErrorBoundary";
76
- export { withErrorBoundary } from "./withErrorBoundary/withErrorBoundary";
77
75
  export { default as Error } from "./Error/Error";
76
+ export { ErrorBoundary } from "./ErrorBoundary/ErrorBoundary";
78
77
  export { IconBookmarked } from "./IconBookmarked/IconBookmarked";
78
+ export { default as QuantitySelectorWithPercentage } from "./QuantitySelectorWithPercentage/QuantitySelectorWithPercentage";
79
+ export { withErrorBoundary } from "./withErrorBoundary/withErrorBoundary";
@@ -0,0 +1,4 @@
1
+ type CurrencyType = "USD" | "BRL";
2
+ type LocaleType = "en-US" | "pt-BR";
3
+
4
+ export type { CurrencyType, LocaleType };
@@ -1,10 +1,11 @@
1
+ export { type AuthRouteProps } from "./AuthRouteProps";
1
2
  export type { BreadcrumbData } from "./BreadcrumbData";
3
+ export type { CurrencyType, LocaleType } from "./CurrencyType";
2
4
  export type { LoaderData } from "./LoaderData";
3
5
  export type { IPagination } from "./Pagination";
4
- export type { ScopeInput } from "./ScopeInput";
5
6
  export type {
6
- PaymentMethodsIdArray,
7
7
  PaymentMethodResponse,
8
+ PaymentMethodsIdArray,
8
9
  PaymentMethodsReqCommonParams,
9
10
  } from "./PaymentMethodsClientTypes";
10
- export { type AuthRouteProps } from "./AuthRouteProps";
11
+ export type { ScopeInput } from "./ScopeInput";
@@ -0,0 +1,24 @@
1
+ export const parseAmount = (raw?: string) => {
2
+ if (!raw) return 0;
3
+ const cleaned = raw
4
+ .toString()
5
+ .replace(/[^\d.,-]/g, "")
6
+ .trim();
7
+
8
+ const lastComma = cleaned.lastIndexOf(",");
9
+ const lastDot = cleaned.lastIndexOf(".");
10
+ let normalized = cleaned;
11
+
12
+ if (lastComma > -1 && lastDot > -1) {
13
+ if (lastComma > lastDot) {
14
+ normalized = cleaned.replace(/\./g, "").replace(",", ".");
15
+ } else {
16
+ normalized = cleaned.replace(/,/g, "");
17
+ }
18
+ } else if (lastComma > -1) {
19
+ normalized = cleaned.replace(",", ".");
20
+ }
21
+
22
+ const n = Number(normalized);
23
+ return Number.isFinite(n) ? n : 0;
24
+ };
@@ -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.16";
16
+ export const CURRENT_VERSION = "1.3.18";