@vtex/faststore-plugin-buyer-portal 2.0.13 → 2.0.14

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 (219) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/cms/b2b/components/.gitkeep +0 -0
  3. package/cms/b2b/pages/cms_content_type__i18n.jsonc +36 -0
  4. package/cypress/integration/localization.test.ts +482 -0
  5. package/cypress/support/commands.ts +8 -0
  6. package/docs/i18n/i18n-messages-format.md +391 -0
  7. package/docs/i18n/locales.json +701 -0
  8. package/docs/i18n/migration-inventory.md +959 -0
  9. package/package.json +3 -3
  10. package/specs/custom-localization-system.md +711 -0
  11. package/specs/multi-locale-e2e-validation.md +277 -0
  12. package/src/__mocks__/discovery.config.ts +12 -0
  13. package/src/features/accounting-fields/components/AccountingConfirmDelete/AccountingConfirmDelete.tsx +14 -4
  14. package/src/features/accounting-fields/components/AccountingFieldConfirmEdit/AccountingFieldConfirmEdit.tsx +11 -6
  15. package/src/features/accounting-fields/components/AccountingFieldDropdown/AccountingFieldDropdown.tsx +5 -3
  16. package/src/features/accounting-fields/components/AccountingFieldForm/AccountingFieldForm.tsx +33 -23
  17. package/src/features/accounting-fields/components/AccountingValueConfirmEdit/AccountingValueConfirmEdit.tsx +10 -6
  18. package/src/features/accounting-fields/components/AccountingValueDropdown/AccountingValueDropdown.tsx +19 -9
  19. package/src/features/accounting-fields/components/AccountingValueForm/AccountingValueForm.tsx +21 -10
  20. package/src/features/accounting-fields/components/CreateAccountingFieldDrawer/CreateAccountingFieldDrawer.tsx +11 -8
  21. package/src/features/accounting-fields/components/CreateAccountingValueDrawer/CreateAccountingValueDrawer.tsx +18 -7
  22. package/src/features/accounting-fields/components/DeleteAccountingFieldDrawer/DeleteAccountingFieldDrawer.tsx +14 -12
  23. package/src/features/accounting-fields/components/DeleteAccountingValueDrawer/DeleteAccountingValueDrawer.tsx +20 -10
  24. package/src/features/accounting-fields/components/EditAccountingFieldDrawer/EditAccountingFieldDrawer.tsx +5 -3
  25. package/src/features/accounting-fields/components/EditAccountingValueDrawer/EditAccountingValueDrawer.tsx +13 -6
  26. package/src/features/accounting-fields/components/RemoveValueFromUnitDrawer/RemoveValueFromUnitDrawer.tsx +19 -8
  27. package/src/features/accounting-fields/layouts/AccountingFieldLayout/AccountingFieldLayout.tsx +11 -9
  28. package/src/features/addresses/components/AddRecipientsDrawer/AddRecipientsDrawer.tsx +13 -7
  29. package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +22 -20
  30. package/src/features/addresses/components/AddressForm/AddressForm.tsx +33 -22
  31. package/src/features/addresses/components/AddressLine/AddressLine.tsx +29 -25
  32. package/src/features/addresses/components/AddressLocationsList/AddressLocationsList.tsx +15 -6
  33. package/src/features/addresses/components/AddressRecipientsList/AddressRecipientsList.tsx +9 -4
  34. package/src/features/addresses/components/AddressesCard/AddressesCard.tsx +8 -5
  35. package/src/features/addresses/components/CreateAddressDrawer/CreateAddressDrawer.tsx +27 -14
  36. package/src/features/addresses/components/CreateAddressSettingsDrawer/CreateAddressSettingsDrawer.tsx +26 -21
  37. package/src/features/addresses/components/DeleteAddressDrawer/DeleteAddressDrawer.tsx +20 -16
  38. package/src/features/addresses/components/DeleteAddressLocationDrawer/DeleteAddressLocationDrawer.tsx +26 -14
  39. package/src/features/addresses/components/DeleteRecipientAddressDrawer/DeleteRecipientAddressDrawer.tsx +20 -17
  40. package/src/features/addresses/components/EditAddressDrawer/EditAddressDrawer.tsx +12 -6
  41. package/src/features/addresses/components/EditAddressLocationDrawer/EditAddressLocationDrawer.tsx +24 -17
  42. package/src/features/addresses/components/EditRecipientAddressDrawer/EditRecipientAddressDrawer.tsx +17 -15
  43. package/src/features/addresses/components/LocationForm/LocationField/LocationField.tsx +8 -5
  44. package/src/features/addresses/components/LocationForm/LocationForm.tsx +9 -3
  45. package/src/features/addresses/components/LocationsDrawer/LocationsDrawer.tsx +15 -9
  46. package/src/features/addresses/components/RecipientsForm/RecipientItem/RecipientItem.tsx +55 -46
  47. package/src/features/addresses/components/RecipientsForm/RecipientsForm.tsx +4 -1
  48. package/src/features/addresses/components/RemoveAddressDrawer/RemoveAddressDrawer.tsx +21 -14
  49. package/src/features/addresses/layouts/AddressDetailsLayout/AddressDetailsLayout.tsx +16 -9
  50. package/src/features/addresses/layouts/AddressesLayout/AddressesLayout.tsx +11 -8
  51. package/src/features/b2b-agent/layouts/B2BAgentLayout/B2BAgentLayout.tsx +3 -1
  52. package/src/features/budgets/components/BudgetAddForm/BudgetAddForm.tsx +14 -12
  53. package/src/features/budgets/components/BudgetAddSuccess/BudgetAddSuccess.tsx +16 -14
  54. package/src/features/budgets/components/BudgetAllocationsSelection/BudgetAllocationsSelection.tsx +8 -5
  55. package/src/features/budgets/components/BudgetAllocationsTable/BudgetAllocationsTable.tsx +9 -6
  56. package/src/features/budgets/components/BudgetDeleteDrawer/BudgetDeleteDrawer.tsx +23 -17
  57. package/src/features/budgets/components/BudgetEditNotificationDrawer/BudgetEditNotificationDrawer.tsx +13 -6
  58. package/src/features/budgets/components/BudgetNotificationForm/BudgetNotificationForm.tsx +31 -24
  59. package/src/features/budgets/components/BudgetNotificationsInfo/BudgetNotificationsInfo.tsx +15 -9
  60. package/src/features/budgets/components/BudgetRemainingBalance/BudgetRemainingBalance.tsx +6 -2
  61. package/src/features/budgets/components/BudgetSettingsInfo/BudgetSettingsInfo.tsx +27 -10
  62. package/src/features/budgets/components/BudgetUsersTable/BudgetUsersTable.tsx +14 -7
  63. package/src/features/budgets/components/BudgetsTable/BudgetsTable.tsx +11 -8
  64. package/src/features/budgets/components/CreateBudgetAllocationDrawer/CreateBudgetAllocationDrawer.tsx +8 -5
  65. package/src/features/budgets/components/CreateBudgetDrawer/CreateBudgetDrawer.tsx +31 -18
  66. package/src/features/budgets/components/DeleteBudgetAllocationDrawer/DeleteBudgetAllocationDrawer.tsx +17 -12
  67. package/src/features/budgets/components/EditBudgetDrawer/EditBudgetDrawer.tsx +14 -8
  68. package/src/features/budgets/components/EmptyState.tsx/EmptyState.tsx +4 -1
  69. package/src/features/budgets/layouts/BudgetsDetailsLayout/BudgetsDetailsLayout.tsx +7 -5
  70. package/src/features/budgets/layouts/BudgetsLayout/BudgetsLayout.tsx +5 -3
  71. package/src/features/budgets/messages/budgetNotifications.ts +5 -5
  72. package/src/features/budgets/utils/handleBudgetError.tsx +5 -8
  73. package/src/features/budgets/utils/handleBudgetError.types.ts +3 -0
  74. package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingFieldCriteriaSelector.tsx +5 -3
  75. package/src/features/buying-policies/components/AccountingFieldCriteriaSelector/AccountingValueCriteriaSelector.tsx +20 -9
  76. package/src/features/buying-policies/components/AddBuyingPolicyDrawer/AddBuyingPolicyDrawer.tsx +8 -5
  77. package/src/features/buying-policies/components/BasicBuyingPolicyDrawer/BasicBuyingPolicyDrawer.tsx +49 -33
  78. package/src/features/buying-policies/components/BudgetCriteriaSelector/BudgetCriteriaSelector.tsx +8 -2
  79. package/src/features/buying-policies/components/BuyingPolicyDropdownMenu/BuyingPolicyDropdownMenu.tsx +5 -3
  80. package/src/features/buying-policies/components/DeleteBuyingPolicyDrawer/DeleteBuyingPolicyDrawer.tsx +28 -24
  81. package/src/features/buying-policies/components/UpdateBuyingPolicyDrawer/UpdateBuyingPolicyDrawer.tsx +10 -7
  82. package/src/features/buying-policies/layouts/BuyingPoliciesLayout/BuyingPoliciesLayout.tsx +11 -4
  83. package/src/features/buying-policies/layouts/BuyingPolicyDetailsLayout/BuyingPolicyDetailsLayout.tsx +26 -9
  84. package/src/features/buying-policies/utils/accountingFieldCriteriaHelpers.ts +2 -2
  85. package/src/features/buying-policies/utils/buyingPoliciesWorkflowTypes.ts +3 -3
  86. package/src/features/buying-policies/utils/orderFieldsCriteriaOptions.ts +8 -8
  87. package/src/features/buying-policies/utils/spendingLimitsCriteriaOptions.ts +6 -6
  88. package/src/features/contracts/components/ContractsCard/ContractsCard.tsx +7 -5
  89. package/src/features/contracts/layouts/ContractsLayout/ContractsLayout.tsx +9 -4
  90. package/src/features/credit-cards/components/CreateCreditCardDrawer/CreateCreditCardDrawer.tsx +17 -11
  91. package/src/features/credit-cards/components/CreditCardDropdownMenu/CreditCardDropdownMenu.tsx +17 -11
  92. package/src/features/credit-cards/components/CreditCardForm/CreditCardForm.tsx +8 -5
  93. package/src/features/credit-cards/components/CreditCardSettingsDrawer/CreditCardSettingsDrawer.tsx +10 -8
  94. package/src/features/credit-cards/components/DeleteCreditCardDrawer/DeleteCreditCardDrawer.tsx +19 -14
  95. package/src/features/credit-cards/components/EditCreditCardDrawer/EditCreditCardDrawer.tsx +14 -13
  96. package/src/features/credit-cards/components/RemoveCreditCardDrawer/RemoveCreditCardDrawer.tsx +18 -15
  97. package/src/features/credit-cards/layouts/CreditCardsLayout/CreditCardLayout.tsx +10 -4
  98. package/src/features/order-entry/layouts/OrderEntryLayout.tsx +3 -1
  99. package/src/features/org-units/components/AddAllToOrgUnitDropdown/AddAllToOrgUnitDropdown.tsx +11 -9
  100. package/src/features/org-units/components/AuthSetupDrawer/AuthSetupDrawer.tsx +28 -16
  101. package/src/features/org-units/components/CreateOrgUnitDrawer/CreateOrgUnitDrawer.tsx +18 -12
  102. package/src/features/org-units/components/DeleteOrgUnitDrawer/DeleteOrgUnitDrawer.tsx +28 -29
  103. package/src/features/org-units/components/OrgUnitBreadcrumb/OrgUnitBreadcrumbLink.tsx +4 -4
  104. package/src/features/org-units/components/OrgUnitDetailsDropdownMenu/OrgUnitDetailsDropdownMenu.tsx +4 -2
  105. package/src/features/org-units/components/OrgUnitDetailsNavbar/OrgUnitDetailsNavbar.tsx +23 -5
  106. package/src/features/org-units/components/OrgUnitDetailsNavbar/org-unit-details-navbar.scss +18 -2
  107. package/src/features/org-units/components/OrgUnitsDropdownMenu/OrgUnitsDropdownMenu.tsx +6 -4
  108. package/src/features/org-units/components/OrgUnitsHierarchyTree/OrgUnitsHierarchyTree.tsx +9 -6
  109. package/src/features/org-units/components/OrganizationalUnitsCard/OrganizationalUnitsCard.tsx +15 -7
  110. package/src/features/org-units/components/UpdateOrgUnitDrawer/UpdateOrgUnitDrawer.tsx +15 -9
  111. package/src/features/org-units/layouts/OrgUnitDetailsLayout/OrgUnitDetailsLayout.tsx +27 -26
  112. package/src/features/org-units/layouts/OrgUnitsLayout/OrgUnitsLayout.tsx +8 -3
  113. package/src/features/payment-methods/components/AddPaymentMethodsDrawer/AddPaymentMethodsDrawer.tsx +29 -18
  114. package/src/features/payment-methods/components/PaymentMethodSettingsDrawer/PaymentMethodSettingsDrawer.tsx +13 -9
  115. package/src/features/payment-methods/components/RemoveMethodButton/RemoveMethodButton.tsx +5 -2
  116. package/src/features/payment-methods/components/RemovePaymentMethodsDrawer/RemovePaymentMethodsDrawer.tsx +10 -7
  117. package/src/features/payment-methods/hooks/useRemovePaymentMethodSubmit.ts +6 -4
  118. package/src/features/payment-methods/layouts/PaymentMethodsLayout/PaymentMethodsLayout.tsx +21 -15
  119. package/src/features/product-assortment/components/ProductAssortmentTable/ProductAssortmentTable.tsx +11 -7
  120. package/src/features/product-assortment/layouts/ProductAssortmentLayout/ProductAssortmentLayout.tsx +9 -6
  121. package/src/features/profile/components/ProfileCard/ProfileCard.tsx +8 -5
  122. package/src/features/profile/components/ProfileSummary/ProfileSummary.tsx +13 -6
  123. package/src/features/profile/layouts/ProfileLayout/ProfileLayout.tsx +16 -6
  124. package/src/features/roles/layout/RoleDetailsLayout/RoleDetailsLayout.tsx +4 -2
  125. package/src/features/roles/layout/RolesLayout/RolesLayout.tsx +5 -3
  126. package/src/features/shared/components/BasicCard/BasicCard.tsx +6 -4
  127. package/src/features/shared/components/BasicCard/BasicCardLine.tsx +5 -4
  128. package/src/features/shared/components/CustomerSwitch/CustomerSwitchDrawer.tsx +10 -2
  129. package/src/features/shared/components/CustomerSwitch/CustomerSwitchSearch.tsx +4 -1
  130. package/src/features/shared/components/DropdownFilter/DropdownFilter.tsx +11 -7
  131. package/src/features/shared/components/Error/Error.tsx +13 -6
  132. package/src/features/shared/components/HeaderInside/HeaderInside.tsx +5 -2
  133. package/src/features/shared/components/InternalSearch/InternalSearch.tsx +4 -1
  134. package/src/features/shared/components/InternalTopbar/InternalTopbar.tsx +7 -5
  135. package/src/features/shared/components/MainLinksDropdownMenu/MainLinksDropdownMenu.tsx +23 -8
  136. package/src/features/shared/components/OrgUnitInputSearch/OrgUnitInputSearch.tsx +7 -2
  137. package/src/features/shared/components/Pagination/Pagination.tsx +6 -2
  138. package/src/features/shared/components/Paginator/Counter.tsx +11 -5
  139. package/src/features/shared/components/Paginator/Interval.tsx +4 -3
  140. package/src/features/shared/components/Paginator/NextPageButton.tsx +6 -2
  141. package/src/features/shared/components/SettingsDrawer/SettingsDrawer.tsx +6 -4
  142. package/src/features/shared/components/SettingsDrawer/SettingsDrawerListType.tsx +11 -10
  143. package/src/features/shared/components/SettingsDrawer/listTypeOptions.ts +10 -7
  144. package/src/features/shared/components/SortFilter/SortFilter.tsx +15 -8
  145. package/src/features/shared/components/Table/TableRow/TableRow.tsx +5 -1
  146. package/src/features/shared/components/VerticalNav/VerticalNavLink.tsx +7 -3
  147. package/src/features/shared/layouts/BaseTabsLayout/Navbar.tsx +28 -4
  148. package/src/features/shared/layouts/BaseTabsLayout/about-drawer/AboutDrawer.tsx +15 -5
  149. package/src/features/shared/layouts/BaseTabsLayout/base-tabs-layout.scss +24 -9
  150. package/src/features/shared/layouts/BaseTabsLayout/sidebar-drawer/SidebarDrawer.tsx +5 -2
  151. package/src/features/shared/layouts/ContractTabsLayout/ContractTabsLayout.tsx +10 -5
  152. package/src/features/shared/layouts/ErrorTabsLayout/ErrorTabsLayout.tsx +7 -4
  153. package/src/features/shared/layouts/FinanceTabsLayout/FinanceTabsLayout.tsx +9 -4
  154. package/src/features/shared/layouts/LoadingTabsLayout/LoadingTabsLayout.tsx +7 -4
  155. package/src/features/shared/layouts/OrgUnitTabsLayout/OrgUnitTabLayout.tsx +3 -0
  156. package/src/features/shared/layouts/SumaPageLayout/FullSidebarNav.tsx +13 -7
  157. package/src/features/shared/layouts/SumaPageLayout/SumaSidebar.tsx +9 -5
  158. package/src/features/shared/localization/LocalizationContext.tsx +35 -0
  159. package/src/features/shared/localization/__tests__/createLocalize.test.ts +166 -0
  160. package/src/features/shared/localization/__tests__/get-localization-labels.service.test.ts +202 -0
  161. package/src/features/shared/localization/clients/LocalizationClient.ts +19 -0
  162. package/src/features/shared/localization/constants.ts +31 -0
  163. package/src/features/shared/localization/createLocalize.ts +78 -0
  164. package/src/features/shared/localization/services/get-localization-labels.service.ts +76 -0
  165. package/src/features/shared/localization/types.ts +66 -0
  166. package/src/features/shared/localization/withLocalization.tsx +22 -0
  167. package/src/features/shared/types/AuthRouteProps.ts +5 -0
  168. package/src/features/shared/types/LoaderData.ts +1 -0
  169. package/src/features/shared/utils/addresLabelToPropMapping.ts +10 -10
  170. package/src/features/shared/utils/constants.ts +1 -4
  171. package/src/features/shared/utils/getContractSettingsLinks.ts +17 -12
  172. package/src/features/shared/utils/getFinanceSettingsLinks.ts +14 -9
  173. package/src/features/shared/utils/getKeyByValue.ts +2 -2
  174. package/src/features/shared/utils/getOrganizationSettingsLinks.ts +13 -3
  175. package/src/features/shared/utils/getSumaListingSidebarLinks.ts +15 -10
  176. package/src/features/shared/utils/index.ts +2 -0
  177. package/src/features/shared/utils/routeLayoutMapping.ts +30 -30
  178. package/src/features/shared/utils/search.tsx +10 -0
  179. package/src/features/shared/utils/withAuth.tsx +6 -0
  180. package/src/features/shared/utils/withAuthLoader.ts +11 -0
  181. package/src/features/shared/utils/withLoaderErrorBoundary.ts +9 -0
  182. package/src/features/shared/utils/withProviders.tsx +13 -2
  183. package/src/features/users/components/CreateUserDrawer/CreateUserDrawer.tsx +39 -27
  184. package/src/features/users/components/CreateUserDrawerWithUsername/CreateUserDrawerWithUsername.tsx +41 -39
  185. package/src/features/users/components/DeleteUserDrawer/DeleteUserDrawer.tsx +27 -23
  186. package/src/features/users/components/EmailTransactionalSection/EmailTransactionalSection.tsx +6 -8
  187. package/src/features/users/components/LoginField/LoginField.tsx +4 -4
  188. package/src/features/users/components/ReassignOrgUnitDrawer/ReassignOrgUnitDrawer.tsx +22 -19
  189. package/src/features/users/components/ResetPasswordDrawer/ResetPasswordDrawer.tsx +16 -21
  190. package/src/features/users/components/UpdateUserDrawer/UpdateUserDrawer.tsx +26 -15
  191. package/src/features/users/components/UpdateUserDrawerWithUsername/UpdateUserDrawerWithUsername.tsx +22 -8
  192. package/src/features/users/components/UserDropdownMenu/UserDropdownMenu.tsx +7 -5
  193. package/src/features/users/components/UserFormFields/UserFormFields.tsx +9 -8
  194. package/src/features/users/components/UsersCard/UsersCard.tsx +4 -2
  195. package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +26 -10
  196. package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +12 -8
  197. package/src/pages/accounting-field.tsx +28 -15
  198. package/src/pages/address-details.tsx +37 -26
  199. package/src/pages/addresses.tsx +27 -12
  200. package/src/pages/budgets-details.tsx +25 -15
  201. package/src/pages/budgets.tsx +31 -17
  202. package/src/pages/buyer-organization-manager.tsx +23 -12
  203. package/src/pages/buying-policies.tsx +38 -20
  204. package/src/pages/buying-policy-details.tsx +20 -19
  205. package/src/pages/contracts.tsx +15 -1
  206. package/src/pages/credit-cards.tsx +30 -17
  207. package/src/pages/order-entry.tsx +26 -15
  208. package/src/pages/org-unit-details.tsx +20 -6
  209. package/src/pages/org-units.tsx +20 -6
  210. package/src/pages/payment-groups.tsx +15 -12
  211. package/src/pages/productAssortment.tsx +10 -1
  212. package/src/pages/profile.tsx +14 -1
  213. package/src/pages/role-details.tsx +19 -6
  214. package/src/pages/roles.tsx +27 -14
  215. package/src/pages/user-details.tsx +29 -17
  216. package/src/pages/users.tsx +11 -0
  217. package/vitest.config.ts +13 -0
  218. package/src/features/addresses/constants/messages.ts +0 -8
  219. package/src/features/credit-cards/constants/messages.ts +0 -6
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Maps each buyer portal feature to its CMS localization entry UUID.
3
+ * These UUIDs identify the localization entries in the Content Platform API
4
+ * (see LOCALIZATION_BASE_URL) and are used to fetch localized strings per feature at runtime.
5
+ */
6
+ export const LOCALIZATION_ENTRY_IDS = {
7
+ addresses: "29747c90-344b-45db-9ef0-192b537b12e7",
8
+ budgets: "54f982e2-a823-40c6-954d-f35945413bde",
9
+ users: "7cba4de2-c752-4ee6-96c1-a7cbd6a39b19",
10
+ shared: "4785424c-3eb7-4ab7-b13e-78a1029c8f4d",
11
+ accountingFields: "588dab31-0afe-437e-9f3b-63c76ff7552b",
12
+ buyingPolicies: "e3603de6-d427-44de-aba0-e77d597b9f22",
13
+ creditCards: "cccdd4dc-f73d-4b43-85a3-3e1a2bbc8b1c",
14
+ orgUnits: "2bf471ed-736d-40b7-8162-139ce9ed5a99",
15
+ paymentMethods: "7a8cde2e-3fac-4f30-b264-e135d0ded2d7",
16
+ productAssortment: "cd636c6c-37cf-4078-bc83-378e55751718",
17
+ profile: "57c0716a-12bf-460c-aa57-1679bf48c043",
18
+ contracts: "81d0f596-15f5-4fa8-a6a7-f3e5040e6eb9",
19
+ roles: "13ce6df5-7fdc-4b4b-be9d-f8c4f0a0a488",
20
+ b2bAgent: "dc4fcf7d-00a6-4c9f-ac26-94833288cc46",
21
+ orderEntry: "3cd8c183-129f-4622-ac48-0db54154ecf8",
22
+ } as const;
23
+
24
+ export type LocalizationFeature = keyof typeof LOCALIZATION_ENTRY_IDS;
25
+ export type LocalizationEntryId =
26
+ (typeof LOCALIZATION_ENTRY_IDS)[LocalizationFeature];
27
+
28
+ /** Content type slug in the Content Platform API path (see cms_content_type__i18n.jsonc). */
29
+ export const LOCALIZATION_CONTENT_TYPE = "b2bLocalization";
30
+
31
+ export const LOCALIZATION_BASE_URL = `https://vtex.vtexcommercestable.com.br/api/content-platform/data/vtex/b2b/${LOCALIZATION_CONTENT_TYPE}`;
@@ -0,0 +1,78 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import type { LabelMap, LocalizeFn } from "./types";
4
+
5
+ const PLACEHOLDER_PATTERN = /\{(\d+)\}/g;
6
+
7
+ /**
8
+ * Returns true when the interpolations array contains at least one value that is
9
+ * not a plain string or number — i.e. a React node (element, fragment, etc.).
10
+ */
11
+ const hasReactNodes = (
12
+ interpolations: (string | number | ReactNode)[]
13
+ ): boolean =>
14
+ interpolations.some(
15
+ (v) =>
16
+ v !== null &&
17
+ v !== undefined &&
18
+ typeof v !== "string" &&
19
+ typeof v !== "number"
20
+ );
21
+
22
+ /**
23
+ * Builds an array of alternating text segments and React nodes by walking the
24
+ * template string with exec() rather than replace(), so non-string values can
25
+ * be inserted at placeholder positions without stringification.
26
+ */
27
+ const interpolateNodes = (
28
+ template: string,
29
+ interpolations: ReactNode[]
30
+ ): ReactNode[] => {
31
+ const result: ReactNode[] = [];
32
+ const regex = new RegExp(PLACEHOLDER_PATTERN.source, "g");
33
+ let lastIndex = 0;
34
+ let match: RegExpExecArray | null;
35
+
36
+ while ((match = regex.exec(template)) !== null) {
37
+ if (match.index > lastIndex) {
38
+ result.push(template.slice(lastIndex, match.index));
39
+ }
40
+ const idx = Number(match[1]);
41
+ const value = interpolations[idx];
42
+ result.push(value !== undefined ? value : match[0]);
43
+ lastIndex = match.index + match[0].length;
44
+ }
45
+
46
+ if (lastIndex < template.length) {
47
+ result.push(template.slice(lastIndex));
48
+ }
49
+
50
+ return result;
51
+ };
52
+
53
+ export const createLocalize = (labels: LabelMap): LocalizeFn => {
54
+ function t(key: string, interpolations?: (string | number)[]): string;
55
+ function t(key: string, interpolations: ReactNode[]): ReactNode;
56
+ function t(
57
+ key: string,
58
+ interpolations?: (string | number | ReactNode)[]
59
+ ): string | ReactNode {
60
+ const template = labels[key];
61
+
62
+ if (template === undefined) return key;
63
+
64
+ if (!interpolations || interpolations.length === 0) return template;
65
+
66
+ if (hasReactNodes(interpolations)) {
67
+ return interpolateNodes(template, interpolations as ReactNode[]);
68
+ }
69
+
70
+ // Pure string/number path — O(1) replace, same as before.
71
+ return template.replace(PLACEHOLDER_PATTERN, (token, index: string) => {
72
+ const value = (interpolations as (string | number)[])[Number(index)];
73
+ return value === undefined ? token : String(value);
74
+ });
75
+ }
76
+
77
+ return t as LocalizeFn;
78
+ };
@@ -0,0 +1,76 @@
1
+ import { logError } from "../../services/logger";
2
+ import { localizationClient } from "../clients/LocalizationClient";
3
+ import { LOCALIZATION_ENTRY_IDS, type LocalizationEntryId } from "../constants";
4
+
5
+ import type {
6
+ LocalizationLabelType,
7
+ LabelMap,
8
+ GetLocalizationLabelsParams,
9
+ } from "../types";
10
+
11
+ // Permanent process-local cache + single-flight, keyed by (entryId, locale).
12
+ const resolved = new Map<string, LabelMap>();
13
+ const inFlight = new Map<string, Promise<LabelMap>>();
14
+
15
+ const toLabelMap = (labels: LocalizationLabelType[]): LabelMap =>
16
+ labels.reduce<LabelMap>((acc, { key, value }) => {
17
+ acc[key] = value;
18
+ return acc;
19
+ }, {});
20
+
21
+ // Fetches (and permanently caches) a single entry.
22
+ // Logs an error and falls back to {} on failure — never throws.
23
+ const fetchEntry = (entryId: string, locale: string): Promise<LabelMap> => {
24
+ const cacheKey = `${entryId}::${locale}`;
25
+
26
+ const cached = resolved.get(cacheKey);
27
+ if (cached) return Promise.resolve(cached);
28
+
29
+ const ongoing = inFlight.get(cacheKey);
30
+ if (ongoing) return ongoing;
31
+
32
+ const promise = localizationClient
33
+ .getEntry(entryId, locale)
34
+ .then((data) => {
35
+ const map = toLabelMap(data?.labels ?? []);
36
+ resolved.set(cacheKey, map); // permanent cache (no TTL)
37
+ return map;
38
+ })
39
+ .catch((error: unknown) => {
40
+ logError("Failed to fetch localization labels", {
41
+ entryId,
42
+ locale,
43
+ error_message: error instanceof Error ? error.message : String(error),
44
+ });
45
+ return {} as LabelMap; // do NOT write to `resolved`; allow retry on next request
46
+ })
47
+ .finally(() => {
48
+ inFlight.delete(cacheKey);
49
+ });
50
+
51
+ inFlight.set(cacheKey, promise);
52
+ return promise;
53
+ };
54
+
55
+ /** Clears the process-local cache. Intended for use in tests only. */
56
+ export const _resetLocalizationCache = () => {
57
+ resolved.clear();
58
+ inFlight.clear();
59
+ };
60
+
61
+ export const getLocalizationLabelsService = async ({
62
+ entryIds,
63
+ locale = "",
64
+ }: GetLocalizationLabelsParams): Promise<LabelMap> => {
65
+ const requested = Array.isArray(entryIds) ? entryIds : [entryIds];
66
+
67
+ // Always include `shared`; de-duplicate via Set.
68
+ const ids = Array.from(
69
+ new Set<LocalizationEntryId>([LOCALIZATION_ENTRY_IDS.shared, ...requested])
70
+ );
71
+
72
+ const maps = await Promise.all(ids.map((id) => fetchEntry(id, locale)));
73
+
74
+ // Namespaced keys make this merge collision-safe.
75
+ return Object.assign({}, ...maps) as LabelMap;
76
+ };
@@ -0,0 +1,66 @@
1
+ export type LocalizationLanguageType = {
2
+ name: string;
3
+ language_code: string;
4
+ native_name: string;
5
+ script: string;
6
+ text_direction: "ltr" | "rtl" | string;
7
+ utf_encoding: string;
8
+ };
9
+
10
+ export type LocalizationRegionType = {
11
+ name: string;
12
+ flag: string;
13
+ country_code: string;
14
+ continent: string;
15
+ date_format: string;
16
+ time_format: "12h" | "24h" | string;
17
+ time_format_mask: string;
18
+ unit_system: "imperial" | "metric" | string;
19
+ default_timezone: string;
20
+ };
21
+
22
+ export type LocalizationLocaleMetadataType = {
23
+ code: string;
24
+ locale: string;
25
+ language: LocalizationLanguageType;
26
+ region: LocalizationRegionType;
27
+ };
28
+
29
+ export type LocalizationLabelType = {
30
+ key: string;
31
+ value: string;
32
+ };
33
+
34
+ export type LocalizationLocaleDataType = {
35
+ locale_metadata: LocalizationLocaleMetadataType; // typed, not consumed yet
36
+ labels: LocalizationLabelType[];
37
+ };
38
+
39
+ import type { ReactNode } from "react";
40
+
41
+ import type { LocalizationEntryId } from "./constants";
42
+
43
+ /** Flat, O(1)-lookup label map produced by the service. */
44
+ export type LabelMap = Record<string, string>;
45
+
46
+ /** Parameters accepted by `getLocalizationLabelsService`. */
47
+ export type GetLocalizationLabelsParams = {
48
+ entryIds: LocalizationEntryId | LocalizationEntryId[];
49
+ locale?: string;
50
+ };
51
+
52
+ /**
53
+ * Translation function exposed to components.
54
+ *
55
+ * Two call signatures:
56
+ * - `t(key, (string | number)[])` → `string` – pure text interpolation (O(1) replace)
57
+ * - `t(key, ReactNode[])` → `ReactNode` – node interpolation; returns an array of
58
+ * alternating text segments and the supplied React nodes, ready to spread inside JSX.
59
+ *
60
+ * The second overload is selected automatically by TypeScript whenever at least one
61
+ * interpolation value is not a `string` or `number` (e.g. a JSX element).
62
+ */
63
+ export type LocalizeFn = {
64
+ (key: string, interpolations?: (string | number)[]): string;
65
+ (key: string, interpolations: ReactNode[]): ReactNode;
66
+ };
@@ -0,0 +1,22 @@
1
+ import { type ComponentType } from "react";
2
+
3
+ import { LocalizationProvider } from "./LocalizationContext";
4
+
5
+ import type { LabelMap } from "./types";
6
+
7
+ export const withLocalization = <T extends Record<string, unknown>>(
8
+ Component: ComponentType<T>
9
+ ) =>
10
+ function WrappedWithLocalization(
11
+ props: T & { context: { localization?: { labels: LabelMap } } }
12
+ ) {
13
+ const labels = props.context?.localization?.labels ?? {};
14
+ return (
15
+ <LocalizationProvider labels={labels}>
16
+ {/* Forward ALL props (including `context`) so `withBuyerPortal` downstream
17
+ still receives it. `withLocalization` must sit BEFORE `withBuyerPortal`,
18
+ which strips `context` (see Decision 1 in specs/custom-localization-system.md). */}
19
+ <Component {...props} />
20
+ </LocalizationProvider>
21
+ );
22
+ };
@@ -1,6 +1,8 @@
1
1
  import { ErrorBoundaryProps } from "../components/ErrorBoundary/types";
