@greatapps/common 1.1.678 → 1.1.679

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.
@@ -28,6 +28,7 @@ import {
28
28
  } from "../constants";
29
29
  import { useTimezones } from "../../../modules/accounts/hooks/useTimezones";
30
30
  import { useCurrencies } from "../../../modules/accounts/hooks/useCurrencies";
31
+ import { getCurrencyForGateway } from "../../../utils/format/currency";
31
32
  function PreferencesSection({ onClose }) {
32
33
  const translate = useTranslations();
33
34
  const translateLanguage = useTranslations("common.languages");
@@ -73,7 +74,9 @@ function PreferencesSection({ onClose }) {
73
74
  language: LANGUAGE_OPTIONS.find(
74
75
  (o) => o.apiValue === user.language?.toLowerCase()
75
76
  )?.value ?? "pt-br",
76
- currency: account.currency,
77
+ // A moeda é derivada do gateway da conta (read-only para o usuário) —
78
+ // espelha o que useCurrencyFormatter usa no display de preços.
79
+ currency: getCurrencyForGateway(account.gateway).toUpperCase(),
77
80
  timezone: account.timezone,
78
81
  timeFormat: "24h",
79
82
  receive_sms: user.receive_sms ?? false,
@@ -115,7 +118,7 @@ function PreferencesSection({ onClose }) {
115
118
  const onSubmit = (data) => {
116
119
  const accountFields = {
117
120
  ...isOwner && dirtyFields.companyName && { name: data.companyName },
118
- ...canEditPreferences && dirtyFields.currency && { currency: data.currency },
121
+ // currency é read-only (derivada do gateway) — não enviada no update.
119
122
  ...canEditPreferences && dirtyFields.timezone && { timezone: data.timezone }
120
123
  };
121
124
  const globalFieldChanged = Object.keys(accountFields).length > 0;
@@ -175,9 +178,9 @@ function PreferencesSection({ onClose }) {
175
178
  containerClassName: "w-full",
176
179
  options: currencies,
177
180
  value: watch("currency"),
178
- onChange: (value) => setValue("currency", value, { shouldDirty: true }),
179
- icon: !canEditPreferences ? /* @__PURE__ */ jsx(IconLock, { className: "size-4 text-gray-400" }) : void 0,
180
- disabled: !canEditPreferences
181
+ onChange: () => void 0,
182
+ icon: /* @__PURE__ */ jsx(IconLock, { className: "size-4 text-gray-400" }),
183
+ disabled: true
181
184
  }
182
185
  )
183
186
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/account/sections/PreferencesSection.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslations, useLocale } from 'next-intl';\nimport { useRouter } from 'next/navigation';\nimport { toast } from 'sonner';\nimport { IconLock } from '@tabler/icons-react';\nimport { cn } from '../../../infra/utils/clsx';\nimport type { Locale } from '../../../i18n/config';\nimport { setLocaleCookie } from '../../../utils/intl/locale-cookie';\nimport { Button } from '../../ui/buttons/Button';\nimport { Separator } from '../../ui/data-display/Separator';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { FormField } from '../../ui/form/FormField';\nimport { ComboboxField } from '../../ui/form/ComboboxField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport ConfirmGlobalPreferencesModal from '../ConfirmGlobalPreferencesModal';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useWhitelabel } from '../../../providers/whitelabel.provider';\nimport { UserProfile } from '../../../modules/users/schema';\nimport type { UpdateAccountRequest } from '../../../modules/accounts/types';\nimport {\n useUpdateAccountUser,\n useUpdateAccount,\n} from '../../../modules/accounts/hooks/useAccountManagement';\nimport {\n LANGUAGE_OPTIONS,\n TIME_FORMAT_OPTIONS,\n} from '../constants';\nimport { useTimezones } from '../../../modules/accounts/hooks/useTimezones';\nimport { useCurrencies } from '../../../modules/accounts/hooks/useCurrencies';\n\ninterface PreferencesFormValues {\n companyName: string;\n language: string;\n currency: string;\n timezone: string;\n timeFormat: string;\n receive_sms: boolean;\n receive_email: boolean;\n}\n\ninterface PreferencesSectionProps {\n onClose: () => void;\n}\n\nexport function PreferencesSection({ onClose }: PreferencesSectionProps) {\n // Padrão raiz: um único `translate` chamado pela chave inteira (`common.<modulo>.<chave>`).\n const translate = useTranslations();\n // Exceção: chave dinâmica (option.value) numa lista → namespace escopado.\n const translateLanguage = useTranslations('common.languages');\n const currentLocale = useLocale();\n const router = useRouter();\n const { data: timezones = [] } = useTimezones();\n const { data: currencies = [] } = useCurrencies();\n const { user, account } = useAuth();\n const { whitelabel } = useWhitelabel();\n const languageOptions = LANGUAGE_OPTIONS.map((option) => ({\n ...option,\n label: translateLanguage(option.value as Locale),\n displayValue: translateLanguage(option.value as Locale),\n }));\n const timeFormatOptions = TIME_FORMAT_OPTIONS.map((option) => ({\n ...option,\n label: translate(\n `common.preferences.timeFormatOptions.${option.value === '24h' ? 'h24' : 'h12'}`,\n ),\n }));\n const isOwner = user?.profile === UserProfile.owner;\n const canEditPreferences = isOwner || user?.profile === UserProfile.admin;\n const updateAccountUser = useUpdateAccountUser();\n const updateAccount = useUpdateAccount();\n const [confirmGlobalOpen, setConfirmGlobalOpen] = useState(false);\n const [pendingSubmit, setPendingSubmit] = useState<{\n data: PreferencesFormValues;\n accountFields: UpdateAccountRequest;\n } | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n\n const { watch, setValue, handleSubmit, reset, formState: { dirtyFields } } =\n useForm<PreferencesFormValues>({\n defaultValues: {\n companyName: '',\n language: '',\n currency: '',\n timezone: '',\n timeFormat: '24h',\n receive_sms: false,\n receive_email: false,\n },\n });\n\n useEffect(() => {\n if (!user || !account) return;\n reset({\n companyName: account.name ?? '',\n language: LANGUAGE_OPTIONS.find(\n (o) => o.apiValue === user.language?.toLowerCase()\n )?.value ?? 'pt-br',\n currency: account.currency,\n timezone: account.timezone,\n timeFormat: '24h',\n receive_sms: user.receive_sms ?? false,\n receive_email: user.receive_email ?? false,\n });\n }, [user, account, reset]);\n\n const doSave = async (data: PreferencesFormValues, accountFields: UpdateAccountRequest) => {\n setIsSaving(true);\n try {\n const apiLanguage = LANGUAGE_OPTIONS.find((o) => o.value === data.language)?.apiValue ?? data.language.toLowerCase();\n await updateAccountUser.mutateAsync({\n language: apiLanguage,\n receive_sms: data.receive_sms,\n receive_email: data.receive_email,\n });\n\n if (canEditPreferences && Object.keys(accountFields).length > 0) {\n await updateAccount.mutateAsync(accountFields);\n }\n\n reset(data);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('common.preferences.savedSuccess')} toastId={toastId} />\n ));\n onClose();\n if (data.language !== currentLocale) {\n setLocaleCookie(data.language, whitelabel?.domain);\n router.refresh();\n }\n } catch (error) {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={error instanceof Error ? error.message : translate('common.preferences.saveError')}\n toastId={toastId}\n />\n ));\n } finally {\n setIsSaving(false);\n }\n };\n\n const onSubmit = (data: PreferencesFormValues) => {\n const accountFields: UpdateAccountRequest = {\n ...(isOwner && dirtyFields.companyName && { name: data.companyName }),\n ...(canEditPreferences && dirtyFields.currency && { currency: data.currency }),\n ...(canEditPreferences && dirtyFields.timezone && { timezone: data.timezone }),\n };\n const globalFieldChanged = Object.keys(accountFields).length > 0;\n\n if (globalFieldChanged) {\n setPendingSubmit({ data, accountFields });\n setConfirmGlobalOpen(true);\n } else {\n doSave(data, accountFields);\n }\n };\n\n const handleConfirmGlobal = () => {\n setConfirmGlobalOpen(false);\n if (pendingSubmit) {\n doSave(pendingSubmit.data, pendingSubmit.accountFields);\n setPendingSubmit(null);\n }\n };\n\n const handleCancelGlobal = () => {\n setConfirmGlobalOpen(false);\n setPendingSubmit(null);\n };\n\n return (\n <>\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col h-full\">\n <div className=\"overflow-y-auto overscroll-contain px-4 pb-4 lg:p-5 flex flex-col gap-5 lg:gap-6 flex-1\">\n <span className=\"paragraph-medium-semibold text-gray-950\">{translate('common.preferences.title')}</span>\n <div className=\"flex flex-col gap-8\">\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <FormField\n label={translate('common.preferences.companyName')}\n placeholder={translate('common.preferences.companyNamePlaceholder')}\n classnameContainer={cn('w-full', !isOwner && 'cursor-not-allowed opacity-50')}\n value={watch('companyName')}\n onChange={(e) => setValue('companyName', e.target.value, { shouldDirty: true })}\n disabled={!isOwner}\n />\n </div>\n\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.language')}\n placeholder={translate('common.preferences.languagePlaceholder')}\n searchPlaceholder={translate('common.preferences.languageSearch')}\n containerClassName=\"w-full\"\n options={languageOptions}\n value={watch('language')}\n onChange={(value) => setValue('language', value, { shouldDirty: true })}\n />\n <ComboboxField\n label={translate('common.preferences.currency')}\n placeholder={translate('common.preferences.currencyPlaceholder')}\n searchPlaceholder={translate('common.preferences.currencySearch')}\n containerClassName=\"w-full\"\n options={currencies}\n value={watch('currency')}\n onChange={(value) => setValue('currency', value, { shouldDirty: true })}\n icon={!canEditPreferences ? <IconLock className=\"size-4 text-gray-400\" /> : undefined}\n disabled={!canEditPreferences}\n />\n </div>\n\n <Separator />\n\n <div className=\"flex flex-col gap-4\">\n <span className=\"paragraph-medium-semibold text-gray-950\">{translate('common.preferences.timeDisplay')}</span>\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.timezone')}\n placeholder={translate('common.preferences.timezonePlaceholder')}\n searchPlaceholder={translate('common.preferences.timezoneSearch')}\n containerClassName=\"w-full\"\n options={timezones}\n value={watch('timezone')}\n onChange={(value) => setValue('timezone', value, { shouldDirty: true })}\n icon={!canEditPreferences ? <IconLock className=\"size-4 text-gray-400\" /> : undefined}\n disabled={!canEditPreferences}\n />\n <SelectField\n label={translate('common.preferences.timeFormat')}\n placeholder={translate('common.preferences.timeFormatPlaceholder')}\n className=\"h-10!\"\n containerClassName=\"w-full\"\n options={timeFormatOptions}\n value={watch('timeFormat')}\n onChange={(value) =>\n setValue('timeFormat', value as string, { shouldDirty: true })\n }\n icon={<IconLock className=\"size-4 text-gray-400\" />}\n disabled\n />\n </div>\n </div>\n\n </div>\n </div>\n\n <div className=\"fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-5 border-t border-gray-200 justify-between bg-white z-10\">\n <Button\n variant=\"secondary\"\n className=\"h-10!\"\n type=\"button\"\n disabled={isSaving}\n onClick={() => {\n reset();\n onClose();\n }}\n >\n {translate('common.actions.cancel')}\n </Button>\n <Button className=\"h-10!\" type=\"submit\" disabled={isSaving}>\n {isSaving ? translate('common.actions.saving') : translate('common.actions.saveChanges')}\n </Button>\n </div>\n </form>\n\n <ConfirmGlobalPreferencesModal\n open={confirmGlobalOpen}\n onConfirm={handleConfirmGlobal}\n onCancel={handleCancelGlobal}\n />\n </>\n );\n}\n"],"mappings":";AA4HQ,SAkDJ,UAlDI,KAkEI,YAlEJ;AA1HR,SAAS,UAAU,iBAAiB;AACpC,SAAS,eAAe;AACxB,SAAS,iBAAiB,iBAAiB;AAC3C,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,UAAU;AAEnB,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AACtB,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,OAAO,mCAAmC;AAC1C,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAE5B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAgBvB,SAAS,mBAAmB,EAAE,QAAQ,GAA4B;AAEvE,QAAM,YAAY,gBAAgB;AAElC,QAAM,oBAAoB,gBAAgB,kBAAkB;AAC5D,QAAM,gBAAgB,UAAU;AAChC,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,MAAM,YAAY,CAAC,EAAE,IAAI,aAAa;AAC9C,QAAM,EAAE,MAAM,aAAa,CAAC,EAAE,IAAI,cAAc;AAChD,QAAM,EAAE,MAAM,QAAQ,IAAI,QAAQ;AAClC,QAAM,EAAE,WAAW,IAAI,cAAc;AACrC,QAAM,kBAAkB,iBAAiB,IAAI,CAAC,YAAY;AAAA,IACxD,GAAG;AAAA,IACH,OAAO,kBAAkB,OAAO,KAAe;AAAA,IAC/C,cAAc,kBAAkB,OAAO,KAAe;AAAA,EACxD,EAAE;AACF,QAAM,oBAAoB,oBAAoB,IAAI,CAAC,YAAY;AAAA,IAC7D,GAAG;AAAA,IACH,OAAO;AAAA,MACL,wCAAwC,OAAO,UAAU,QAAQ,QAAQ,KAAK;AAAA,IAChF;AAAA,EACF,EAAE;AACF,QAAM,UAAU,MAAM,YAAY,YAAY;AAC9C,QAAM,qBAAqB,WAAW,MAAM,YAAY,YAAY;AACpE,QAAM,oBAAoB,qBAAqB;AAC/C,QAAM,gBAAgB,iBAAiB;AACvC,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,KAAK;AAChE,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAGhC,IAAI;AACd,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,QAAM,EAAE,OAAO,UAAU,cAAc,OAAO,WAAW,EAAE,YAAY,EAAE,IACvE,QAA+B;AAAA,IAC7B,eAAe;AAAA,MACb,aAAa;AAAA,MACb,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,eAAe;AAAA,IACjB;AAAA,EACF,CAAC;AAEH,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ,CAAC,QAAS;AACvB,UAAM;AAAA,MACJ,aAAa,QAAQ,QAAQ;AAAA,MAC7B,UAAU,iBAAiB;AAAA,QACzB,CAAC,MAAM,EAAE,aAAa,KAAK,UAAU,YAAY;AAAA,MACnD,GAAG,SAAS;AAAA,MACZ,UAAU,QAAQ;AAAA,MAClB,UAAU,QAAQ;AAAA,MAClB,YAAY;AAAA,MACZ,aAAa,KAAK,eAAe;AAAA,MACjC,eAAe,KAAK,iBAAiB;AAAA,IACvC,CAAC;AAAA,EACH,GAAG,CAAC,MAAM,SAAS,KAAK,CAAC;AAEzB,QAAM,SAAS,OAAO,MAA6B,kBAAwC;AACzF,gBAAY,IAAI;AAChB,QAAI;AACF,YAAM,cAAc,iBAAiB,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,QAAQ,GAAG,YAAY,KAAK,SAAS,YAAY;AACnH,YAAM,kBAAkB,YAAY;AAAA,QAClC,UAAU;AAAA,QACV,aAAa,KAAK;AAAA,QAClB,eAAe,KAAK;AAAA,MACtB,CAAC;AAED,UAAI,sBAAsB,OAAO,KAAK,aAAa,EAAE,SAAS,GAAG;AAC/D,cAAM,cAAc,YAAY,aAAa;AAAA,MAC/C;AAEA,YAAM,IAAI;AACV,YAAM,OAAO,CAAC,YACZ,oBAAC,SAAM,SAAQ,WAAU,SAAS,UAAU,iCAAiC,GAAG,SAAkB,CACnG;AACD,cAAQ;AACR,UAAI,KAAK,aAAa,eAAe;AACnC,wBAAgB,KAAK,UAAU,YAAY,MAAM;AACjD,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,OAAO,CAAC,YACZ;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,iBAAiB,QAAQ,MAAM,UAAU,UAAU,8BAA8B;AAAA,UAC1F;AAAA;AAAA,MACF,CACD;AAAA,IACH,UAAE;AACA,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,WAAW,CAAC,SAAgC;AAChD,UAAM,gBAAsC;AAAA,MAC1C,GAAI,WAAW,YAAY,eAAe,EAAE,MAAM,KAAK,YAAY;AAAA,MACnE,GAAI,sBAAsB,YAAY,YAAY,EAAE,UAAU,KAAK,SAAS;AAAA,MAC5E,GAAI,sBAAsB,YAAY,YAAY,EAAE,UAAU,KAAK,SAAS;AAAA,IAC9E;AACA,UAAM,qBAAqB,OAAO,KAAK,aAAa,EAAE,SAAS;AAE/D,QAAI,oBAAoB;AACtB,uBAAiB,EAAE,MAAM,cAAc,CAAC;AACxC,2BAAqB,IAAI;AAAA,IAC3B,OAAO;AACL,aAAO,MAAM,aAAa;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,sBAAsB,MAAM;AAChC,yBAAqB,KAAK;AAC1B,QAAI,eAAe;AACjB,aAAO,cAAc,MAAM,cAAc,aAAa;AACtD,uBAAiB,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAM;AAC/B,yBAAqB,KAAK;AAC1B,qBAAiB,IAAI;AAAA,EACvB;AAEA,SACE,iCACE;AAAA,yBAAC,UAAK,UAAU,aAAa,QAAQ,GAAG,WAAU,wBAChD;AAAA,2BAAC,SAAI,WAAU,2FACb;AAAA,4BAAC,UAAK,WAAU,2CAA2C,oBAAU,0BAA0B,GAAE;AAAA,QACjG,qBAAC,SAAI,WAAU,uBACb;AAAA,8BAAC,SAAI,WAAU,gDACb;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,UAAU,gCAAgC;AAAA,cACjD,aAAa,UAAU,2CAA2C;AAAA,cAClE,oBAAoB,GAAG,UAAU,CAAC,WAAW,+BAA+B;AAAA,cAC5E,OAAO,MAAM,aAAa;AAAA,cAC1B,UAAU,CAAC,MAAM,SAAS,eAAe,EAAE,OAAO,OAAO,EAAE,aAAa,KAAK,CAAC;AAAA,cAC9E,UAAU,CAAC;AAAA;AAAA,UACb,GACF;AAAA,UAEA,qBAAC,SAAI,WAAU,gDACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,UAAU,6BAA6B;AAAA,gBAC9C,aAAa,UAAU,wCAAwC;AAAA,gBAC/D,mBAAmB,UAAU,mCAAmC;AAAA,gBAChE,oBAAmB;AAAA,gBACnB,SAAS;AAAA,gBACT,OAAO,MAAM,UAAU;AAAA,gBACvB,UAAU,CAAC,UAAU,SAAS,YAAY,OAAO,EAAE,aAAa,KAAK,CAAC;AAAA;AAAA,YACxE;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,UAAU,6BAA6B;AAAA,gBAC9C,aAAa,UAAU,wCAAwC;AAAA,gBAC/D,mBAAmB,UAAU,mCAAmC;AAAA,gBAChE,oBAAmB;AAAA,gBACnB,SAAS;AAAA,gBACT,OAAO,MAAM,UAAU;AAAA,gBACvB,UAAU,CAAC,UAAU,SAAS,YAAY,OAAO,EAAE,aAAa,KAAK,CAAC;AAAA,gBACtE,MAAM,CAAC,qBAAqB,oBAAC,YAAS,WAAU,wBAAuB,IAAK;AAAA,gBAC5E,UAAU,CAAC;AAAA;AAAA,YACb;AAAA,aACF;AAAA,UAEA,oBAAC,aAAU;AAAA,UAEX,qBAAC,SAAI,WAAU,uBACb;AAAA,gCAAC,UAAK,WAAU,2CAA2C,oBAAU,gCAAgC,GAAE;AAAA,YACvG,qBAAC,SAAI,WAAU,gDACb;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO,UAAU,6BAA6B;AAAA,kBAC9C,aAAa,UAAU,wCAAwC;AAAA,kBAC/D,mBAAmB,UAAU,mCAAmC;AAAA,kBAChE,oBAAmB;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO,MAAM,UAAU;AAAA,kBACvB,UAAU,CAAC,UAAU,SAAS,YAAY,OAAO,EAAE,aAAa,KAAK,CAAC;AAAA,kBACtE,MAAM,CAAC,qBAAqB,oBAAC,YAAS,WAAU,wBAAuB,IAAK;AAAA,kBAC5E,UAAU,CAAC;AAAA;AAAA,cACb;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO,UAAU,+BAA+B;AAAA,kBAChD,aAAa,UAAU,0CAA0C;AAAA,kBACjE,WAAU;AAAA,kBACV,oBAAmB;AAAA,kBACnB,SAAS;AAAA,kBACT,OAAO,MAAM,YAAY;AAAA,kBACzB,UAAU,CAAC,UACT,SAAS,cAAc,OAAiB,EAAE,aAAa,KAAK,CAAC;AAAA,kBAE/D,MAAM,oBAAC,YAAS,WAAU,wBAAuB;AAAA,kBACjD,UAAQ;AAAA;AAAA,cACV;AAAA,eACF;AAAA,aACF;AAAA,WAEF;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,wIACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,WAAU;AAAA,YACV,MAAK;AAAA,YACL,UAAU;AAAA,YACV,SAAS,MAAM;AACb,oBAAM;AACN,sBAAQ;AAAA,YACV;AAAA,YAEC,oBAAU,uBAAuB;AAAA;AAAA,QACpC;AAAA,QACA,oBAAC,UAAO,WAAU,SAAQ,MAAK,UAAS,UAAU,UAC/C,qBAAW,UAAU,uBAAuB,IAAI,UAAU,4BAA4B,GACzF;AAAA,SACF;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,WAAW;AAAA,QACX,UAAU;AAAA;AAAA,IACZ;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/account/sections/PreferencesSection.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslations, useLocale } from 'next-intl';\nimport { useRouter } from 'next/navigation';\nimport { toast } from 'sonner';\nimport { IconLock } from '@tabler/icons-react';\nimport { cn } from '../../../infra/utils/clsx';\nimport type { Locale } from '../../../i18n/config';\nimport { setLocaleCookie } from '../../../utils/intl/locale-cookie';\nimport { Button } from '../../ui/buttons/Button';\nimport { Separator } from '../../ui/data-display/Separator';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { FormField } from '../../ui/form/FormField';\nimport { ComboboxField } from '../../ui/form/ComboboxField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport ConfirmGlobalPreferencesModal from '../ConfirmGlobalPreferencesModal';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useWhitelabel } from '../../../providers/whitelabel.provider';\nimport { UserProfile } from '../../../modules/users/schema';\nimport type { UpdateAccountRequest } from '../../../modules/accounts/types';\nimport {\n useUpdateAccountUser,\n useUpdateAccount,\n} from '../../../modules/accounts/hooks/useAccountManagement';\nimport {\n LANGUAGE_OPTIONS,\n TIME_FORMAT_OPTIONS,\n} from '../constants';\nimport { useTimezones } from '../../../modules/accounts/hooks/useTimezones';\nimport { useCurrencies } from '../../../modules/accounts/hooks/useCurrencies';\nimport { getCurrencyForGateway } from '../../../utils/format/currency';\n\ninterface PreferencesFormValues {\n companyName: string;\n language: string;\n currency: string;\n timezone: string;\n timeFormat: string;\n receive_sms: boolean;\n receive_email: boolean;\n}\n\ninterface PreferencesSectionProps {\n onClose: () => void;\n}\n\nexport function PreferencesSection({ onClose }: PreferencesSectionProps) {\n // Padrão raiz: um único `translate` chamado pela chave inteira (`common.<modulo>.<chave>`).\n const translate = useTranslations();\n // Exceção: chave dinâmica (option.value) numa lista → namespace escopado.\n const translateLanguage = useTranslations('common.languages');\n const currentLocale = useLocale();\n const router = useRouter();\n const { data: timezones = [] } = useTimezones();\n const { data: currencies = [] } = useCurrencies();\n const { user, account } = useAuth();\n const { whitelabel } = useWhitelabel();\n const languageOptions = LANGUAGE_OPTIONS.map((option) => ({\n ...option,\n label: translateLanguage(option.value as Locale),\n displayValue: translateLanguage(option.value as Locale),\n }));\n const timeFormatOptions = TIME_FORMAT_OPTIONS.map((option) => ({\n ...option,\n label: translate(\n `common.preferences.timeFormatOptions.${option.value === '24h' ? 'h24' : 'h12'}`,\n ),\n }));\n const isOwner = user?.profile === UserProfile.owner;\n const canEditPreferences = isOwner || user?.profile === UserProfile.admin;\n const updateAccountUser = useUpdateAccountUser();\n const updateAccount = useUpdateAccount();\n const [confirmGlobalOpen, setConfirmGlobalOpen] = useState(false);\n const [pendingSubmit, setPendingSubmit] = useState<{\n data: PreferencesFormValues;\n accountFields: UpdateAccountRequest;\n } | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n\n const { watch, setValue, handleSubmit, reset, formState: { dirtyFields } } =\n useForm<PreferencesFormValues>({\n defaultValues: {\n companyName: '',\n language: '',\n currency: '',\n timezone: '',\n timeFormat: '24h',\n receive_sms: false,\n receive_email: false,\n },\n });\n\n useEffect(() => {\n if (!user || !account) return;\n reset({\n companyName: account.name ?? '',\n language: LANGUAGE_OPTIONS.find(\n (o) => o.apiValue === user.language?.toLowerCase()\n )?.value ?? 'pt-br',\n // A moeda é derivada do gateway da conta (read-only para o usuário) —\n // espelha o que useCurrencyFormatter usa no display de preços.\n currency: getCurrencyForGateway(account.gateway).toUpperCase(),\n timezone: account.timezone,\n timeFormat: '24h',\n receive_sms: user.receive_sms ?? false,\n receive_email: user.receive_email ?? false,\n });\n }, [user, account, reset]);\n\n const doSave = async (data: PreferencesFormValues, accountFields: UpdateAccountRequest) => {\n setIsSaving(true);\n try {\n const apiLanguage = LANGUAGE_OPTIONS.find((o) => o.value === data.language)?.apiValue ?? data.language.toLowerCase();\n await updateAccountUser.mutateAsync({\n language: apiLanguage,\n receive_sms: data.receive_sms,\n receive_email: data.receive_email,\n });\n\n if (canEditPreferences && Object.keys(accountFields).length > 0) {\n await updateAccount.mutateAsync(accountFields);\n }\n\n reset(data);\n toast.custom((toastId) => (\n <Toast variant=\"success\" message={translate('common.preferences.savedSuccess')} toastId={toastId} />\n ));\n onClose();\n if (data.language !== currentLocale) {\n setLocaleCookie(data.language, whitelabel?.domain);\n router.refresh();\n }\n } catch (error) {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={error instanceof Error ? error.message : translate('common.preferences.saveError')}\n toastId={toastId}\n />\n ));\n } finally {\n setIsSaving(false);\n }\n };\n\n const onSubmit = (data: PreferencesFormValues) => {\n const accountFields: UpdateAccountRequest = {\n ...(isOwner && dirtyFields.companyName && { name: data.companyName }),\n // currency é read-only (derivada do gateway) — não enviada no update.\n ...(canEditPreferences && dirtyFields.timezone && { timezone: data.timezone }),\n };\n const globalFieldChanged = Object.keys(accountFields).length > 0;\n\n if (globalFieldChanged) {\n setPendingSubmit({ data, accountFields });\n setConfirmGlobalOpen(true);\n } else {\n doSave(data, accountFields);\n }\n };\n\n const handleConfirmGlobal = () => {\n setConfirmGlobalOpen(false);\n if (pendingSubmit) {\n doSave(pendingSubmit.data, pendingSubmit.accountFields);\n setPendingSubmit(null);\n }\n };\n\n const handleCancelGlobal = () => {\n setConfirmGlobalOpen(false);\n setPendingSubmit(null);\n };\n\n return (\n <>\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col h-full\">\n <div className=\"overflow-y-auto overscroll-contain px-4 pb-4 lg:p-5 flex flex-col gap-5 lg:gap-6 flex-1\">\n <span className=\"paragraph-medium-semibold text-gray-950\">{translate('common.preferences.title')}</span>\n <div className=\"flex flex-col gap-8\">\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <FormField\n label={translate('common.preferences.companyName')}\n placeholder={translate('common.preferences.companyNamePlaceholder')}\n classnameContainer={cn('w-full', !isOwner && 'cursor-not-allowed opacity-50')}\n value={watch('companyName')}\n onChange={(e) => setValue('companyName', e.target.value, { shouldDirty: true })}\n disabled={!isOwner}\n />\n </div>\n\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.language')}\n placeholder={translate('common.preferences.languagePlaceholder')}\n searchPlaceholder={translate('common.preferences.languageSearch')}\n containerClassName=\"w-full\"\n options={languageOptions}\n value={watch('language')}\n onChange={(value) => setValue('language', value, { shouldDirty: true })}\n />\n <ComboboxField\n label={translate('common.preferences.currency')}\n placeholder={translate('common.preferences.currencyPlaceholder')}\n searchPlaceholder={translate('common.preferences.currencySearch')}\n containerClassName=\"w-full\"\n options={currencies}\n value={watch('currency')}\n onChange={() => undefined}\n icon={<IconLock className=\"size-4 text-gray-400\" />}\n disabled\n />\n </div>\n\n <Separator />\n\n <div className=\"flex flex-col gap-4\">\n <span className=\"paragraph-medium-semibold text-gray-950\">{translate('common.preferences.timeDisplay')}</span>\n <div className=\"flex flex-col lg:flex-row items-center gap-3\">\n <ComboboxField\n label={translate('common.preferences.timezone')}\n placeholder={translate('common.preferences.timezonePlaceholder')}\n searchPlaceholder={translate('common.preferences.timezoneSearch')}\n containerClassName=\"w-full\"\n options={timezones}\n value={watch('timezone')}\n onChange={(value) => setValue('timezone', value, { shouldDirty: true })}\n icon={!canEditPreferences ? <IconLock className=\"size-4 text-gray-400\" /> : undefined}\n disabled={!canEditPreferences}\n />\n <SelectField\n label={translate('common.preferences.timeFormat')}\n placeholder={translate('common.preferences.timeFormatPlaceholder')}\n className=\"h-10!\"\n containerClassName=\"w-full\"\n options={timeFormatOptions}\n value={watch('timeFormat')}\n onChange={(value) =>\n setValue('timeFormat', value as string, { shouldDirty: true })\n }\n icon={<IconLock className=\"size-4 text-gray-400\" />}\n disabled\n />\n </div>\n </div>\n\n </div>\n </div>\n\n <div className=\"fixed bottom-0 left-0 right-0 lg:absolute flex items-center h-20 px-4 lg:px-5 border-t border-gray-200 justify-between bg-white z-10\">\n <Button\n variant=\"secondary\"\n className=\"h-10!\"\n type=\"button\"\n disabled={isSaving}\n onClick={() => {\n reset();\n onClose();\n }}\n >\n {translate('common.actions.cancel')}\n </Button>\n <Button className=\"h-10!\" type=\"submit\" disabled={isSaving}>\n {isSaving ? translate('common.actions.saving') : translate('common.actions.saveChanges')}\n </Button>\n </div>\n </form>\n\n <ConfirmGlobalPreferencesModal\n open={confirmGlobalOpen}\n onConfirm={handleConfirmGlobal}\n onCancel={handleCancelGlobal}\n />\n </>\n );\n}\n"],"mappings":"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","names":[]}
@@ -158,7 +158,7 @@ function BuyCreditsModal() {
158
158
  ] }),
