@greatapps/common 1.1.676 → 1.1.678
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/account/ConfigurationsMyAccountModal.mjs +8 -6
- package/dist/components/account/ConfigurationsMyAccountModal.mjs.map +1 -1
- package/dist/components/account/ConfirmDeleteAccountModal.mjs +20 -19
- package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -1
- package/dist/components/account/ConfirmGlobalPreferencesModal.mjs +10 -11
- package/dist/components/account/ConfirmGlobalPreferencesModal.mjs.map +1 -1
- package/dist/components/account/DeleteAccountModal.mjs +34 -28
- package/dist/components/account/DeleteAccountModal.mjs.map +1 -1
- package/dist/components/account/DisableTwoFactorAuthModal.mjs +7 -5
- package/dist/components/account/DisableTwoFactorAuthModal.mjs.map +1 -1
- package/dist/components/account/IsntPossibleDeleteAccountModal.mjs +8 -9
- package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -1
- package/dist/components/account/TwoFactorAuthModal.mjs +22 -23
- package/dist/components/account/TwoFactorAuthModal.mjs.map +1 -1
- package/dist/components/account/constants.mjs +4 -4
- package/dist/components/account/constants.mjs.map +1 -1
- package/dist/components/account/hooks/useChangeEmailForm.mjs +5 -3
- package/dist/components/account/hooks/useChangeEmailForm.mjs.map +1 -1
- package/dist/components/account/hooks/useChangePasswordForm.mjs +26 -3
- package/dist/components/account/hooks/useChangePasswordForm.mjs.map +1 -1
- package/dist/components/account/hooks/useChangePhoneForm.mjs +18 -9
- package/dist/components/account/hooks/useChangePhoneForm.mjs.map +1 -1
- package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs +26 -3
- package/dist/components/account/hooks/useDisableTwoFactorAuthForm.mjs.map +1 -1
- package/dist/components/account/hooks/useOtpVerification.mjs +19 -3
- package/dist/components/account/hooks/useOtpVerification.mjs.map +1 -1
- package/dist/components/account/hooks/useTwoFactorAuthForm.mjs +27 -4
- package/dist/components/account/hooks/useTwoFactorAuthForm.mjs.map +1 -1
- package/dist/components/account/sections/ChangeEmailModal.mjs +25 -28
- package/dist/components/account/sections/ChangeEmailModal.mjs.map +1 -1
- package/dist/components/account/sections/ChangePasswordSection.mjs +8 -6
- package/dist/components/account/sections/ChangePasswordSection.mjs.map +1 -1
- package/dist/components/account/sections/ChangePhoneModal.mjs +24 -27
- package/dist/components/account/sections/ChangePhoneModal.mjs.map +1 -1
- package/dist/components/account/sections/MyProfileSection.mjs +37 -22
- package/dist/components/account/sections/MyProfileSection.mjs.map +1 -1
- package/dist/components/account/sections/PreferencesSection.mjs +48 -24
- package/dist/components/account/sections/PreferencesSection.mjs.map +1 -1
- package/dist/components/account/sections/SecuritySection.mjs +11 -9
- package/dist/components/account/sections/SecuritySection.mjs.map +1 -1
- package/dist/components/account/sections/TokenSection.mjs +24 -8
- package/dist/components/account/sections/TokenSection.mjs.map +1 -1
- package/dist/components/layouts/NavBar.mjs +5 -3
- package/dist/components/layouts/NavBar.mjs.map +1 -1
- package/dist/components/layouts/NotificationsPopover.mjs +5 -3
- package/dist/components/layouts/NotificationsPopover.mjs.map +1 -1
- package/dist/components/layouts/ProfilePopover.mjs +6 -4
- package/dist/components/layouts/ProfilePopover.mjs.map +1 -1
- package/dist/components/layouts/UsersSelectorPopover.mjs +9 -7
- package/dist/components/layouts/UsersSelectorPopover.mjs.map +1 -1
- package/dist/components/modals/BuyCreditsModal.mjs +17 -18
- package/dist/components/modals/BuyCreditsModal.mjs.map +1 -1
- package/dist/components/modals/CreditsDisabledModal.mjs +6 -4
- package/dist/components/modals/CreditsDisabledModal.mjs.map +1 -1
- package/dist/components/modals/PaidPlanRequiredModal.mjs +5 -3
- package/dist/components/modals/PaidPlanRequiredModal.mjs.map +1 -1
- package/dist/components/modals/cards/AddCardModal.mjs +64 -46
- package/dist/components/modals/cards/AddCardModal.mjs.map +1 -1
- package/dist/components/modals/cards/CannotDeleteCardModal.mjs +9 -7
- package/dist/components/modals/cards/CannotDeleteCardModal.mjs.map +1 -1
- package/dist/components/modals/cards/CardFormFields.mjs +65 -51
- package/dist/components/modals/cards/CardFormFields.mjs.map +1 -1
- package/dist/components/modals/cards/DeleteCardModal.mjs +10 -8
- package/dist/components/modals/cards/DeleteCardModal.mjs.map +1 -1
- package/dist/components/navigation/CancelledSubscriptionBanner.mjs +4 -2
- package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
- package/dist/components/navigation/OverdueInvoiceBanner.mjs +4 -2
- package/dist/components/navigation/OverdueInvoiceBanner.mjs.map +1 -1
- package/dist/components/navigation/PendingCancellationBanner.mjs +4 -6
- package/dist/components/navigation/PendingCancellationBanner.mjs.map +1 -1
- package/dist/components/navigation/ProjectSelectorPopover.mjs +8 -6
- package/dist/components/navigation/ProjectSelectorPopover.mjs.map +1 -1
- package/dist/components/navigation/UpcomingInvoiceBanner.mjs +4 -6
- package/dist/components/navigation/UpcomingInvoiceBanner.mjs.map +1 -1
- package/dist/components/navigation/subcomponents/BottomLinks.mjs +5 -3
- package/dist/components/navigation/subcomponents/BottomLinks.mjs.map +1 -1
- package/dist/components/navigation/subcomponents/CreditsCard.mjs +3 -1
- package/dist/components/navigation/subcomponents/CreditsCard.mjs.map +1 -1
- package/dist/components/navigation/subcomponents/PlanCard.mjs +3 -1
- package/dist/components/navigation/subcomponents/PlanCard.mjs.map +1 -1
- package/dist/components/navigation/subcomponents/ProjectSelector.mjs +5 -3
- package/dist/components/navigation/subcomponents/ProjectSelector.mjs.map +1 -1
- package/dist/components/pages/NotFoundPage.mjs +5 -3
- package/dist/components/pages/NotFoundPage.mjs.map +1 -1
- package/dist/components/pages/notifications/Notifications.mjs +13 -10
- package/dist/components/pages/notifications/Notifications.mjs.map +1 -1
- package/dist/components/ui/buttons/CopyButton.mjs +9 -5
- package/dist/components/ui/buttons/CopyButton.mjs.map +1 -1
- package/dist/components/ui/data-display/CardItem.mjs +7 -5
- package/dist/components/ui/data-display/CardItem.mjs.map +1 -1
- package/dist/components/ui/data-display/PaymentInfoCard.mjs +10 -8
- package/dist/components/ui/data-display/PaymentInfoCard.mjs.map +1 -1
- package/dist/components/ui/feedback/CircularProgress.mjs.map +1 -1
- package/dist/components/ui/feedback/DefaultCircularProgress.mjs +3 -1
- package/dist/components/ui/feedback/DefaultCircularProgress.mjs.map +1 -1
- package/dist/components/ui/form/ComboboxField.mjs +11 -6
- package/dist/components/ui/form/ComboboxField.mjs.map +1 -1
- package/dist/components/ui/form/DatePicker.mjs +5 -2
- package/dist/components/ui/form/DatePicker.mjs.map +1 -1
- package/dist/components/ui/form/DateRangePicker.mjs +5 -2
- package/dist/components/ui/form/DateRangePicker.mjs.map +1 -1
- package/dist/components/ui/form/PhoneInput.mjs +5 -3
- package/dist/components/ui/form/PhoneInput.mjs.map +1 -1
- package/dist/components/ui/form/SelectField.mjs +6 -3
- package/dist/components/ui/form/SelectField.mjs.map +1 -1
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs +11 -18
- package/dist/components/widgets/ImageUpload/ImageCropModal.mjs.map +1 -1
- package/dist/components/widgets/ImageUpload/ImageTooSmallModal.mjs +6 -15
- package/dist/components/widgets/ImageUpload/ImageTooSmallModal.mjs.map +1 -1
- package/dist/i18n/config.mjs +13 -0
- package/dist/i18n/config.mjs.map +1 -0
- package/dist/i18n/country-language.mjs +74 -0
- package/dist/i18n/country-language.mjs.map +1 -0
- package/dist/i18n/messages/en-us.mjs +495 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -0
- package/dist/i18n/messages/es-es.mjs +495 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -0
- package/dist/i18n/messages/index.mjs +16 -0
- package/dist/i18n/messages/index.mjs.map +1 -0
- package/dist/i18n/messages/pt-br.mjs +508 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -0
- package/dist/i18n/normalize.mjs +12 -0
- package/dist/i18n/normalize.mjs.map +1 -0
- package/dist/i18n/resolve-locale.mjs +42 -0
- package/dist/i18n/resolve-locale.mjs.map +1 -0
- package/dist/i18n.mjs +14 -0
- package/dist/i18n.mjs.map +1 -0
- package/dist/index.mjs +19 -3
- package/dist/index.mjs.map +1 -1
- package/dist/modules/accounts/hooks/use-currency-formatter.hook.mjs +20 -0
- package/dist/modules/accounts/hooks/use-currency-formatter.hook.mjs.map +1 -0
- package/dist/modules/cards/hooks/handle-delete-card.hook.mjs +2 -1
- package/dist/modules/cards/hooks/handle-delete-card.hook.mjs.map +1 -1
- package/dist/modules/images/utils/validate-image.mjs +8 -8
- package/dist/modules/images/utils/validate-image.mjs.map +1 -1
- package/dist/modules/plans/handlers/list-plans.handler.mjs +1 -1
- package/dist/modules/plans/handlers/list-plans.handler.mjs.map +1 -1
- package/dist/modules/plans/hooks/list-plans.hook.mjs +2 -2
- package/dist/modules/plans/hooks/list-plans.hook.mjs.map +1 -1
- package/dist/modules/plans/services/plans.service.mjs +1 -1
- package/dist/modules/plans/services/plans.service.mjs.map +1 -1
- package/dist/modules/plans/types/plan.type.mjs +9 -2
- package/dist/modules/plans/types/plan.type.mjs.map +1 -1
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs +66 -34
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
- package/dist/modules/projects/services/projects.service.mjs +9 -0
- package/dist/modules/projects/services/projects.service.mjs.map +1 -1
- package/dist/modules/subscriptions/utils/periodicity.mjs +20 -14
- package/dist/modules/subscriptions/utils/periodicity.mjs.map +1 -1
- package/dist/providers/auth.provider.mjs +9 -1
- package/dist/providers/auth.provider.mjs.map +1 -1
- package/dist/server.mjs +2 -0
- package/dist/server.mjs.map +1 -1
- package/dist/utils/format/currency.mjs +32 -4
- package/dist/utils/format/currency.mjs.map +1 -1
- package/dist/utils/intl/cookie-domain.mjs +17 -0
- package/dist/utils/intl/cookie-domain.mjs.map +1 -0
- package/dist/utils/intl/locale-cookie.mjs +13 -0
- package/dist/utils/intl/locale-cookie.mjs.map +1 -0
- package/dist/utils/intl/locales.mjs +3 -2
- package/dist/utils/intl/locales.mjs.map +1 -1
- package/package.json +7 -1
- package/src/components/account/ConfigurationsMyAccountModal.tsx +10 -6
- package/src/components/account/ConfirmDeleteAccountModal.tsx +22 -13
- package/src/components/account/ConfirmGlobalPreferencesModal.tsx +12 -10
- package/src/components/account/DeleteAccountModal.tsx +34 -24
- package/src/components/account/DisableTwoFactorAuthModal.tsx +11 -6
- package/src/components/account/IsntPossibleDeleteAccountModal.tsx +8 -6
- package/src/components/account/TwoFactorAuthModal.tsx +33 -29
- package/src/components/account/constants.ts +8 -5
- package/src/components/account/hooks/useChangeEmailForm.tsx +5 -3
- package/src/components/account/hooks/useChangePasswordForm.tsx +20 -6
- package/src/components/account/hooks/useChangePhoneForm.tsx +16 -10
- package/src/components/account/hooks/useDisableTwoFactorAuthForm.tsx +20 -6
- package/src/components/account/hooks/useOtpVerification.tsx +16 -4
- package/src/components/account/hooks/useTwoFactorAuthForm.tsx +21 -7
- package/src/components/account/sections/ChangeEmailModal.tsx +35 -21
- package/src/components/account/sections/ChangePasswordSection.tsx +10 -6
- package/src/components/account/sections/ChangePhoneModal.tsx +50 -22
- package/src/components/account/sections/MyProfileSection.tsx +37 -23
- package/src/components/account/sections/PreferencesSection.tsx +52 -25
- package/src/components/account/sections/SecuritySection.tsx +19 -10
- package/src/components/account/sections/TokenSection.tsx +22 -10
- package/src/components/layouts/NavBar.tsx +5 -3
- package/src/components/layouts/NotificationsPopover.tsx +5 -3
- package/src/components/layouts/ProfilePopover.tsx +6 -4
- package/src/components/layouts/UsersSelectorPopover.tsx +9 -7
- package/src/components/modals/BuyCreditsModal.tsx +18 -16
- package/src/components/modals/CreditsDisabledModal.tsx +6 -6
- package/src/components/modals/PaidPlanRequiredModal.tsx +5 -3
- package/src/components/modals/cards/AddCardModal.tsx +89 -62
- package/src/components/modals/cards/CannotDeleteCardModal.tsx +9 -8
- package/src/components/modals/cards/CardFormFields.tsx +93 -75
- package/src/components/modals/cards/DeleteCardModal.tsx +10 -8
- package/src/components/navigation/CancelledSubscriptionBanner.tsx +4 -2
- package/src/components/navigation/OverdueInvoiceBanner.tsx +4 -2
- package/src/components/navigation/PendingCancellationBanner.tsx +4 -2
- package/src/components/navigation/ProjectSelectorPopover.tsx +8 -6
- package/src/components/navigation/UpcomingInvoiceBanner.tsx +4 -2
- package/src/components/navigation/subcomponents/BottomLinks.tsx +5 -3
- package/src/components/navigation/subcomponents/CreditsCard.tsx +3 -1
- package/src/components/navigation/subcomponents/PlanCard.tsx +3 -1
- package/src/components/navigation/subcomponents/ProjectSelector.tsx +5 -3
- package/src/components/pages/NotFoundPage.tsx +5 -3
- package/src/components/pages/notifications/Notifications.tsx +13 -10
- package/src/components/ui/buttons/CopyButton.tsx +10 -5
- package/src/components/ui/data-display/CardItem.tsx +7 -5
- package/src/components/ui/data-display/PaymentInfoCard.tsx +10 -8
- package/src/components/ui/feedback/CircularProgress.tsx +4 -0
- package/src/components/ui/feedback/DefaultCircularProgress.tsx +3 -1
- package/src/components/ui/form/ComboboxField.tsx +11 -6
- package/src/components/ui/form/DatePicker.tsx +5 -2
- package/src/components/ui/form/DateRangePicker.tsx +5 -2
- package/src/components/ui/form/PhoneInput.tsx +5 -3
- package/src/components/ui/form/SelectField.tsx +6 -3
- package/src/components/widgets/ImageUpload/ImageCropModal.tsx +19 -11
- package/src/components/widgets/ImageUpload/ImageTooSmallModal.tsx +6 -5
- package/src/i18n/config.ts +17 -0
- package/src/i18n/country-language.ts +60 -0
- package/src/i18n/messages/en-us.ts +515 -0
- package/src/i18n/messages/es-es.ts +516 -0
- package/src/i18n/messages/index.ts +24 -0
- package/src/i18n/messages/pt-br.ts +539 -0
- package/src/i18n/normalize.ts +15 -0
- package/src/i18n/resolve-locale.ts +77 -0
- package/src/i18n.ts +10 -0
- package/src/index.ts +13 -2
- package/src/modules/accounts/hooks/use-currency-formatter.hook.tsx +32 -0
- package/src/modules/cards/hooks/handle-delete-card.hook.ts +8 -3
- package/src/modules/images/utils/validate-image.ts +23 -8
- package/src/modules/plans/handlers/list-plans.handler.ts +1 -1
- package/src/modules/plans/hooks/list-plans.hook.ts +2 -2
- package/src/modules/plans/services/plans.service.ts +1 -1
- package/src/modules/plans/types/plan.type.ts +9 -1
- package/src/modules/plans/utils/map-api-plan-to-ui.ts +115 -46
- package/src/modules/projects/services/projects.service.ts +12 -0
- package/src/modules/subscriptions/utils/periodicity.ts +29 -12
- package/src/providers/auth.provider.tsx +13 -1
- package/src/server.ts +2 -0
- package/src/utils/format/currency.ts +62 -7
- package/src/utils/intl/cookie-domain.ts +29 -0
- package/src/utils/intl/locale-cookie.ts +18 -0
- package/src/utils/intl/locales.ts +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/ui/form/DateRangePicker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, forwardRef, useEffect } from \"react\";\nimport { format } from \"date-fns\";\nimport { IconCalendar } from '@tabler/icons-react';\nimport { DateRange } from \"react-day-picker\";\nimport { Calendar } from \"./Calendar\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../overlay/Popover\";\nimport { cn } from \"../../../infra/utils/clsx\";\n\n// Converte string ISO (yyyy-MM-dd) para Date no fuso horário local\n// Evita bug de timezone onde new Date(\"2025-08-02\") interpreta como UTC\nconst isoParaDataLocal = (iso: string): Date => {\n const [ano, mes, dia] = iso.split(\"-\").map(Number);\n return new Date(ano, mes - 1, dia);\n};\n\ninterface DateRangePickerProps {\n value?: { from: string; to: string };\n onChange?: (range: { from: string; to: string } | undefined) => void;\n placeholder?: string;\n className?: string;\n disabled?: boolean;\n}\n\nexport const DateRangePicker = forwardRef<HTMLButtonElement, DateRangePickerProps>(\n ({ value, onChange, placeholder
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/ui/form/DateRangePicker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, forwardRef, useEffect } from \"react\";\nimport { useTranslations } from \"next-intl\";\nimport { format } from \"date-fns\";\nimport { IconCalendar } from '@tabler/icons-react';\nimport { DateRange } from \"react-day-picker\";\nimport { Calendar } from \"./Calendar\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../overlay/Popover\";\nimport { cn } from \"../../../infra/utils/clsx\";\n\n// Converte string ISO (yyyy-MM-dd) para Date no fuso horário local\n// Evita bug de timezone onde new Date(\"2025-08-02\") interpreta como UTC\nconst isoParaDataLocal = (iso: string): Date => {\n const [ano, mes, dia] = iso.split(\"-\").map(Number);\n return new Date(ano, mes - 1, dia);\n};\n\ninterface DateRangePickerProps {\n value?: { from: string; to: string };\n onChange?: (range: { from: string; to: string } | undefined) => void;\n placeholder?: string;\n className?: string;\n disabled?: boolean;\n}\n\nexport const DateRangePicker = forwardRef<HTMLButtonElement, DateRangePickerProps>(\n ({ value, onChange, placeholder, className, disabled }, ref) => {\n const translate = useTranslations();\n const resolvedPlaceholder = placeholder ?? translate('common.forms.dateRangePicker.placeholder');\n const [open, setOpen] = useState(false);\n const [range, setRange] = useState<DateRange | undefined>(() => {\n if (value?.from && value?.to) {\n return {\n from: isoParaDataLocal(value.from),\n to: isoParaDataLocal(value.to)\n };\n }\n return undefined;\n });\n\n useEffect(() => {\n if (value?.from && value?.to) {\n setRange({\n from: isoParaDataLocal(value.from),\n to: isoParaDataLocal(value.to)\n });\n } else {\n setRange(undefined);\n }\n }, [value]);\n\n const handleRangeSelect = (newRange: DateRange | undefined) => {\n setRange(newRange);\n\n if (newRange?.from && newRange?.to) {\n onChange?.({\n from: format(newRange.from, \"yyyy-MM-dd\"),\n to: format(newRange.to, \"yyyy-MM-dd\")\n });\n } else {\n onChange?.(undefined);\n }\n };\n\n const displayText = range?.from && range?.to\n ? `${format(range.from, \"dd/MM/yyyy\")} - ${format(range.to, \"dd/MM/yyyy\")}`\n : resolvedPlaceholder;\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n ref={ref}\n disabled={disabled}\n className={cn(\n \"flex items-center gap-2 px-4 h-10 border rounded-lg transition-colors w-full justify-start\",\n \"border-gray-200 hover:border-gray-400 focus-within:border-gray-950\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n className\n )}\n >\n <IconCalendar size={20} className=\"text-gray-400\" />\n <span className={cn(\n \"paragraph-small-medium\",\n range?.from && range?.to ? \"text-gray-950\" : \"text-gray-500\"\n )}>\n {displayText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent\n className=\"w-auto p-0 bg-white rounded-2xl shadow-md border border-gray-200\"\n align=\"start\"\n sideOffset={8}\n >\n <div className=\"p-3\">\n <Calendar\n mode=\"range\"\n selected={range}\n onSelect={handleRangeSelect}\n numberOfMonths={1}\n />\n </div>\n </PopoverContent>\n </Popover>\n );\n }\n);\n\nDateRangePicker.displayName = \"DateRangePicker\";\n"],"mappings":";AAwEU,SAUE,KAVF;AAtEV,SAAS,UAAU,YAAY,iBAAiB;AAChD,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,oBAAoB;AAE7B,SAAS,gBAAgB;AACzB,SAAS,SAAS,gBAAgB,sBAAsB;AACxD,SAAS,UAAU;AAInB,MAAM,mBAAmB,CAAC,QAAsB;AAC9C,QAAM,CAAC,KAAK,KAAK,GAAG,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM;AACjD,SAAO,IAAI,KAAK,KAAK,MAAM,GAAG,GAAG;AACnC;AAUO,MAAM,kBAAkB;AAAA,EAC7B,CAAC,EAAE,OAAO,UAAU,aAAa,WAAW,SAAS,GAAG,QAAQ;AAC9D,UAAM,YAAY,gBAAgB;AAClC,UAAM,sBAAsB,eAAe,UAAU,0CAA0C;AAC/F,UAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,UAAM,CAAC,OAAO,QAAQ,IAAI,SAAgC,MAAM;AAC9D,UAAI,OAAO,QAAQ,OAAO,IAAI;AAC5B,eAAO;AAAA,UACL,MAAM,iBAAiB,MAAM,IAAI;AAAA,UACjC,IAAI,iBAAiB,MAAM,EAAE;AAAA,QAC/B;AAAA,MACF;AACA,aAAO;AAAA,IACT,CAAC;AAED,cAAU,MAAM;AACd,UAAI,OAAO,QAAQ,OAAO,IAAI;AAC5B,iBAAS;AAAA,UACP,MAAM,iBAAiB,MAAM,IAAI;AAAA,UACjC,IAAI,iBAAiB,MAAM,EAAE;AAAA,QAC/B,CAAC;AAAA,MACH,OAAO;AACL,iBAAS,MAAS;AAAA,MACpB;AAAA,IACF,GAAG,CAAC,KAAK,CAAC;AAEV,UAAM,oBAAoB,CAAC,aAAoC;AAC7D,eAAS,QAAQ;AAEjB,UAAI,UAAU,QAAQ,UAAU,IAAI;AAClC,mBAAW;AAAA,UACT,MAAM,OAAO,SAAS,MAAM,YAAY;AAAA,UACxC,IAAI,OAAO,SAAS,IAAI,YAAY;AAAA,QACtC,CAAC;AAAA,MACH,OAAO;AACL,mBAAW,MAAS;AAAA,MACtB;AAAA,IACF;AAEA,UAAM,cAAc,OAAO,QAAQ,OAAO,KACtC,GAAG,OAAO,MAAM,MAAM,YAAY,CAAC,MAAM,OAAO,MAAM,IAAI,YAAY,CAAC,KACvE;AAEJ,WACE,qBAAC,WAAQ,MAAY,cAAc,SACjC;AAAA,0BAAC,kBAAe,SAAO,MACrB;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,UAEA;AAAA,gCAAC,gBAAa,MAAM,IAAI,WAAU,iBAAgB;AAAA,YAClD,oBAAC,UAAK,WAAW;AAAA,cACf;AAAA,cACA,OAAO,QAAQ,OAAO,KAAK,kBAAkB;AAAA,YAC/C,GACG,uBACH;AAAA;AAAA;AAAA,MACF,GACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAM;AAAA,UACN,YAAY;AAAA,UAEZ,8BAAC,SAAI,WAAU,OACb;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,UAAU;AAAA,cACV,UAAU;AAAA,cACV,gBAAgB;AAAA;AAAA,UAClB,GACF;AAAA;AAAA,MACF;AAAA,OACF;AAAA,EAEJ;AACF;AAEA,gBAAgB,cAAc;","names":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useRef } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import { AsYouType } from "libphonenumber-js";
|
|
5
6
|
import { Input, Separator, Popover, PopoverTrigger, PopoverContent, cn } from "@greatapps/common";
|
|
6
7
|
import { IconChevronDown, IconSearch } from "@tabler/icons-react";
|
|
@@ -24,6 +25,7 @@ function PhoneInput({
|
|
|
24
25
|
classnameContainer,
|
|
25
26
|
className
|
|
26
27
|
}) {
|
|
28
|
+
const translate = useTranslations();
|
|
27
29
|
const parsed = parsePhoneValue(value ?? "");
|
|
28
30
|
const [selectedCountry, setSelectedCountry] = useState(parsed.country);
|
|
29
31
|
const [phoneNumber, setPhoneNumber] = useState(parsed.number);
|
|
@@ -83,7 +85,7 @@ function PhoneInput({
|
|
|
83
85
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
84
86
|
label && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 paragraph-xsmall-semibold", children: [
|
|
85
87
|
/* @__PURE__ */ jsx("span", { className: "text-zinc-600", children: label }),
|
|
86
|
-
optional && /* @__PURE__ */ jsx("span", { className: "text-zinc-500", children: "
|
|
88
|
+
optional && /* @__PURE__ */ jsx("span", { className: "text-zinc-500", children: translate("common.forms.phoneInput.optional") }),
|
|
87
89
|
required && /* @__PURE__ */ jsx("span", { className: "text-red-600", children: "*" })
|
|
88
90
|
] }),
|
|
89
91
|
/* @__PURE__ */ jsxs(
|
|
@@ -118,7 +120,7 @@ function PhoneInput({
|
|
|
118
120
|
{
|
|
119
121
|
type: "text",
|
|
120
122
|
className: "flex-1 text-sm outline-none placeholder:text-zinc-400",
|
|
121
|
-
placeholder: "
|
|
123
|
+
placeholder: translate("common.forms.phoneInput.searchPlaceholder"),
|
|
122
124
|
value: search,
|
|
123
125
|
onChange: (e) => setSearch(e.target.value),
|
|
124
126
|
autoFocus: true
|
|
@@ -143,7 +145,7 @@ function PhoneInput({
|
|
|
143
145
|
},
|
|
144
146
|
country.iso
|
|
145
147
|
)),
|
|
146
|
-
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-zinc-500 p-3 text-center", children: "
|
|
148
|
+
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-zinc-500 p-3 text-center", children: translate("common.forms.phoneInput.emptyMessage") })
|
|
147
149
|
] })
|
|
148
150
|
] })
|
|
149
151
|
] }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/ui/form/PhoneInput.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, useRef } from 'react';\nimport { AsYouType, CountryCode } from 'libphonenumber-js';\nimport { Input, Separator, Popover, PopoverTrigger, PopoverContent, cn } from '@greatapps/common';\nimport { IconChevronDown, IconSearch } from '@tabler/icons-react';\nimport { COUNTRIES, Country, flagUrl, parsePhoneValue } from '../../../utils/countries';\n\nfunction formatNational(digits: string, iso: string): string {\n const raw = digits.replace(/\\D/g, '');\n if (!raw) return '';\n return new AsYouType(iso as CountryCode).input(raw);\n}\n\ninterface PhoneInputProps {\n value?: string;\n onChange?: (value: string) => void;\n label?: string;\n optional?: boolean;\n required?: boolean;\n hintMessage?: string;\n error?: boolean;\n errorMessage?: string;\n placeholder?: string;\n disabled?: boolean;\n classnameContainer?: string;\n className?: string;\n}\n\nexport default function PhoneInput({\n value,\n onChange,\n label,\n optional = false,\n required = false,\n hintMessage,\n error = false,\n errorMessage,\n placeholder,\n disabled = false,\n classnameContainer,\n className,\n}: PhoneInputProps) {\n const parsed = parsePhoneValue(value ?? '');\n const [selectedCountry, setSelectedCountry] = useState<Country>(parsed.country);\n const [phoneNumber, setPhoneNumber] = useState(parsed.number);\n const [search, setSearch] = useState('');\n const [open, setOpen] = useState(false);\n const prevValueRef = useRef(value);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (value !== prevValueRef.current) {\n prevValueRef.current = value;\n const p = parsePhoneValue(value ?? '');\n setSelectedCountry(p.country);\n setPhoneNumber(formatNational(p.number, p.country.iso));\n }\n }, [value]);\n\n const emit = (country: Country, number: string) => {\n onChange?.(`${country.dial} ${number}`);\n };\n\n const handleCountrySelect = (country: Country) => {\n const reformatted = formatNational(phoneNumber, country.iso);\n setSelectedCountry(country);\n setPhoneNumber(reformatted);\n setOpen(false);\n setSearch('');\n emit(country, reformatted);\n };\n\n const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const rawInput = e.target.value;\n const digits = rawInput.replace(/\\D/g, '');\n if (digits.length > 15) return;\n\n const cursorPos = e.target.selectionStart ?? rawInput.length;\n const digitsBeforeCursor = rawInput.slice(0, cursorPos).replace(/\\D/g, '').length;\n\n const formatted = formatNational(rawInput, selectedCountry.iso);\n setPhoneNumber(formatted);\n emit(selectedCountry, formatted);\n\n requestAnimationFrame(() => {\n if (!inputRef.current) return;\n let digitCount = 0;\n let newPos = formatted.length;\n for (let i = 0; i < formatted.length; i++) {\n if (/\\d/.test(formatted[i])) {\n digitCount++;\n if (digitCount === digitsBeforeCursor) {\n newPos = i + 1;\n break;\n }\n }\n }\n inputRef.current.setSelectionRange(newPos, newPos);\n });\n };\n\n const filtered = COUNTRIES.filter(\n (c) =>\n c.name.toLowerCase().includes(search.toLowerCase()) ||\n c.dial.includes(search) ||\n c.iso.toLowerCase().includes(search.toLowerCase())\n );\n\n const finalMessage = error && errorMessage ? errorMessage : hintMessage;\n const showMessage = finalMessage && finalMessage.trim() !== '';\n\n return (\n <div className=\"flex flex-col gap-1.5\">\n {label && (\n <div className=\"flex items-center gap-1 paragraph-xsmall-semibold\">\n <span className=\"text-zinc-600\">{label}</span>\n {optional && <span className=\"text-zinc-500\">(Opcional)</span>}\n {required && <span className=\"text-red-600\">*</span>}\n </div>\n )}\n\n <div\n className={cn(\n 'flex gap-2 items-center border rounded-lg h-10 transition-colors',\n error\n ? 'border-red-600 hover:border-red-700'\n : 'border-zinc-200 hover:border-zinc-400 focus-within:border-zinc-950',\n disabled && 'cursor-not-allowed',\n classnameContainer\n )}\n >\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"flex items-center gap-1.5 px-3 h-full shrink-0 focus:outline-none\"\n disabled={disabled}\n >\n <img src={flagUrl(selectedCountry.iso)} alt={selectedCountry.iso} className=\"w-5 h-5 rounded-full\" />\n <span className=\"paragraph-small-regular text-zinc-950\">{selectedCountry.dial}</span>\n <IconChevronDown size={14} className={cn(\"text-zinc-500 transition-transform duration-200\", open && \"rotate-180\")} />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-72 p-0 z-[1010]\" align=\"start\" side=\"top\">\n <div className=\"flex items-center gap-2 p-2 border-b border-zinc-100\">\n <IconSearch size={14} className=\"text-zinc-400 shrink-0\" />\n <input\n type=\"text\"\n className=\"flex-1 text-sm outline-none placeholder:text-zinc-400\"\n placeholder=\"Buscar país...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n autoFocus\n />\n </div>\n <div className=\"max-h-60 overflow-y-auto overscroll-contain\" onTouchMove={(e) => e.stopPropagation()} onWheel={(e) => e.stopPropagation()}>\n {filtered.map((country) => (\n <button\n key={country.iso}\n type=\"button\"\n className={cn(\n 'flex items-center gap-2.5 w-full px-3 py-2 text-left hover:bg-zinc-50 transition-colors',\n selectedCountry.iso === country.iso && 'bg-zinc-100'\n )}\n onClick={() => handleCountrySelect(country)}\n >\n <img src={flagUrl(country.iso)} alt={country.iso} className=\"w-5 h-5 rounded-full shrink-0\" />\n <span className=\"paragraph-small-regular text-zinc-950 flex-1 truncate\">\n {country.name}\n </span>\n <span className=\"paragraph-small-regular text-zinc-500 shrink-0\">\n {country.dial}\n </span>\n </button>\n ))}\n {filtered.length === 0 && (\n <p className=\"text-sm text-zinc-500 p-3 text-center\">Nenhum país encontrado</p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n\n <Separator orientation=\"vertical\" />\n\n <Input\n ref={inputRef}\n type=\"tel\"\n value={phoneNumber}\n onChange={handleNumberChange}\n placeholder={placeholder}\n disabled={disabled}\n className={cn(\n 'paragraph-small-medium w-full h-full text-zinc-950 border-0 focus-visible:ring-0 focus-visible:ring-offset-0',\n disabled && 'cursor-not-allowed',\n className\n )}\n />\n </div>\n\n {showMessage && (\n <span className={cn('paragraph-xsmall-medium', error ? 'text-red-600' : 'text-zinc-600')}>\n {finalMessage}\n </span>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/ui/form/PhoneInput.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useTranslations } from 'next-intl';\nimport { AsYouType, CountryCode } from 'libphonenumber-js';\nimport { Input, Separator, Popover, PopoverTrigger, PopoverContent, cn } from '@greatapps/common';\nimport { IconChevronDown, IconSearch } from '@tabler/icons-react';\nimport { COUNTRIES, Country, flagUrl, parsePhoneValue } from '../../../utils/countries';\n\nfunction formatNational(digits: string, iso: string): string {\n const raw = digits.replace(/\\D/g, '');\n if (!raw) return '';\n return new AsYouType(iso as CountryCode).input(raw);\n}\n\ninterface PhoneInputProps {\n value?: string;\n onChange?: (value: string) => void;\n label?: string;\n optional?: boolean;\n required?: boolean;\n hintMessage?: string;\n error?: boolean;\n errorMessage?: string;\n placeholder?: string;\n disabled?: boolean;\n classnameContainer?: string;\n className?: string;\n}\n\nexport default function PhoneInput({\n value,\n onChange,\n label,\n optional = false,\n required = false,\n hintMessage,\n error = false,\n errorMessage,\n placeholder,\n disabled = false,\n classnameContainer,\n className,\n}: PhoneInputProps) {\n const translate = useTranslations();\n const parsed = parsePhoneValue(value ?? '');\n const [selectedCountry, setSelectedCountry] = useState<Country>(parsed.country);\n const [phoneNumber, setPhoneNumber] = useState(parsed.number);\n const [search, setSearch] = useState('');\n const [open, setOpen] = useState(false);\n const prevValueRef = useRef(value);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (value !== prevValueRef.current) {\n prevValueRef.current = value;\n const p = parsePhoneValue(value ?? '');\n setSelectedCountry(p.country);\n setPhoneNumber(formatNational(p.number, p.country.iso));\n }\n }, [value]);\n\n const emit = (country: Country, number: string) => {\n onChange?.(`${country.dial} ${number}`);\n };\n\n const handleCountrySelect = (country: Country) => {\n const reformatted = formatNational(phoneNumber, country.iso);\n setSelectedCountry(country);\n setPhoneNumber(reformatted);\n setOpen(false);\n setSearch('');\n emit(country, reformatted);\n };\n\n const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const rawInput = e.target.value;\n const digits = rawInput.replace(/\\D/g, '');\n if (digits.length > 15) return;\n\n const cursorPos = e.target.selectionStart ?? rawInput.length;\n const digitsBeforeCursor = rawInput.slice(0, cursorPos).replace(/\\D/g, '').length;\n\n const formatted = formatNational(rawInput, selectedCountry.iso);\n setPhoneNumber(formatted);\n emit(selectedCountry, formatted);\n\n requestAnimationFrame(() => {\n if (!inputRef.current) return;\n let digitCount = 0;\n let newPos = formatted.length;\n for (let i = 0; i < formatted.length; i++) {\n if (/\\d/.test(formatted[i])) {\n digitCount++;\n if (digitCount === digitsBeforeCursor) {\n newPos = i + 1;\n break;\n }\n }\n }\n inputRef.current.setSelectionRange(newPos, newPos);\n });\n };\n\n const filtered = COUNTRIES.filter(\n (c) =>\n c.name.toLowerCase().includes(search.toLowerCase()) ||\n c.dial.includes(search) ||\n c.iso.toLowerCase().includes(search.toLowerCase())\n );\n\n const finalMessage = error && errorMessage ? errorMessage : hintMessage;\n const showMessage = finalMessage && finalMessage.trim() !== '';\n\n return (\n <div className=\"flex flex-col gap-1.5\">\n {label && (\n <div className=\"flex items-center gap-1 paragraph-xsmall-semibold\">\n <span className=\"text-zinc-600\">{label}</span>\n {optional && <span className=\"text-zinc-500\">{translate('common.forms.phoneInput.optional')}</span>}\n {required && <span className=\"text-red-600\">*</span>}\n </div>\n )}\n\n <div\n className={cn(\n 'flex gap-2 items-center border rounded-lg h-10 transition-colors',\n error\n ? 'border-red-600 hover:border-red-700'\n : 'border-zinc-200 hover:border-zinc-400 focus-within:border-zinc-950',\n disabled && 'cursor-not-allowed',\n classnameContainer\n )}\n >\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"flex items-center gap-1.5 px-3 h-full shrink-0 focus:outline-none\"\n disabled={disabled}\n >\n <img src={flagUrl(selectedCountry.iso)} alt={selectedCountry.iso} className=\"w-5 h-5 rounded-full\" />\n <span className=\"paragraph-small-regular text-zinc-950\">{selectedCountry.dial}</span>\n <IconChevronDown size={14} className={cn(\"text-zinc-500 transition-transform duration-200\", open && \"rotate-180\")} />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-72 p-0 z-[1010]\" align=\"start\" side=\"top\">\n <div className=\"flex items-center gap-2 p-2 border-b border-zinc-100\">\n <IconSearch size={14} className=\"text-zinc-400 shrink-0\" />\n <input\n type=\"text\"\n className=\"flex-1 text-sm outline-none placeholder:text-zinc-400\"\n placeholder={translate('common.forms.phoneInput.searchPlaceholder')}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n autoFocus\n />\n </div>\n <div className=\"max-h-60 overflow-y-auto overscroll-contain\" onTouchMove={(e) => e.stopPropagation()} onWheel={(e) => e.stopPropagation()}>\n {filtered.map((country) => (\n <button\n key={country.iso}\n type=\"button\"\n className={cn(\n 'flex items-center gap-2.5 w-full px-3 py-2 text-left hover:bg-zinc-50 transition-colors',\n selectedCountry.iso === country.iso && 'bg-zinc-100'\n )}\n onClick={() => handleCountrySelect(country)}\n >\n <img src={flagUrl(country.iso)} alt={country.iso} className=\"w-5 h-5 rounded-full shrink-0\" />\n <span className=\"paragraph-small-regular text-zinc-950 flex-1 truncate\">\n {country.name}\n </span>\n <span className=\"paragraph-small-regular text-zinc-500 shrink-0\">\n {country.dial}\n </span>\n </button>\n ))}\n {filtered.length === 0 && (\n <p className=\"text-sm text-zinc-500 p-3 text-center\">{translate('common.forms.phoneInput.emptyMessage')}</p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n\n <Separator orientation=\"vertical\" />\n\n <Input\n ref={inputRef}\n type=\"tel\"\n value={phoneNumber}\n onChange={handleNumberChange}\n placeholder={placeholder}\n disabled={disabled}\n className={cn(\n 'paragraph-small-medium w-full h-full text-zinc-950 border-0 focus-visible:ring-0 focus-visible:ring-offset-0',\n disabled && 'cursor-not-allowed',\n className\n )}\n />\n </div>\n\n {showMessage && (\n <span className={cn('paragraph-xsmall-medium', error ? 'text-red-600' : 'text-zinc-600')}>\n {finalMessage}\n </span>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import {
|
|
4
5
|
Select,
|
|
5
6
|
SelectContent,
|
|
@@ -15,7 +16,7 @@ function SelectField({
|
|
|
15
16
|
hintMessage,
|
|
16
17
|
error = false,
|
|
17
18
|
errorMessage,
|
|
18
|
-
placeholder
|
|
19
|
+
placeholder,
|
|
19
20
|
options,
|
|
20
21
|
value,
|
|
21
22
|
defaultValue,
|
|
@@ -25,6 +26,8 @@ function SelectField({
|
|
|
25
26
|
className,
|
|
26
27
|
containerClassName
|
|
27
28
|
}) {
|
|
29
|
+
const translate = useTranslations();
|
|
30
|
+
const resolvedPlaceholder = placeholder ?? translate("common.forms.select.placeholder");
|
|
28
31
|
const handleValueChange = (selectedValue) => {
|
|
29
32
|
if (!onChange) return;
|
|
30
33
|
const selectedOption = options.find((option) => String(option.value) === selectedValue);
|
|
@@ -37,7 +40,7 @@ function SelectField({
|
|
|
37
40
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1.5", containerClassName), children: [
|
|
38
41
|
label && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 paragraph-xsmall-semibold", children: [
|
|
39
42
|
/* @__PURE__ */ jsx("span", { className: "text-gray-600", children: label }),
|
|
40
|
-
optional && /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: "
|
|
43
|
+
optional && /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: translate("common.forms.select.optional") }),
|
|
41
44
|
required && /* @__PURE__ */ jsx("span", { className: "text-red-600", children: "*" })
|
|
42
45
|
] }),
|
|
43
46
|
/* @__PURE__ */ jsxs(
|
|
@@ -58,7 +61,7 @@ function SelectField({
|
|
|
58
61
|
className
|
|
59
62
|
),
|
|
60
63
|
icon,
|
|
61
|
-
children: /* @__PURE__ */ jsx(SelectValue, { placeholder, children: value !== void 0 && (() => {
|
|
64
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: resolvedPlaceholder, children: value !== void 0 && (() => {
|
|
62
65
|
const selectedOption = options.find((opt) => String(opt.value) === String(value));
|
|
63
66
|
return selectedOption?.displayValue || selectedOption?.label;
|
|
64
67
|
})() })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/ui/form/SelectField.tsx"],"sourcesContent":["'use client';\r\n\r\nimport React from 'react';\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from './Select';\r\nimport { cn } from '../../../infra/utils/clsx';\r\n\r\ntype SelectOption = {\r\n value: string | number | boolean;\r\n label: React.ReactNode;\r\n displayValue?: string;\r\n};\r\n\r\ninterface SelectFieldProps {\r\n options: SelectOption[];\r\n label?: string;\r\n optional?: boolean;\r\n required?: boolean;\r\n hintMessage?: string;\r\n error?: boolean;\r\n errorMessage?: string;\r\n placeholder?: string;\r\n value?: string | number | boolean;\r\n defaultValue?: string | number | boolean;\r\n onChange?: (value: string | number | boolean) => void;\r\n disabled?: boolean;\r\n icon?: React.ReactNode;\r\n className?: string;\r\n containerClassName?: string;\r\n}\r\n\r\nexport function SelectField({\r\n label,\r\n optional = false,\r\n required = false,\r\n hintMessage,\r\n error = false,\r\n errorMessage,\r\n placeholder
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/ui/form/SelectField.tsx"],"sourcesContent":["'use client';\r\n\r\nimport React from 'react';\r\nimport { useTranslations } from 'next-intl';\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from './Select';\r\nimport { cn } from '../../../infra/utils/clsx';\r\n\r\ntype SelectOption = {\r\n value: string | number | boolean;\r\n label: React.ReactNode;\r\n displayValue?: string;\r\n};\r\n\r\ninterface SelectFieldProps {\r\n options: SelectOption[];\r\n label?: string;\r\n optional?: boolean;\r\n required?: boolean;\r\n hintMessage?: string;\r\n error?: boolean;\r\n errorMessage?: string;\r\n placeholder?: string;\r\n value?: string | number | boolean;\r\n defaultValue?: string | number | boolean;\r\n onChange?: (value: string | number | boolean) => void;\r\n disabled?: boolean;\r\n icon?: React.ReactNode;\r\n className?: string;\r\n containerClassName?: string;\r\n}\r\n\r\nexport function SelectField({\r\n label,\r\n optional = false,\r\n required = false,\r\n hintMessage,\r\n error = false,\r\n errorMessage,\r\n placeholder,\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n disabled = false,\r\n icon,\r\n className,\r\n containerClassName,\r\n}: SelectFieldProps) {\r\n const translate = useTranslations();\r\n const resolvedPlaceholder = placeholder ?? translate('common.forms.select.placeholder');\r\n const handleValueChange = (selectedValue: string) => {\r\n if (!onChange) return;\r\n const selectedOption = options.find((option) => String(option.value) === selectedValue);\r\n if (selectedOption) {\r\n onChange(selectedOption.value);\r\n }\r\n };\r\n\r\n const finalMessage = error && errorMessage ? errorMessage : hintMessage;\r\n const messageColor = error ? 'text-red-600' : 'text-gray-600';\r\n\r\n return (\r\n <div className={cn('flex flex-col gap-1.5', containerClassName)}>\r\n {label && (\r\n <div className=\"flex items-center gap-1 paragraph-xsmall-semibold\">\r\n <span className=\"text-gray-600\">{label}</span>\r\n {optional && <span className=\"text-gray-500\">{translate('common.forms.select.optional')}</span>}\r\n {required && <span className=\"text-red-600\">*</span>}\r\n </div>\r\n )}\r\n\r\n <Select\r\n value={value !== undefined ? String(value) : undefined}\r\n defaultValue={defaultValue !== undefined ? String(defaultValue) : undefined}\r\n onValueChange={handleValueChange}\r\n disabled={disabled}\r\n >\r\n <SelectTrigger\r\n className={cn(\r\n 'paragraph-small-medium w-full h-10 text-gray-950 px-4 transition-colors',\r\n error\r\n ? 'border-red-600 hover:border-red-700 focus:border-red-600'\r\n : 'border-gray-200 hover:border-gray-400 focus-within:border-gray-950 focus:border-gray-950',\r\n disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',\r\n className\r\n )}\r\n icon={icon}\r\n >\r\n <SelectValue placeholder={resolvedPlaceholder}>\r\n {value !== undefined &&\r\n (() => {\r\n const selectedOption = options.find((opt) => String(opt.value) === String(value));\r\n return selectedOption?.displayValue || selectedOption?.label;\r\n })()}\r\n </SelectValue>\r\n </SelectTrigger>\r\n <SelectContent sideOffset={4}>\r\n {options.map((option) => (\r\n <SelectItem key={String(option.value)} value={String(option.value)}>\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n\r\n {finalMessage && (\r\n <span className={cn('paragraph-xsmall-medium', messageColor)}>{finalMessage}</span>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nSelectField.displayName = 'SelectField';\r\n\r\nexport default SelectField;\r\n"],"mappings":";AAsEQ,SACE,KADF;AAnER,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,UAAU;AA0BZ,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,YAAY,gBAAgB;AAClC,QAAM,sBAAsB,eAAe,UAAU,iCAAiC;AACtF,QAAM,oBAAoB,CAAC,kBAA0B;AACnD,QAAI,CAAC,SAAU;AACf,UAAM,iBAAiB,QAAQ,KAAK,CAAC,WAAW,OAAO,OAAO,KAAK,MAAM,aAAa;AACtF,QAAI,gBAAgB;AAClB,eAAS,eAAe,KAAK;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,eAAe,SAAS,eAAe,eAAe;AAC5D,QAAM,eAAe,QAAQ,iBAAiB;AAE9C,SACE,qBAAC,SAAI,WAAW,GAAG,yBAAyB,kBAAkB,GAC3D;AAAA,aACC,qBAAC,SAAI,WAAU,qDACb;AAAA,0BAAC,UAAK,WAAU,iBAAiB,iBAAM;AAAA,MACtC,YAAY,oBAAC,UAAK,WAAU,iBAAiB,oBAAU,8BAA8B,GAAE;AAAA,MACvF,YAAY,oBAAC,UAAK,WAAU,gBAAe,eAAC;AAAA,OAC/C;AAAA,IAGF;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,UAAU,SAAY,OAAO,KAAK,IAAI;AAAA,QAC7C,cAAc,iBAAiB,SAAY,OAAO,YAAY,IAAI;AAAA,QAClE,eAAe;AAAA,QACf;AAAA,QAEA;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,QACI,6DACA;AAAA,gBACJ,WAAW,kCAAkC;AAAA,gBAC7C;AAAA,cACF;AAAA,cACA;AAAA,cAEA,8BAAC,eAAY,aAAa,qBACvB,oBAAU,WACR,MAAM;AACL,sBAAM,iBAAiB,QAAQ,KAAK,CAAC,QAAQ,OAAO,IAAI,KAAK,MAAM,OAAO,KAAK,CAAC;AAChF,uBAAO,gBAAgB,gBAAgB,gBAAgB;AAAA,cACzD,GAAG,GACP;AAAA;AAAA,UACF;AAAA,UACA,oBAAC,iBAAc,YAAY,GACxB,kBAAQ,IAAI,CAAC,WACZ,oBAAC,cAAsC,OAAO,OAAO,OAAO,KAAK,GAC9D,iBAAO,SADO,OAAO,OAAO,KAAK,CAEpC,CACD,GACH;AAAA;AAAA;AAAA,IACF;AAAA,IAEC,gBACC,oBAAC,UAAK,WAAW,GAAG,2BAA2B,YAAY,GAAI,wBAAa;AAAA,KAEhF;AAEJ;AAEA,YAAY,cAAc;AAE1B,IAAO,sBAAQ;","names":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
4
5
|
import ReactCrop, {
|
|
5
6
|
centerCrop,
|
|
6
7
|
makeAspectCrop
|
|
@@ -148,6 +149,7 @@ function ImageCropModal({
|
|
|
148
149
|
fileName,
|
|
149
150
|
onSelectAnother
|
|
150
151
|
}) {
|
|
152
|
+
const translate = useTranslations();
|
|
151
153
|
const imgRef = useRef(null);
|
|
152
154
|
const [crop, setCrop] = useState();
|
|
153
155
|
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
|
|
@@ -226,23 +228,14 @@ function ImageCropModal({
|
|
|
226
228
|
className: isTooSmall ? "w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!" : "w-full md:w-[650px]! lg:w-[650px]! max-w-[650px]! max-[650px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!",
|
|
227
229
|
overlayClassName: "z-[1002]!",
|
|
228
230
|
children: isTooSmall ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
229
|
-
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-center text-[16px]!", children: dimensionText ?
|
|
230
|
-
/* @__PURE__ */ jsx("div", { className: "px-5 pb-5", children: /* @__PURE__ */
|
|
231
|
-
fileName ? `O arquivo "${fileName}"` : "A imagem selecionada",
|
|
232
|
-
" \xE9 menor",
|
|
233
|
-
dimensionText ? ` que ${dimensionText}` : " que o tamanho m\xEDnimo exigido",
|
|
234
|
-
".",
|
|
235
|
-
" ",
|
|
236
|
-
"Fa\xE7a envio de uma imagem ",
|
|
237
|
-
dimensionText ? `com pelo menos ${dimensionText}` : "maior",
|
|
238
|
-
" para continuar."
|
|
239
|
-
] }) }),
|
|
231
|
+
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-center text-[16px]!", children: dimensionText ? translate("common.imageUpload.tooSmallTitleWithDimension", { dimensionText }) : translate("common.imageUpload.tooSmallTitleGeneric") }) }),
|
|
232
|
+
/* @__PURE__ */ jsx("div", { className: "px-5 pb-5", children: /* @__PURE__ */ jsx("p", { className: "paragraph-small-regular text-zinc-600 text-center", children: fileName && dimensionText ? translate("common.imageUpload.tooSmallBodyFileWithDimension", { fileName, dimensionText }) : fileName && !dimensionText ? translate("common.imageUpload.tooSmallBodyFileGeneric", { fileName }) : !fileName && dimensionText ? translate("common.imageUpload.tooSmallBodyNoFileWithDimension", { dimensionText }) : translate("common.imageUpload.tooSmallBodyNoFileGeneric") }) }),
|
|
240
233
|
/* @__PURE__ */ jsxs(DialogFooter, { className: "flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200", children: [
|
|
241
|
-
onSelectAnother && /* @__PURE__ */ jsx(Button, { onClick: onSelectAnother, children: "
|
|
242
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: handleCancel, children: "
|
|
234
|
+
onSelectAnother && /* @__PURE__ */ jsx(Button, { onClick: onSelectAnother, children: translate("common.imageUpload.sendAnother") }),
|
|
235
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: handleCancel, children: translate("common.actions.cancel") })
|
|
243
236
|
] })
|
|
244
237
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
245
|
-
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-center text-[16px]!", children: "
|
|
238
|
+
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-center text-[16px]!", children: translate("common.imageUpload.cropTitle") }) }),
|
|
246
239
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 px-5 pb-5", children: [
|
|
247
240
|
/* @__PURE__ */ jsx("div", { className: "flex justify-center overflow-hidden rounded-lg min-h-[200px]", children: imageStatus === "ok" && /* @__PURE__ */ jsx(
|
|
248
241
|
ReactCrop,
|
|
@@ -260,7 +253,7 @@ function ImageCropModal({
|
|
|
260
253
|
{
|
|
261
254
|
ref: imgRef,
|
|
262
255
|
src: imageSrc,
|
|
263
|
-
alt: "
|
|
256
|
+
alt: translate("common.imageUpload.cropAlt"),
|
|
264
257
|
onLoad: onCropImgLoad,
|
|
265
258
|
draggable: false,
|
|
266
259
|
className: "max-h-[50vh] object-contain select-none"
|
|
@@ -268,11 +261,11 @@ function ImageCropModal({
|
|
|
268
261
|
)
|
|
269
262
|
}
|
|
270
263
|
) }),
|
|
271
|
-
/* @__PURE__ */ jsx("p", { className: "paragraph-small-regular text-zinc-500 text-center", children: "
|
|
264
|
+
/* @__PURE__ */ jsx("p", { className: "paragraph-small-regular text-zinc-500 text-center", children: translate("common.imageUpload.cropHint") })
|
|
272
265
|
] }),
|
|
273
266
|
/* @__PURE__ */ jsxs(DialogFooter, { className: "flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200", children: [
|
|
274
|
-
/* @__PURE__ */ jsx(Button, { onClick: handleConfirm, disabled: imageStatus !== "ok" || !crop, children: "
|
|
275
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: handleCancel, children: "
|
|
267
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleConfirm, disabled: imageStatus !== "ok" || !crop, children: translate("common.actions.confirm") }),
|
|
268
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: handleCancel, children: translate("common.actions.cancel") })
|
|
276
269
|
] })
|
|
277
270
|
] })
|
|
278
271
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageCropModal.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef, useState } from 'react'\nimport ReactCrop, {\n type Crop,\n centerCrop,\n makeAspectCrop,\n} from 'react-image-crop'\nimport 'react-image-crop/dist/ReactCrop.css'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\n// CSS customizado para grid pontilhado fino e círculo\nconst baseCropStyles = `\n .custom-crop-area .ReactCrop__crop-selection {\n border: 2px solid white !important;\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5) !important;\n }\n\n /* Grid de 9 quadrados - linhas pontilhadas finas */\n .custom-crop-area .ReactCrop__crop-selection .crop-grid {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line {\n position: absolute;\n background: repeating-linear-gradient(\n to right,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.horizontal {\n left: 0;\n right: 0;\n height: 1px;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.vertical {\n top: 0;\n bottom: 0;\n width: 1px;\n background: repeating-linear-gradient(\n to bottom,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h1 { top: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h2 { top: 66.66%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v1 { left: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v2 { left: 66.66%; }\n\n /* Círculo inscrito */\n .custom-crop-area.with-circle .ReactCrop__crop-selection .crop-circle {\n position: absolute;\n inset: 0;\n border: 1px dashed rgba(255, 255, 255, 0.6);\n border-radius: 50%;\n pointer-events: none;\n }\n\n /* Ocultar handles das laterais (manter só os 4 cantos) */\n .custom-crop-area .ReactCrop__drag-handle.ord-n,\n .custom-crop-area .ReactCrop__drag-handle.ord-s,\n .custom-crop-area .ReactCrop__drag-handle.ord-e,\n .custom-crop-area .ReactCrop__drag-handle.ord-w {\n display: none !important;\n }\n\n /* Estilizar handles dos cantos — indicador preto em L nos cantos */\n .custom-crop-area .ReactCrop__drag-handle.ord-nw,\n .custom-crop-area .ReactCrop__drag-handle.ord-ne,\n .custom-crop-area .ReactCrop__drag-handle.ord-sw,\n .custom-crop-area .ReactCrop__drag-handle.ord-se {\n width: 20px;\n height: 20px;\n background: transparent !important;\n border: none !important;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-nw::after,\n .custom-crop-area .ReactCrop__drag-handle.ord-ne::after,\n .custom-crop-area .ReactCrop__drag-handle.ord-sw::after,\n .custom-crop-area .ReactCrop__drag-handle.ord-se::after {\n content: '';\n position: absolute;\n width: 22px;\n height: 22px;\n border-color: white;\n border-style: solid;\n border-width: 0;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-nw::after {\n top: -2px; left: -2px;\n border-top-width: 4px;\n border-left-width: 4px;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-ne::after {\n top: -2px; right: -2px;\n border-top-width: 4px;\n border-right-width: 4px;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-sw::after {\n bottom: -2px; left: -2px;\n border-bottom-width: 4px;\n border-left-width: 4px;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-se::after {\n bottom: -2px; right: -2px;\n border-bottom-width: 4px;\n border-right-width: 4px;\n }\n\n /* Desabilitar drag da imagem de fundo */\n .custom-crop-area img {\n pointer-events: none;\n user-select: none;\n -webkit-user-drag: none;\n }\n`\n\ninterface ImageCropModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n imageSrc: string\n aspectRatio: number\n onConfirm: (image: HTMLImageElement, crop: Crop) => void\n onCancel?: () => void\n onImageLoad?: (naturalWidth: number, naturalHeight: number) => void\n showCircleGuide?: boolean\n minWidth?: number\n minHeight?: number\n /**\n * Nome do arquivo atual, usado na mensagem da variante \"imagem muito pequena\".\n * Opcional — se ausente, a mensagem cai num texto genérico.\n */\n fileName?: string\n /**\n * Callback disparado quando o usuário clica em \"Enviar outra\" na variante\n * \"imagem muito pequena\". Normalmente abre um file input. Se não for passado,\n * o botão \"Enviar outra\" é escondido.\n */\n onSelectAnother?: () => void\n}\n\ntype ImageStatus = 'loading' | 'too-small' | 'ok'\n\nexport function ImageCropModal({\n open,\n onOpenChange,\n imageSrc,\n aspectRatio,\n onConfirm,\n onCancel,\n onImageLoad: onImageLoadCallback,\n showCircleGuide,\n minWidth,\n minHeight,\n fileName,\n onSelectAnother,\n}: ImageCropModalProps) {\n const imgRef = useRef<HTMLImageElement>(null)\n const [crop, setCrop] = useState<Crop>()\n const [imageSize, setImageSize] = useState({ width: 0, height: 0 })\n const [imageStatus, setImageStatus] = useState<ImageStatus>('loading')\n\n // Ref pra callback externo — evita re-rodar o effect de preload toda vez que\n // a identidade do callback muda.\n const onImageLoadCallbackRef = useRef(onImageLoadCallback)\n useEffect(() => {\n onImageLoadCallbackRef.current = onImageLoadCallback\n })\n\n // Pre-carrega a imagem fora do DOM pra medir dimensões ANTES de montar\n // o ReactCrop. Assim conseguimos decidir entre a variante de crop e a\n // variante \"imagem muito pequena\" sem flash de UI.\n useEffect(() => {\n if (!open || !imageSrc) {\n setImageStatus('loading')\n return\n }\n\n setImageStatus('loading')\n let cancelled = false\n\n const img = new window.Image()\n img.onload = () => {\n if (cancelled) return\n const { naturalWidth, naturalHeight } = img\n setImageSize({ width: naturalWidth, height: naturalHeight })\n onImageLoadCallbackRef.current?.(naturalWidth, naturalHeight)\n\n const tooSmall =\n (minWidth !== undefined && naturalWidth < minWidth) ||\n (minHeight !== undefined && naturalHeight < minHeight)\n setImageStatus(tooSmall ? 'too-small' : 'ok')\n }\n img.onerror = () => {\n if (cancelled) return\n setImageStatus('too-small')\n }\n img.src = imageSrc\n\n return () => {\n cancelled = true\n }\n }, [open, imageSrc, minWidth, minHeight])\n\n // Inicializa o crop quando o <img> do ReactCrop monta. A imagem já está em\n // cache vinda do preload, então carrega instantâneo.\n const onCropImgLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const { naturalWidth, naturalHeight } = e.currentTarget\n const initialCrop = centerCrop(\n makeAspectCrop(\n { unit: '%', width: 90 },\n aspectRatio,\n naturalWidth,\n naturalHeight\n ),\n naturalWidth,\n naturalHeight\n )\n setCrop(initialCrop)\n }\n\n // Calcula tamanho mínimo em porcentagem baseado nas dimensões mínimas em pixels\n const minCropWidth = minWidth && imageSize.width > 0\n ? Math.min((minWidth / imageSize.width) * 100, 100)\n : undefined\n const minCropHeight = minHeight && imageSize.height > 0\n ? Math.min((minHeight / imageSize.height) * 100, 100)\n : undefined\n\n const handleConfirm = () => {\n if (imgRef.current && crop) {\n onConfirm(imgRef.current, crop)\n }\n }\n\n const handleCancel = () => {\n onCancel?.()\n onOpenChange(false)\n }\n\n // Determina se deve mostrar círculo (aspect ratio 1:1 por padrão)\n const shouldShowCircle = showCircleGuide ?? aspectRatio === 1\n\n // Renderiza grid, círculo e bolinhas dentro da área de crop\n const renderSelectionAddon = () => (\n <>\n {/* Grid de 9 quadrados com linhas pontilhadas */}\n <div className=\"crop-grid\">\n <div className=\"crop-grid-line horizontal h1\" />\n <div className=\"crop-grid-line horizontal h2\" />\n <div className=\"crop-grid-line vertical v1\" />\n <div className=\"crop-grid-line vertical v2\" />\n </div>\n {/* Círculo inscrito */}\n {shouldShowCircle && <div className=\"crop-circle\" />}\n </>\n )\n\n const dimensionText =\n minWidth !== undefined && minHeight !== undefined\n ? `${minWidth}x${minHeight}px`\n : null\n\n const isTooSmall = imageStatus === 'too-small'\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <style>{baseCropStyles}</style>\n <DialogContent\n className={\n isTooSmall\n ? 'w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!'\n : 'w-full md:w-[650px]! lg:w-[650px]! max-w-[650px]! max-[650px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!'\n }\n overlayClassName=\"z-[1002]!\"\n >\n {isTooSmall ? (\n <>\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n {dimensionText ? `Foto menor que ${dimensionText}` : 'Foto muito pequena'}\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"px-5 pb-5\">\n <p className=\"paragraph-small-regular text-zinc-600 text-center\">\n {fileName ? `O arquivo \"${fileName}\"` : 'A imagem selecionada'} é menor\n {dimensionText ? ` que ${dimensionText}` : ' que o tamanho mínimo exigido'}.\n {' '}Faça envio de uma imagem {dimensionText ? `com pelo menos ${dimensionText}` : 'maior'} para continuar.\n </p>\n </div>\n\n <DialogFooter className=\"flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200\">\n {onSelectAnother && <Button onClick={onSelectAnother}>Enviar outra</Button>}\n <Button variant=\"secondary\" onClick={handleCancel}>\n Cancelar\n </Button>\n </DialogFooter>\n </>\n ) : (\n <>\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n Ajustar imagem\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"flex flex-col gap-4 px-5 pb-5\">\n <div className=\"flex justify-center overflow-hidden rounded-lg min-h-[200px]\">\n {imageStatus === 'ok' && (\n <ReactCrop\n crop={crop}\n onChange={(c: Crop) => setCrop(c)}\n aspect={aspectRatio}\n className={`max-h-[50vh] custom-crop-area ${shouldShowCircle ? 'with-circle' : ''}`}\n keepSelection\n minWidth={minCropWidth}\n minHeight={minCropHeight}\n renderSelectionAddon={renderSelectionAddon}\n >\n <img\n ref={imgRef}\n src={imageSrc}\n alt=\"Crop\"\n onLoad={onCropImgLoad}\n draggable={false}\n className=\"max-h-[50vh] object-contain select-none\"\n />\n </ReactCrop>\n )}\n </div>\n\n <p className=\"paragraph-small-regular text-zinc-500 text-center\">\n Arraste para ajustar a área de corte\n </p>\n </div>\n\n <DialogFooter className=\"flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200\">\n <Button onClick={handleConfirm} disabled={imageStatus !== 'ok' || !crop}>\n Confirmar\n </Button>\n <Button variant=\"secondary\" onClick={handleCancel}>\n Cancelar\n </Button>\n </DialogFooter>\n </>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageCropModal.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef, useState } from 'react'\nimport { useTranslations } from 'next-intl'\nimport ReactCrop, {\n type Crop,\n centerCrop,\n makeAspectCrop,\n} from 'react-image-crop'\nimport 'react-image-crop/dist/ReactCrop.css'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\n// CSS customizado para grid pontilhado fino e círculo\nconst baseCropStyles = `\n .custom-crop-area .ReactCrop__crop-selection {\n border: 2px solid white !important;\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5) !important;\n }\n\n /* Grid de 9 quadrados - linhas pontilhadas finas */\n .custom-crop-area .ReactCrop__crop-selection .crop-grid {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line {\n position: absolute;\n background: repeating-linear-gradient(\n to right,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.horizontal {\n left: 0;\n right: 0;\n height: 1px;\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.vertical {\n top: 0;\n bottom: 0;\n width: 1px;\n background: repeating-linear-gradient(\n to bottom,\n rgba(255, 255, 255, 0.5) 0,\n rgba(255, 255, 255, 0.5) 4px,\n transparent 4px,\n transparent 8px\n );\n }\n\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h1 { top: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.h2 { top: 66.66%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v1 { left: 33.33%; }\n .custom-crop-area .ReactCrop__crop-selection .crop-grid-line.v2 { left: 66.66%; }\n\n /* Círculo inscrito */\n .custom-crop-area.with-circle .ReactCrop__crop-selection .crop-circle {\n position: absolute;\n inset: 0;\n border: 1px dashed rgba(255, 255, 255, 0.6);\n border-radius: 50%;\n pointer-events: none;\n }\n\n /* Ocultar handles das laterais (manter só os 4 cantos) */\n .custom-crop-area .ReactCrop__drag-handle.ord-n,\n .custom-crop-area .ReactCrop__drag-handle.ord-s,\n .custom-crop-area .ReactCrop__drag-handle.ord-e,\n .custom-crop-area .ReactCrop__drag-handle.ord-w {\n display: none !important;\n }\n\n /* Estilizar handles dos cantos — indicador preto em L nos cantos */\n .custom-crop-area .ReactCrop__drag-handle.ord-nw,\n .custom-crop-area .ReactCrop__drag-handle.ord-ne,\n .custom-crop-area .ReactCrop__drag-handle.ord-sw,\n .custom-crop-area .ReactCrop__drag-handle.ord-se {\n width: 20px;\n height: 20px;\n background: transparent !important;\n border: none !important;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-nw::after,\n .custom-crop-area .ReactCrop__drag-handle.ord-ne::after,\n .custom-crop-area .ReactCrop__drag-handle.ord-sw::after,\n .custom-crop-area .ReactCrop__drag-handle.ord-se::after {\n content: '';\n position: absolute;\n width: 22px;\n height: 22px;\n border-color: white;\n border-style: solid;\n border-width: 0;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-nw::after {\n top: -2px; left: -2px;\n border-top-width: 4px;\n border-left-width: 4px;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-ne::after {\n top: -2px; right: -2px;\n border-top-width: 4px;\n border-right-width: 4px;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-sw::after {\n bottom: -2px; left: -2px;\n border-bottom-width: 4px;\n border-left-width: 4px;\n }\n\n .custom-crop-area .ReactCrop__drag-handle.ord-se::after {\n bottom: -2px; right: -2px;\n border-bottom-width: 4px;\n border-right-width: 4px;\n }\n\n /* Desabilitar drag da imagem de fundo */\n .custom-crop-area img {\n pointer-events: none;\n user-select: none;\n -webkit-user-drag: none;\n }\n`\n\ninterface ImageCropModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n imageSrc: string\n aspectRatio: number\n onConfirm: (image: HTMLImageElement, crop: Crop) => void\n onCancel?: () => void\n onImageLoad?: (naturalWidth: number, naturalHeight: number) => void\n showCircleGuide?: boolean\n minWidth?: number\n minHeight?: number\n /**\n * Nome do arquivo atual, usado na mensagem da variante \"imagem muito pequena\".\n * Opcional — se ausente, a mensagem cai num texto genérico.\n */\n fileName?: string\n /**\n * Callback disparado quando o usuário clica em \"Enviar outra\" na variante\n * \"imagem muito pequena\". Normalmente abre um file input. Se não for passado,\n * o botão \"Enviar outra\" é escondido.\n */\n onSelectAnother?: () => void\n}\n\ntype ImageStatus = 'loading' | 'too-small' | 'ok'\n\nexport function ImageCropModal({\n open,\n onOpenChange,\n imageSrc,\n aspectRatio,\n onConfirm,\n onCancel,\n onImageLoad: onImageLoadCallback,\n showCircleGuide,\n minWidth,\n minHeight,\n fileName,\n onSelectAnother,\n}: ImageCropModalProps) {\n const translate = useTranslations()\n const imgRef = useRef<HTMLImageElement>(null)\n const [crop, setCrop] = useState<Crop>()\n const [imageSize, setImageSize] = useState({ width: 0, height: 0 })\n const [imageStatus, setImageStatus] = useState<ImageStatus>('loading')\n\n // Ref pra callback externo — evita re-rodar o effect de preload toda vez que\n // a identidade do callback muda.\n const onImageLoadCallbackRef = useRef(onImageLoadCallback)\n useEffect(() => {\n onImageLoadCallbackRef.current = onImageLoadCallback\n })\n\n // Pre-carrega a imagem fora do DOM pra medir dimensões ANTES de montar\n // o ReactCrop. Assim conseguimos decidir entre a variante de crop e a\n // variante \"imagem muito pequena\" sem flash de UI.\n useEffect(() => {\n if (!open || !imageSrc) {\n setImageStatus('loading')\n return\n }\n\n setImageStatus('loading')\n let cancelled = false\n\n const img = new window.Image()\n img.onload = () => {\n if (cancelled) return\n const { naturalWidth, naturalHeight } = img\n setImageSize({ width: naturalWidth, height: naturalHeight })\n onImageLoadCallbackRef.current?.(naturalWidth, naturalHeight)\n\n const tooSmall =\n (minWidth !== undefined && naturalWidth < minWidth) ||\n (minHeight !== undefined && naturalHeight < minHeight)\n setImageStatus(tooSmall ? 'too-small' : 'ok')\n }\n img.onerror = () => {\n if (cancelled) return\n setImageStatus('too-small')\n }\n img.src = imageSrc\n\n return () => {\n cancelled = true\n }\n }, [open, imageSrc, minWidth, minHeight])\n\n // Inicializa o crop quando o <img> do ReactCrop monta. A imagem já está em\n // cache vinda do preload, então carrega instantâneo.\n const onCropImgLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const { naturalWidth, naturalHeight } = e.currentTarget\n const initialCrop = centerCrop(\n makeAspectCrop(\n { unit: '%', width: 90 },\n aspectRatio,\n naturalWidth,\n naturalHeight\n ),\n naturalWidth,\n naturalHeight\n )\n setCrop(initialCrop)\n }\n\n // Calcula tamanho mínimo em porcentagem baseado nas dimensões mínimas em pixels\n const minCropWidth = minWidth && imageSize.width > 0\n ? Math.min((minWidth / imageSize.width) * 100, 100)\n : undefined\n const minCropHeight = minHeight && imageSize.height > 0\n ? Math.min((minHeight / imageSize.height) * 100, 100)\n : undefined\n\n const handleConfirm = () => {\n if (imgRef.current && crop) {\n onConfirm(imgRef.current, crop)\n }\n }\n\n const handleCancel = () => {\n onCancel?.()\n onOpenChange(false)\n }\n\n // Determina se deve mostrar círculo (aspect ratio 1:1 por padrão)\n const shouldShowCircle = showCircleGuide ?? aspectRatio === 1\n\n // Renderiza grid, círculo e bolinhas dentro da área de crop\n const renderSelectionAddon = () => (\n <>\n {/* Grid de 9 quadrados com linhas pontilhadas */}\n <div className=\"crop-grid\">\n <div className=\"crop-grid-line horizontal h1\" />\n <div className=\"crop-grid-line horizontal h2\" />\n <div className=\"crop-grid-line vertical v1\" />\n <div className=\"crop-grid-line vertical v2\" />\n </div>\n {/* Círculo inscrito */}\n {shouldShowCircle && <div className=\"crop-circle\" />}\n </>\n )\n\n const dimensionText =\n minWidth !== undefined && minHeight !== undefined\n ? `${minWidth}x${minHeight}px`\n : null\n\n const isTooSmall = imageStatus === 'too-small'\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <style>{baseCropStyles}</style>\n <DialogContent\n className={\n isTooSmall\n ? 'w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!'\n : 'w-full md:w-[650px]! lg:w-[650px]! max-w-[650px]! max-[650px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!'\n }\n overlayClassName=\"z-[1002]!\"\n >\n {isTooSmall ? (\n <>\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n {dimensionText\n ? translate('common.imageUpload.tooSmallTitleWithDimension', { dimensionText })\n : translate('common.imageUpload.tooSmallTitleGeneric')}\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"px-5 pb-5\">\n <p className=\"paragraph-small-regular text-zinc-600 text-center\">\n {fileName && dimensionText\n ? translate('common.imageUpload.tooSmallBodyFileWithDimension', { fileName, dimensionText })\n : fileName && !dimensionText\n ? translate('common.imageUpload.tooSmallBodyFileGeneric', { fileName })\n : !fileName && dimensionText\n ? translate('common.imageUpload.tooSmallBodyNoFileWithDimension', { dimensionText })\n : translate('common.imageUpload.tooSmallBodyNoFileGeneric')}\n </p>\n </div>\n\n <DialogFooter className=\"flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200\">\n {onSelectAnother && <Button onClick={onSelectAnother}>{translate('common.imageUpload.sendAnother')}</Button>}\n <Button variant=\"secondary\" onClick={handleCancel}>\n {translate('common.actions.cancel')}\n </Button>\n </DialogFooter>\n </>\n ) : (\n <>\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n {translate('common.imageUpload.cropTitle')}\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"flex flex-col gap-4 px-5 pb-5\">\n <div className=\"flex justify-center overflow-hidden rounded-lg min-h-[200px]\">\n {imageStatus === 'ok' && (\n <ReactCrop\n crop={crop}\n onChange={(c: Crop) => setCrop(c)}\n aspect={aspectRatio}\n className={`max-h-[50vh] custom-crop-area ${shouldShowCircle ? 'with-circle' : ''}`}\n keepSelection\n minWidth={minCropWidth}\n minHeight={minCropHeight}\n renderSelectionAddon={renderSelectionAddon}\n >\n <img\n ref={imgRef}\n src={imageSrc}\n alt={translate('common.imageUpload.cropAlt')}\n onLoad={onCropImgLoad}\n draggable={false}\n className=\"max-h-[50vh] object-contain select-none\"\n />\n </ReactCrop>\n )}\n </div>\n\n <p className=\"paragraph-small-regular text-zinc-500 text-center\">\n {translate('common.imageUpload.cropHint')}\n </p>\n </div>\n\n <DialogFooter className=\"flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200\">\n <Button onClick={handleConfirm} disabled={imageStatus !== 'ok' || !crop}>\n {translate('common.actions.confirm')}\n </Button>\n <Button variant=\"secondary\" onClick={handleCancel}>\n {translate('common.actions.cancel')}\n </Button>\n </DialogFooter>\n </>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import {
|
|
4
5
|
Dialog,
|
|
5
6
|
DialogContent,
|
|
@@ -17,6 +18,7 @@ function ImageTooSmallModal({
|
|
|
17
18
|
onSelectAnother,
|
|
18
19
|
onCancel
|
|
19
20
|
}) {
|
|
21
|
+
const translate = useTranslations();
|
|
20
22
|
const dimensionText = `${minWidth}x${minHeight}px`;
|
|
21
23
|
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
|
|
22
24
|
DialogContent,
|
|
@@ -24,22 +26,11 @@ function ImageTooSmallModal({
|
|
|
24
26
|
className: "w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!",
|
|
25
27
|
overlayClassName: "z-[1002]!",
|
|
26
28
|
children: [
|
|
27
|
-
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */
|
|
28
|
-
|
|
29
|
-
dimensionText
|
|
30
|
-
] }) }),
|
|
31
|
-
/* @__PURE__ */ jsx("div", { className: "px-5 pb-5", children: /* @__PURE__ */ jsxs("p", { className: "paragraph-small-regular text-zinc-600 text-center", children: [
|
|
32
|
-
'O arquivo "',
|
|
33
|
-
fileName,
|
|
34
|
-
'" \xE9 menor que ',
|
|
35
|
-
dimensionText,
|
|
36
|
-
". Fa\xE7a envio de uma imagem com pelo menos ",
|
|
37
|
-
dimensionText,
|
|
38
|
-
" para continuar."
|
|
39
|
-
] }) }),
|
|
29
|
+
/* @__PURE__ */ jsx(DialogHeader, { className: "p-5", children: /* @__PURE__ */ jsx(DialogTitle, { className: "text-center text-[16px]!", children: translate("common.imageUpload.tooSmallTitleWithDimension", { dimensionText }) }) }),
|
|
30
|
+
/* @__PURE__ */ jsx("div", { className: "px-5 pb-5", children: /* @__PURE__ */ jsx("p", { className: "paragraph-small-regular text-zinc-600 text-center", children: translate("common.imageUpload.tooSmallBodyFileWithDimension", { fileName, dimensionText }) }) }),
|
|
40
31
|
/* @__PURE__ */ jsxs(DialogFooter, { className: "flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200", children: [
|
|
41
|
-
/* @__PURE__ */ jsx(Button, { onClick: onSelectAnother, children: "
|
|
42
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onCancel, children: "
|
|
32
|
+
/* @__PURE__ */ jsx(Button, { onClick: onSelectAnother, children: translate("common.imageUpload.sendAnother") }),
|
|
33
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onCancel, children: translate("common.actions.cancel") })
|
|
43
34
|
] })
|
|
44
35
|
]
|
|
45
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageTooSmallModal.tsx"],"sourcesContent":["'use client'\n\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\ninterface ImageTooSmallModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n fileName: string\n minWidth: number\n minHeight: number\n onSelectAnother: () => void\n onCancel: () => void\n}\n\nexport function ImageTooSmallModal({\n open,\n onOpenChange,\n fileName,\n minWidth,\n minHeight,\n onSelectAnother,\n onCancel,\n}: ImageTooSmallModalProps) {\n const dimensionText = `${minWidth}x${minHeight}px`\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n className=\"w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!\"\n overlayClassName=\"z-[1002]!\"\n >\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/widgets/ImageUpload/ImageTooSmallModal.tsx"],"sourcesContent":["'use client'\n\nimport { useTranslations } from 'next-intl'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../../ui/overlay/Dialog'\nimport { Button } from '../../ui/buttons/Button'\n\ninterface ImageTooSmallModalProps {\n open: boolean\n onOpenChange: (open: boolean) => void\n fileName: string\n minWidth: number\n minHeight: number\n onSelectAnother: () => void\n onCancel: () => void\n}\n\nexport function ImageTooSmallModal({\n open,\n onOpenChange,\n fileName,\n minWidth,\n minHeight,\n onSelectAnother,\n onCancel,\n}: ImageTooSmallModalProps) {\n const translate = useTranslations()\n const dimensionText = `${minWidth}x${minHeight}px`\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n className=\"w-full md:w-[480px]! lg:w-[480px]! max-w-[480px]! max-[480px]:w-[calc(100%-32px)] p-0 gap-0 z-[1003]!\"\n overlayClassName=\"z-[1002]!\"\n >\n <DialogHeader className=\"p-5\">\n <DialogTitle className=\"text-center text-[16px]!\">\n {translate('common.imageUpload.tooSmallTitleWithDimension', { dimensionText })}\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"px-5 pb-5\">\n <p className=\"paragraph-small-regular text-zinc-600 text-center\">\n {translate('common.imageUpload.tooSmallBodyFileWithDimension', { fileName, dimensionText })}\n </p>\n </div>\n\n <DialogFooter className=\"flex-row! items-center justify-start! gap-3 p-5 border-t border-zinc-200\">\n <Button onClick={onSelectAnother}>{translate('common.imageUpload.sendAnother')}</Button>\n <Button variant=\"secondary\" onClick={onCancel}>\n {translate('common.actions.cancel')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":";AAyCU,cAWF,YAXE;AAvCV,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AAYhB,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,YAAY,gBAAgB;AAClC,QAAM,gBAAgB,GAAG,QAAQ,IAAI,SAAS;AAE9C,SACE,oBAAC,UAAO,MAAY,cAClB;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,kBAAiB;AAAA,MAEjB;AAAA,4BAAC,gBAAa,WAAU,OACtB,8BAAC,eAAY,WAAU,4BACpB,oBAAU,iDAAiD,EAAE,cAAc,CAAC,GAC/E,GACF;AAAA,QAEA,oBAAC,SAAI,WAAU,aACb,8BAAC,OAAE,WAAU,qDACV,oBAAU,oDAAoD,EAAE,UAAU,cAAc,CAAC,GAC5F,GACF;AAAA,QAEA,qBAAC,gBAAa,WAAU,4EACtB;AAAA,8BAAC,UAAO,SAAS,iBAAkB,oBAAU,gCAAgC,GAAE;AAAA,UAC/E,oBAAC,UAAO,SAAQ,aAAY,SAAS,UAClC,oBAAU,uBAAuB,GACpC;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const LOCALES = ["pt-br", "en-us", "es-es"];
|
|
2
|
+
const DEFAULT_LOCALE = "pt-br";
|
|
3
|
+
const LOCALE_COOKIE = "NEXT_LOCALE";
|
|
4
|
+
function isLocale(value) {
|
|
5
|
+
return typeof value === "string" && LOCALES.includes(value);
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
DEFAULT_LOCALE,
|
|
9
|
+
LOCALES,
|
|
10
|
+
LOCALE_COOKIE,
|
|
11
|
+
isLocale
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/i18n/config.ts"],"sourcesContent":["/**\n * Locales suportados pela plataforma GreatApps.\n * Identificadores no padrão `idioma-REGIÃO` em minúsculas: `pt-br`, `en-us`, `es-es`.\n */\nexport const LOCALES = ['pt-br', 'en-us', 'es-es'] as const;\n\nexport type Locale = (typeof LOCALES)[number];\n\n/** Idioma padrão — fallback final (o produto nasce em pt-BR). */\nexport const DEFAULT_LOCALE: Locale = 'pt-br';\n\n/** Nome do cookie onde o locale resolvido é persistido (convenção next-intl). */\nexport const LOCALE_COOKIE = 'NEXT_LOCALE';\n\nexport function isLocale(value: unknown): value is Locale {\n return typeof value === 'string' && (LOCALES as readonly string[]).includes(value);\n}\n"],"mappings":"AAIO,MAAM,UAAU,CAAC,SAAS,SAAS,OAAO;AAK1C,MAAM,iBAAyB;AAG/B,MAAM,gBAAgB;AAEtB,SAAS,SAAS,OAAiC;AACxD,SAAO,OAAO,UAAU,YAAa,QAA8B,SAAS,KAAK;AACnF;","names":[]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const PORTUGUESE_COUNTRIES = /* @__PURE__ */ new Set([
|
|
2
|
+
"BR",
|
|
3
|
+
// Brasil
|
|
4
|
+
"PT",
|
|
5
|
+
// Portugal
|
|
6
|
+
"AO",
|
|
7
|
+
// Angola
|
|
8
|
+
"MZ",
|
|
9
|
+
// Moçambique
|
|
10
|
+
"CV",
|
|
11
|
+
// Cabo Verde
|
|
12
|
+
"GW",
|
|
13
|
+
// Guiné-Bissau
|
|
14
|
+
"ST",
|
|
15
|
+
// São Tomé e Príncipe
|
|
16
|
+
"TL"
|
|
17
|
+
// Timor-Leste
|
|
18
|
+
]);
|
|
19
|
+
const SPANISH_COUNTRIES = /* @__PURE__ */ new Set([
|
|
20
|
+
"ES",
|
|
21
|
+
// Espanha
|
|
22
|
+
"MX",
|
|
23
|
+
// México
|
|
24
|
+
"AR",
|
|
25
|
+
// Argentina
|
|
26
|
+
"CO",
|
|
27
|
+
// Colômbia
|
|
28
|
+
"PE",
|
|
29
|
+
// Peru
|
|
30
|
+
"VE",
|
|
31
|
+
// Venezuela
|
|
32
|
+
"CL",
|
|
33
|
+
// Chile
|
|
34
|
+
"EC",
|
|
35
|
+
// Equador
|
|
36
|
+
"GT",
|
|
37
|
+
// Guatemala
|
|
38
|
+
"CU",
|
|
39
|
+
// Cuba
|
|
40
|
+
"BO",
|
|
41
|
+
// Bolívia
|
|
42
|
+
"DO",
|
|
43
|
+
// República Dominicana
|
|
44
|
+
"HN",
|
|
45
|
+
// Honduras
|
|
46
|
+
"PY",
|
|
47
|
+
// Paraguai
|
|
48
|
+
"SV",
|
|
49
|
+
// El Salvador
|
|
50
|
+
"NI",
|
|
51
|
+
// Nicarágua
|
|
52
|
+
"CR",
|
|
53
|
+
// Costa Rica
|
|
54
|
+
"PA",
|
|
55
|
+
// Panamá
|
|
56
|
+
"UY",
|
|
57
|
+
// Uruguai
|
|
58
|
+
"PR",
|
|
59
|
+
// Porto Rico
|
|
60
|
+
"GQ"
|
|
61
|
+
// Guiné Equatorial
|
|
62
|
+
]);
|
|
63
|
+
function localeFromCountry(country) {
|
|
64
|
+
if (!country) return null;
|
|
65
|
+
const code = country.trim().toUpperCase();
|
|
66
|
+
if (code === "XX" || code === "T1") return null;
|
|
67
|
+
if (PORTUGUESE_COUNTRIES.has(code)) return "pt-br";
|
|
68
|
+
if (SPANISH_COUNTRIES.has(code)) return "es-es";
|
|
69
|
+
return "en-us";
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
localeFromCountry
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=country-language.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/i18n/country-language.ts"],"sourcesContent":["import { Locale } from './config';\n\n/**\n * Países de língua portuguesa (ISO 3166-1 alpha-2).\n */\nconst PORTUGUESE_COUNTRIES = new Set<string>([\n 'BR', // Brasil\n 'PT', // Portugal\n 'AO', // Angola\n 'MZ', // Moçambique\n 'CV', // Cabo Verde\n 'GW', // Guiné-Bissau\n 'ST', // São Tomé e Príncipe\n 'TL', // Timor-Leste\n]);\n\n/**\n * Países de língua espanhola (ISO 3166-1 alpha-2).\n */\nconst SPANISH_COUNTRIES = new Set<string>([\n 'ES', // Espanha\n 'MX', // México\n 'AR', // Argentina\n 'CO', // Colômbia\n 'PE', // Peru\n 'VE', // Venezuela\n 'CL', // Chile\n 'EC', // Equador\n 'GT', // Guatemala\n 'CU', // Cuba\n 'BO', // Bolívia\n 'DO', // República Dominicana\n 'HN', // Honduras\n 'PY', // Paraguai\n 'SV', // El Salvador\n 'NI', // Nicarágua\n 'CR', // Costa Rica\n 'PA', // Panamá\n 'UY', // Uruguai\n 'PR', // Porto Rico\n 'GQ', // Guiné Equatorial\n]);\n\n/**\n * Infere o idioma a partir do código de país (ex.: header `CF-IPCountry` da Cloudflare\n * ou `session.location.country` do JWT):\n * - país lusófono → `pt`\n * - país hispanófono → `es`\n * - demais países → `en`\n *\n * Retorna `null` quando o país é desconhecido/ausente (deixa o fallback de locale decidir).\n */\nexport function localeFromCountry(country?: string | null): Locale | null {\n if (!country) return null;\n const code = country.trim().toUpperCase();\n if (code === 'XX' || code === 'T1') return null; // Cloudflare: desconhecido / Tor\n if (PORTUGUESE_COUNTRIES.has(code)) return 'pt-br';\n if (SPANISH_COUNTRIES.has(code)) return 'es-es';\n return 'en-us';\n}\n"],"mappings":"AAKA,MAAM,uBAAuB,oBAAI,IAAY;AAAA,EAC3C;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF,CAAC;AAKD,MAAM,oBAAoB,oBAAI,IAAY;AAAA,EACxC;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF,CAAC;AAWM,SAAS,kBAAkB,SAAwC;AACxE,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,OAAO,QAAQ,KAAK,EAAE,YAAY;AACxC,MAAI,SAAS,QAAQ,SAAS,KAAM,QAAO;AAC3C,MAAI,qBAAqB,IAAI,IAAI,EAAG,QAAO;AAC3C,MAAI,kBAAkB,IAAI,IAAI,EAAG,QAAO;AACxC,SAAO;AACT;","names":[]}
|