@greatapps/common 1.1.675 → 1.1.677

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 (240) hide show
  1. package/dist/components/account/ConfigurationsMyAccountModal.mjs +8 -6
  2. package/dist/components/account/ConfigurationsMyAccountModal.mjs.map +1 -1
  3. package/dist/components/account/ConfirmDeleteAccountModal.mjs +20 -19
  4. package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -1
  5. package/dist/components/account/ConfirmGlobalPreferencesModal.mjs +10 -11
  6. package/dist/components/account/ConfirmGlobalPreferencesModal.mjs.map +1 -1
  7. package/dist/components/account/DeleteAccountModal.mjs +34 -28
  8. package/dist/components/account/DeleteAccountModal.mjs.map +1 -1
  9. package/dist/components/account/DisableTwoFactorAuthModal.mjs +7 -5
  10. package/dist/components/account/DisableTwoFactorAuthModal.mjs.map +1 -1
  11. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs +8 -9
  12. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -1
  13. package/dist/components/account/TwoFactorAuthModal.mjs +22 -23
  14. package/dist/components/account/TwoFactorAuthModal.mjs.map +1 -1
  15. package/dist/components/account/constants.mjs +4 -4
  16. package/dist/components/account/constants.mjs.map +1 -1
  17. package/dist/components/account/hooks/useChangeEmailForm.mjs +5 -3
  18. package/dist/components/account/hooks/useChangeEmailForm.mjs.map +1 -1
  19. package/dist/components/account/hooks/useChangePasswordForm.mjs +26 -3
  20. package/dist/components/account/hooks/useChangePasswordForm.mjs.map +1 -1
  21. package/dist/components/account/hooks/useChangePhoneForm.mjs +18 -9
  22. package/dist/components/account/hooks/useChangePhoneForm.mjs.map +1 -1
  23. package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs +26 -3
  24. package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs.map +1 -1
  25. package/dist/components/account/hooks/useOtpVerification.mjs +19 -3
  26. package/dist/components/account/hooks/useOtpVerification.mjs.map +1 -1
  27. package/dist/components/account/hooks/useTwoFactorAuthForm.mjs +27 -4
  28. package/dist/components/account/hooks/useTwoFactorAuthForm.mjs.map +1 -1
  29. package/dist/components/account/sections/ChangeEmailModal.mjs +25 -28
  30. package/dist/components/account/sections/ChangeEmailModal.mjs.map +1 -1
  31. package/dist/components/account/sections/ChangePasswordSection.mjs +8 -6
  32. package/dist/components/account/sections/ChangePasswordSection.mjs.map +1 -1
  33. package/dist/components/account/sections/ChangePhoneModal.mjs +24 -27
  34. package/dist/components/account/sections/ChangePhoneModal.mjs.map +1 -1
  35. package/dist/components/account/sections/MyProfileSection.mjs +37 -22
  36. package/dist/components/account/sections/MyProfileSection.mjs.map +1 -1
  37. package/dist/components/account/sections/PreferencesSection.mjs +48 -24
  38. package/dist/components/account/sections/PreferencesSection.mjs.map +1 -1
  39. package/dist/components/account/sections/SecuritySection.mjs +11 -9
  40. package/dist/components/account/sections/SecuritySection.mjs.map +1 -1
  41. package/dist/components/account/sections/TokenSection.mjs +24 -8
  42. package/dist/components/account/sections/TokenSection.mjs.map +1 -1
  43. package/dist/components/layouts/AppMobileNavBar.mjs +2 -148
  44. package/dist/components/layouts/AppMobileNavBar.mjs.map +1 -1
  45. package/dist/components/layouts/NavBar.mjs +5 -3
  46. package/dist/components/layouts/NavBar.mjs.map +1 -1
  47. package/dist/components/layouts/NotificationsPopover.mjs +5 -3
  48. package/dist/components/layouts/NotificationsPopover.mjs.map +1 -1
  49. package/dist/components/layouts/ProfilePopover.mjs +6 -4
  50. package/dist/components/layouts/ProfilePopover.mjs.map +1 -1
  51. package/dist/components/layouts/UsersSelectorPopover.mjs +9 -7
  52. package/dist/components/layouts/UsersSelectorPopover.mjs.map +1 -1
  53. package/dist/components/modals/BuyCreditsModal.mjs +17 -18
  54. package/dist/components/modals/BuyCreditsModal.mjs.map +1 -1
  55. package/dist/components/modals/CreditsDisabledModal.mjs +6 -4
  56. package/dist/components/modals/CreditsDisabledModal.mjs.map +1 -1
  57. package/dist/components/modals/PaidPlanRequiredModal.mjs +5 -3
  58. package/dist/components/modals/PaidPlanRequiredModal.mjs.map +1 -1
  59. package/dist/components/modals/cards/AddCardModal.mjs +64 -46
  60. package/dist/components/modals/cards/AddCardModal.mjs.map +1 -1
  61. package/dist/components/modals/cards/CannotDeleteCardModal.mjs +9 -7
  62. package/dist/components/modals/cards/CannotDeleteCardModal.mjs.map +1 -1
  63. package/dist/components/modals/cards/CardFormFields.mjs +65 -51
  64. package/dist/components/modals/cards/CardFormFields.mjs.map +1 -1
  65. package/dist/components/modals/cards/DeleteCardModal.mjs +10 -8
  66. package/dist/components/modals/cards/DeleteCardModal.mjs.map +1 -1
  67. package/dist/components/navigation/CancelledSubscriptionBanner.mjs +4 -2
  68. package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
  69. package/dist/components/navigation/OverdueInvoiceBanner.mjs +4 -2
  70. package/dist/components/navigation/OverdueInvoiceBanner.mjs.map +1 -1
  71. package/dist/components/navigation/PendingCancellationBanner.mjs +4 -6
  72. package/dist/components/navigation/PendingCancellationBanner.mjs.map +1 -1
  73. package/dist/components/navigation/ProjectSelectorPopover.mjs +8 -6
  74. package/dist/components/navigation/ProjectSelectorPopover.mjs.map +1 -1
  75. package/dist/components/navigation/UpcomingInvoiceBanner.mjs +4 -6
  76. package/dist/components/navigation/UpcomingInvoiceBanner.mjs.map +1 -1
  77. package/dist/components/navigation/subcomponents/BottomLinks.mjs +5 -3
  78. package/dist/components/navigation/subcomponents/BottomLinks.mjs.map +1 -1
  79. package/dist/components/navigation/subcomponents/CreditsCard.mjs +3 -1
  80. package/dist/components/navigation/subcomponents/CreditsCard.mjs.map +1 -1
  81. package/dist/components/navigation/subcomponents/PlanCard.mjs +3 -1
  82. package/dist/components/navigation/subcomponents/PlanCard.mjs.map +1 -1
  83. package/dist/components/navigation/subcomponents/ProjectSelector.mjs +5 -3
  84. package/dist/components/navigation/subcomponents/ProjectSelector.mjs.map +1 -1
  85. package/dist/components/pages/NotFoundPage.mjs +5 -3
  86. package/dist/components/pages/NotFoundPage.mjs.map +1 -1
  87. package/dist/components/pages/notifications/Notifications.mjs +13 -10
  88. package/dist/components/pages/notifications/Notifications.mjs.map +1 -1
  89. package/dist/components/ui/buttons/CopyButton.mjs +9 -5
  90. package/dist/components/ui/buttons/CopyButton.mjs.map +1 -1
  91. package/dist/components/ui/data-display/CardItem.mjs +7 -5
  92. package/dist/components/ui/data-display/CardItem.mjs.map +1 -1
  93. package/dist/components/ui/data-display/PaymentInfoCard.mjs +10 -8
  94. package/dist/components/ui/data-display/PaymentInfoCard.mjs.map +1 -1
  95. package/dist/components/ui/feedback/CircularProgress.mjs.map +1 -1
  96. package/dist/components/ui/feedback/DefaultCircularProgress.mjs +3 -1
  97. package/dist/components/ui/feedback/DefaultCircularProgress.mjs.map +1 -1
  98. package/dist/components/ui/form/ComboboxField.mjs +11 -6
  99. package/dist/components/ui/form/ComboboxField.mjs.map +1 -1
  100. package/dist/components/ui/form/DatePicker.mjs +5 -2
  101. package/dist/components/ui/form/DatePicker.mjs.map +1 -1
  102. package/dist/components/ui/form/DateRangePicker.mjs +5 -2
  103. package/dist/components/ui/form/DateRangePicker.mjs.map +1 -1
  104. package/dist/components/ui/form/PhoneInput.mjs +5 -3
  105. package/dist/components/ui/form/PhoneInput.mjs.map +1 -1
  106. package/dist/components/ui/form/SelectField.mjs +6 -3
  107. package/dist/components/ui/form/SelectField.mjs.map +1 -1
  108. package/dist/components/widgets/ImageUpload/ImageCropModal.mjs +11 -18
  109. package/dist/components/widgets/ImageUpload/ImageCropModal.mjs.map +1 -1
  110. package/dist/components/widgets/ImageUpload/ImageTooSmallModal.mjs +6 -15
  111. package/dist/components/widgets/ImageUpload/ImageTooSmallModal.mjs.map +1 -1
  112. package/dist/i18n/config.mjs +13 -0
  113. package/dist/i18n/config.mjs.map +1 -0
  114. package/dist/i18n/country-language.mjs +74 -0
  115. package/dist/i18n/country-language.mjs.map +1 -0
  116. package/dist/i18n/messages/en-us.mjs +494 -0
  117. package/dist/i18n/messages/en-us.mjs.map +1 -0
  118. package/dist/i18n/messages/es-es.mjs +494 -0
  119. package/dist/i18n/messages/es-es.mjs.map +1 -0
  120. package/dist/i18n/messages/index.mjs +16 -0
  121. package/dist/i18n/messages/index.mjs.map +1 -0
  122. package/dist/i18n/messages/pt-br.mjs +507 -0
  123. package/dist/i18n/messages/pt-br.mjs.map +1 -0
  124. package/dist/i18n/normalize.mjs +12 -0
  125. package/dist/i18n/normalize.mjs.map +1 -0
  126. package/dist/i18n/resolve-locale.mjs +42 -0
  127. package/dist/i18n/resolve-locale.mjs.map +1 -0
  128. package/dist/i18n.mjs +14 -0
  129. package/dist/i18n.mjs.map +1 -0
  130. package/dist/index.mjs +19 -3
  131. package/dist/index.mjs.map +1 -1
  132. package/dist/modules/accounts/hooks/use-currency-formatter.hook.mjs +20 -0
  133. package/dist/modules/accounts/hooks/use-currency-formatter.hook.mjs.map +1 -0
  134. package/dist/modules/cards/hooks/handle-delete-card.hook.mjs +2 -1
  135. package/dist/modules/cards/hooks/handle-delete-card.hook.mjs.map +1 -1
  136. package/dist/modules/images/utils/validate-image.mjs +8 -8
  137. package/dist/modules/images/utils/validate-image.mjs.map +1 -1
  138. package/dist/modules/plans/hooks/list-plans.hook.mjs +2 -2
  139. package/dist/modules/plans/hooks/list-plans.hook.mjs.map +1 -1
  140. package/dist/modules/plans/types/plan.type.mjs +5 -1
  141. package/dist/modules/plans/types/plan.type.mjs.map +1 -1
  142. package/dist/modules/plans/utils/map-api-plan-to-ui.mjs +61 -35
  143. package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
  144. package/dist/modules/projects/services/projects.service.mjs +9 -0
  145. package/dist/modules/projects/services/projects.service.mjs.map +1 -1
  146. package/dist/modules/subscriptions/utils/periodicity.mjs +20 -14
  147. package/dist/modules/subscriptions/utils/periodicity.mjs.map +1 -1
  148. package/dist/providers/auth.provider.mjs +9 -1
  149. package/dist/providers/auth.provider.mjs.map +1 -1
  150. package/dist/server.mjs +2 -0
  151. package/dist/server.mjs.map +1 -1
  152. package/dist/utils/format/currency.mjs +32 -4
  153. package/dist/utils/format/currency.mjs.map +1 -1
  154. package/dist/utils/intl/cookie-domain.mjs +17 -0
  155. package/dist/utils/intl/cookie-domain.mjs.map +1 -0
  156. package/dist/utils/intl/locale-cookie.mjs +13 -0
  157. package/dist/utils/intl/locale-cookie.mjs.map +1 -0
  158. package/dist/utils/intl/locales.mjs +3 -2
  159. package/dist/utils/intl/locales.mjs.map +1 -1
  160. package/package.json +7 -1
  161. package/src/components/account/ConfigurationsMyAccountModal.tsx +10 -6
  162. package/src/components/account/ConfirmDeleteAccountModal.tsx +22 -13
  163. package/src/components/account/ConfirmGlobalPreferencesModal.tsx +12 -10
  164. package/src/components/account/DeleteAccountModal.tsx +34 -24
  165. package/src/components/account/DisableTwoFactorAuthModal.tsx +11 -6
  166. package/src/components/account/IsntPossibleDeleteAccountModal.tsx +8 -6
  167. package/src/components/account/TwoFactorAuthModal.tsx +33 -29
  168. package/src/components/account/constants.ts +8 -5
  169. package/src/components/account/hooks/useChangeEmailForm.tsx +5 -3
  170. package/src/components/account/hooks/useChangePasswordForm.tsx +20 -6
  171. package/src/components/account/hooks/useChangePhoneForm.tsx +16 -10
  172. package/src/components/account/hooks/useDisableTwoFactorAuthForm.tsx +20 -6
  173. package/src/components/account/hooks/useOtpVerification.tsx +16 -4
  174. package/src/components/account/hooks/useTwoFactorAuthForm.tsx +21 -7
  175. package/src/components/account/sections/ChangeEmailModal.tsx +35 -21
  176. package/src/components/account/sections/ChangePasswordSection.tsx +10 -6
  177. package/src/components/account/sections/ChangePhoneModal.tsx +50 -22
  178. package/src/components/account/sections/MyProfileSection.tsx +37 -23
  179. package/src/components/account/sections/PreferencesSection.tsx +52 -25
  180. package/src/components/account/sections/SecuritySection.tsx +19 -10
  181. package/src/components/account/sections/TokenSection.tsx +22 -10
  182. package/src/components/layouts/AppMobileNavBar.tsx +1 -154
  183. package/src/components/layouts/NavBar.tsx +5 -3
  184. package/src/components/layouts/NotificationsPopover.tsx +5 -3
  185. package/src/components/layouts/ProfilePopover.tsx +6 -4
  186. package/src/components/layouts/UsersSelectorPopover.tsx +9 -7
  187. package/src/components/modals/BuyCreditsModal.tsx +18 -16
  188. package/src/components/modals/CreditsDisabledModal.tsx +6 -6
  189. package/src/components/modals/PaidPlanRequiredModal.tsx +5 -3
  190. package/src/components/modals/cards/AddCardModal.tsx +89 -62
  191. package/src/components/modals/cards/CannotDeleteCardModal.tsx +9 -8
  192. package/src/components/modals/cards/CardFormFields.tsx +93 -75
  193. package/src/components/modals/cards/DeleteCardModal.tsx +10 -8
  194. package/src/components/navigation/CancelledSubscriptionBanner.tsx +4 -2
  195. package/src/components/navigation/OverdueInvoiceBanner.tsx +4 -2
  196. package/src/components/navigation/PendingCancellationBanner.tsx +4 -2
  197. package/src/components/navigation/ProjectSelectorPopover.tsx +8 -6
  198. package/src/components/navigation/UpcomingInvoiceBanner.tsx +4 -2
  199. package/src/components/navigation/subcomponents/BottomLinks.tsx +5 -3
  200. package/src/components/navigation/subcomponents/CreditsCard.tsx +3 -1
  201. package/src/components/navigation/subcomponents/PlanCard.tsx +3 -1
  202. package/src/components/navigation/subcomponents/ProjectSelector.tsx +5 -3
  203. package/src/components/pages/NotFoundPage.tsx +5 -3
  204. package/src/components/pages/notifications/Notifications.tsx +13 -10
  205. package/src/components/ui/buttons/CopyButton.tsx +10 -5
  206. package/src/components/ui/data-display/CardItem.tsx +7 -5
  207. package/src/components/ui/data-display/PaymentInfoCard.tsx +10 -8
  208. package/src/components/ui/feedback/CircularProgress.tsx +4 -0
  209. package/src/components/ui/feedback/DefaultCircularProgress.tsx +3 -1
  210. package/src/components/ui/form/ComboboxField.tsx +11 -6
  211. package/src/components/ui/form/DatePicker.tsx +5 -2
  212. package/src/components/ui/form/DateRangePicker.tsx +5 -2
  213. package/src/components/ui/form/PhoneInput.tsx +5 -3
  214. package/src/components/ui/form/SelectField.tsx +6 -3
  215. package/src/components/widgets/ImageUpload/ImageCropModal.tsx +19 -11
  216. package/src/components/widgets/ImageUpload/ImageTooSmallModal.tsx +6 -5
  217. package/src/i18n/config.ts +17 -0
  218. package/src/i18n/country-language.ts +60 -0
  219. package/src/i18n/messages/en-us.ts +514 -0
  220. package/src/i18n/messages/es-es.ts +515 -0
  221. package/src/i18n/messages/index.ts +24 -0
  222. package/src/i18n/messages/pt-br.ts +538 -0
  223. package/src/i18n/normalize.ts +15 -0
  224. package/src/i18n/resolve-locale.ts +77 -0
  225. package/src/i18n.ts +10 -0
  226. package/src/index.ts +13 -2
  227. package/src/modules/accounts/hooks/use-currency-formatter.hook.tsx +32 -0
  228. package/src/modules/cards/hooks/handle-delete-card.hook.ts +8 -3
  229. package/src/modules/images/utils/validate-image.ts +23 -8
  230. package/src/modules/plans/hooks/list-plans.hook.ts +2 -2
  231. package/src/modules/plans/types/plan.type.ts +4 -0
  232. package/src/modules/plans/utils/map-api-plan-to-ui.ts +109 -47
  233. package/src/modules/projects/services/projects.service.ts +12 -0
  234. package/src/modules/subscriptions/utils/periodicity.ts +29 -12
  235. package/src/providers/auth.provider.tsx +13 -1
  236. package/src/server.ts +2 -0
  237. package/src/utils/format/currency.ts +62 -7
  238. package/src/utils/intl/cookie-domain.ts +29 -0
  239. package/src/utils/intl/locale-cookie.ts +18 -0
  240. package/src/utils/intl/locales.ts +3 -2
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Button, Progress } from '../../../index';
4
+ import { useTranslations } from 'next-intl';
4
5
 