159
159
  displayValue: translate("common.credits.buy.creditsOptionDisplayValue", { credits: opt.credits.toLocaleString("pt-BR") })
160
160
  }));
161
- }, []);
161
+ }, [translate]);
162
162
  const onSubmit = useCallback(
163
163
  async (data) => {
164
164
  if (!subscriptionId) return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/modals/BuyCreditsModal.tsx"],"sourcesContent":["'use client';\n\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '../ui/overlay/Dialog';\nimport { Button } from '../ui/buttons/Button';\nimport { Separator } from '../ui/data-display/Separator';\nimport { Toast } from '../ui/feedback/Toast';\nimport { SelectField } from '../ui/form/SelectField';\nimport { Skeleton } from '../ui/feedback/Skeleton';\nimport { PaymentInfoCard } from '../ui/data-display/PaymentInfoCard';\nimport { ADDON_IDS } from '../../modules/subscriptions/constants/addons.constants';\nimport {\n AI_CREDIT_OPTIONS,\n AI_CREDIT_VALUES,\n} from '../../modules/subscriptions/constants/addons.constants';\nimport { useBuyCreditsModal } from '../../store/useBuyCreditsModal';\nimport { useModalManager } from '../../store/useModalManager';\nimport { useCards } from '../../modules/cards/hooks/cards.hook';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { useCalculateSubscription } from '../../modules/subscriptions/hooks/calculate-subscription.hook';\nimport { useUpdateSubscriptionPlan } from '../../modules/subscriptions/hooks/update-subscription-plan.hook';\nimport { getPriceFromCalculatedData } from '../../modules/subscriptions/utils/periodicity';\nimport { useIsDefaultWhitelabel, useExternalContracting } from '../../providers/whitelabel.provider';\nimport { IconInfoCircle, IconX, IconLoader2, IconSparkles } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport z from 'zod';\nimport { toast } from 'sonner';\n\nconst buyCreditSchema = z.object({\n creditOption: z.number().refine((val) => AI_CREDIT_VALUES.has(val), {\n message: 'Selecione uma opção de créditos',\n }),\n selectedCardId: z.string().min(1, 'Selecione um cartão'),\n});\n\ntype BuyCreditFormData = z.infer<typeof buyCreditSchema>;\n\nexport default function BuyCreditsModal() {\n const translate = useTranslations();\n const { open, closeModal } = useBuyCreditsModal();\n const { openModal } = useModalManager();\n const isDefaultWl = useIsDefaultWhitelabel();\n const { isExternalContracting, redirectToExternal } = useExternalContracting();\n const { data: cardsData } = useCards();\n const { data: subscriptionData } = useActiveSubscription();\n const subscription = subscriptionData?.data?.[0] ?? null;\n const defaultCardId =\n cardsData?.data?.find((card) => card.is_default)?.id?.toString() ??\n cardsData?.data?.[0]?.id?.toString() ??\n '';\n\n const {\n mutate: calculate,\n isPending: isCalculatePending,\n data: calculateData,\n } = useCalculateSubscription();\n const updatePlanMutation = useUpdateSubscriptionPlan();\n\n const aiItem = useMemo(\n () => subscription?.items?.find((item) => item.id_addon === ADDON_IDS.AI),\n [subscription]\n );\n\n const currentCredits = aiItem?.quantity ?? 0;\n const subscriptionId = subscription?.id;\n const planId = subscription?.id_plan;\n\n const form = useForm<BuyCreditFormData>({\n resolver: zodResolver(buyCreditSchema),\n mode: 'onChange',\n defaultValues: {\n creditOption: currentCredits,\n selectedCardId: defaultCardId,\n },\n });\n\n const hasInitialized = useRef(false);\n\n useEffect(() => {\n if (!open) {\n hasInitialized.current = false;\n return;\n }\n\n if (hasInitialized.current || !subscription) return;\n hasInitialized.current = true;\n\n form.reset({\n creditOption: currentCredits,\n selectedCardId: defaultCardId,\n });\n\n calculate({\n id_product: 1,\n ...(planId != null && { id_plan: planId }),\n ...(subscriptionId != null && { id_subscription: subscriptionId }),\n plan_extras: [\n { id_addon: ADDON_IDS.AI, quantity: currentCredits },\n ],\n });\n }, [\n open,\n subscription,\n currentCredits,\n defaultCardId,\n form,\n calculate,\n planId,\n subscriptionId,\n ]);\n\n // Sync selectedCardId when cards load after modal opened\n useEffect(() => {\n if (open && defaultCardId && !form.getValues('selectedCardId')) {\n form.setValue('selectedCardId', defaultCardId, { shouldValidate: true });\n }\n }, [open, defaultCardId, form]);\n\n const creditOption = form.watch('creditOption');\n const selectedCardId = form.watch('selectedCardId');\n const { isDirty, isSubmitting, isValid } = form.formState;\n\n\n const handleCreditOptionChange = useCallback(\n (value: string | number | boolean) => {\n const credits = Number(value);\n\n form.setValue('creditOption', credits, {\n shouldValidate: true,\n shouldDirty: true,\n });\n\n const option = AI_CREDIT_OPTIONS.find((opt) => opt.credits === credits);\n if (!option) return;\n\n calculate({\n id_product: 1,\n ...(planId != null && { id_plan: planId }),\n ...(subscriptionId != null && { id_subscription: subscriptionId }),\n plan_extras: [\n { id_addon: ADDON_IDS.AI, quantity: option.credits, currency: 'BRL' },\n ],\n });\n },\n [form, subscriptionId, planId, calculate]\n );\n\n const calculatedData = calculateData?.data ?? null;\n\n const isIncreasingCredits = creditOption > currentCredits;\n const hasChanged = creditOption !== currentCredits;\n\n const immediateChargeTotal = calculatedData?.target?.immediate_charge?.total ?? null;\n\n const totalPrice = useMemo(() => {\n if (!calculatedData) return null;\n return getPriceFromCalculatedData(calculatedData, subscription?.periodicity);\n }, [calculatedData, subscription]);\n\n const formatPrice = useCallback((price: number) => {\n return price.toLocaleString('pt-BR', {\n style: 'currency',\n currency: 'BRL',\n });\n }, []);\n\n const isConfirmDisabled = !isDirty || !isValid || isCalculatePending || isSubmitting;\n\n const handleClose = useCallback(() => {\n form.reset();\n closeModal();\n }, [form, closeModal]);\n\n const handleOpenAddCardModal = useCallback(() => {\n openModal('addCardModal');\n }, [openModal]);\n\n const selectOptions = useMemo(() => {\n return AI_CREDIT_OPTIONS.map((opt) => ({\n value: opt.credits,\n label: (\n <div className=\"flex items-center justify-between gap-2 w-full\">\n <span className=\"paragraph-small-medium text-zinc-950 flex-1\">\n {translate('common.credits.buy.creditsOptionLabel', { credits: opt.credits.toLocaleString('pt-BR') })}\n </span>\n <span className=\"paragraph-small-medium text-zinc-500 opacity-70\">{opt.priceLabel}</span>\n </div>\n ),\n displayValue: translate('common.credits.buy.creditsOptionDisplayValue', { credits: opt.credits.toLocaleString('pt-BR') }),\n }));\n }, []);\n\n const onSubmit = useCallback(\n async (data: BuyCreditFormData) => {\n if (!subscriptionId) return;\n\n const option = AI_CREDIT_OPTIONS.find((opt) => opt.credits === data.creditOption);\n if (!option) return;\n\n await updatePlanMutation.mutateAsync({\n subscriptionId,\n data: {\n plan_extras: [\n {\n id_addon: ADDON_IDS.AI,\n quantity: option.credits,\n },\n ],\n },\n });\n\n toast.custom((t) => (\n <Toast\n variant=\"success\"\n message={translate('common.credits.buy.creditsChangedToast', { credits: data.creditOption.toLocaleString('pt-BR') })}\n toastId={t}\n />\n ));\n form.reset();\n closeModal();\n },\n [subscriptionId, updatePlanMutation, form, closeModal]\n );\n\n const renderPriceDisplay = useCallback(() => {\n if (isCalculatePending) {\n return <Skeleton className=\"h-7 w-24\" />;\n }\n if (totalPrice !== null) {\n return (\n <span className=\"paragraph-xlarge-semibold text-zinc-950\">\n {totalPrice === 0 ? translate('common.credits.buy.defaultPrice') : formatPrice(totalPrice)}\n </span>\n );\n }\n return <span className=\"paragraph-xlarge-semibold text-zinc-400\">--</span>;\n }, [isCalculatePending, totalPrice, formatPrice]);\n\n if (isExternalContracting) {\n return (\n <Dialog open={open} onOpenChange={handleClose}>\n <DialogContent className=\"flex flex-col p-0 gap-0 w-full md:w-[410px] lg:w-[410px]\">\n <DialogHeader className=\"gap-3 text-left p-5\">\n <div className=\"flex items-center justify-center w-10 h-10 bg-zinc-50 rounded-lg\">\n <IconSparkles size={24} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-2\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950\">\n {translate('common.credits.buy.externalTitle')}\n </DialogTitle>\n <span className=\"paragraph-small-regular text-zinc-600\">\n {translate('common.credits.buy.externalDescription')}\n </span>\n </div>\n </DialogHeader>\n <div className=\"flex items-center gap-2 p-5 border-t border-zinc-200\">\n <Button\n className=\"w-fit h-10!\"\n onClick={() => {\n closeModal();\n redirectToExternal('contracting');\n }}\n >\n {translate('common.credits.buy.externalButton')}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n );\n }\n\n return (\n <Dialog open={open} onOpenChange={handleClose}>\n <DialogContent\n showCloseButton={false}\n className=\"flex flex-col p-0 gap-0 max-w-full sm:max-w-full border-0 rounded-t-2xl rounded-b-none h-dvh top-0 bottom-0 left-0 right-0 translate-x-0 translate-y-0 lg:max-w-[433px] lg:h-auto lg:max-h-[90vh] lg:rounded-lg lg:border lg:top-[50%] lg:left-[50%] lg:right-auto lg:bottom-auto lg:translate-x-[-50%] lg:translate-y-[-50%] overflow-hidden lg:overflow-y-auto\"\n >\n <DialogHeader className=\"px-4 py-3 border-b border-zinc-200 shrink-0 relative\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950 text-center\">\n {translate('common.credits.buy.title')}\n </DialogTitle>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 size-8 flex items-center justify-center rounded-lg cursor-pointer hover:bg-zinc-100 transition-colors\"\n >\n <IconX size={18} className=\"text-zinc-400\" />\n </button>\n </DialogHeader>\n\n <form\n onSubmit={form.handleSubmit(onSubmit)}\n className=\"flex flex-col flex-1 lg:flex-none overflow-hidden\"\n >\n <div className=\"flex flex-col gap-6 lg:gap-8 p-4 lg:p-5 flex-1 lg:flex-none overflow-y-auto\">\n <div className=\"flex flex-col gap-4\">\n <SelectField\n options={selectOptions}\n value={creditOption}\n onChange={handleCreditOptionChange}\n className=\"h-12!\"\n />\n\n <PaymentInfoCard\n selectedCardId={selectedCardId}\n onCardSelect={(cardId) =>\n form.setValue('selectedCardId', cardId, { shouldValidate: true })\n }\n onOpenAddCard={handleOpenAddCardModal}\n />\n\n {hasChanged && (\n <div className={`flex items-center gap-3 p-4 rounded-lg ${isDefaultWl ? 'bg-cyan-50' : 'bg-zinc-50'}`}>\n <IconInfoCircle className=\"size-4 text-zinc-950 shrink-0\" />\n <span className=\"paragraph-small-regular text-zinc-950\">\n {isIncreasingCredits\n ? translate('common.credits.buy.paymentOnConfirm')\n : translate('common.credits.buy.nextInvoiceChanges')}\n </span>\n </div>\n )}\n </div>\n\n <Separator className=\"bg-zinc-200 hidden lg:block\" />\n\n <div className=\"hidden lg:flex flex-col gap-2\">\n {immediateChargeTotal !== null && immediateChargeTotal > 0 && (\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('common.credits.buy.immediateCharge')}</span>\n {isCalculatePending ? (\n <Skeleton className=\"h-5 w-20\" />\n ) : (\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {formatPrice(immediateChargeTotal)}\n </span>\n )}\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-small-medium text-zinc-500\">{translate('common.credits.buy.totalPlanValue')}</span>\n {renderPriceDisplay()}\n </div>\n </div>\n\n <Button\n type=\"submit\"\n disabled={isConfirmDisabled}\n className=\"w-full hidden lg:inline-flex\"\n >\n {isSubmitting && <IconLoader2 className=\"size-4 animate-spin\" />}\n {translate('common.credits.buy.confirmAndPay')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-4 p-4 border-t border-zinc-200 shrink-0 lg:hidden\">\n <div className=\"flex flex-col gap-2\">\n {immediateChargeTotal !== null && immediateChargeTotal > 0 && (\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('common.credits.buy.immediateCharge')}</span>\n {isCalculatePending ? (\n <Skeleton className=\"h-5 w-20\" />\n ) : (\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {formatPrice(immediateChargeTotal)}\n </span>\n )}\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-small-medium text-zinc-500\">{translate('common.credits.buy.totalPlanValue')}</span>\n {renderPriceDisplay()}\n </div>\n </div>\n <Button type=\"submit\" disabled={isConfirmDisabled} className=\"w-full\">\n {isSubmitting && <IconLoader2 className=\"size-4 animate-spin\" />}\n {translate('common.credits.buy.confirmAndPay')}\n </Button>\n </div>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/modals/BuyCreditsModal.tsx"],"sourcesContent":["'use client';\n\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '../ui/overlay/Dialog';\nimport { Button } from '../ui/buttons/Button';\nimport { Separator } from '../ui/data-display/Separator';\nimport { Toast } from '../ui/feedback/Toast';\nimport { SelectField } from '../ui/form/SelectField';\nimport { Skeleton } from '../ui/feedback/Skeleton';\nimport { PaymentInfoCard } from '../ui/data-display/PaymentInfoCard';\nimport { ADDON_IDS } from '../../modules/subscriptions/constants/addons.constants';\nimport {\n AI_CREDIT_OPTIONS,\n AI_CREDIT_VALUES,\n} from '../../modules/subscriptions/constants/addons.constants';\nimport { useBuyCreditsModal } from '../../store/useBuyCreditsModal';\nimport { useModalManager } from '../../store/useModalManager';\nimport { useCards } from '../../modules/cards/hooks/cards.hook';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { useCalculateSubscription } from '../../modules/subscriptions/hooks/calculate-subscription.hook';\nimport { useUpdateSubscriptionPlan } from '../../modules/subscriptions/hooks/update-subscription-plan.hook';\nimport { getPriceFromCalculatedData } from '../../modules/subscriptions/utils/periodicity';\nimport { useIsDefaultWhitelabel, useExternalContracting } from '../../providers/whitelabel.provider';\nimport { IconInfoCircle, IconX, IconLoader2, IconSparkles } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport z from 'zod';\nimport { toast } from 'sonner';\n\nconst buyCreditSchema = z.object({\n creditOption: z.number().refine((val) => AI_CREDIT_VALUES.has(val), {\n message: 'Selecione uma opção de créditos',\n }),\n selectedCardId: z.string().min(1, 'Selecione um cartão'),\n});\n\ntype BuyCreditFormData = z.infer<typeof buyCreditSchema>;\n\nexport default function BuyCreditsModal() {\n const translate = useTranslations();\n const { open, closeModal } = useBuyCreditsModal();\n const { openModal } = useModalManager();\n const isDefaultWl = useIsDefaultWhitelabel();\n const { isExternalContracting, redirectToExternal } = useExternalContracting();\n const { data: cardsData } = useCards();\n const { data: subscriptionData } = useActiveSubscription();\n const subscription = subscriptionData?.data?.[0] ?? null;\n const defaultCardId =\n cardsData?.data?.find((card) => card.is_default)?.id?.toString() ??\n cardsData?.data?.[0]?.id?.toString() ??\n '';\n\n const {\n mutate: calculate,\n isPending: isCalculatePending,\n data: calculateData,\n } = useCalculateSubscription();\n const updatePlanMutation = useUpdateSubscriptionPlan();\n\n const aiItem = useMemo(\n () => subscription?.items?.find((item) => item.id_addon === ADDON_IDS.AI),\n [subscription]\n );\n\n const currentCredits = aiItem?.quantity ?? 0;\n const subscriptionId = subscription?.id;\n const planId = subscription?.id_plan;\n\n const form = useForm<BuyCreditFormData>({\n resolver: zodResolver(buyCreditSchema),\n mode: 'onChange',\n defaultValues: {\n creditOption: currentCredits,\n selectedCardId: defaultCardId,\n },\n });\n\n const hasInitialized = useRef(false);\n\n useEffect(() => {\n if (!open) {\n hasInitialized.current = false;\n return;\n }\n\n if (hasInitialized.current || !subscription) return;\n hasInitialized.current = true;\n\n form.reset({\n creditOption: currentCredits,\n selectedCardId: defaultCardId,\n });\n\n calculate({\n id_product: 1,\n ...(planId != null && { id_plan: planId }),\n ...(subscriptionId != null && { id_subscription: subscriptionId }),\n plan_extras: [\n { id_addon: ADDON_IDS.AI, quantity: currentCredits },\n ],\n });\n }, [\n open,\n subscription,\n currentCredits,\n defaultCardId,\n form,\n calculate,\n planId,\n subscriptionId,\n ]);\n\n // Sync selectedCardId when cards load after modal opened\n useEffect(() => {\n if (open && defaultCardId && !form.getValues('selectedCardId')) {\n form.setValue('selectedCardId', defaultCardId, { shouldValidate: true });\n }\n }, [open, defaultCardId, form]);\n\n const creditOption = form.watch('creditOption');\n const selectedCardId = form.watch('selectedCardId');\n const { isDirty, isSubmitting, isValid } = form.formState;\n\n\n const handleCreditOptionChange = useCallback(\n (value: string | number | boolean) => {\n const credits = Number(value);\n\n form.setValue('creditOption', credits, {\n shouldValidate: true,\n shouldDirty: true,\n });\n\n const option = AI_CREDIT_OPTIONS.find((opt) => opt.credits === credits);\n if (!option) return;\n\n calculate({\n id_product: 1,\n ...(planId != null && { id_plan: planId }),\n ...(subscriptionId != null && { id_subscription: subscriptionId }),\n plan_extras: [\n { id_addon: ADDON_IDS.AI, quantity: option.credits, currency: 'BRL' },\n ],\n });\n },\n [form, subscriptionId, planId, calculate]\n );\n\n const calculatedData = calculateData?.data ?? null;\n\n const isIncreasingCredits = creditOption > currentCredits;\n const hasChanged = creditOption !== currentCredits;\n\n const immediateChargeTotal = calculatedData?.target?.immediate_charge?.total ?? null;\n\n const totalPrice = useMemo(() => {\n if (!calculatedData) return null;\n return getPriceFromCalculatedData(calculatedData, subscription?.periodicity);\n }, [calculatedData, subscription]);\n\n const formatPrice = useCallback((price: number) => {\n return price.toLocaleString('pt-BR', {\n style: 'currency',\n currency: 'BRL',\n });\n }, []);\n\n const isConfirmDisabled = !isDirty || !isValid || isCalculatePending || isSubmitting;\n\n const handleClose = useCallback(() => {\n form.reset();\n closeModal();\n }, [form, closeModal]);\n\n const handleOpenAddCardModal = useCallback(() => {\n openModal('addCardModal');\n }, [openModal]);\n\n const selectOptions = useMemo(() => {\n return AI_CREDIT_OPTIONS.map((opt) => ({\n value: opt.credits,\n label: (\n <div className=\"flex items-center justify-between gap-2 w-full\">\n <span className=\"paragraph-small-medium text-zinc-950 flex-1\">\n {translate('common.credits.buy.creditsOptionLabel', { credits: opt.credits.toLocaleString('pt-BR') })}\n </span>\n <span className=\"paragraph-small-medium text-zinc-500 opacity-70\">{opt.priceLabel}</span>\n </div>\n ),\n displayValue: translate('common.credits.buy.creditsOptionDisplayValue', { credits: opt.credits.toLocaleString('pt-BR') }),\n }));\n }, [translate]);\n\n const onSubmit = useCallback(\n async (data: BuyCreditFormData) => {\n if (!subscriptionId) return;\n\n const option = AI_CREDIT_OPTIONS.find((opt) => opt.credits === data.creditOption);\n if (!option) return;\n\n await updatePlanMutation.mutateAsync({\n subscriptionId,\n data: {\n plan_extras: [\n {\n id_addon: ADDON_IDS.AI,\n quantity: option.credits,\n },\n ],\n },\n });\n\n toast.custom((t) => (\n <Toast\n variant=\"success\"\n message={translate('common.credits.buy.creditsChangedToast', { credits: data.creditOption.toLocaleString('pt-BR') })}\n toastId={t}\n />\n ));\n form.reset();\n closeModal();\n },\n [subscriptionId, updatePlanMutation, form, closeModal]\n );\n\n const renderPriceDisplay = useCallback(() => {\n if (isCalculatePending) {\n return <Skeleton className=\"h-7 w-24\" />;\n }\n if (totalPrice !== null) {\n return (\n <span className=\"paragraph-xlarge-semibold text-zinc-950\">\n {totalPrice === 0 ? translate('common.credits.buy.defaultPrice') : formatPrice(totalPrice)}\n </span>\n );\n }\n return <span className=\"paragraph-xlarge-semibold text-zinc-400\">--</span>;\n }, [isCalculatePending, totalPrice, formatPrice]);\n\n if (isExternalContracting) {\n return (\n <Dialog open={open} onOpenChange={handleClose}>\n <DialogContent className=\"flex flex-col p-0 gap-0 w-full md:w-[410px] lg:w-[410px]\">\n <DialogHeader className=\"gap-3 text-left p-5\">\n <div className=\"flex items-center justify-center w-10 h-10 bg-zinc-50 rounded-lg\">\n <IconSparkles size={24} className=\"text-zinc-950\" />\n </div>\n <div className=\"flex flex-col gap-2\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950\">\n {translate('common.credits.buy.externalTitle')}\n </DialogTitle>\n <span className=\"paragraph-small-regular text-zinc-600\">\n {translate('common.credits.buy.externalDescription')}\n </span>\n </div>\n </DialogHeader>\n <div className=\"flex items-center gap-2 p-5 border-t border-zinc-200\">\n <Button\n className=\"w-fit h-10!\"\n onClick={() => {\n closeModal();\n redirectToExternal('contracting');\n }}\n >\n {translate('common.credits.buy.externalButton')}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n );\n }\n\n return (\n <Dialog open={open} onOpenChange={handleClose}>\n <DialogContent\n showCloseButton={false}\n className=\"flex flex-col p-0 gap-0 max-w-full sm:max-w-full border-0 rounded-t-2xl rounded-b-none h-dvh top-0 bottom-0 left-0 right-0 translate-x-0 translate-y-0 lg:max-w-[433px] lg:h-auto lg:max-h-[90vh] lg:rounded-lg lg:border lg:top-[50%] lg:left-[50%] lg:right-auto lg:bottom-auto lg:translate-x-[-50%] lg:translate-y-[-50%] overflow-hidden lg:overflow-y-auto\"\n >\n <DialogHeader className=\"px-4 py-3 border-b border-zinc-200 shrink-0 relative\">\n <DialogTitle className=\"paragraph-medium-semibold text-zinc-950 text-center\">\n {translate('common.credits.buy.title')}\n </DialogTitle>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"absolute right-3 top-1/2 -translate-y-1/2 size-8 flex items-center justify-center rounded-lg cursor-pointer hover:bg-zinc-100 transition-colors\"\n >\n <IconX size={18} className=\"text-zinc-400\" />\n </button>\n </DialogHeader>\n\n <form\n onSubmit={form.handleSubmit(onSubmit)}\n className=\"flex flex-col flex-1 lg:flex-none overflow-hidden\"\n >\n <div className=\"flex flex-col gap-6 lg:gap-8 p-4 lg:p-5 flex-1 lg:flex-none overflow-y-auto\">\n <div className=\"flex flex-col gap-4\">\n <SelectField\n options={selectOptions}\n value={creditOption}\n onChange={handleCreditOptionChange}\n className=\"h-12!\"\n />\n\n <PaymentInfoCard\n selectedCardId={selectedCardId}\n onCardSelect={(cardId) =>\n form.setValue('selectedCardId', cardId, { shouldValidate: true })\n }\n onOpenAddCard={handleOpenAddCardModal}\n />\n\n {hasChanged && (\n <div className={`flex items-center gap-3 p-4 rounded-lg ${isDefaultWl ? 'bg-cyan-50' : 'bg-zinc-50'}`}>\n <IconInfoCircle className=\"size-4 text-zinc-950 shrink-0\" />\n <span className=\"paragraph-small-regular text-zinc-950\">\n {isIncreasingCredits\n ? translate('common.credits.buy.paymentOnConfirm')\n : translate('common.credits.buy.nextInvoiceChanges')}\n </span>\n </div>\n )}\n </div>\n\n <Separator className=\"bg-zinc-200 hidden lg:block\" />\n\n <div className=\"hidden lg:flex flex-col gap-2\">\n {immediateChargeTotal !== null && immediateChargeTotal > 0 && (\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('common.credits.buy.immediateCharge')}</span>\n {isCalculatePending ? (\n <Skeleton className=\"h-5 w-20\" />\n ) : (\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {formatPrice(immediateChargeTotal)}\n </span>\n )}\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-small-medium text-zinc-500\">{translate('common.credits.buy.totalPlanValue')}</span>\n {renderPriceDisplay()}\n </div>\n </div>\n\n <Button\n type=\"submit\"\n disabled={isConfirmDisabled}\n className=\"w-full hidden lg:inline-flex\"\n >\n {isSubmitting && <IconLoader2 className=\"size-4 animate-spin\" />}\n {translate('common.credits.buy.confirmAndPay')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-4 p-4 border-t border-zinc-200 shrink-0 lg:hidden\">\n <div className=\"flex flex-col gap-2\">\n {immediateChargeTotal !== null && immediateChargeTotal > 0 && (\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-xsmall-medium text-zinc-500\">{translate('common.credits.buy.immediateCharge')}</span>\n {isCalculatePending ? (\n <Skeleton className=\"h-5 w-20\" />\n ) : (\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {formatPrice(immediateChargeTotal)}\n </span>\n )}\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-small-medium text-zinc-500\">{translate('common.credits.buy.totalPlanValue')}</span>\n {renderPriceDisplay()}\n </div>\n </div>\n <Button type=\"submit\" disabled={isConfirmDisabled} className=\"w-full\">\n {isSubmitting && <IconLoader2 className=\"size-4 animate-spin\" />}\n {translate('common.credits.buy.confirmAndPay')}\n </Button>\n </div>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/ui/feedback/CircularProgress.tsx"],"sourcesContent":["import { ReactNode } from \"react\";\nimport {\n calculateRadius,\n calculateCircumference,\n calculatePercentage,\n calculateCircularProgressOffset,\n} from \"../../../infra/utils/percentage\";\n\ninterface CircularProgressProps {\n current: number;\n total: number;\n size?: number;\n strokeWidth?: number;\n children?: ReactNode;\n}\n\nexport default function CircularProgress({\n current,\n total,\n size = 62,\n strokeWidth = 3,\n children,\n}: CircularProgressProps) {\n const radius = calculateRadius(size, strokeWidth);\n const circumference = calculateCircumference(radius);\n const percentage = calculatePercentage(current, total);\n const offset = calculateCircularProgressOffset(percentage, circumference);\n\n return (\n <div\n className=\"relative flex items-center justify-center\"\n style={{ width: size, height: size }}\n role=\"progressbar\"\n aria-valuenow={current}\n aria-valuemin={0}\n aria-valuemax={total}\n // i18n-TODO: aria-label hardcoded — CircularProgress has no 'use client' directive and\n // receives no translate prop, so useTranslations() cannot be called here without\n // converting it to a client component. Key to use: common.forms.progressAriaLabel ('Progresso').\n // Either add 'use client' + useTranslations(), or accept a translate prop from the caller.\n aria-label=\"Progresso\"\n >\n <svg width={size} height={size} className=\"absolute\">\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n stroke=\"var(--color-gray-50)\"\n strokeWidth={strokeWidth}\n fill=\"white\"\n />\n\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n stroke=\"var(--color-gray-950)\"\n strokeWidth={strokeWidth}\n fill=\"none\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n strokeLinecap=\"round\"\n transform={`rotate(-90 ${size / 2} ${size / 2})`}\n className=\"transition-all duration-300 ease-in-out\"\n />\n </svg>\n\n <div className=\"relative z-10 flex items-center justify-center w-[50px] h-[50px] rounded-full bg-gray-50 paragraph-small-semibold\">\n {children ?? (\n <>\n <span className=\"text-gray-600\">{current}</span>\n <span className=\"text-gray-500\">/</span>\n <span className=\"text-gray-600\">{total}</span>\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"AA0CM,SA2BI,UA1BF,KADF;AAzCN;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUQ,SAAR,iBAAkC;AAAA,EACvC;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AACF,GAA0B;AACxB,QAAM,SAAS,gBAAgB,MAAM,WAAW;AAChD,QAAM,gBAAgB,uBAAuB,MAAM;AACnD,QAAM,aAAa,oBAAoB,SAAS,KAAK;AACrD,QAAM,SAAS,gCAAgC,YAAY,aAAa;AAExE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,MACnC,MAAK;AAAA,MACL,iBAAe;AAAA,MACf,iBAAe;AAAA,MACf,iBAAe;AAAA,MAKf,cAAW;AAAA,MAEX;AAAA,6BAAC,SAAI,OAAO,MAAM,QAAQ,MAAM,WAAU,YACxC;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,QAAO;AAAA,cACP;AAAA,cACA,MAAK;AAAA;AAAA,UACP;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,QAAO;AAAA,cACP;AAAA,cACA,MAAK;AAAA,cACL,iBAAiB;AAAA,cACjB,kBAAkB;AAAA,cAClB,eAAc;AAAA,cACd,WAAW,cAAc,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,cAC7C,WAAU;AAAA;AAAA,UACZ;AAAA,WACF;AAAA,QAEA,oBAAC,SAAI,WAAU,qHACZ,sBACC,iCACE;AAAA,8BAAC,UAAK,WAAU,iBAAiB,mBAAQ;AAAA,UACzC,oBAAC,UAAK,WAAU,iBAAgB,eAAC;AAAA,UACjC,oBAAC,UAAK,WAAU,iBAAiB,iBAAM;AAAA,WACzC,GAEJ;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/ui/feedback/CircularProgress.tsx"],"sourcesContent":["import { ReactNode } from \"react\";\nimport {\n calculateRadius,\n calculateCircumference,\n calculatePercentage,\n calculateCircularProgressOffset,\n} from \"../../../infra/utils/percentage\";\n\ninterface CircularProgressProps {\n current: number;\n total: number;\n size?: number;\n strokeWidth?: number;\n children?: ReactNode;\n}\n\nexport default function CircularProgress({\n current,\n total,\n size = 62,\n strokeWidth = 3,\n children,\n}: CircularProgressProps) {\n const radius = calculateRadius(size, strokeWidth);\n const circumference = calculateCircumference(radius);\n const percentage = calculatePercentage(current, total);\n const offset = calculateCircularProgressOffset(percentage, circumference);\n\n return (\n <div\n className=\"relative flex items-center justify-center\"\n style={{ width: size, height: size }}\n role=\"progressbar\"\n aria-valuenow={current}\n aria-valuemin={0}\n aria-valuemax={total}\n // i18n-TODO: aria-label hardcoded — CircularProgress has no 'use client' directive and\n // receives no translate prop, so useTranslations() cannot be called here without\n // converting it to a client component. Key to use: common.forms.circularProgress.ariaLabel ('Progresso').\n // Either add 'use client' + useTranslations(), or accept a translate prop from the caller.\n aria-label=\"Progresso\"\n >\n <svg width={size} height={size} className=\"absolute\">\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n stroke=\"var(--color-gray-50)\"\n strokeWidth={strokeWidth}\n fill=\"white\"\n />\n\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n stroke=\"var(--color-gray-950)\"\n strokeWidth={strokeWidth}\n fill=\"none\"\n strokeDasharray={circumference}\n strokeDashoffset={offset}\n strokeLinecap=\"round\"\n transform={`rotate(-90 ${size / 2} ${size / 2})`}\n className=\"transition-all duration-300 ease-in-out\"\n />\n </svg>\n\n <div className=\"relative z-10 flex items-center justify-center w-[50px] h-[50px] rounded-full bg-gray-50 paragraph-small-semibold\">\n {children ?? (\n <>\n <span className=\"text-gray-600\">{current}</span>\n <span className=\"text-gray-500\">/</span>\n <span className=\"text-gray-600\">{total}</span>\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"AA0CM,SA2BI,UA1BF,KADF;AAzCN;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUQ,SAAR,iBAAkC;AAAA,EACvC;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AACF,GAA0B;AACxB,QAAM,SAAS,gBAAgB,MAAM,WAAW;AAChD,QAAM,gBAAgB,uBAAuB,MAAM;AACnD,QAAM,aAAa,oBAAoB,SAAS,KAAK;AACrD,QAAM,SAAS,gCAAgC,YAAY,aAAa;AAExE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,MACnC,MAAK;AAAA,MACL,iBAAe;AAAA,MACf,iBAAe;AAAA,MACf,iBAAe;AAAA,MAKf,cAAW;AAAA,MAEX;AAAA,6BAAC,SAAI,OAAO,MAAM,QAAQ,MAAM,WAAU,YACxC;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,QAAO;AAAA,cACP;AAAA,cACA,MAAK;AAAA;AAAA,UACP;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI,OAAO;AAAA,cACX,IAAI,OAAO;AAAA,cACX,GAAG;AAAA,cACH,QAAO;AAAA,cACP;AAAA,cACA,MAAK;AAAA,cACL,iBAAiB;AAAA,cACjB,kBAAkB;AAAA,cAClB,eAAc;AAAA,cACd,WAAW,cAAc,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,cAC7C,WAAU;AAAA;AAAA,UACZ;AAAA,WACF;AAAA,QAEA,oBAAC,SAAI,WAAU,qHACZ,sBACC,iCACE;AAAA,8BAAC,UAAK,WAAU,iBAAiB,mBAAQ;AAAA,UACzC,oBAAC,UAAK,WAAU,iBAAgB,eAAC;AAAA,UACjC,oBAAC,UAAK,WAAU,iBAAiB,iBAAM;AAAA,WACzC,GAEJ;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -10,7 +10,8 @@ function createListPlansHandler() {
10
10
  sort: "id:ASC"
11
11
  });
12
12
  if (!apiPlans?.length) return [];
13
- return [...apiPlans].sort((a, b) => (a.value ?? 0) - (b.value ?? 0));
13
+ const sortValue = (v) => v == null ? Number.POSITIVE_INFINITY : v;
14
+ return [...apiPlans].sort((a, b) => sortValue(a.value) - sortValue(b.value));
14
15
  });