2
2
  import { ClientContext } from "../utils";
3
3
 
4
+ import type { LabelMap } from "../localization/types";
5
+
4
6
  export type AuthRouteProps<T> =
5
7
  | {
6
8
  authorized: true;
@@ -15,4 +17,7 @@ export type AuthRouteProps<T> =
15
17
  error?: ErrorBoundaryProps;
16
18
  data?: T;
17
19
  clientContext?: ClientContext;
20
+ context?: {
21
+ localization?: { labels: LabelMap };
22
+ };
18
23
  };
@@ -7,4 +7,5 @@ export type LoaderData<QueryType = any> = {
7
7
  writeHead: (status: number, headers: Record<string, string>) => void;
8
8
  end: () => void;
9
9
  };
10
+ locale?: string;
10
11
  } & (QueryType extends undefined ? { query?: never } : { query: QueryType });
@@ -1,12 +1,12 @@
1
1
  export const addressLabelToPropMapping = {
2
- name: "Address Name",
3
- streetAddress: "Street address",
4
- streetNumber: "Number",
5
- streetAddress2: "Apt, Suite, Building",
6
- country: "Country",
7
- zip: "Postal Code",
8
- state: "State",
9
- city: "City",
10
- geoCoordinates: "Geo coordinate",
11
- types: "Address Type",
2
+ name: "shared.labels.addressName",
3
+ streetAddress: "shared.labels.streetAddress",
4
+ streetNumber: "shared.labels.streetNumber",
5
+ streetAddress2: "shared.labels.streetAddress2",
6
+ country: "shared.labels.country",
7
+ zip: "shared.labels.postalCode",
8
+ state: "shared.labels.state",
9
+ city: "shared.labels.city",
10
+ geoCoordinates: "shared.labels.geoCoordinate",
11
+ types: "shared.labels.addressType",
12
12
  };