5
6
  export interface PlanCardProps {
6
7
  planName: string;
@@ -10,6 +11,7 @@ export interface PlanCardProps {
10
11
  }
11
12
 
12
13
  export function PlanCard({ planName, trialLabel, progressValue, onKnowPlans }: PlanCardProps) {
14
+ const translate = useTranslations();
13
15
  return (
14
16
  <div className="flex flex-col rounded-md p-4 gap-4 bg-gray-50">
15
17
  <div className="flex flex-col gap-1">
@@ -22,7 +24,7 @@ export function PlanCard({ planName, trialLabel, progressValue, onKnowPlans }: P
22
24
  className="paragraph-small-semibold text-gray-950"
23
25
  onClick={onKnowPlans}
24
26
  >
25
- Conheça os planos
27
+ {translate('common.navigation.planCard.knowPlans')}
26
28
  </Button>
27
29
  </div>
28
30
  );
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useRef, useEffect, useMemo, RefObject } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { useMobileNavbarSheet } from '../../../store/useMobileNavbarSheet';
5
6
  import { useAppNavigationContext } from '../AppNavigationContext';
6
7
  import { ProjectSelectorPopover } from '../ProjectSelectorPopover';
@@ -30,6 +31,7 @@ export function ProjectSelector({
30
31
  onSelectProject,
31
32
  onManageProjects,
32
33
  }: ProjectSelectorProps) {
34
+ const translate = useTranslations();
33
35
  const isMainWhitelabel = useIsDefaultWhitelabel();
34
36
  const { breakpoint } = useAppNavigationContext();
35
37
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
@@ -124,10 +126,10 @@ export function ProjectSelector({
124
126
  {activeSection === 'projects' && (
125
127
  <div className="flex flex-col p-3 gap-2 border border-gray-200 rounded-lg animate-in fade-in duration-400">
126
128
  <div className="flex items-center justify-between">
127
- <span className="paragraph-small-semibold">Projetos</span>
129
+ <span className="paragraph-small-semibold">{translate('common.navigation.projectSelector.title')}</span>
128
130
  </div>
129
131
  <Command className="gap-2" shouldFilter={false}>
130
- <CommandInput placeholder="Busque aqui" value={search} onValueChange={setSearch} />
132
+ <CommandInput placeholder={translate('common.navigation.projectSelector.searchPlaceholder')} value={search} onValueChange={setSearch} />
131
133
  <CommandList
132
134
  className="custom-scrollbar paragraph-small-medium text-gray-600 h-[227px]"
133
135
  onScroll={(event) => {
@@ -141,7 +143,7 @@ export function ProjectSelector({
141
143
  }
142
144
  }}
143
145
  >
144
- <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
146
+ <CommandEmpty>{translate('common.navigation.projectSelector.noResults')}</CommandEmpty>
145
147
  {showListSkeleton ? (
146
148
  <>
147
149
  <ProjectItemSkeleton />
@@ -2,6 +2,7 @@
2
2
 
3
3
  import Image from "next/image";
4
4
  import { useEffect } from "react";
5
+ import { useTranslations } from "next-intl";
5
6
  import { useRouter } from "next/navigation";
6
7
  import { useWhitelabel, useIsDefaultWhitelabel } from "../../providers/whitelabel.provider";
7
8
  import { Button } from "../ui/buttons/Button";
@@ -18,6 +19,7 @@ function GreatAppsLogo() {
18
19
  }
19
20
 
20
21
  export function NotFoundPage() {
22
+ const translate = useTranslations();
21
23
  const router = useRouter();
22
24
 
23
25
  useEffect(() => {
@@ -47,11 +49,11 @@ export function NotFoundPage() {
47
49
  </div>
48
50
 
49
51
  <h1 className="max-w-[554px] px-6 font-[family-name:var(--font-outfit)] text-[32px] leading-[36px] font-semibold text-zinc-950 sm:px-0 sm:text-[56px] sm:leading-[56px]">
50
- Página não foi encontrada!
52
+ {translate('common.notFound.title')}
51
53
  </h1>
52
54
 
53
55
  <p className="max-w-[496px] px-6 paragraph-medium-medium text-zinc-950 sm:px-0">
54
- A página que você está procurando não existe. Verifique se digitou corretamente o link que está procurando.
56
+ {translate('common.notFound.description')}
55
57
  </p>
56
58
 
57
59
  <Button
@@ -65,7 +67,7 @@ export function NotFoundPage() {
65
67
  }
66
68
  }}
67
69
  >
68
- Voltar para o início
70
+ {translate('common.notFound.backToHome')}
69
71
  </Button>
70
72
  </div>
71
73
  </div>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { IconBell, IconCheck, IconLoader2 } from '@tabler/icons-react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { Button } from "../../ui/buttons/Button";
5
6
  import {
6
7
  Tabs,
@@ -34,6 +35,7 @@ function NotificationsList({
34
35
  hasNextPage: boolean;
35
36
  fetchNextPage: () => void;
36
37
  }) {
38
+ const translate = useTranslations();
37
39
  const notifications = pages?.pages.flatMap((p) => p.data) ?? [];
38
40
 
39
41
  const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
@@ -50,7 +52,7 @@ function NotificationsList({
50
52
  return (
51
53
  <div className="rounded-lg border border-zinc-200 bg-white">
52
54
  <div className="flex flex-col items-center justify-center py-16 gap-2">
53
- <span className="paragraph-medium-regular text-zinc-500">Nenhuma notificação encontrada.</span>
55
+ <span className="paragraph-medium-regular text-zinc-500">{translate('common.notifications.page.empty')}</span>
54
56
  </div>
55
57
  </div>
56
58
  );
@@ -80,7 +82,7 @@ function NotificationsList({
80
82
  actions={
81
83
  detailsUrl ? (
82
84
  <Button asChild variant="secondary" className="h-8! px-3! paragraph-small-semibold">
83
- <Link href={detailsUrl}>Detalhes</Link>
85
+ <Link href={detailsUrl}>{translate('common.notifications.page.details')}</Link>
84
86
  </Button>
85
87
  ) : undefined
86
88
  }
@@ -97,6 +99,7 @@ function NotificationsList({
97
99
  }
98
100
 
99
101
  export function NotificationPageContent() {
102
+ const translate = useTranslations();
100
103
  const unreadNotifications = useInfiniteMessages({
101
104
  readed: false,
102
105
  type: "notification",
@@ -116,12 +119,12 @@ export function NotificationPageContent() {
116
119
  markAllAsRead(undefined, {
117
120
  onSuccess: () => {
118
121
  toast.custom((t) => (
119
- <Toast variant="success" message="Todas as notificações foram marcadas como lidas" toastId={t} />
122
+ <Toast variant="success" message={translate('common.notifications.page.markAllSuccess')} toastId={t} />
120
123
  ));
121
124
  },
122
125
  onError: () => {
123
126
  toast.custom((t) => (
124
- <Toast variant="error" message="Erro ao marcar notificações como lidas" toastId={t} />
127
+ <Toast variant="error" message={translate('common.notifications.page.markAllError')} toastId={t} />
125
128
  ));
126
129
  },
127
130
  });
@@ -132,10 +135,10 @@ export function NotificationPageContent() {
132
135
  <div className="flex items-center justify-between gap-4">
133
136
  <div className="flex flex-col gap-2">
134
137
  <h1 className="title-large-semibold text-gray-950">
135
- Notificações
138
+ {translate('common.notifications.page.title')}
136
139
  </h1>
137
140
  <p className="paragraph-small-regular text-gray-600 hidden lg:block">
138
- Tenha controle total sobre páginas, experiências e conversões.
141
+ {translate('common.notifications.page.subtitle')}
139
142
  </p>
140
143
  </div>
141
144
  <Button
@@ -145,8 +148,8 @@ export function NotificationPageContent() {
145
148
  disabled={isMarkingAll || totalUnread === 0}
146
149
  >
147
150
  {isMarkingAll ? <IconLoader2 size={20} className="animate-spin" /> : <IconCheck size={20} />}
148
- <span className="lg:block hidden">Marcar todos como lidos</span>
149
- <span className="lg:hidden">Marcar como lidos</span>
151
+ <span className="lg:block hidden">{translate('common.notifications.page.markAllReadDesktop')}</span>
152
+ <span className="lg:hidden">{translate('common.notifications.page.markAllReadMobile')}</span>
150
153
  </Button>
151
154
  </div>
152
155
  <Tabs
@@ -155,11 +158,11 @@ export function NotificationPageContent() {
155
158
  >
156
159
  <TabsList className="h-auto pb-0 bg-transparent">
157
160
  <TabsTrigger className="gap-2" value="all">
158
- Todas as notificações{" "}
161
+ {translate('common.notifications.page.tabAll')}{" "}
159
162
  <span className="text-gray-500">({totalAll})</span>
160
163
  </TabsTrigger>
161
164
  <TabsTrigger className="gap-2" value="unreads" disabled={totalUnread === 0}>
162
- Não lidas{" "}
165
+ {translate('common.notifications.page.tabUnread')}{" "}
163
166
  <span className="text-gray-500">({totalUnread})</span>
164
167
  </TabsTrigger>
165
168
  </TabsList>
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { useTranslations } from 'next-intl';
3
4
  import { IconCheck, IconCopy } from '@tabler/icons-react';
4
5
  import { toast } from "sonner";
5
6
  import { Toast } from "../feedback/Toast";
@@ -19,18 +20,22 @@ export function CopyButton({
19
20
  textToCopy,
20
21
  size = 14,
21
22
  className,
22
- tooltipText = "Copiar link",
23
- successMessage = "Link copiado",
23
+ tooltipText,
24
+ successMessage,
24
25
  }: CopyButtonProps) {
26
+ const translate = useTranslations();
27
+ const resolvedTooltipText = tooltipText ?? translate('common.forms.copyButton.tooltipDefault');
28
+ const resolvedSuccessMessage = successMessage ?? translate('common.forms.copyButton.successDefault');
29
+
25
30
  const { isCopied, copy } = useCopyToClipboard({
26
31
  onSuccess: () => {
27
32
  toast.custom((t) => (
28
- <Toast variant="success" message={successMessage} toastId={t} />
33
+ <Toast variant="success" message={resolvedSuccessMessage} toastId={t} />
29
34
  ));
30
35
  },
31
36
  onError: () => {
32
37
  toast.custom((t) => (
33
- <Toast variant="error" message="Não foi possível copiar o link" toastId={t} />
38
+ <Toast variant="error" message={translate('common.forms.copyButton.errorMessage')} toastId={t} />
34
39
  ));
35
40
  },
36
41
  });
@@ -48,7 +53,7 @@ export function CopyButton({
48
53
  {isCopied ? <IconCheck size={size} /> : <IconCopy size={size} />}
49
54
  </span>
50
55
  </TooltipTrigger>
51
- <TooltipContent>{isCopied ? "Copiado!" : tooltipText}</TooltipContent>
56
+ <TooltipContent>{isCopied ? translate('common.actions.copied') : resolvedTooltipText}</TooltipContent>
52
57
  </Tooltip>
53
58
  );
54
59
  }
@@ -10,6 +10,7 @@ import { CardBrandIcon } from './CardBrandIcons';
10
10
  import { useSetDefaultCard } from '../../../modules/cards/hooks/set-default-card.hook';
11
11
  import { useHandleDeleteCard } from '../../../modules/cards/hooks/handle-delete-card.hook';
12
12
  import type { Card } from '../../../modules/cards/types';
13
+ import { useTranslations } from 'next-intl';
13
14
 
14
15
  type CardItemProps = {
15
16
  card: Card;
@@ -32,6 +33,7 @@ export function CardItem({
32
33
  onAfterDelete,
33
34
  onAfterSetDefault,
34
35
  }: CardItemProps) {
36
+ const translate = useTranslations();
35
37
  const [popoverOpen, setPopoverOpen] = useState(false);
36
38
 
37
39
  const setDefaultCardMutation = useSetDefaultCard();
@@ -44,7 +46,7 @@ export function CardItem({
44
46
  toast.custom((t) => (
45
47
  <Toast
46
48
  variant="success"
47
- message="Cartão definido como padrão"
49
+ message={translate('common.cards.item.setDefaultSuccess')}
48
50
  toastId={t}
49
51
  />
50
52
  ));
@@ -54,7 +56,7 @@ export function CardItem({
54
56
  toast.custom((t) => (
55
57
  <Toast
56
58
  variant="error"
57
- message="Erro ao definir cartão padrão"
59
+ message={translate('common.cards.item.setDefaultError')}
58
60
  toastId={t}
59
61
  />
60
62
  ));
@@ -86,7 +88,7 @@ export function CardItem({
86
88
  </div>
87
89
  {card.is_default && (
88
90
  <span className="paragraph-xsmall-semibold text-zinc-600 bg-zinc-100 px-2 py-0.5 rounded-lg whitespace-nowrap">
89
- Padrão
91
+ {translate('common.cards.item.defaultBadge')}
90
92
  </span>
91
93
  )}
92
94
  <CardBrandIcon brand={card.brand} />
@@ -108,7 +110,7 @@ export function CardItem({
108
110
  {menuHasSetDefault && (
109
111
  <NavBarItem
110
112
  icon={<IconStar size={18} className="text-zinc-400" />}
111
- label="Tornar padrão"
113
+ label={translate('common.cards.item.makeDefault')}
112
114
  onClick={() => {
113
115
  setPopoverOpen(false);
114
116
  handleSetDefault();
@@ -118,7 +120,7 @@ export function CardItem({
118
120
  {menuHasDelete && (
119
121
  <NavBarItem
120
122
  icon={<IconTrash size={18} className="text-zinc-400" />}
121
- label="Excluir"
123
+ label={translate('common.actions.delete')}
122
124
  onClick={() => {
123
125
  setPopoverOpen(false);
124
126
  handleDeleteCard(card);
@@ -9,6 +9,7 @@ import { useCards } from '../../../modules/cards/hooks/cards.hook';
9
9
  import { useModalManager } from '../../../store/useModalManager';
10
10
  import { useIsDefaultWhitelabel } from '../../../providers/whitelabel.provider';
11
11
  import type { Card } from '../../../modules/cards/types';
12
+ import { useTranslations } from 'next-intl';
12
13
 
13
14
  type PaymentInfoCardProps = {
14
15
  email?: string;
@@ -29,6 +30,7 @@ export function PaymentInfoCard({
29
30
  hideDelete,
30
31
  hideSetDefault,
31
32
  }: PaymentInfoCardProps) {
33
+ const translate = useTranslations();
32
34
  const isMobile = useIsMobile();
33
35
  const { openModal } = useModalManager();
34
36
  const isDefaultWl = useIsDefaultWhitelabel();
@@ -83,19 +85,19 @@ export function PaymentInfoCard({
83
85
  <div className="border border-zinc-200 rounded-lg overflow-hidden">
84
86
  {email && (
85
87
  <div className="flex items-center h-[60px] px-4 border-b border-zinc-200 gap-3">
86
- <span className="paragraph-small-medium text-zinc-500 w-[100px]">E-mail</span>
88
+ <span className="paragraph-small-medium text-zinc-500 w-[100px]">{translate('common.cards.paymentInfo.email')}</span>
87
89
  <span className="paragraph-small-medium text-zinc-950 flex-1">{email}</span>
88
90
  </div>
89
91
  )}
90
92
  <div className="flex items-center h-[60px] px-4 gap-3">
91
- <span className="paragraph-small-medium text-zinc-500 w-[100px]">Pagamento</span>
93
+ <span className="paragraph-small-medium text-zinc-500 w-[100px]">{translate('common.cards.paymentInfo.payment')}</span>
92
94
  <button
93
95
  type="button"
94
96
  onClick={handleOpenAddCardModal}
95
97
  className="flex items-center h-8 px-2 paragraph-small-semibold text-zinc-950 border border-zinc-200 rounded-md hover:border-zinc-400 transition-colors cursor-pointer w-fit"
96
98
  >
97
99
  <IconPlus className="size-[18px]" />
98
- <span className="px-1.5">Adicionar cartão</span>
100
+ <span className="px-1.5">{translate('common.cards.paymentInfo.addCard')}</span>
99
101
  </button>
100
102
  </div>
101
103
  </div>
@@ -107,7 +109,7 @@ export function PaymentInfoCard({
107
109
  <div className="border border-zinc-200 rounded-lg overflow-hidden">
108
110
  {email && (
109
111
  <div className="flex items-center h-[60px] px-4 border-b border-zinc-200 gap-3">
110
- <span className="paragraph-small-medium text-zinc-500 w-[100px]">E-mail</span>
112
+ <span className="paragraph-small-medium text-zinc-500 w-[100px]">{translate('common.cards.paymentInfo.email')}</span>
111
113
  <span className="paragraph-small-medium text-zinc-950 flex-1">{email}</span>
112
114
  </div>
113
115
  )}
@@ -118,7 +120,7 @@ export function PaymentInfoCard({
118
120
  >
119
121
  {!isExpanded ? (
120
122
  <>
121
- <span className="paragraph-small-medium text-zinc-500 w-[100px]">Pagamento</span>
123
+ <span className="paragraph-small-medium text-zinc-500 w-[100px]">{translate('common.cards.paymentInfo.payment')}</span>
122
124
  <div className="flex flex-col gap-0.5 flex-1 min-w-0">
123
125
  <span className="paragraph-small-semibold text-zinc-950 truncate">
124
126
  {selectedCard?.name}
@@ -135,7 +137,7 @@ export function PaymentInfoCard({
135
137
  ) : (
136
138
  <>
137
139
  <span className="paragraph-small-semibold text-zinc-950 flex-1">
138
- Selecione a forma de pagamento
140
+ {translate('common.cards.paymentInfo.selectPaymentMethod')}
139
141
  </span>
140
142
  <div className="size-8 flex items-center justify-center rounded-lg">
141
143
  <IconChevronDown className="size-[18px] text-zinc-500 rotate-180" />
@@ -170,7 +172,7 @@ export function PaymentInfoCard({
170
172
  : 'text-zinc-600 border border-zinc-200 hover:bg-zinc-50'
171
173
  }`}
172
174
  >
173
- Gerenciar cartões
175
+ {translate('common.cards.paymentInfo.manageCards')}
174
176
  </button>
175
177
  )}
176
178
  </div>
@@ -183,7 +185,7 @@ export function PaymentInfoCard({
183
185
  >
184
186
  <IconPlus className="size-[18px]" />
185
187
  <span className="px-1.5">
186
- {isExpanded ? 'Adicionar cartão' : 'Nova forma de pagamento'}
188
+ {isExpanded ? translate('common.cards.paymentInfo.addCard') : translate('common.cards.paymentInfo.newPaymentMethod')}
187
189
  </span>
188
190
  </button>
189
191
  </div>
@@ -34,6 +34,10 @@ export default function CircularProgress({
34
34
  aria-valuenow={current}
35
35
  aria-valuemin={0}
36
36
  aria-valuemax={total}
37
+ // i18n-TODO: aria-label hardcoded — CircularProgress has no 'use client' directive and
38
+ // receives no translate prop, so useTranslations() cannot be called here without
39
+ // converting it to a client component. Key to use: common.forms.progressAriaLabel ('Progresso').
40
+ // Either add 'use client' + useTranslations(), or accept a translate prop from the caller.
37
41
  aria-label="Progresso"
38
42
  >
39
43
  <svg width={size} height={size} className="absolute">
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { useTranslations } from 'next-intl';
3
4
  import {
4
5
  calculateRadius,
5
6
  calculateCircumference,
@@ -21,6 +22,7 @@ export default function DefaultCircularProgress({
21
22
  size = 62,
22
23
  strokeWidth = 3,
23
24
  }: CircularProgressProps) {
25
+ const translate = useTranslations();
24
26
  const isDefaultWl = useIsDefaultWhitelabel();
25
27
  const radius = calculateRadius(size, strokeWidth);
26
28
  const circumference = calculateCircumference(radius);
@@ -35,7 +37,7 @@ export default function DefaultCircularProgress({
35
37
  aria-valuenow={current}
36
38
  aria-valuemin={0}
37
39
  aria-valuemax={total}
38
- aria-label="Progresso de exportação"
40
+ aria-label={translate('common.forms.circularProgress.ariaLabel')}
39
41
  >
40
42
  <svg width={size} height={size} className="absolute">
41
43
  <circle
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useMemo } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-react';
5
6
  import { Popover, PopoverContent, PopoverTrigger } from '../overlay/Popover';
6
7
  import { cn } from '../../../infra/utils/clsx';
@@ -28,9 +29,9 @@ interface ComboboxFieldProps {
28
29
  export function ComboboxField({
29
30
  options,
30
31
  label,
31
- placeholder = 'Selecione uma opção',
32
- searchPlaceholder = 'Buscar...',
33
- emptyMessage = 'Nenhuma opção encontrada.',
32
+ placeholder,
33
+ searchPlaceholder,
34
+ emptyMessage,
34
35
  value,
35
36
  onChange,
36
37
  disabled = false,
@@ -38,6 +39,10 @@ export function ComboboxField({
38
39
  className,
39
40
  containerClassName,
40
41
  }: ComboboxFieldProps) {
42
+ const translate = useTranslations();
43
+ const resolvedPlaceholder = placeholder ?? translate('common.forms.combobox.placeholder');
44
+ const resolvedSearchPlaceholder = searchPlaceholder ?? translate('common.forms.combobox.searchPlaceholder');
45
+ const resolvedEmptyMessage = emptyMessage ?? translate('common.forms.combobox.emptyMessage');
41
46
  const [open, setOpen] = useState(false);
42
47
  const [search, setSearch] = useState('');
43
48
 
@@ -90,7 +95,7 @@ export function ComboboxField({
90
95
  <span className={cn('truncate text-left', !selectedOption && 'text-gray-400')}>
91
96
  {selectedOption
92
97
  ? (selectedOption.displayValue ?? selectedOption.label)
93
- : placeholder}
98
+ : resolvedPlaceholder}
94
99
  </span>
95
100
  {icon ?? (
96
101
  <IconChevronDown
@@ -114,7 +119,7 @@ export function ComboboxField({
114
119
  <IconSearch className="size-4 shrink-0 opacity-50 text-gray-500" />
115
120
  <input
116
121
  autoFocus
117
- placeholder={searchPlaceholder}
122
+ placeholder={resolvedSearchPlaceholder}
118
123
  value={search}
119
124
  onChange={(e) => setSearch(e.target.value)}
120
125
  className="flex h-full w-full bg-transparent text-sm outline-none placeholder:text-gray-400 paragraph-small-medium text-gray-950"
@@ -123,7 +128,7 @@ export function ComboboxField({
123
128
 
124
129
  <div className="overflow-y-auto max-h-[280px] p-1">
125
130
  {filteredOptions.length === 0 ? (
126
- <p className="py-6 text-center paragraph-small-medium text-gray-500">{emptyMessage}</p>
131
+ <p className="py-6 text-center paragraph-small-medium text-gray-500">{resolvedEmptyMessage}</p>
127
132
  ) : (
128
133
  filteredOptions.map((option) => {
129
134
  const isSelected = value === option.value;
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, forwardRef, useEffect } from "react";
4
+ import { useTranslations } from "next-intl";
4
5
  import { format } from "date-fns";
5
6
  import { Calendar } from "./Calendar";
6
7
  import { Popover, PopoverContent, PopoverTrigger } from "../overlay/Popover";
@@ -23,9 +24,11 @@ interface DatePickerProps {
23
24
 
24
25
  export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
25
26
  (
26
- { value, onChange, placeholder = "Selecione a data", className, disabled },
27
+ { value, onChange, placeholder, className, disabled },
27
28
  ref,
28
29
  ) => {
30
+ const translate = useTranslations();
31
+ const resolvedPlaceholder = placeholder ?? translate('common.forms.datePicker.placeholder');
29
32
  const [open, setOpen] = useState(false);
30
33
  const [date, setDate] = useState<Date | undefined>(() => {
31
34
  return value ? isoParaDataLocal(value) : undefined;
@@ -51,7 +54,7 @@ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
51
54
  }
52
55
  };
53
56
 
54
- const displayText = date ? format(date, "dd/MM/yyyy") : placeholder;
57
+ const displayText = date ? format(date, "dd/MM/yyyy") : resolvedPlaceholder;
55
58
 
56
59
  return (
57
60
  <Popover open={open} onOpenChange={setOpen}>
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, forwardRef, useEffect } from "react";
4
+ import { useTranslations } from "next-intl";
4
5
  import { format } from "date-fns";
5
6
  import { IconCalendar } from '@tabler/icons-react';
6
7
  import { DateRange } from "react-day-picker";
@@ -24,7 +25,9 @@ interface DateRangePickerProps {
24
25
  }
25
26
 
26
27
  export const DateRangePicker = forwardRef<HTMLButtonElement, DateRangePickerProps>(
27
- ({ value, onChange, placeholder = "Selecione o período", className, disabled }, ref) => {
28
+ ({ value, onChange, placeholder, className, disabled }, ref) => {
29
+ const translate = useTranslations();
30
+ const resolvedPlaceholder = placeholder ?? translate('common.forms.dateRangePicker.placeholder');
28
31
  const [open, setOpen] = useState(false);
29
32
  const [range, setRange] = useState<DateRange | undefined>(() => {
30
33
  if (value?.from && value?.to) {
@@ -62,7 +65,7 @@ export const DateRangePicker = forwardRef<HTMLButtonElement, DateRangePickerProp
62
65
 
63
66
  const displayText = range?.from && range?.to
64
67
  ? `${format(range.from, "dd/MM/yyyy")} - ${format(range.to, "dd/MM/yyyy")}`
65
- : placeholder;
68
+ : resolvedPlaceholder;
66
69
 
67
70
  return (
68
71
  <Popover open={open} onOpenChange={setOpen}>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useRef } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { AsYouType, CountryCode } from 'libphonenumber-js';
5
6
  import { Input, Separator, Popover, PopoverTrigger, PopoverContent, cn } from '@greatapps/common';
6
7
  import { IconChevronDown, IconSearch } from '@tabler/icons-react';
@@ -41,6 +42,7 @@ export default function PhoneInput({
41
42
  classnameContainer,
42
43
  className,
43
44
  }: PhoneInputProps) {
45
+ const translate = useTranslations();
44
46
  const parsed = parsePhoneValue(value ?? '');
45
47
  const [selectedCountry, setSelectedCountry] = useState<Country>(parsed.country);
46
48
  const [phoneNumber, setPhoneNumber] = useState(parsed.number);
@@ -115,7 +117,7 @@ export default function PhoneInput({
115
117
  {label && (
116
118
  <div className="flex items-center gap-1 paragraph-xsmall-semibold">
117
119
  <span className="text-zinc-600">{label}</span>
118
- {optional && <span className="text-zinc-500">(Opcional)</span>}
120
+ {optional && <span className="text-zinc-500">{translate('common.forms.phoneInput.optional')}</span>}
119
121
  {required && <span className="text-red-600">*</span>}
120
122
  </div>
121
123
  )}
@@ -148,7 +150,7 @@ export default function PhoneInput({
148
150
  <input
149
151
  type="text"
150
152
  className="flex-1 text-sm outline-none placeholder:text-zinc-400"
151
- placeholder="Buscar país..."
153
+ placeholder={translate('common.forms.phoneInput.searchPlaceholder')}
152
154
  value={search}
153
155
  onChange={(e) => setSearch(e.target.value)}
154
156
  autoFocus
@@ -175,7 +177,7 @@ export default function PhoneInput({
175
177
  </button>
176
178
  ))}
177
179
  {filtered.length === 0 && (
178
- <p className="text-sm text-zinc-500 p-3 text-center">Nenhum país encontrado</p>
180
+ <p className="text-sm text-zinc-500 p-3 text-center">{translate('common.forms.phoneInput.emptyMessage')}</p>
179
181
  )}
180
182
  </div>
181
183
  </PopoverContent>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import {
5
6
  Select,
6
7
  SelectContent,
@@ -41,7 +42,7 @@ export function SelectField({
41
42
  hintMessage,
42
43
  error = false,
43
44
  errorMessage,
44
- placeholder = 'Selecione uma opção',
45
+ placeholder,
45
46
  options,
46
47
  value,
47
48
  defaultValue,
@@ -51,6 +52,8 @@ export function SelectField({
51
52
  className,
52
53
  containerClassName,
53
54
  }: SelectFieldProps) {
55
+ const translate = useTranslations();
56
+ const resolvedPlaceholder = placeholder ?? translate('common.forms.select.placeholder');
54
57
  const handleValueChange = (selectedValue: string) => {
55
58
  if (!onChange) return;
56
59
  const selectedOption = options.find((option) => String(option.value) === selectedValue);
@@ -67,7 +70,7 @@ export function SelectField({
67
70
  {label && (
68
71
  <div className="flex items-center gap-1 paragraph-xsmall-semibold">
69
72
  <span className="text-gray-600">{label}</span>
70
- {optional && <span className="text-gray-500">(Opcional)</span>}
73
+ {optional && <span className="text-gray-500">{translate('common.forms.select.optional')}</span>}
71
74
  {required && <span className="text-red-600">*</span>}
72
75
  </div>
73
76
  )}
@@ -89,7 +92,7 @@ export function SelectField({
89
92
  )}
90
93
  icon={icon}
91
94
  >
92
- <SelectValue placeholder={placeholder}>
95
+ <SelectValue placeholder={resolvedPlaceholder}>
93
96
  {value !== undefined &&
94
97
  (() => {
95
98
  const selectedOption = options.find((opt) => String(opt.value) === String(value));