15
16
  };
16
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/plans/handlers/list-plans.handler.ts"],"sourcesContent":["import 'server-only';\n\nimport { safeRouteHandler } from '../../../infra/route/safe-route-handler';\nimport { plansService } from '../services/plans.service';\n\nexport function createListPlansHandler() {\n return async function GET() {\n return safeRouteHandler(async () => {\n const { data: apiPlans } = await plansService.listPlans({\n active: true,\n search: 'client',\n sort: 'id:ASC',\n });\n\n if (!apiPlans?.length) return [];\n\n return [...apiPlans].sort((a, b) => (a.value ?? 0) - (b.value ?? 0));\n });\n };\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAEtB,SAAS,yBAAyB;AACvC,SAAO,eAAe,MAAM;AAC1B,WAAO,iBAAiB,YAAY;AAClC,YAAM,EAAE,MAAM,SAAS,IAAI,MAAM,aAAa,UAAU;AAAA,QACtD,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AAED,UAAI,CAAC,UAAU,OAAQ,QAAO,CAAC;AAE/B,aAAO,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,MAAM,EAAE,SAAS,EAAE;AAAA,IACrE,CAAC;AAAA,EACH;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/plans/handlers/list-plans.handler.ts"],"sourcesContent":["import 'server-only';\n\nimport { safeRouteHandler } from '../../../infra/route/safe-route-handler';\nimport { plansService } from '../services/plans.service';\n\nexport function createListPlansHandler() {\n return async function GET() {\n return safeRouteHandler(async () => {\n const { data: apiPlans } = await plansService.listPlans({\n active: true,\n search: 'client',\n sort: 'id:ASC',\n });\n\n if (!apiPlans?.length) return [];\n\n // null/undefined (USD price não configurado) ordena por último — evita aparecer como \"o mais barato\"\n const sortValue = (v: number | null | undefined) => (v == null ? Number.POSITIVE_INFINITY : v);\n return [...apiPlans].sort((a, b) => sortValue(a.value) - sortValue(b.value));\n });\n };\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAEtB,SAAS,yBAAyB;AACvC,SAAO,eAAe,MAAM;AAC1B,WAAO,iBAAiB,YAAY;AAClC,YAAM,EAAE,MAAM,SAAS,IAAI,MAAM,aAAa,UAAU;AAAA,QACtD,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AAED,UAAI,CAAC,UAAU,OAAQ,QAAO,CAAC;AAG/B,YAAM,YAAY,CAAC,MAAkC,KAAK,OAAO,OAAO,oBAAoB;AAC5F,aAAO,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,UAAU,EAAE,KAAK,IAAI,UAAU,EAAE,KAAK,CAAC;AAAA,IAC7E,CAAC;AAAA,EACH;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.678",
3
+ "version": "1.1.679",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -30,6 +30,7 @@ import {
30
30
  } from '../constants';
31
31
  import { useTimezones } from '../../../modules/accounts/hooks/useTimezones';
32
32
  import { useCurrencies } from '../../../modules/accounts/hooks/useCurrencies';
33
+ import { getCurrencyForGateway } from '../../../utils/format/currency';
33
34
 
34
35
  interface PreferencesFormValues {
35
36
  companyName: string;
@@ -98,7 +99,9 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
98
99
  language: LANGUAGE_OPTIONS.find(
99
100
  (o) => o.apiValue === user.language?.toLowerCase()
100
101
  )?.value ?? 'pt-br',
101
- currency: account.currency,
102
+ // A moeda é derivada do gateway da conta (read-only para o usuário) —
103
+ // espelha o que useCurrencyFormatter usa no display de preços.
104
+ currency: getCurrencyForGateway(account.gateway).toUpperCase(),
102
105
  timezone: account.timezone,
103
106
  timeFormat: '24h',
104
107
  receive_sms: user.receive_sms ?? false,
@@ -145,7 +148,7 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
145
148
  const onSubmit = (data: PreferencesFormValues) => {
146
149
  const accountFields: UpdateAccountRequest = {
147
150
  ...(isOwner && dirtyFields.companyName && { name: data.companyName }),
148
- ...(canEditPreferences && dirtyFields.currency && { currency: data.currency }),
151
+ // currency é read-only (derivada do gateway) não enviada no update.
149
152
  ...(canEditPreferences && dirtyFields.timezone && { timezone: data.timezone }),
150
153
  };
151
154
  const globalFieldChanged = Object.keys(accountFields).length > 0;
@@ -205,9 +208,9 @@ export function PreferencesSection({ onClose }: PreferencesSectionProps) {
205
208
  containerClassName="w-full"
206
209
  options={currencies}
207
210
  value={watch('currency')}
208
- onChange={(value) => setValue('currency', value, { shouldDirty: true })}
209
- icon={!canEditPreferences ? <IconLock className="size-4 text-gray-400" /> : undefined}
210
- disabled={!canEditPreferences}
211
+ onChange={() => undefined}
212
+ icon={<IconLock className="size-4 text-gray-400" />}
213
+ disabled
211
214
  />
212
215
  </div>
213
216
 
@@ -195,7 +195,7 @@ export default function BuyCreditsModal() {
195
195
  ),
196
196
  displayValue: translate('common.credits.buy.creditsOptionDisplayValue', { credits: opt.credits.toLocaleString('pt-BR') }),
197
197
  }));
198
- }, []);
198
+ }, [translate]);
199
199
 
200
200
  const onSubmit = useCallback(
201
201
  async (data: BuyCreditFormData) => {
@@ -36,7 +36,7 @@ export default function CircularProgress({
36
36
  aria-valuemax={total}
37
37
  // i18n-TODO: aria-label hardcoded — CircularProgress has no 'use client' directive and
38
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').
39
+ // converting it to a client component. Key to use: common.forms.circularProgress.ariaLabel ('Progresso').
40
40
  // Either add 'use client' + useTranslations(), or accept a translate prop from the caller.
41
41
  aria-label="Progresso"
42
42
  >
@@ -14,7 +14,9 @@ export function createListPlansHandler() {
14
14
 
15
15
  if (!apiPlans?.length) return [];
16
16
 
17
- return [...apiPlans].sort((a, b) => (a.value ?? 0) - (b.value ?? 0));
17
+ // null/undefined (USD price não configurado) ordena por último evita aparecer como "o mais barato"
18
+ const sortValue = (v: number | null | undefined) => (v == null ? Number.POSITIVE_INFINITY : v);
19
+ return [...apiPlans].sort((a, b) => sortValue(a.value) - sortValue(b.value));
18
20
  });
19
21
  };
20
22
  }