@@ -22,7 +22,4 @@ export const SCOPE_KEYS = {
22
22
  CREDIT_CARDS: "creditCards",
23
23
  } as const;
24
24
 
25
- export const CURRENT_VERSION = "2.0.13";
26
-
27
- export const CHANGES_TIMEOUT_MESSAGE =
28
- "Changes may take up to 10 minutes to apply.";
25
+ export const CURRENT_VERSION = "2.0.14";
@@ -1,30 +1,35 @@
1
1
  import { buyerPortalRoutes } from "./buyerPortalRoutes";
2
2
 
3
- export const getContractSettingsLinks = ({
4
- orgUnitId,
5
- contractId,
6
- }: {
7
- orgUnitId: string;
8
- contractId: string;
9
- }) => [
3
+ import type { LocalizeFn } from "../localization/types";
4
+
5
+ export const getContractSettingsLinks = (
6
+ {
7
+ orgUnitId,
8
+ contractId,
9
+ }: {
10
+ orgUnitId: string;
11
+ contractId: string;
12
+ },
13
+ t: LocalizeFn
14
+ ) => [
10
15
  {
11
- name: "Profile",
16
+ name: t("layouts.navigation.profile"),
12
17
  link: buyerPortalRoutes.profileDetails({ orgUnitId, contractId }),
13
18
  },
14
19
  {
15
- name: "Addresses",
20
+ name: t("layouts.navigation.addresses"),
16
21
  link: buyerPortalRoutes.addresses({ orgUnitId, contractId }),
17
22
  },
18
23
  {
19
- name: "Payment methods",
24
+ name: t("layouts.navigation.paymentMethods"),
20
25
  link: buyerPortalRoutes.paymentMethods({ orgUnitId, contractId }),
21
26
  },
22
27
  {
23
- name: "Credit cards",
28
+ name: t("layouts.navigation.creditCards"),
24
29
  link: buyerPortalRoutes.creditCards({ orgUnitId, contractId }),
25
30
  },
26
31
  {
27
- name: "Assortments",
32
+ name: t("layouts.navigation.assortments"),
28
33
  link: buyerPortalRoutes.productAssortment({ orgUnitId, contractId }),
29
34
  },
30
35
  ];
@@ -1,18 +1,23 @@
1
1
  import { buyerPortalRoutes } from "./buyerPortalRoutes";
2
2
 
3
- export const getFinanceSettingsLinks = ({
4
- orgUnitId,
5
- contractId,
6
- }: {
7
- orgUnitId: string;
8
- contractId: string;
9
- }) => [
3
+ import type { LocalizeFn } from "../localization/types";
4
+
5
+ export const getFinanceSettingsLinks = (
6
+ {
7
+ orgUnitId,
8
+ contractId,
9
+ }: {
10
+ orgUnitId: string;
11
+ contractId: string;
12
+ },
13
+ t: LocalizeFn
14
+ ) => [
10
15
  {
11
- name: "Budgets",
16
+ name: t("layouts.navigation.budgets"),
12
17
  link: buyerPortalRoutes.budgets({ orgUnitId, contractId }),
13
18
  },
14
19
  {
15
- name: "Buying Policies",
20
+ name: t("layouts.navigation.buyingPolicies"),
16
21
  link: buyerPortalRoutes.buyingPolicies({ orgUnitId, contractId }),
17
22
  },
18
23
  ];
@@ -5,6 +5,6 @@ export const getKeyByValue = (object: Dictionary<string>, value: string) => {
5
5
  };
6
6
 
7
7
  export const sortingOptions: Dictionary<string> = {
8
- Users: "users",
9
- "Shipping Addresses": "addresses",
8
+ "shared.labels.users": "users",
9
+ "shared.labels.shippingAddresses": "addresses",
10
10
  };
@@ -1,16 +1,26 @@
1
1
  import { buyerPortalRoutes } from "./buyerPortalRoutes";
2
2
 
3
+ import type { LocalizeFn } from "../localization/types";
4
+
3
5
  export const getOrganizationSettingsLinks = ({
4
6
  orgUnitId,
5
7
  contractId,
8
+ t,
6
9
  }: {
7
10
  orgUnitId: string;
8
11
  contractId?: string;
12
+ t: LocalizeFn;
9
13
  }) => [
10
- { name: "Users", link: buyerPortalRoutes.users({ orgUnitId, contractId }) },
11
- { name: "Roles", link: buyerPortalRoutes.roles({ orgUnitId, contractId }) },
12
14
  {
13
- name: "Organizational Units",
15
+ name: t("layouts.navigation.users"),
16
+ link: buyerPortalRoutes.users({ orgUnitId, contractId }),
17
+ },
18
+ {
19
+ name: t("layouts.navigation.roles"),
20
+ link: buyerPortalRoutes.roles({ orgUnitId, contractId }),
21
+ },
22
+ {
23
+ name: t("layouts.navigation.organizationalUnits"),
14
24
  link: buyerPortalRoutes.orgUnits({ orgUnitId, contractId }),
15
25
  },
16
26
  ];
@@ -2,17 +2,22 @@ import { buyerPortalRoutes } from "./buyerPortalRoutes";
2
2
  import { getFinanceSettingsLinks } from "./getFinanceSettingsLinks";
3
3
  import { getOrganizationSettingsLinks } from "./getOrganizationSettingsLinks";
4
4
 
5
- export const getSumaListingSidebarLinks = ({
6
- orgUnitId,
7
- contractId,
8
- }: {
9
- orgUnitId: string;
10
- contractId: string;
11
- }) => [
5
+ import type { LocalizeFn } from "../localization/types";
6
+
7
+ export const getSumaListingSidebarLinks = (
8
+ {
9
+ orgUnitId,
10
+ contractId,
11
+ }: {
12
+ orgUnitId: string;
13
+ contractId: string;
14
+ },
15
+ t: LocalizeFn
16
+ ) => [
12
17
  {
13
- name: "Contracts",
18
+ name: t("layouts.navigation.contracts"),
14
19
  link: buyerPortalRoutes.contracts({ orgUnitId }),
15
20
  },
16
- ...getOrganizationSettingsLinks({ orgUnitId, contractId }),
17
- ...getFinanceSettingsLinks({ orgUnitId, contractId }),
21
+ ...getOrganizationSettingsLinks({ orgUnitId, t }),
22
+ ...getFinanceSettingsLinks({ orgUnitId, contractId }, t),
18
23
  ];
@@ -32,7 +32,9 @@ export { roles, type Role } from "./roles";
32
32
  export {
33
33
  sortAscending,
34
34
  sortDescending,
35
+ sortingOptionI18nKeys,
35
36
  sortingOptions,
37
+ statusFilterI18nKeys,
36
38
  statusFilters,
37
39
  updateSort,
38
40
  userTypesFilters,
@@ -14,81 +14,81 @@ export const ROUTE_TABS_LAYOUT_MAPPING: Record<string, RouteTabsLayoutConfig> =
14
14
  {
15
15
  "/pvt/organization-account/profile/[orgUnitId]/[contractId]": {
16
16
  layout: "ContractTabsLayout",
17
- pageName: "Contract",
18
- pageTitle: "Profile",
17
+ pageName: "layouts.tabs.contract",
18
+ pageTitle: "layouts.titles.profile",
19
19
  },
20
20
  "/pvt/organization-account/addresses/[orgUnitId]/[contractId]": {
21
21
  layout: "ContractTabsLayout",
22
- pageName: "Contract",
23
- pageTitle: "Addresses",
22
+ pageName: "layouts.tabs.contract",
23
+ pageTitle: "layouts.titles.addresses",
24
24
  },
25
25
  "/pvt/organization-account/address/[orgUnitId]/[contractId]/[addressId]": {
26
26
  layout: "ContractTabsLayout",
27
- pageName: "Contract",
28
- pageTitle: "Address Details",
27
+ pageName: "layouts.tabs.contract",
28
+ pageTitle: "layouts.titles.addressDetails",
29
29
  },
30
30
  "/pvt/organization-account/payment-groups/[orgUnitId]/[contractId]": {
31
31
  layout: "ContractTabsLayout",
32
- pageName: "Contract",
33
- pageTitle: "Payment Methods",
32
+ pageName: "layouts.tabs.contract",
33
+ pageTitle: "layouts.titles.paymentMethods",
34
34
  },
35
35
  "/pvt/organization-account/credit-cards/[orgUnitId]/[contractId]": {
36
36
  layout: "ContractTabsLayout",
37
- pageName: "Contract",
38
- pageTitle: "Credit Cards",
37
+ pageName: "layouts.tabs.contract",
38
+ pageTitle: "layouts.titles.creditCards",
39
39
  },
40
40
  "/pvt/organization-account/product-assortment/[orgUnitId]/[contractId]": {
41
41
  layout: "ContractTabsLayout",
42
- pageName: "Contract",
43
- pageTitle: "Assortments",
42
+ pageName: "layouts.tabs.contract",
43
+ pageTitle: "layouts.titles.assortments",
44
44
  },
45
45
  "/pvt/organization-account/accounting-field/[orgUnitId]/[contractId]/[accountingFieldId]":
46
46
  {
47
47
  layout: "ContractTabsLayout",
48
- pageName: "Contract",
49
- pageTitle: "Accounting field",
48
+ pageName: "layouts.tabs.contract",
49
+ pageTitle: "layouts.titles.accountingField",
50
50
  },
51
51
  "/pvt/organization-account/buying-policies/[orgUnitId]/[contractId]": {
52
52
  layout: "FinanceTabsLayout",
53
- pageName: "Finance and Compliance",
54
- pageTitle: "Buying Policies",
53
+ pageName: "layouts.tabs.financeAndCompliance",
54
+ pageTitle: "layouts.titles.buyingPolicies",
55
55
  },
56
56
  "/pvt/organization-account/buying-policy/[orgUnitId]/[contractId]/[buyingPolicyId]":
57
57
  {
58
58
  layout: "FinanceTabsLayout",
59
- pageName: "Finance and Compliance",
60
- pageTitle: "Buying Policy Details",
59
+ pageName: "layouts.tabs.financeAndCompliance",
60
+ pageTitle: "layouts.titles.buyingPolicyDetails",
61
61
  },
62
62
 
63
63
  "/pvt/organization-account/roles/[orgUnitId]": {
64
64
  layout: "OrgUnitTabsLayout",
65
- pageName: "Organization",
66
- pageTitle: "Roles",
65
+ pageName: "layouts.tabs.organization",
66
+ pageTitle: "layouts.titles.roles",
67
67
  },
68
68
  "/pvt/organization-account/roles/[orgUnitId]/[roleName]": {
69
69
  layout: "OrgUnitTabsLayout",
70
- pageName: "Organization",
71
- pageTitle: "Role Details",
70
+ pageName: "layouts.tabs.organization",
71
+ pageTitle: "layouts.titles.roleDetails",
72
72
  },
73
73
  "/pvt/organization-account/users/[orgUnitId]": {
74
74
  layout: "OrgUnitTabsLayout",
75
- pageName: "Organization",
76
- pageTitle: "Users",
75
+ pageName: "layouts.tabs.organization",
76
+ pageTitle: "layouts.titles.users",
77
77
  },
78
78
  "/pvt/organization-account/user/[orgUnitId]/[userId]": {
79
79
  layout: "OrgUnitTabsLayout",
80
- pageName: "Organization",
81
- pageTitle: "User Details",
80
+ pageName: "layouts.tabs.organization",
81
+ pageTitle: "layouts.titles.userDetails",
82
82
  },
83
83
  "/pvt/organization-account/org-units/[orgUnitId]": {
84
84
  layout: "OrgUnitTabsLayout",
85
- pageName: "Organization",
86
- pageTitle: "Organization Details",
85
+ pageName: "layouts.tabs.organization",
86
+ pageTitle: "layouts.titles.organizationDetails",
87
87
  },
88
88
  "/pvt/organization-account/org-unit/[orgUnitId]": {
89
89
  layout: "OrgUnitDetailsLayout",
90
- pageName: "Organization",
91
- pageTitle: "Organization Details",
90
+ pageName: "layouts.tabs.organization",
91
+ pageTitle: "layouts.titles.organizationDetails",
92
92
  },
93
93
  };
94
94
 
@@ -8,6 +8,11 @@ export const statusFilters: Dictionary<boolean> = {
8
8
  Inativo: false,
9
9
  };
10
10
 
11
+ export const statusFilterI18nKeys: Dictionary<string> = {
12
+ Ativo: "shared.labels.active",
13
+ Inativo: "shared.labels.inactive",
14
+ };
15
+
11
16
  export const userTypesFilters = ["Admin", "Buyer"];
12
17
 
13
18
  export const sortingOptions: Dictionary<string> = {
@@ -15,6 +20,11 @@ export const sortingOptions: Dictionary<string> = {
15
20
  "Alphabetical Order Desc": "name",
16
21
  };
17
22
 
23
+ export const sortingOptionI18nKeys: Dictionary<string> = {
24
+ "Alphabetical Order": "shared.labels.alphabeticalOrder",
25
+ "Alphabetical Order Desc": "shared.labels.alphabeticalOrderDesc",
26
+ };
27
+
18
28
  export const updateSort = (arrayToSort: any[], selectedOption: string) => {
19
29
  switch (selectedOption) {
20
30
  case "Alphabetical Order": {
@@ -31,10 +31,16 @@ export const withAuth = <T extends Record<string, unknown>>(
31
31
  return null;
32
32
  }
33
33
 
34
+ // In the error case props.data is undefined; forward props.context so withLocalization
35
+ // can read localization labels. In the success case context comes from props.data spread.
36
+ const errorContext =
37
+ !props?.authorized && "context" in props ? props.context : undefined;
38
+
34
39
  // If authorized, render the component with the data
35
40
  return (
36
41
  <Component
37
42
  {...(props.data as T)}
43
+ {...(errorContext ? { context: errorContext } : {})}
38
44
  hasError={props?.hasError}
39
45
  error={props?.error}
40
46
  />