@greatapps/common 1.1.729 → 1.1.731

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.
@@ -103,7 +103,7 @@ function ConfigurationsMyAccountModal() {
103
103
  /* @__PURE__ */ jsx(IconSettings2, { size: 20, className: "shrink-0" }),
104
104
  /* @__PURE__ */ jsx("span", { className: "paragraph-small-medium", children: translate("common.preferences.title") })
105
105
  ] }),
106
- isOwner && whitelabel?.id === 1 && /* @__PURE__ */ jsxs(TabsTrigger, { value: AccountSectionType.TOKEN, className: tabTriggerClasses, children: [
106
+ isOwner && /* @__PURE__ */ jsxs(TabsTrigger, { value: AccountSectionType.TOKEN, className: tabTriggerClasses, children: [
107
107
  /* @__PURE__ */ jsx(IconKey, { size: 20, className: "shrink-0" }),
108
108
  /* @__PURE__ */ jsx("span", { className: "paragraph-small-medium", children: translate("common.account.token.title") })
109
109
  ] }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/account/ConfigurationsMyAccountModal.tsx"],"sourcesContent":["'use client';\n\nimport { IconKey, IconLock, IconSettings2, IconX } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/overlay/Dialog';\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/data-display/Tabs';\nimport { cn } from '../../infra/utils/clsx';\nimport { AccountSectionType } from '../../enums/AccountSectionType';\nimport { PreferencesSection } from './sections/PreferencesSection';\nimport { ChangePasswordSection } from './sections/ChangePasswordSection';\nimport { TokenSection } from './sections/TokenSection';\nimport { SecuritySection } from './sections/SecuritySection';\nimport useConfigurationsModal from './hooks/useConfigurationsModal';\nimport useIsMobile from '../../hooks/useIsMobile';\nimport { useAccountModals } from '../../store/useAccountModals';\nimport { useAuth } from '../../providers/auth.provider';\nimport { UserProfile } from '../../modules/users/schema';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { hasSubscriptionExpired } from '../../modules/subscriptions/utils/has-subscription-expired';\nimport { useWhitelabel } from '../../providers/whitelabel.provider';\n\nexport default function ConfigurationsMyAccountModal() {\n const translate = useTranslations();\n const {\n activeModal,\n openDeleteAccount,\n openIsntPossibleDelete,\n close,\n } = useAccountModals();\n const { whitelabel } = useWhitelabel();\n\n const open = activeModal === 'configurations';\n\n const { data: subscriptionData } = useActiveSubscription();\n const subscription = subscriptionData?.data?.[0] ?? null;\n const isPaidSubscriptionType =\n !!subscription &&\n subscription.type !== 'free' &&\n subscription.type !== 'trial';\n const isBlockingSubscription =\n isPaidSubscriptionType &&\n subscription.active === true &&\n !subscription.date_cancellation &&\n !hasSubscriptionExpired(subscription.date_due);\n\n const { user } = useAuth();\n const isOwner = user?.profile === UserProfile.owner;\n\n const isMobile = useIsMobile();\n const { activeSection, setActiveSection } = useConfigurationsModal({\n isOpen: open,\n });\n\n const handleDeleteAccount = () => {\n close();\n if (isBlockingSubscription) {\n openIsntPossibleDelete();\n } else {\n openDeleteAccount();\n }\n };\n\n const tabTriggerClasses = cn(\n 'border-0! px-0 py-0',\n\n 'lg:rounded-md lg:flex lg:items-center lg:gap-2 lg:p-3 lg:justify-start lg:w-full',\n 'lg:bg-white lg:hover:bg-gray-100 lg:active:bg-gray-50 lg:border-0',\n 'lg:data-[state=active]:bg-gray-50',\n\n 'flex items-center gap-1.5 px-3 py-3 justify-center whitespace-nowrap',\n 'border-b-2 border-transparent',\n 'data-[state=active]:bg-gray-50 rounded-lg',\n\n 'text-gray-500 data-[state=active]:text-gray-950',\n 'hover:text-gray-900 transition-colors'\n );\n\n return (\n <Dialog open={open} onOpenChange={close}>\n <DialogContent\n showCloseButton={!isMobile}\n overlayClassName=\"bg-black/20 lg:bg-black/50\"\n className=\"\n flex flex-col p-0 gap-0 max-w-full sm:max-w-full overflow-x-hidden border-0\n rounded-t-2xl rounded-b-none h-[100dvh] top-0 bottom-0 left-0 right-0 translate-x-0 translate-y-0\n lg:flex-row lg:w-[892px] lg:max-w-[892px] lg:h-[626px] lg:rounded-lg lg:border\n lg:top-[50%] lg:bottom-auto lg:left-[50%] lg:right-auto lg:translate-x-[-50%] lg:translate-y-[-50%]\"\n >\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{translate('common.account.configurations.title')}</DialogTitle>\n </DialogHeader>\n <div className=\"md:hidden flex items-center justify-between px-5 py-3 border-b border-gray-200\">\n <span className=\"text-gray-950 paragraph-medium-semibold\">\n {translate('common.account.configurations.title')}\n </span>\n <button\n type=\"button\"\n className=\"size-8 flex items-center justify-center text-gray-500 cursor-pointer\"\n onClick={close}\n >\n <IconX size={18} />\n </button>\n </div>\n <Tabs\n value={activeSection}\n onValueChange={(value) => setActiveSection(value as AccountSectionType)}\n orientation=\"vertical\"\n className=\"flex lg:flex-row flex-col w-full h-full flex-1 lg:h-full lg:pt-0 lg:gap-0\"\n >\n <TabsList\n className={cn(\n 'flex gap-0 bg-transparent border-b-0',\n\n 'lg:flex-col lg:items-stretch lg:justify-start lg:w-auto lg:min-w-60',\n 'lg:border-r lg:border-gray-200 lg:pl-5 lg:pr-5 lg:pt-5 lg:gap-5 lg:overflow-x-visible lg:self-stretch',\n\n 'flex-row items-center justify-start w-full overflow-x-auto scrollbar-hide shrink-0',\n 'px-4 py-3',\n '[mask-image:linear-gradient(to_right,transparent,black_16px,black_calc(100%-24px),transparent)] lg:[mask-image:none]'\n )}\n >\n <span className=\"hidden lg:block paragraph-medium-semibold text-gray-950 mb-0\">\n {translate('common.account.configurations.title')}\n </span>\n\n <div className=\"lg:flex lg:flex-col flex flex-row lg:gap-0 gap-0 lg:w-full\">\n <TabsTrigger value={AccountSectionType.PREFERENCES} className={tabTriggerClasses}>\n <IconSettings2 size={20} className=\"shrink-0\" />\n <span className=\"paragraph-small-medium\">{translate('common.preferences.title')}</span>\n </TabsTrigger>\n\n {isOwner && whitelabel?.id === 1 && (\n <TabsTrigger value={AccountSectionType.TOKEN} className={tabTriggerClasses}>\n <IconKey size={20} className=\"shrink-0\" />\n <span className=\"paragraph-small-medium\">{translate('common.account.token.title')}</span>\n </TabsTrigger>\n )}\n\n {isOwner && (\n <TabsTrigger value={AccountSectionType.SECURITY} className={tabTriggerClasses}>\n <IconLock size={20} className=\"shrink-0\" />\n <span className=\"paragraph-small-medium\">{translate('common.account.security.title')}</span>\n </TabsTrigger>\n )}\n </div>\n </TabsList>\n\n <div className=\"w-full flex-1 min-h-0 lg:h-full overflow-hidden lg:overflow-visible relative flex flex-col\">\n <TabsContent value={AccountSectionType.PREFERENCES} className=\"h-full! relative overflow-hidden\">\n <PreferencesSection onClose={close} />\n </TabsContent>\n\n {isOwner && (\n <TabsContent value={AccountSectionType.TOKEN} className=\"h-full! relative overflow-hidden\">\n <TokenSection />\n </TabsContent>\n )}\n\n {isOwner && (\n <>\n <TabsContent value={AccountSectionType.SECURITY} className=\"h-full! relative overflow-hidden\">\n <SecuritySection\n setActiveSection={setActiveSection}\n onClose={close}\n onDeleteAccount={handleDeleteAccount}\n />\n </TabsContent>\n\n <TabsContent value={AccountSectionType.CHANGE_PASSWORD} className=\"h-full! relative overflow-hidden\">\n <ChangePasswordSection onBack={() => setActiveSection(AccountSectionType.SECURITY)} />\n </TabsContent>\n </>\n )}\n </div>\n </Tabs>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/account/ConfigurationsMyAccountModal.tsx"],"sourcesContent":["'use client';\n\nimport { IconKey, IconLock, IconSettings2, IconX } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/overlay/Dialog';\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/data-display/Tabs';\nimport { cn } from '../../infra/utils/clsx';\nimport { AccountSectionType } from '../../enums/AccountSectionType';\nimport { PreferencesSection } from './sections/PreferencesSection';\nimport { ChangePasswordSection } from './sections/ChangePasswordSection';\nimport { TokenSection } from './sections/TokenSection';\nimport { SecuritySection } from './sections/SecuritySection';\nimport useConfigurationsModal from './hooks/useConfigurationsModal';\nimport useIsMobile from '../../hooks/useIsMobile';\nimport { useAccountModals } from '../../store/useAccountModals';\nimport { useAuth } from '../../providers/auth.provider';\nimport { UserProfile } from '../../modules/users/schema';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { hasSubscriptionExpired } from '../../modules/subscriptions/utils/has-subscription-expired';\nimport { useWhitelabel } from '../../providers/whitelabel.provider';\n\nexport default function ConfigurationsMyAccountModal() {\n const translate = useTranslations();\n const {\n activeModal,\n openDeleteAccount,\n openIsntPossibleDelete,\n close,\n } = useAccountModals();\n const { whitelabel } = useWhitelabel();\n\n const open = activeModal === 'configurations';\n\n const { data: subscriptionData } = useActiveSubscription();\n const subscription = subscriptionData?.data?.[0] ?? null;\n const isPaidSubscriptionType =\n !!subscription &&\n subscription.type !== 'free' &&\n subscription.type !== 'trial';\n const isBlockingSubscription =\n isPaidSubscriptionType &&\n subscription.active === true &&\n !subscription.date_cancellation &&\n !hasSubscriptionExpired(subscription.date_due);\n\n const { user } = useAuth();\n const isOwner = user?.profile === UserProfile.owner;\n\n const isMobile = useIsMobile();\n const { activeSection, setActiveSection } = useConfigurationsModal({\n isOpen: open,\n });\n\n const handleDeleteAccount = () => {\n close();\n if (isBlockingSubscription) {\n openIsntPossibleDelete();\n } else {\n openDeleteAccount();\n }\n };\n\n const tabTriggerClasses = cn(\n 'border-0! px-0 py-0',\n\n 'lg:rounded-md lg:flex lg:items-center lg:gap-2 lg:p-3 lg:justify-start lg:w-full',\n 'lg:bg-white lg:hover:bg-gray-100 lg:active:bg-gray-50 lg:border-0',\n 'lg:data-[state=active]:bg-gray-50',\n\n 'flex items-center gap-1.5 px-3 py-3 justify-center whitespace-nowrap',\n 'border-b-2 border-transparent',\n 'data-[state=active]:bg-gray-50 rounded-lg',\n\n 'text-gray-500 data-[state=active]:text-gray-950',\n 'hover:text-gray-900 transition-colors'\n );\n\n return (\n <Dialog open={open} onOpenChange={close}>\n <DialogContent\n showCloseButton={!isMobile}\n overlayClassName=\"bg-black/20 lg:bg-black/50\"\n className=\"\n flex flex-col p-0 gap-0 max-w-full sm:max-w-full overflow-x-hidden border-0\n rounded-t-2xl rounded-b-none h-[100dvh] top-0 bottom-0 left-0 right-0 translate-x-0 translate-y-0\n lg:flex-row lg:w-[892px] lg:max-w-[892px] lg:h-[626px] lg:rounded-lg lg:border\n lg:top-[50%] lg:bottom-auto lg:left-[50%] lg:right-auto lg:translate-x-[-50%] lg:translate-y-[-50%]\"\n >\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{translate('common.account.configurations.title')}</DialogTitle>\n </DialogHeader>\n <div className=\"md:hidden flex items-center justify-between px-5 py-3 border-b border-gray-200\">\n <span className=\"text-gray-950 paragraph-medium-semibold\">\n {translate('common.account.configurations.title')}\n </span>\n <button\n type=\"button\"\n className=\"size-8 flex items-center justify-center text-gray-500 cursor-pointer\"\n onClick={close}\n >\n <IconX size={18} />\n </button>\n </div>\n <Tabs\n value={activeSection}\n onValueChange={(value) => setActiveSection(value as AccountSectionType)}\n orientation=\"vertical\"\n className=\"flex lg:flex-row flex-col w-full h-full flex-1 lg:h-full lg:pt-0 lg:gap-0\"\n >\n <TabsList\n className={cn(\n 'flex gap-0 bg-transparent border-b-0',\n\n 'lg:flex-col lg:items-stretch lg:justify-start lg:w-auto lg:min-w-60',\n 'lg:border-r lg:border-gray-200 lg:pl-5 lg:pr-5 lg:pt-5 lg:gap-5 lg:overflow-x-visible lg:self-stretch',\n\n 'flex-row items-center justify-start w-full overflow-x-auto scrollbar-hide shrink-0',\n 'px-4 py-3',\n '[mask-image:linear-gradient(to_right,transparent,black_16px,black_calc(100%-24px),transparent)] lg:[mask-image:none]'\n )}\n >\n <span className=\"hidden lg:block paragraph-medium-semibold text-gray-950 mb-0\">\n {translate('common.account.configurations.title')}\n </span>\n\n <div className=\"lg:flex lg:flex-col flex flex-row lg:gap-0 gap-0 lg:w-full\">\n <TabsTrigger value={AccountSectionType.PREFERENCES} className={tabTriggerClasses}>\n <IconSettings2 size={20} className=\"shrink-0\" />\n <span className=\"paragraph-small-medium\">{translate('common.preferences.title')}</span>\n </TabsTrigger>\n\n {isOwner && (\n <TabsTrigger value={AccountSectionType.TOKEN} className={tabTriggerClasses}>\n <IconKey size={20} className=\"shrink-0\" />\n <span className=\"paragraph-small-medium\">{translate('common.account.token.title')}</span>\n </TabsTrigger>\n )}\n\n {isOwner && (\n <TabsTrigger value={AccountSectionType.SECURITY} className={tabTriggerClasses}>\n <IconLock size={20} className=\"shrink-0\" />\n <span className=\"paragraph-small-medium\">{translate('common.account.security.title')}</span>\n </TabsTrigger>\n )}\n </div>\n </TabsList>\n\n <div className=\"w-full flex-1 min-h-0 lg:h-full overflow-hidden lg:overflow-visible relative flex flex-col\">\n <TabsContent value={AccountSectionType.PREFERENCES} className=\"h-full! relative overflow-hidden\">\n <PreferencesSection onClose={close} />\n </TabsContent>\n\n {isOwner && (\n <TabsContent value={AccountSectionType.TOKEN} className=\"h-full! relative overflow-hidden\">\n <TokenSection />\n </TabsContent>\n )}\n\n {isOwner && (\n <>\n <TabsContent value={AccountSectionType.SECURITY} className=\"h-full! relative overflow-hidden\">\n <SecuritySection\n setActiveSection={setActiveSection}\n onClose={close}\n onDeleteAccount={handleDeleteAccount}\n />\n </TabsContent>\n\n <TabsContent value={AccountSectionType.CHANGE_PASSWORD} className=\"h-full! relative overflow-hidden\">\n <ChangePasswordSection onBack={() => setActiveSection(AccountSectionType.SECURITY)} />\n </TabsContent>\n </>\n )}\n </div>\n </Tabs>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":[]}
@@ -8,11 +8,12 @@ import { Button } from "../../ui/buttons/Button";
8
8
  import { FormField } from "../../ui/form/FormField";
9
9
  import useCopyToClipboard from "../../../hooks/copy-to-clipboard.hook";
10
10
  import { useAccountToken } from "../../../modules/accounts/hooks/use-account-token.hook";
11
- import { useIsDefaultWhitelabel } from "../../../providers/whitelabel.provider";
11
+ import { useIsDefaultWhitelabel, useWhitelabel } from "../../../providers/whitelabel.provider";
12
12
  function TokenSection() {
13
13
  const translate = useTranslations();
14
14
  const { data: token = "", isLoading } = useAccountToken();
15
15
  const isDefaultWl = useIsDefaultWhitelabel();
16
+ const { whitelabel } = useWhitelabel();
16
17
  const { isCopied, copy } = useCopyToClipboard({
17
18
  onSuccess: () => {
18
19
  toast.custom((toastId) => /* @__PURE__ */ jsx(
@@ -69,7 +70,7 @@ function TokenSection() {
69
70
  /* @__PURE__ */ jsx(IconInfoCircle, { className: "size-4 text-zinc-950 shrink-0" }),
70
71
  /* @__PURE__ */ jsx("span", { className: "paragraph-small-regular text-zinc-950 max-w-[420px]", children: translate("common.account.token.info") })
71
72
  ] }),
72
- /* @__PURE__ */ jsx(
73
+ whitelabel?.id === 1 && /* @__PURE__ */ jsx(
73
74
  "a",
74
75
  {
75
76
  href: "https://documenter.getpostman.com/view/4861938/T1Djiz8H",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/account/sections/TokenSection.tsx"],"sourcesContent":["'use client';\n\nimport { IconCheck, IconCopy, IconInfoCircle } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { Button } from '../../ui/buttons/Button';\nimport { FormField } from '../../ui/form/FormField';\nimport useCopyToClipboard from '../../../hooks/copy-to-clipboard.hook';\nimport { useAccountToken } from '../../../modules/accounts/hooks/use-account-token.hook';\nimport { useIsDefaultWhitelabel } from '../../../providers/whitelabel.provider';\n\nexport function TokenSection() {\n const translate = useTranslations();\n const { data: token = '', isLoading } = useAccountToken();\n const isDefaultWl = useIsDefaultWhitelabel();\n\n const { isCopied, copy } = useCopyToClipboard({\n onSuccess: () => {\n toast.custom((toastId) => (\n <Toast\n variant=\"success\"\n message={translate('common.account.token.copySuccess')}\n toastId={toastId}\n />\n ));\n },\n onError: () => {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={translate('common.account.token.copyError')}\n toastId={toastId}\n />\n ));\n },\n });\n\n return (\n <div className=\"h-full overflow-y-auto overscroll-contain px-4 pb-20 lg:p-5 lg:pt-5 flex flex-col gap-5 lg:gap-6 lg:pb-31 lg:overscroll-auto\">\n <span className=\"paragraph-medium-semibold text-gray-950\">\n {translate('common.account.token.title')}\n </span>\n\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-end gap-3\">\n <FormField\n label={translate('common.account.token.label')}\n value={isLoading ? translate('common.actions.loading') : token}\n disabled\n readOnly\n classnameContainer=\"bg-gray-50 cursor-default opacity-100!\"\n className=\"text-gray-950/50\"\n />\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"h-10! shrink-0 gap-2\"\n onClick={() => copy(token)}\n disabled={isLoading || !token}\n >\n {isCopied ? <IconCheck size={16} /> : <IconCopy size={16} />}\n {isCopied ? translate('common.actions.copied') : translate('common.actions.copy')}\n </Button>\n </div>\n\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 max-w-[420px]\">\n {translate('common.account.token.info')}\n </span>\n </div>\n\n <a\n href=\"https://documenter.getpostman.com/view/4861938/T1Djiz8H\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"paragraph-small-medium text-gray-500 underline hover:text-gray-700 transition-colors w-fit\"\n >\n {translate('common.account.token.docsLink')}\n </a>\n </div>\n </div>\n );\n}\n"],"mappings":";AAoBQ,cAkCE,YAlCF;AAlBR,SAAS,WAAW,UAAU,sBAAsB;AACpD,SAAS,uBAAuB;AAChC,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,OAAO,wBAAwB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,8BAA8B;AAEhC,SAAS,eAAe;AAC7B,QAAM,YAAY,gBAAgB;AAClC,QAAM,EAAE,MAAM,QAAQ,IAAI,UAAU,IAAI,gBAAgB;AACxD,QAAM,cAAc,uBAAuB;AAE3C,QAAM,EAAE,UAAU,KAAK,IAAI,mBAAmB;AAAA,IAC5C,WAAW,MAAM;AACf,YAAM,OAAO,CAAC,YACZ;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,UAAU,kCAAkC;AAAA,UACrD;AAAA;AAAA,MACF,CACD;AAAA,IACH;AAAA,IACA,SAAS,MAAM;AACb,YAAM,OAAO,CAAC,YACZ;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,UAAU,gCAAgC;AAAA,UACnD;AAAA;AAAA,MACF,CACD;AAAA,IACH;AAAA,EACF,CAAC;AAED,SACE,qBAAC,SAAI,WAAU,gIACb;AAAA,wBAAC,UAAK,WAAU,2CACb,oBAAU,4BAA4B,GACzC;AAAA,IAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,2BAAC,SAAI,WAAU,wBACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,UAAU,4BAA4B;AAAA,YAC7C,OAAO,YAAY,UAAU,wBAAwB,IAAI;AAAA,YACzD,UAAQ;AAAA,YACR,UAAQ;AAAA,YACR,oBAAmB;AAAA,YACnB,WAAU;AAAA;AAAA,QACZ;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,WAAU;AAAA,YACV,SAAS,MAAM,KAAK,KAAK;AAAA,YACzB,UAAU,aAAa,CAAC;AAAA,YAEvB;AAAA,yBAAW,oBAAC,aAAU,MAAM,IAAI,IAAK,oBAAC,YAAS,MAAM,IAAI;AAAA,cACzD,WAAW,UAAU,uBAAuB,IAAI,UAAU,qBAAqB;AAAA;AAAA;AAAA,QAClF;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAW,0CAA0C,cAAc,eAAe,YAAY,IACjG;AAAA,4BAAC,kBAAe,WAAU,iCAAgC;AAAA,QAC1D,oBAAC,UAAK,WAAU,uDACb,oBAAU,2BAA2B,GACxC;AAAA,SACF;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,QAAO;AAAA,UACP,KAAI;AAAA,UACJ,WAAU;AAAA,UAET,oBAAU,+BAA+B;AAAA;AAAA,MAC5C;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/account/sections/TokenSection.tsx"],"sourcesContent":["'use client';\n\nimport { IconCheck, IconCopy, IconInfoCircle } from '@tabler/icons-react';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { Button } from '../../ui/buttons/Button';\nimport { FormField } from '../../ui/form/FormField';\nimport useCopyToClipboard from '../../../hooks/copy-to-clipboard.hook';\nimport { useAccountToken } from '../../../modules/accounts/hooks/use-account-token.hook';\nimport { useIsDefaultWhitelabel, useWhitelabel } from '../../../providers/whitelabel.provider';\n\nexport function TokenSection() {\n const translate = useTranslations();\n const { data: token = '', isLoading } = useAccountToken();\n const isDefaultWl = useIsDefaultWhitelabel();\n const { whitelabel } = useWhitelabel();\n \n\n const { isCopied, copy } = useCopyToClipboard({\n onSuccess: () => {\n toast.custom((toastId) => (\n <Toast\n variant=\"success\"\n message={translate('common.account.token.copySuccess')}\n toastId={toastId}\n />\n ));\n },\n onError: () => {\n toast.custom((toastId) => (\n <Toast\n variant=\"error\"\n message={translate('common.account.token.copyError')}\n toastId={toastId}\n />\n ));\n },\n });\n\n return (\n <div className=\"h-full overflow-y-auto overscroll-contain px-4 pb-20 lg:p-5 lg:pt-5 flex flex-col gap-5 lg:gap-6 lg:pb-31 lg:overscroll-auto\">\n <span className=\"paragraph-medium-semibold text-gray-950\">\n {translate('common.account.token.title')}\n </span>\n\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-end gap-3\">\n <FormField\n label={translate('common.account.token.label')}\n value={isLoading ? translate('common.actions.loading') : token}\n disabled\n readOnly\n classnameContainer=\"bg-gray-50 cursor-default opacity-100!\"\n className=\"text-gray-950/50\"\n />\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"h-10! shrink-0 gap-2\"\n onClick={() => copy(token)}\n disabled={isLoading || !token}\n >\n {isCopied ? <IconCheck size={16} /> : <IconCopy size={16} />}\n {isCopied ? translate('common.actions.copied') : translate('common.actions.copy')}\n </Button>\n </div>\n\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 max-w-[420px]\">\n {translate('common.account.token.info')}\n </span>\n </div>\n\n {whitelabel?.id === 1 && (\n <a\n href=\"https://documenter.getpostman.com/view/4861938/T1Djiz8H\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"paragraph-small-medium text-gray-500 underline hover:text-gray-700 transition-colors w-fit\"\n >\n {translate('common.account.token.docsLink')}\n </a>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";AAsBQ,cAkCE,YAlCF;AApBR,SAAS,WAAW,UAAU,sBAAsB;AACpD,SAAS,uBAAuB;AAChC,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,OAAO,wBAAwB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,wBAAwB,qBAAqB;AAE/C,SAAS,eAAe;AAC7B,QAAM,YAAY,gBAAgB;AAClC,QAAM,EAAE,MAAM,QAAQ,IAAI,UAAU,IAAI,gBAAgB;AACxD,QAAM,cAAc,uBAAuB;AACzC,QAAM,EAAE,WAAW,IAAI,cAAc;AAGvC,QAAM,EAAE,UAAU,KAAK,IAAI,mBAAmB;AAAA,IAC5C,WAAW,MAAM;AACf,YAAM,OAAO,CAAC,YACZ;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,UAAU,kCAAkC;AAAA,UACrD;AAAA;AAAA,MACF,CACD;AAAA,IACH;AAAA,IACA,SAAS,MAAM;AACb,YAAM,OAAO,CAAC,YACZ;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,UAAU,gCAAgC;AAAA,UACnD;AAAA;AAAA,MACF,CACD;AAAA,IACH;AAAA,EACF,CAAC;AAED,SACE,qBAAC,SAAI,WAAU,gIACb;AAAA,wBAAC,UAAK,WAAU,2CACb,oBAAU,4BAA4B,GACzC;AAAA,IAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,2BAAC,SAAI,WAAU,wBACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,UAAU,4BAA4B;AAAA,YAC7C,OAAO,YAAY,UAAU,wBAAwB,IAAI;AAAA,YACzD,UAAQ;AAAA,YACR,UAAQ;AAAA,YACR,oBAAmB;AAAA,YACnB,WAAU;AAAA;AAAA,QACZ;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,WAAU;AAAA,YACV,SAAS,MAAM,KAAK,KAAK;AAAA,YACzB,UAAU,aAAa,CAAC;AAAA,YAEvB;AAAA,yBAAW,oBAAC,aAAU,MAAM,IAAI,IAAK,oBAAC,YAAS,MAAM,IAAI;AAAA,cACzD,WAAW,UAAU,uBAAuB,IAAI,UAAU,qBAAqB;AAAA;AAAA;AAAA,QAClF;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAW,0CAA0C,cAAc,eAAe,YAAY,IACjG;AAAA,4BAAC,kBAAe,WAAU,iCAAgC;AAAA,QAC1D,oBAAC,UAAK,WAAU,uDACb,oBAAU,2BAA2B,GACxC;AAAA,SACF;AAAA,MAEC,YAAY,OAAO,KAClB;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,QAAO;AAAA,UACP,KAAI;AAAA,UACJ,WAAU;AAAA,UAET,oBAAU,+BAA+B;AAAA;AAAA,MAC5C;AAAA,OAEJ;AAAA,KACF;AAEJ;","names":[]}
@@ -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 // Créditos inclusos no plano têm value == 0; o select representa só o contratado.\n const aiItem = useMemo(\n () =>\n subscription?.items?.find(\n (item) => item.id_addon === ADDON_IDS.AI && (item.value ?? 0) > 0\n ),\n [subscription]\n );\n\n const currentCredits = aiItem?.quantity ?? 0;\n const subscriptionId = subscription?.id;\n const planId = subscription?.id_plan;\n\n const buildPlanExtras = useCallback(\n (aiQuantity: number) => {\n const aiOption = AI_CREDIT_OPTIONS.find(opt => opt.credits === aiQuantity);\n return [\n ...(subscription?.items ?? [])\n .filter((item) => item.id_addon != null && item.id_addon !== ADDON_IDS.AI)\n .map((item: any) => ({ \n id_addon: Number(item.id_addon), \n quantity: item.quantity ?? 0,\n value: Number(item.value ?? 0),\n price: item.price,\n })),\n { \n id_addon: ADDON_IDS.AI, \n quantity: aiQuantity,\n value: aiOption?.price ?? 0 \n },\n ];\n },\n [subscription]\n );\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: buildPlanExtras(currentCredits),\n });\n }, [\n open,\n subscription,\n currentCredits,\n defaultCardId,\n form,\n calculate,\n planId,\n subscriptionId,\n buildPlanExtras,\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: buildPlanExtras(option.credits),\n });\n },\n [form, subscriptionId, planId, calculate, buildPlanExtras]\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: buildPlanExtras(option.credits),\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, buildPlanExtras]\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 () =>\n subscription?.items?.find(\n (item) => item.id_addon === ADDON_IDS.AI && (item.value ?? 0) > 0\n ),\n [subscription]\n );\n\n const currentCredits = aiItem?.quantity ?? 0;\n const subscriptionId = subscription?.id;\n const planId = subscription?.id_plan;\n\n const buildPlanExtras = useCallback(\n (aiQuantity: number) => {\n const aiOption = AI_CREDIT_OPTIONS.find(opt => opt.credits === aiQuantity);\n return [\n ...(subscription?.items ?? [])\n .filter((item) => item.id_addon != null && item.id_addon !== ADDON_IDS.AI)\n .map((item: any) => ({ \n id_addon: Number(item.id_addon), \n quantity: item.quantity ?? 0,\n value: Number(item.value ?? 0),\n price: item.price,\n })),\n { \n id_addon: ADDON_IDS.AI, \n quantity: aiQuantity,\n value: aiOption?.price ?? 0 \n },\n ];\n },\n [subscription]\n );\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: buildPlanExtras(currentCredits),\n });\n }, [\n open,\n subscription,\n currentCredits,\n defaultCardId,\n form,\n calculate,\n planId,\n subscriptionId,\n buildPlanExtras,\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: buildPlanExtras(option.credits),\n });\n },\n [form, subscriptionId, planId, calculate, buildPlanExtras]\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: buildPlanExtras(option.credits),\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, buildPlanExtras]\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":";AAkNQ,SACE,KADF;AAhNR;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,6BAA6B;AACtC,SAAS,gCAAgC;AACzC,SAAS,iCAAiC;AAC1C,SAAS,kCAAkC;AAC3C,SAAS,wBAAwB,8BAA8B;AAC/D,SAAS,gBAAgB,OAAO,aAAa,oBAAoB;AACjE,SAAS,uBAAuB;AAChC,SAAS,aAAa,WAAW,SAAS,cAAc;AACxD,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,OAAO,OAAO;AACd,SAAS,aAAa;AAEtB,MAAM,kBAAkB,EAAE,OAAO;AAAA,EAC/B,cAAc,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,iBAAiB,IAAI,GAAG,GAAG;AAAA,IAClE,SAAS;AAAA,EACX,CAAC;AAAA,EACD,gBAAgB,EAAE,OAAO,EAAE,IAAI,GAAG,wBAAqB;AACzD,CAAC;AAIc,SAAR,kBAAmC;AACxC,QAAM,YAAY,gBAAgB;AAClC,QAAM,EAAE,MAAM,WAAW,IAAI,mBAAmB;AAChD,QAAM,EAAE,UAAU,IAAI,gBAAgB;AACtC,QAAM,cAAc,uBAAuB;AAC3C,QAAM,EAAE,uBAAuB,mBAAmB,IAAI,uBAAuB;AAC7E,QAAM,EAAE,MAAM,UAAU,IAAI,SAAS;AACrC,QAAM,EAAE,MAAM,iBAAiB,IAAI,sBAAsB;AACzD,QAAM,eAAe,kBAAkB,OAAO,CAAC,KAAK;AACpD,QAAM,gBACJ,WAAW,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,GAAG,IAAI,SAAS,KAC/D,WAAW,OAAO,CAAC,GAAG,IAAI,SAAS,KACnC;AAEF,QAAM;AAAA,IACJ,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,MAAM;AAAA,EACR,IAAI,yBAAyB;AAC7B,QAAM,qBAAqB,0BAA0B;AAErD,QAAM,SAAS;AAAA,IACb,MACE,cAAc,OAAO;AAAA,MACnB,CAAC,SAAS,KAAK,aAAa,UAAU,OAAO,KAAK,SAAS,KAAK;AAAA,IAClE;AAAA,IACF,CAAC,YAAY;AAAA,EACf;AAEA,QAAM,iBAAiB,QAAQ,YAAY;AAC3C,QAAM,iBAAiB,cAAc;AACrC,QAAM,SAAS,cAAc;AAE7B,QAAM,kBAAkB;AAAA,IACtB,CAAC,eAAuB;AACtB,YAAM,WAAW,kBAAkB,KAAK,SAAO,IAAI,YAAY,UAAU;AACzE,aAAO;AAAA,QACL,IAAI,cAAc,SAAS,CAAC,GACzB,OAAO,CAAC,SAAS,KAAK,YAAY,QAAQ,KAAK,aAAa,UAAU,EAAE,EACxE,IAAI,CAAC,UAAe;AAAA,UACnB,UAAU,OAAO,KAAK,QAAQ;AAAA,UAC9B,UAAU,KAAK,YAAY;AAAA,UAC3B,OAAO,OAAO,KAAK,SAAS,CAAC;AAAA,UAC7B,OAAO,KAAK;AAAA,QACd,EAAE;AAAA,QACJ;AAAA,UACE,UAAU,UAAU;AAAA,UACpB,UAAU;AAAA,UACV,OAAO,UAAU,SAAS;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,YAAY;AAAA,EACf;AAEA,QAAM,OAAO,QAA2B;AAAA,IACtC,UAAU,YAAY,eAAe;AAAA,IACrC,MAAM;AAAA,IACN,eAAe;AAAA,MACb,cAAc;AAAA,MACd,gBAAgB;AAAA,IAClB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,OAAO,KAAK;AAEnC,YAAU,MAAM;AACd,QAAI,CAAC,MAAM;AACT,qBAAe,UAAU;AACzB;AAAA,IACF;AAEA,QAAI,eAAe,WAAW,CAAC,aAAc;AAC7C,mBAAe,UAAU;AAEzB,SAAK,MAAM;AAAA,MACT,cAAc;AAAA,MACd,gBAAgB;AAAA,IAClB,CAAC;AAED,cAAU;AAAA,MACR,YAAY;AAAA,MACZ,GAAI,UAAU,QAAQ,EAAE,SAAS,OAAO;AAAA,MACxC,GAAI,kBAAkB,QAAQ,EAAE,iBAAiB,eAAe;AAAA,MAChE,aAAa,gBAAgB,cAAc;AAAA,IAC7C,CAAC;AAAA,EACH,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,YAAU,MAAM;AACd,QAAI,QAAQ,iBAAiB,CAAC,KAAK,UAAU,gBAAgB,GAAG;AAC9D,WAAK,SAAS,kBAAkB,eAAe,EAAE,gBAAgB,KAAK,CAAC;AAAA,IACzE;AAAA,EACF,GAAG,CAAC,MAAM,eAAe,IAAI,CAAC;AAE9B,QAAM,eAAe,KAAK,MAAM,cAAc;AAC9C,QAAM,iBAAiB,KAAK,MAAM,gBAAgB;AAClD,QAAM,EAAE,SAAS,cAAc,QAAQ,IAAI,KAAK;AAGhD,QAAM,2BAA2B;AAAA,IAC/B,CAAC,UAAqC;AACpC,YAAM,UAAU,OAAO,KAAK;AAE5B,WAAK,SAAS,gBAAgB,SAAS;AAAA,QACrC,gBAAgB;AAAA,QAChB,aAAa;AAAA,MACf,CAAC;AAED,YAAM,SAAS,kBAAkB,KAAK,CAAC,QAAQ,IAAI,YAAY,OAAO;AACtE,UAAI,CAAC,OAAQ;AAEb,gBAAU;AAAA,QACR,YAAY;AAAA,QACZ,GAAI,UAAU,QAAQ,EAAE,SAAS,OAAO;AAAA,QACxC,GAAI,kBAAkB,QAAQ,EAAE,iBAAiB,eAAe;AAAA,QAChE,aAAa,gBAAgB,OAAO,OAAO;AAAA,MAC7C,CAAC;AAAA,IACH;AAAA,IACA,CAAC,MAAM,gBAAgB,QAAQ,WAAW,eAAe;AAAA,EAC3D;AAEA,QAAM,iBAAiB,eAAe,QAAQ;AAE9C,QAAM,sBAAsB,eAAe;AAC3C,QAAM,aAAa,iBAAiB;AAEpC,QAAM,uBAAuB,gBAAgB,QAAQ,kBAAkB,SAAS;AAEhF,QAAM,aAAa,QAAQ,MAAM;AAC/B,QAAI,CAAC,eAAgB,QAAO;AAC5B,WAAO,2BAA2B,gBAAgB,cAAc,WAAW;AAAA,EAC7E,GAAG,CAAC,gBAAgB,YAAY,CAAC;AAEjC,QAAM,cAAc,YAAY,CAAC,UAAkB;AACjD,WAAO,MAAM,eAAe,SAAS;AAAA,MACnC,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoB,CAAC,WAAW,CAAC,WAAW,sBAAsB;AAExE,QAAM,cAAc,YAAY,MAAM;AACpC,SAAK,MAAM;AACX,eAAW;AAAA,EACb,GAAG,CAAC,MAAM,UAAU,CAAC;AAErB,QAAM,yBAAyB,YAAY,MAAM;AAC/C,cAAU,cAAc;AAAA,EAC1B,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,gBAAgB,QAAQ,MAAM;AAClC,WAAO,kBAAkB,IAAI,CAAC,SAAS;AAAA,MACrC,OAAO,IAAI;AAAA,MACX,OACE,qBAAC,SAAI,WAAU,kDACb;AAAA,4BAAC,UAAK,WAAU,+CACb,oBAAU,yCAAyC,EAAE,SAAS,IAAI,QAAQ,eAAe,OAAO,EAAE,CAAC,GACtG;AAAA,QACA,oBAAC,UAAK,WAAU,mDAAmD,cAAI,YAAW;AAAA,SACpF;AAAA,MAEF,cAAc,UAAU,gDAAgD,EAAE,SAAS,IAAI,QAAQ,eAAe,OAAO,EAAE,CAAC;AAAA,IAC1H,EAAE;AAAA,EACJ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,WAAW;AAAA,IACf,OAAO,SAA4B;AACjC,UAAI,CAAC,eAAgB;AAErB,YAAM,SAAS,kBAAkB,KAAK,CAAC,QAAQ,IAAI,YAAY,KAAK,YAAY;AAChF,UAAI,CAAC,OAAQ;AAEb,YAAM,mBAAmB,YAAY;AAAA,QACnC;AAAA,QACA,MAAM;AAAA,UACJ,aAAa,gBAAgB,OAAO,OAAO;AAAA,QAC7C;AAAA,MACF,CAAC;AAED,YAAM,OAAO,CAAC,MACZ;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,SAAS,UAAU,0CAA0C,EAAE,SAAS,KAAK,aAAa,eAAe,OAAO,EAAE,CAAC;AAAA,UACnH,SAAS;AAAA;AAAA,MACX,CACD;AACD,WAAK,MAAM;AACX,iBAAW;AAAA,IACb;AAAA,IACA,CAAC,gBAAgB,oBAAoB,MAAM,YAAY,eAAe;AAAA,EACxE;AAEA,QAAM,qBAAqB,YAAY,MAAM;AAC3C,QAAI,oBAAoB;AACtB,aAAO,oBAAC,YAAS,WAAU,YAAW;AAAA,IACxC;AACA,QAAI,eAAe,MAAM;AACvB,aACE,oBAAC,UAAK,WAAU,2CACb,yBAAe,IAAI,UAAU,iCAAiC,IAAI,YAAY,UAAU,GAC3F;AAAA,IAEJ;AACA,WAAO,oBAAC,UAAK,WAAU,2CAA0C,gBAAE;AAAA,EACrE,GAAG,CAAC,oBAAoB,YAAY,WAAW,CAAC;AAEhD,MAAI,uBAAuB;AACzB,WACE,oBAAC,UAAO,MAAY,cAAc,aAChC,+BAAC,iBAAc,WAAU,4DACvB;AAAA,2BAAC,gBAAa,WAAU,uBACtB;AAAA,4BAAC,SAAI,WAAU,oEACb,8BAAC,gBAAa,MAAM,IAAI,WAAU,iBAAgB,GACpD;AAAA,QACA,qBAAC,SAAI,WAAU,uBACb;AAAA,8BAAC,eAAY,WAAU,2CACpB,oBAAU,kCAAkC,GAC/C;AAAA,UACA,oBAAC,UAAK,WAAU,yCACb,oBAAU,wCAAwC,GACrD;AAAA,WACF;AAAA,SACF;AAAA,MACA,oBAAC,SAAI,WAAU,wDACb;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,SAAS,MAAM;AACb,uBAAW;AACX,+BAAmB,aAAa;AAAA,UAClC;AAAA,UAEC,oBAAU,mCAAmC;AAAA;AAAA,MAChD,GACF;AAAA,OACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,UAAO,MAAY,cAAc,aAChC;AAAA,IAAC;AAAA;AAAA,MACC,iBAAiB;AAAA,MACjB,WAAU;AAAA,MAEV;AAAA,6BAAC,gBAAa,WAAU,wDACtB;AAAA,8BAAC,eAAY,WAAU,uDACpB,oBAAU,0BAA0B,GACvC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAU;AAAA,cAEV,8BAAC,SAAM,MAAM,IAAI,WAAU,iBAAgB;AAAA;AAAA,UAC7C;AAAA,WACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,UAAU,KAAK,aAAa,QAAQ;AAAA,YACpC,WAAU;AAAA,YAEV;AAAA,mCAAC,SAAI,WAAU,+EACb;AAAA,qCAAC,SAAI,WAAU,uBACb;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAS;AAAA,sBACT,OAAO;AAAA,sBACP,UAAU;AAAA,sBACV,WAAU;AAAA;AAAA,kBACZ;AAAA,kBAEA;AAAA,oBAAC;AAAA;AAAA,sBACC;AAAA,sBACA,cAAc,CAAC,WACb,KAAK,SAAS,kBAAkB,QAAQ,EAAE,gBAAgB,KAAK,CAAC;AAAA,sBAElE,eAAe;AAAA;AAAA,kBACjB;AAAA,kBAEC,cACC,qBAAC,SAAI,WAAW,0CAA0C,cAAc,eAAe,YAAY,IACjG;AAAA,wCAAC,kBAAe,WAAU,iCAAgC;AAAA,oBAC1D,oBAAC,UAAK,WAAU,yCACb,gCACG,UAAU,qCAAqC,IAC/C,UAAU,uCAAuC,GACvD;AAAA,qBACF;AAAA,mBAEJ;AAAA,gBAEA,oBAAC,aAAU,WAAU,+BAA8B;AAAA,gBAEnD,qBAAC,SAAI,WAAU,iCACZ;AAAA,2CAAyB,QAAQ,uBAAuB,KACvD,qBAAC,SAAI,WAAU,qCACb;AAAA,wCAAC,UAAK,WAAU,yCAAyC,oBAAU,oCAAoC,GAAE;AAAA,oBACxG,qBACC,oBAAC,YAAS,WAAU,YAAW,IAE/B,oBAAC,UAAK,WAAU,0CACb,sBAAY,oBAAoB,GACnC;AAAA,qBAEJ;AAAA,kBAEF,qBAAC,SAAI,WAAU,qCACb;AAAA,wCAAC,UAAK,WAAU,wCAAwC,oBAAU,mCAAmC,GAAE;AAAA,oBACtG,mBAAmB;AAAA,qBACtB;AAAA,mBACF;AAAA,gBAEA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,UAAU;AAAA,oBACV,WAAU;AAAA,oBAET;AAAA,sCAAgB,oBAAC,eAAY,WAAU,uBAAsB;AAAA,sBAC7D,UAAU,kCAAkC;AAAA;AAAA;AAAA,gBAC/C;AAAA,iBACF;AAAA,cAEA,qBAAC,SAAI,WAAU,uEACb;AAAA,qCAAC,SAAI,WAAU,uBACZ;AAAA,2CAAyB,QAAQ,uBAAuB,KACvD,qBAAC,SAAI,WAAU,qCACb;AAAA,wCAAC,UAAK,WAAU,yCAAyC,oBAAU,oCAAoC,GAAE;AAAA,oBACxG,qBACC,oBAAC,YAAS,WAAU,YAAW,IAE/B,oBAAC,UAAK,WAAU,0CACb,sBAAY,oBAAoB,GACnC;AAAA,qBAEJ;AAAA,kBAEF,qBAAC,SAAI,WAAU,qCACb;AAAA,wCAAC,UAAK,WAAU,wCAAwC,oBAAU,mCAAmC,GAAE;AAAA,oBACtG,mBAAmB;AAAA,qBACtB;AAAA,mBACF;AAAA,gBACA,qBAAC,UAAO,MAAK,UAAS,UAAU,mBAAmB,WAAU,UAC1D;AAAA,kCAAgB,oBAAC,eAAY,WAAU,uBAAsB;AAAA,kBAC7D,UAAU,kCAAkC;AAAA,mBAC/C;AAAA,iBACF;AAAA;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}
@@ -1,6 +1,7 @@
1
1
  import greatCache from "@greatapps/cache";
2
2
  import { ApiError } from "../../../infra/api/types";
3
3
  import { normalizeHostname } from "../utils/normalize-hostname";
4
+ const NOT_FOUND_SENTINEL = "__WL_TOKEN_NOT_FOUND__";
4
5
  class WhitelabelService {
5
6
  getApiUrl() {
6
7
  const apiUrl = process.env.GAPPS_R3_API_URL;
@@ -47,6 +48,10 @@ class WhitelabelService {
47
48
  }
48
49
  });
49
50
  if (!response.ok) {
51
+ const body = await response.json().catch(() => null);
52
+ if (response.status === 404 || body?.message === "Whitelabel not found") {
53
+ throw new ApiError("Whitelabel token not found", "TOKEN_NOT_FOUND", 404);
54
+ }
50
55
  console.error("[WhitelabelService] Failed to fetch whitelabel token", {
51
56
  response
52
57
  });
@@ -76,12 +81,26 @@ class WhitelabelService {
76
81
  cachedData
77
82
  });
78
83
  if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
84
+ if (cachedData.data === NOT_FOUND_SENTINEL) {
85
+ console.log("[WhitelabelService] Negative cache hit for domain", { hostname });
86
+ throw new ApiError("Whitelabel token not found", "TOKEN_NOT_FOUND", 404);
87
+ }
79
88
  console.log("[WhitelabelService] Cache hit for domain", { hostname });
80
89
  return JSON.parse(cachedData.data);
81
90
  }
82
- const data = await this.fetchFromApi(hostname);
83
- await cache.insert(cacheKey, JSON.stringify(data), 604800);
84
- return data;
91
+ try {
92
+ const data = await this.fetchFromApi(hostname);
93
+ await cache.insert(cacheKey, JSON.stringify(data), 604800);
94
+ return data;
95
+ } catch (error) {
96
+ if (ApiError.isApiError(error) && error.code === "TOKEN_NOT_FOUND") {
97
+ try {
98
+ await cache.insert(cacheKey, NOT_FOUND_SENTINEL, 60);
99
+ } catch {
100
+ }
101
+ }
102
+ throw error;
103
+ }
85
104
  }
86
105
  async getTokenByWhitelabelId(idWl) {
87
106
  const apiUrl = this.getApiUrl();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["import greatCache from \"@greatapps/cache\";\r\nimport { ApiError } from \"../../../infra/api/types\";\r\nimport { WhitelabelTokenApiResponse, WhitelabelTokenData } from \"../schema\";\r\nimport { normalizeHostname } from \"../utils/normalize-hostname\";\r\n\r\nclass WhitelabelService {\r\n private getApiUrl(): string {\r\n const apiUrl = process.env.GAPPS_R3_API_URL;\r\n if (!apiUrl) {\r\n throw new ApiError(\r\n \"GAPPS_R3_API_URL not configured\",\r\n \"CONFIG_ERROR\",\r\n 500,\r\n );\r\n }\r\n return apiUrl;\r\n }\r\n\r\n private getToken(): string {\r\n const token = process.env.WHITELABEL_TOKEN_MASTER;\r\n if (!token) {\r\n throw new ApiError(\r\n \"WHITELABEL_TOKEN_MASTER not configured\",\r\n \"CONFIG_ERROR\",\r\n 500,\r\n );\r\n }\r\n return token;\r\n }\r\n\r\n private createCache() {\r\n return new greatCache({\r\n service: \"whitelabel-service\",\r\n version: \"1.0.3\",\r\n domain: \"whitelabel-cache.greatapps.com.br\",\r\n ambient: process.env.NODE_ENV || \"development\",\r\n });\r\n }\r\n\r\n private getCacheKey(hostname: string): string {\r\n return `whitelabel-token-${hostname}`;\r\n }\r\n\r\n private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData> {\r\n const apiUrl = this.getApiUrl();\r\n const whitelabelMasterToken = this.getToken();\r\n const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;\r\n\r\n console.log(\"[WhitelabelService] Fetching token for domain\", { url });\r\n\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n headers: {\r\n authorization: whitelabelMasterToken,\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n console.error(\"[WhitelabelService] Failed to fetch whitelabel token\", {\r\n response,\r\n });\r\n throw new ApiError(\r\n `Failed to fetch whitelabel token: ${response.status}`,\r\n \"FETCH_ERROR\",\r\n response.status,\r\n );\r\n }\r\n\r\n const result: WhitelabelTokenApiResponse = await response.json();\r\n\r\n if (result.status !== 1 || !result.data?.length) {\r\n throw new ApiError(\"Whitelabel token not found\", \"TOKEN_NOT_FOUND\", 404);\r\n }\r\n\r\n return result.data[0];\r\n }\r\n\r\n async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {\r\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\r\n console.debug(\"[WhitelabelService] Getting token for domain\", {\r\n hostname,\r\n byEnv: process.env.WHITELABEL_DOMAIN,\r\n });\r\n\r\n const cache = this.createCache();\r\n const cacheKey = this.getCacheKey(hostname);\r\n\r\n const cachedData = await cache.select(cacheKey);\r\n console.debug(\"[WhitelabelService] Cache lookup for domain\", {\r\n cacheKey,\r\n cachedData,\r\n });\r\n\r\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\r\n console.log(\"[WhitelabelService] Cache hit for domain\", { hostname });\r\n return JSON.parse(cachedData.data) as WhitelabelTokenData;\r\n }\r\n\r\n const data = await this.fetchFromApi(hostname);\r\n\r\n await cache.insert(cacheKey, JSON.stringify(data), 604800);\r\n\r\n return data;\r\n }\r\n\r\n async getTokenByWhitelabelId(idWl: number): Promise<string> {\r\n const apiUrl = this.getApiUrl();\r\n const masterToken = this.getToken();\r\n const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;\r\n console.debug(\"[WhitelabelService] Fetching token by whitelabel ID\", {\r\n url,\r\n config: {\r\n method: \"GET\",\r\n headers: { authorization: masterToken },\r\n },\r\n });\r\n\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n headers: { authorization: masterToken },\r\n });\r\n\r\n console.debug(\"[WhitelabelService] Response received for whitelabel ID\", {\r\n response,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new ApiError(\r\n `Failed to fetch whitelabel token: ${response.status}`,\r\n \"WL_TOKEN_NOT_FOUND\",\r\n response.status,\r\n );\r\n }\r\n\r\n const result: WhitelabelTokenApiResponse = await response.json();\r\n\r\n if (result.status !== 1 || !result.data?.length) {\r\n throw new ApiError(\r\n \"Token do whitelabel não encontrado\",\r\n \"WL_TOKEN_NOT_FOUND\",\r\n 404,\r\n );\r\n }\r\n\r\n return result.data[0].token;\r\n }\r\n\r\n async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {\r\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\r\n console.debug(\"[WhitelabelService] Getting token for domain\", {\r\n hostname,\r\n byEnv: process.env.WHITELABEL_DOMAIN,\r\n });\r\n\r\n const cache = this.createCache();\r\n const cacheKey = this.getCacheKey(hostname);\r\n\r\n console.log(\"[WhitelabelService] Revalidating cache for domain\", {\r\n hostname,\r\n cacheKey,\r\n });\r\n \r\n await cache.delete(cacheKey);\r\n\r\n const data = await this.fetchFromApi(hostname);\r\n\r\n await cache.insert(cacheKey, JSON.stringify(data), 604800);\r\n\r\n console.log(\"[WhitelabelService] Cache revalidated for domain\", {\r\n hostname,\r\n });\r\n\r\n return data;\r\n }\r\n}\r\n\r\nexport const whitelabelService = new WhitelabelService();\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["import greatCache from \"@greatapps/cache\";\r\nimport { ApiError } from \"../../../infra/api/types\";\r\nimport { WhitelabelTokenApiResponse, WhitelabelTokenData } from \"../schema\";\r\nimport { normalizeHostname } from \"../utils/normalize-hostname\";\r\n\r\nconst NOT_FOUND_SENTINEL = \"__WL_TOKEN_NOT_FOUND__\";\r\n\r\nclass WhitelabelService {\r\n private getApiUrl(): string {\r\n const apiUrl = process.env.GAPPS_R3_API_URL;\r\n if (!apiUrl) {\r\n throw new ApiError(\r\n \"GAPPS_R3_API_URL not configured\",\r\n \"CONFIG_ERROR\",\r\n 500,\r\n );\r\n }\r\n return apiUrl;\r\n }\r\n\r\n private getToken(): string {\r\n const token = process.env.WHITELABEL_TOKEN_MASTER;\r\n if (!token) {\r\n throw new ApiError(\r\n \"WHITELABEL_TOKEN_MASTER not configured\",\r\n \"CONFIG_ERROR\",\r\n 500,\r\n );\r\n }\r\n return token;\r\n }\r\n\r\n private createCache() {\r\n return new greatCache({\r\n service: \"whitelabel-service\",\r\n version: \"1.0.3\",\r\n domain: \"whitelabel-cache.greatapps.com.br\",\r\n ambient: process.env.NODE_ENV || \"development\",\r\n });\r\n }\r\n\r\n private getCacheKey(hostname: string): string {\r\n return `whitelabel-token-${hostname}`;\r\n }\r\n\r\n private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData> {\r\n const apiUrl = this.getApiUrl();\r\n const whitelabelMasterToken = this.getToken();\r\n const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;\r\n\r\n console.log(\"[WhitelabelService] Fetching token for domain\", { url });\r\n\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n headers: {\r\n authorization: whitelabelMasterToken,\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n const body = await response.json().catch(() => null);\r\n\r\n if (response.status === 404 || body?.message === \"Whitelabel not found\") {\r\n throw new ApiError(\"Whitelabel token not found\", \"TOKEN_NOT_FOUND\", 404);\r\n }\r\n\r\n console.error(\"[WhitelabelService] Failed to fetch whitelabel token\", {\r\n response,\r\n });\r\n throw new ApiError(\r\n `Failed to fetch whitelabel token: ${response.status}`,\r\n \"FETCH_ERROR\",\r\n response.status,\r\n );\r\n }\r\n\r\n const result: WhitelabelTokenApiResponse = await response.json();\r\n\r\n if (result.status !== 1 || !result.data?.length) {\r\n throw new ApiError(\"Whitelabel token not found\", \"TOKEN_NOT_FOUND\", 404);\r\n }\r\n\r\n return result.data[0];\r\n }\r\n\r\n async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {\r\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\r\n console.debug(\"[WhitelabelService] Getting token for domain\", {\r\n hostname,\r\n byEnv: process.env.WHITELABEL_DOMAIN,\r\n });\r\n\r\n const cache = this.createCache();\r\n const cacheKey = this.getCacheKey(hostname);\r\n\r\n const cachedData = await cache.select(cacheKey);\r\n console.debug(\"[WhitelabelService] Cache lookup for domain\", {\r\n cacheKey,\r\n cachedData,\r\n });\r\n\r\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\r\n if (cachedData.data === NOT_FOUND_SENTINEL) {\r\n console.log(\"[WhitelabelService] Negative cache hit for domain\", { hostname });\r\n throw new ApiError(\"Whitelabel token not found\", \"TOKEN_NOT_FOUND\", 404);\r\n }\r\n\r\n console.log(\"[WhitelabelService] Cache hit for domain\", { hostname });\r\n return JSON.parse(cachedData.data) as WhitelabelTokenData;\r\n }\r\n\r\n try {\r\n const data = await this.fetchFromApi(hostname);\r\n\r\n await cache.insert(cacheKey, JSON.stringify(data), 604800);\r\n\r\n return data;\r\n } catch (error) {\r\n if (ApiError.isApiError(error) && error.code === \"TOKEN_NOT_FOUND\") {\r\n try {\r\n await cache.insert(cacheKey, NOT_FOUND_SENTINEL, 60);\r\n } catch {\r\n // falha do cache não pode substituir o TOKEN_NOT_FOUND original\r\n }\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n async getTokenByWhitelabelId(idWl: number): Promise<string> {\r\n const apiUrl = this.getApiUrl();\r\n const masterToken = this.getToken();\r\n const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;\r\n console.debug(\"[WhitelabelService] Fetching token by whitelabel ID\", {\r\n url,\r\n config: {\r\n method: \"GET\",\r\n headers: { authorization: masterToken },\r\n },\r\n });\r\n\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n headers: { authorization: masterToken },\r\n });\r\n\r\n console.debug(\"[WhitelabelService] Response received for whitelabel ID\", {\r\n response,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new ApiError(\r\n `Failed to fetch whitelabel token: ${response.status}`,\r\n \"WL_TOKEN_NOT_FOUND\",\r\n response.status,\r\n );\r\n }\r\n\r\n const result: WhitelabelTokenApiResponse = await response.json();\r\n\r\n if (result.status !== 1 || !result.data?.length) {\r\n throw new ApiError(\r\n \"Token do whitelabel não encontrado\",\r\n \"WL_TOKEN_NOT_FOUND\",\r\n 404,\r\n );\r\n }\r\n\r\n return result.data[0].token;\r\n }\r\n\r\n async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {\r\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\r\n console.debug(\"[WhitelabelService] Getting token for domain\", {\r\n hostname,\r\n byEnv: process.env.WHITELABEL_DOMAIN,\r\n });\r\n\r\n const cache = this.createCache();\r\n const cacheKey = this.getCacheKey(hostname);\r\n\r\n console.log(\"[WhitelabelService] Revalidating cache for domain\", {\r\n hostname,\r\n cacheKey,\r\n });\r\n \r\n await cache.delete(cacheKey);\r\n\r\n const data = await this.fetchFromApi(hostname);\r\n\r\n await cache.insert(cacheKey, JSON.stringify(data), 604800);\r\n\r\n console.log(\"[WhitelabelService] Cache revalidated for domain\", {\r\n hostname,\r\n });\r\n\r\n return data;\r\n }\r\n}\r\n\r\nexport const whitelabelService = new WhitelabelService();\r\n"],"mappings":"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","names":[]}
@@ -17,6 +17,7 @@ const CATEGORY_PREFIXES = /* @__PURE__ */ new Set([
17
17
  ]);
18
18
  const SIMPLE_TLDS = /* @__PURE__ */ new Set(["online"]);
19
19
  function normalizeHostname(hostname) {
20
+ hostname = hostname.split(":")[0];
20
21
  const parts = hostname.split(".");
21
22
  if (parts.length <= 2) return hostname;
22
23
  const last = parts[parts.length - 1];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/whitelabel/utils/normalize-hostname.ts"],"sourcesContent":["const CATEGORY_PREFIXES = new Set([\n \"com\", \"dev\", \"net\", \"org\", \"edu\", \"gov\",\n \"mil\", \"co\", \"art\", \"inf\", \"blog\", \"wiki\", \"app\", \"online\", \"site\"\n]);\n\nconst SIMPLE_TLDS = new Set([\"online\"]);\n\nexport function normalizeHostname(hostname: string): string {\n const parts = hostname.split(\".\");\n if (parts.length <= 2) return hostname;\n\n const last = parts[parts.length - 1];\n const secondLast = parts[parts.length - 2];\n\n const isCompoundTld =\n last.length <= 3 && CATEGORY_PREFIXES.has(secondLast);\n\n const rootParts = isCompoundTld ? 3 : 2;\n\n if (SIMPLE_TLDS.has(last) || !isCompoundTld) {\n return parts.slice(-2).join(\".\");\n }\n\n if (parts.length <= rootParts) return hostname;\n return parts.slice(-rootParts).join(\".\");\n}\n"],"mappings":"AAAA,MAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACnC;AAAA,EAAO;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAU;AAC9D,CAAC;AAED,MAAM,cAAc,oBAAI,IAAI,CAAC,QAAQ,CAAC;AAE/B,SAAS,kBAAkB,UAA0B;AAC1D,QAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,MAAI,MAAM,UAAU,EAAG,QAAO;AAE9B,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC;AAEzC,QAAM,gBACJ,KAAK,UAAU,KAAK,kBAAkB,IAAI,UAAU;AAEtD,QAAM,YAAY,gBAAgB,IAAI;AAEtC,MAAI,YAAY,IAAI,IAAI,KAAK,CAAC,eAAe;AAC3C,WAAO,MAAM,MAAM,EAAE,EAAE,KAAK,GAAG;AAAA,EACjC;AAEA,MAAI,MAAM,UAAU,UAAW,QAAO;AACtC,SAAO,MAAM,MAAM,CAAC,SAAS,EAAE,KAAK,GAAG;AACzC;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/whitelabel/utils/normalize-hostname.ts"],"sourcesContent":["const CATEGORY_PREFIXES = new Set([\n \"com\", \"dev\", \"net\", \"org\", \"edu\", \"gov\",\n \"mil\", \"co\", \"art\", \"inf\", \"blog\", \"wiki\", \"app\", \"online\", \"site\"\n]);\n\nconst SIMPLE_TLDS = new Set([\"online\"]);\n\nexport function normalizeHostname(hostname: string): string {\n hostname = hostname.split(\":\")[0];\n const parts = hostname.split(\".\");\n if (parts.length <= 2) return hostname;\n\n const last = parts[parts.length - 1];\n const secondLast = parts[parts.length - 2];\n\n const isCompoundTld =\n last.length <= 3 && CATEGORY_PREFIXES.has(secondLast);\n\n const rootParts = isCompoundTld ? 3 : 2;\n\n if (SIMPLE_TLDS.has(last) || !isCompoundTld) {\n return parts.slice(-2).join(\".\");\n }\n\n if (parts.length <= rootParts) return hostname;\n return parts.slice(-rootParts).join(\".\");\n}\n"],"mappings":"AAAA,MAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACnC;AAAA,EAAO;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAU;AAC9D,CAAC;AAED,MAAM,cAAc,oBAAI,IAAI,CAAC,QAAQ,CAAC;AAE/B,SAAS,kBAAkB,UAA0B;AAC1D,aAAW,SAAS,MAAM,GAAG,EAAE,CAAC;AAChC,QAAM,QAAQ,SAAS,MAAM,GAAG;AAChC,MAAI,MAAM,UAAU,EAAG,QAAO;AAE9B,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC;AAEzC,QAAM,gBACJ,KAAK,UAAU,KAAK,kBAAkB,IAAI,UAAU;AAEtD,QAAM,YAAY,gBAAgB,IAAI;AAEtC,MAAI,YAAY,IAAI,IAAI,KAAK,CAAC,eAAe;AAC3C,WAAO,MAAM,MAAM,EAAE,EAAE,KAAK,GAAG;AAAA,EACjC;AAEA,MAAI,MAAM,UAAU,UAAW,QAAO;AACtC,SAAO,MAAM,MAAM,CAAC,SAAS,EAAE,KAAK,GAAG;AACzC;","names":[]}
@@ -1,24 +1,25 @@
1
1
  import { create } from "zustand";
2
2
  import { useEffect } from "react";
3
+ import { readStorage, writeStorage } from "../utils/browser/safe-storage";
3
4
  const STORAGE_KEY = "desktop-sidebar-expanded";
4
5
  const useDesktopSidebarStore = create((set) => ({
5
6
  isExpanded: true,
6
7
  _hydrated: false,
7
8
  toggle: () => set((state) => {
8
9
  const next = !state.isExpanded;
9
- localStorage.setItem(STORAGE_KEY, String(next));
10
+ writeStorage(STORAGE_KEY, String(next));
10
11
  return { isExpanded: next };
11
12
  }),
12
13
  expand: () => {
13
- localStorage.setItem(STORAGE_KEY, "true");
14
+ writeStorage(STORAGE_KEY, "true");
14
15
  return set({ isExpanded: true });
15
16
  },
16
17
  collapse: () => {
17
- localStorage.setItem(STORAGE_KEY, "false");
18
+ writeStorage(STORAGE_KEY, "false");
18
19
  return set({ isExpanded: false });
19
20
  },
20
21
  _hydrate: () => {
21
- const stored = localStorage.getItem(STORAGE_KEY);
22
+ const stored = readStorage(STORAGE_KEY);
22
23
  const value = stored === null ? true : stored === "true";
23
24
  set({ isExpanded: value, _hydrated: true });
24
25
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/store/useDesktopSidebarStore.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { useEffect } from \"react\";\n\nconst STORAGE_KEY = \"desktop-sidebar-expanded\";\n\ntype DesktopSidebarStore = {\n isExpanded: boolean;\n _hydrated: boolean;\n toggle: () => void;\n expand: () => void;\n collapse: () => void;\n _hydrate: () => void;\n};\n\nexport const useDesktopSidebarStore = create<DesktopSidebarStore>((set) => ({\n isExpanded: true,\n _hydrated: false,\n toggle: () => set((state) => {\n const next = !state.isExpanded;\n localStorage.setItem(STORAGE_KEY, String(next));\n return { isExpanded: next };\n }),\n expand: () => {\n localStorage.setItem(STORAGE_KEY, \"true\");\n return set({ isExpanded: true });\n },\n collapse: () => {\n localStorage.setItem(STORAGE_KEY, \"false\");\n return set({ isExpanded: false });\n },\n _hydrate: () => {\n const stored = localStorage.getItem(STORAGE_KEY);\n const value = stored === null ? true : stored === \"true\";\n set({ isExpanded: value, _hydrated: true });\n },\n}));\n\nexport function useDesktopSidebarHydration() {\n const hydrate = useDesktopSidebarStore((s) => s._hydrate);\n const hydrated = useDesktopSidebarStore((s) => s._hydrated);\n\n useEffect(() => {\n if (!hydrated) {\n hydrate();\n }\n }, [hydrate, hydrated]);\n}\n"],"mappings":"AAAA,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAE1B,MAAM,cAAc;AAWb,MAAM,yBAAyB,OAA4B,CAAC,SAAS;AAAA,EACxE,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,QAAQ,MAAM,IAAI,CAAC,UAAU;AACzB,UAAM,OAAO,CAAC,MAAM;AACpB,iBAAa,QAAQ,aAAa,OAAO,IAAI,CAAC;AAC9C,WAAO,EAAE,YAAY,KAAK;AAAA,EAC9B,CAAC;AAAA,EACD,QAAQ,MAAM;AACV,iBAAa,QAAQ,aAAa,MAAM;AACxC,WAAO,IAAI,EAAE,YAAY,KAAK,CAAC;AAAA,EACnC;AAAA,EACA,UAAU,MAAM;AACZ,iBAAa,QAAQ,aAAa,OAAO;AACzC,WAAO,IAAI,EAAE,YAAY,MAAM,CAAC;AAAA,EACpC;AAAA,EACA,UAAU,MAAM;AACZ,UAAM,SAAS,aAAa,QAAQ,WAAW;AAC/C,UAAM,QAAQ,WAAW,OAAO,OAAO,WAAW;AAClD,QAAI,EAAE,YAAY,OAAO,WAAW,KAAK,CAAC;AAAA,EAC9C;AACJ,EAAE;AAEK,SAAS,6BAA6B;AACzC,QAAM,UAAU,uBAAuB,CAAC,MAAM,EAAE,QAAQ;AACxD,QAAM,WAAW,uBAAuB,CAAC,MAAM,EAAE,SAAS;AAE1D,YAAU,MAAM;AACZ,QAAI,CAAC,UAAU;AACX,cAAQ;AAAA,IACZ;AAAA,EACJ,GAAG,CAAC,SAAS,QAAQ,CAAC;AAC1B;","names":[]}
1
+ {"version":3,"sources":["../../src/store/useDesktopSidebarStore.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { useEffect } from \"react\";\nimport { readStorage, writeStorage } from \"../utils/browser/safe-storage\";\n\nconst STORAGE_KEY = \"desktop-sidebar-expanded\";\n\ntype DesktopSidebarStore = {\n isExpanded: boolean;\n _hydrated: boolean;\n toggle: () => void;\n expand: () => void;\n collapse: () => void;\n _hydrate: () => void;\n};\n\nexport const useDesktopSidebarStore = create<DesktopSidebarStore>((set) => ({\n isExpanded: true,\n _hydrated: false,\n toggle: () => set((state) => {\n const next = !state.isExpanded;\n writeStorage(STORAGE_KEY, String(next));\n return { isExpanded: next };\n }),\n expand: () => {\n writeStorage(STORAGE_KEY, \"true\");\n return set({ isExpanded: true });\n },\n collapse: () => {\n writeStorage(STORAGE_KEY, \"false\");\n return set({ isExpanded: false });\n },\n _hydrate: () => {\n const stored = readStorage(STORAGE_KEY);\n const value = stored === null ? true : stored === \"true\";\n set({ isExpanded: value, _hydrated: true });\n },\n}));\n\nexport function useDesktopSidebarHydration() {\n const hydrate = useDesktopSidebarStore((s) => s._hydrate);\n const hydrated = useDesktopSidebarStore((s) => s._hydrated);\n\n useEffect(() => {\n if (!hydrated) {\n hydrate();\n }\n }, [hydrate, hydrated]);\n}\n"],"mappings":"AAAA,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,aAAa,oBAAoB;AAE1C,MAAM,cAAc;AAWb,MAAM,yBAAyB,OAA4B,CAAC,SAAS;AAAA,EACxE,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,QAAQ,MAAM,IAAI,CAAC,UAAU;AACzB,UAAM,OAAO,CAAC,MAAM;AACpB,iBAAa,aAAa,OAAO,IAAI,CAAC;AACtC,WAAO,EAAE,YAAY,KAAK;AAAA,EAC9B,CAAC;AAAA,EACD,QAAQ,MAAM;AACV,iBAAa,aAAa,MAAM;AAChC,WAAO,IAAI,EAAE,YAAY,KAAK,CAAC;AAAA,EACnC;AAAA,EACA,UAAU,MAAM;AACZ,iBAAa,aAAa,OAAO;AACjC,WAAO,IAAI,EAAE,YAAY,MAAM,CAAC;AAAA,EACpC;AAAA,EACA,UAAU,MAAM;AACZ,UAAM,SAAS,YAAY,WAAW;AACtC,UAAM,QAAQ,WAAW,OAAO,OAAO,WAAW;AAClD,QAAI,EAAE,YAAY,OAAO,WAAW,KAAK,CAAC;AAAA,EAC9C;AACJ,EAAE;AAEK,SAAS,6BAA6B;AACzC,QAAM,UAAU,uBAAuB,CAAC,MAAM,EAAE,QAAQ;AACxD,QAAM,WAAW,uBAAuB,CAAC,MAAM,EAAE,SAAS;AAE1D,YAAU,MAAM;AACZ,QAAI,CAAC,UAAU;AACX,cAAQ;AAAA,IACZ;AAAA,EACJ,GAAG,CAAC,SAAS,QAAQ,CAAC;AAC1B;","names":[]}
@@ -1,24 +1,22 @@
1
1
  import { create } from "zustand";
2
+ import { readStorage, writeStorage } from "../utils/browser/safe-storage";
2
3
  const STORAGE_KEY = "md-sidebar-expanded";
3
4
  function getInitialState() {
4
- if (typeof window === "undefined") return false;
5
- const stored = localStorage.getItem(STORAGE_KEY);
6
- if (stored === null) return false;
7
- return stored === "true";
5
+ return readStorage(STORAGE_KEY) === "true";
8
6
  }
9
7
  const useMdSidebarStore = create((set) => ({
10
8
  isExpanded: getInitialState(),
11
9
  toggle: () => set((state) => {
12
10
  const next = !state.isExpanded;
13
- localStorage.setItem(STORAGE_KEY, String(next));
11
+ writeStorage(STORAGE_KEY, String(next));
14
12
  return { isExpanded: next };
15
13
  }),
16
14
  expand: () => {
17
- localStorage.setItem(STORAGE_KEY, "true");
15
+ writeStorage(STORAGE_KEY, "true");
18
16
  return set({ isExpanded: true });
19
17
  },
20
18
  collapse: () => {
21
- localStorage.setItem(STORAGE_KEY, "false");
19
+ writeStorage(STORAGE_KEY, "false");
22
20
  return set({ isExpanded: false });
23
21
  }
24
22
  }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/store/useMdSidebarStore.ts"],"sourcesContent":["import { create } from \"zustand\";\n\nconst STORAGE_KEY = \"md-sidebar-expanded\";\n\nfunction getInitialState(): boolean {\n if (typeof window === \"undefined\") return false;\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored === null) return false;\n return stored === \"true\";\n}\n\ntype MdSidebarStore = {\n isExpanded: boolean;\n toggle: () => void;\n expand: () => void;\n collapse: () => void;\n};\n\nexport const useMdSidebarStore = create<MdSidebarStore>((set) => ({\n isExpanded: getInitialState(),\n toggle: () => set((state) => {\n const next = !state.isExpanded;\n localStorage.setItem(STORAGE_KEY, String(next));\n return { isExpanded: next };\n }),\n expand: () => {\n localStorage.setItem(STORAGE_KEY, \"true\");\n return set({ isExpanded: true });\n },\n collapse: () => {\n localStorage.setItem(STORAGE_KEY, \"false\");\n return set({ isExpanded: false });\n },\n}));\n"],"mappings":"AAAA,SAAS,cAAc;AAEvB,MAAM,cAAc;AAEpB,SAAS,kBAA2B;AAChC,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,SAAS,aAAa,QAAQ,WAAW;AAC/C,MAAI,WAAW,KAAM,QAAO;AAC5B,SAAO,WAAW;AACtB;AASO,MAAM,oBAAoB,OAAuB,CAAC,SAAS;AAAA,EAC9D,YAAY,gBAAgB;AAAA,EAC5B,QAAQ,MAAM,IAAI,CAAC,UAAU;AACzB,UAAM,OAAO,CAAC,MAAM;AACpB,iBAAa,QAAQ,aAAa,OAAO,IAAI,CAAC;AAC9C,WAAO,EAAE,YAAY,KAAK;AAAA,EAC9B,CAAC;AAAA,EACD,QAAQ,MAAM;AACV,iBAAa,QAAQ,aAAa,MAAM;AACxC,WAAO,IAAI,EAAE,YAAY,KAAK,CAAC;AAAA,EACnC;AAAA,EACA,UAAU,MAAM;AACZ,iBAAa,QAAQ,aAAa,OAAO;AACzC,WAAO,IAAI,EAAE,YAAY,MAAM,CAAC;AAAA,EACpC;AACJ,EAAE;","names":[]}
1
+ {"version":3,"sources":["../../src/store/useMdSidebarStore.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { readStorage, writeStorage } from \"../utils/browser/safe-storage\";\n\nconst STORAGE_KEY = \"md-sidebar-expanded\";\n\nfunction getInitialState(): boolean {\n return readStorage(STORAGE_KEY) === \"true\";\n}\n\ntype MdSidebarStore = {\n isExpanded: boolean;\n toggle: () => void;\n expand: () => void;\n collapse: () => void;\n};\n\nexport const useMdSidebarStore = create<MdSidebarStore>((set) => ({\n isExpanded: getInitialState(),\n toggle: () => set((state) => {\n const next = !state.isExpanded;\n writeStorage(STORAGE_KEY, String(next));\n return { isExpanded: next };\n }),\n expand: () => {\n writeStorage(STORAGE_KEY, \"true\");\n return set({ isExpanded: true });\n },\n collapse: () => {\n writeStorage(STORAGE_KEY, \"false\");\n return set({ isExpanded: false });\n },\n}));\n"],"mappings":"AAAA,SAAS,cAAc;AACvB,SAAS,aAAa,oBAAoB;AAE1C,MAAM,cAAc;AAEpB,SAAS,kBAA2B;AAChC,SAAO,YAAY,WAAW,MAAM;AACxC;AASO,MAAM,oBAAoB,OAAuB,CAAC,SAAS;AAAA,EAC9D,YAAY,gBAAgB;AAAA,EAC5B,QAAQ,MAAM,IAAI,CAAC,UAAU;AACzB,UAAM,OAAO,CAAC,MAAM;AACpB,iBAAa,aAAa,OAAO,IAAI,CAAC;AACtC,WAAO,EAAE,YAAY,KAAK;AAAA,EAC9B,CAAC;AAAA,EACD,QAAQ,MAAM;AACV,iBAAa,aAAa,MAAM;AAChC,WAAO,IAAI,EAAE,YAAY,KAAK,CAAC;AAAA,EACnC;AAAA,EACA,UAAU,MAAM;AACZ,iBAAa,aAAa,OAAO;AACjC,WAAO,IAAI,EAAE,YAAY,MAAM,CAAC;AAAA,EACpC;AACJ,EAAE;","names":[]}
@@ -0,0 +1,20 @@
1
+ function readStorage(key) {
2
+ if (typeof window === "undefined") return null;
3
+ try {
4
+ return window.localStorage?.getItem(key) ?? null;
5
+ } catch {
6
+ return null;
7
+ }
8
+ }
9
+ function writeStorage(key, value) {
10
+ if (typeof window === "undefined") return;
11
+ try {
12
+ window.localStorage?.setItem(key, value);
13
+ } catch {
14
+ }
15
+ }
16
+ export {
17
+ readStorage,
18
+ writeStorage
19
+ };
20
+ //# sourceMappingURL=safe-storage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/utils/browser/safe-storage.ts"],"sourcesContent":["// Alguns webviews in-app (Google/Meta, Android com storage desabilitado) tanto podem\n// devolver `window.localStorage === null` quanto lançar SecurityError ao simplesmente\n// acessar o getter. `typeof window === 'undefined'` não cobre nenhum dos dois casos.\nexport function readStorage(key: string): string | null {\n if (typeof window === 'undefined') return null;\n try {\n return window.localStorage?.getItem(key) ?? null;\n } catch {\n return null;\n }\n}\n\nexport function writeStorage(key: string, value: string): void {\n if (typeof window === 'undefined') return;\n try {\n window.localStorage?.setItem(key, value);\n } catch {\n // storage indisponível — segue só com o estado em memória\n }\n}\n"],"mappings":"AAGO,SAAS,YAAY,KAA4B;AACtD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,WAAO,OAAO,cAAc,QAAQ,GAAG,KAAK;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,KAAa,OAAqB;AAC7D,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,WAAO,cAAc,QAAQ,KAAK,KAAK;AAAA,EACzC,QAAQ;AAAA,EAER;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.729",
3
+ "version": "1.1.731",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -129,7 +129,7 @@ export default function ConfigurationsMyAccountModal() {
129
129
  <span className="paragraph-small-medium">{translate('common.preferences.title')}</span>
130
130
  </TabsTrigger>
131
131
 
132
- {isOwner && whitelabel?.id === 1 && (
132
+ {isOwner && (
133
133
  <TabsTrigger value={AccountSectionType.TOKEN} className={tabTriggerClasses}>
134
134
  <IconKey size={20} className="shrink-0" />
135
135
  <span className="paragraph-small-medium">{translate('common.account.token.title')}</span>
@@ -8,12 +8,14 @@ import { Button } from '../../ui/buttons/Button';
8
8
  import { FormField } from '../../ui/form/FormField';
9
9
  import useCopyToClipboard from '../../../hooks/copy-to-clipboard.hook';
10
10
  import { useAccountToken } from '../../../modules/accounts/hooks/use-account-token.hook';
11
- import { useIsDefaultWhitelabel } from '../../../providers/whitelabel.provider';
11
+ import { useIsDefaultWhitelabel, useWhitelabel } from '../../../providers/whitelabel.provider';
12
12
 
13
13
  export function TokenSection() {
14
14
  const translate = useTranslations();
15
15
  const { data: token = '', isLoading } = useAccountToken();
16
16
  const isDefaultWl = useIsDefaultWhitelabel();
17
+ const { whitelabel } = useWhitelabel();
18
+
17
19
 
18
20
  const { isCopied, copy } = useCopyToClipboard({
19
21
  onSuccess: () => {
@@ -71,14 +73,16 @@ export function TokenSection() {
71
73
  </span>
72
74
  </div>
73
75
 
74
- <a
75
- href="https://documenter.getpostman.com/view/4861938/T1Djiz8H"
76
- target="_blank"
77
- rel="noopener noreferrer"
78
- className="paragraph-small-medium text-gray-500 underline hover:text-gray-700 transition-colors w-fit"
79
- >
80
- {translate('common.account.token.docsLink')}
81
- </a>
76
+ {whitelabel?.id === 1 && (
77
+ <a
78
+ href="https://documenter.getpostman.com/view/4861938/T1Djiz8H"
79
+ target="_blank"
80
+ rel="noopener noreferrer"
81
+ className="paragraph-small-medium text-gray-500 underline hover:text-gray-700 transition-colors w-fit"
82
+ >
83
+ {translate('common.account.token.docsLink')}
84
+ </a>
85
+ )}
82
86
  </div>
83
87
  </div>
84
88
  );
@@ -63,7 +63,6 @@ export default function BuyCreditsModal() {
63
63
  } = useCalculateSubscription();
64
64
  const updatePlanMutation = useUpdateSubscriptionPlan();
65
65
 
66
- // Créditos inclusos no plano têm value == 0; o select representa só o contratado.
67
66
  const aiItem = useMemo(
68
67
  () =>
69
68
  subscription?.items?.find(
@@ -3,6 +3,8 @@ import { ApiError } from "../../../infra/api/types";
3
3
  import { WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
4
4
  import { normalizeHostname } from "../utils/normalize-hostname";
5
5
 
6
+ const NOT_FOUND_SENTINEL = "__WL_TOKEN_NOT_FOUND__";
7
+
6
8
  class WhitelabelService {
7
9
  private getApiUrl(): string {
8
10
  const apiUrl = process.env.GAPPS_R3_API_URL;
@@ -56,6 +58,12 @@ class WhitelabelService {
56
58
  });
57
59
 
58
60
  if (!response.ok) {
61
+ const body = await response.json().catch(() => null);
62
+
63
+ if (response.status === 404 || body?.message === "Whitelabel not found") {
64
+ throw new ApiError("Whitelabel token not found", "TOKEN_NOT_FOUND", 404);
65
+ }
66
+
59
67
  console.error("[WhitelabelService] Failed to fetch whitelabel token", {
60
68
  response,
61
69
  });
@@ -92,15 +100,31 @@ class WhitelabelService {
92
100
  });
93
101
 
94
102
  if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
103
+ if (cachedData.data === NOT_FOUND_SENTINEL) {
104
+ console.log("[WhitelabelService] Negative cache hit for domain", { hostname });
105
+ throw new ApiError("Whitelabel token not found", "TOKEN_NOT_FOUND", 404);
106
+ }
107
+
95
108
  console.log("[WhitelabelService] Cache hit for domain", { hostname });
96
109
  return JSON.parse(cachedData.data) as WhitelabelTokenData;
97
110
  }
98
111
 
99
- const data = await this.fetchFromApi(hostname);
100
-
101
- await cache.insert(cacheKey, JSON.stringify(data), 604800);
102
-
103
- return data;
112
+ try {
113
+ const data = await this.fetchFromApi(hostname);
114
+
115
+ await cache.insert(cacheKey, JSON.stringify(data), 604800);
116
+
117
+ return data;
118
+ } catch (error) {
119
+ if (ApiError.isApiError(error) && error.code === "TOKEN_NOT_FOUND") {
120
+ try {
121
+ await cache.insert(cacheKey, NOT_FOUND_SENTINEL, 60);
122
+ } catch {
123
+ // falha do cache não pode substituir o TOKEN_NOT_FOUND original
124
+ }
125
+ }
126
+ throw error;
127
+ }
104
128
  }
105
129
 
106
130
  async getTokenByWhitelabelId(idWl: number): Promise<string> {
@@ -6,6 +6,7 @@ const CATEGORY_PREFIXES = new Set([
6
6
  const SIMPLE_TLDS = new Set(["online"]);
7
7
 
8
8
  export function normalizeHostname(hostname: string): string {
9
+ hostname = hostname.split(":")[0];
9
10
  const parts = hostname.split(".");
10
11
  if (parts.length <= 2) return hostname;
11
12
 
@@ -1,5 +1,6 @@
1
1
  import { create } from "zustand";
2
2
  import { useEffect } from "react";
3
+ import { readStorage, writeStorage } from "../utils/browser/safe-storage";
3
4
 
4
5
  const STORAGE_KEY = "desktop-sidebar-expanded";
5
6
 
@@ -17,19 +18,19 @@ export const useDesktopSidebarStore = create<DesktopSidebarStore>((set) => ({
17
18
  _hydrated: false,
18
19
  toggle: () => set((state) => {
19
20
  const next = !state.isExpanded;
20
- localStorage.setItem(STORAGE_KEY, String(next));
21
+ writeStorage(STORAGE_KEY, String(next));
21
22
  return { isExpanded: next };
22
23
  }),
23
24
  expand: () => {
24
- localStorage.setItem(STORAGE_KEY, "true");
25
+ writeStorage(STORAGE_KEY, "true");
25
26
  return set({ isExpanded: true });
26
27
  },
27
28
  collapse: () => {
28
- localStorage.setItem(STORAGE_KEY, "false");
29
+ writeStorage(STORAGE_KEY, "false");
29
30
  return set({ isExpanded: false });
30
31
  },
31
32
  _hydrate: () => {
32
- const stored = localStorage.getItem(STORAGE_KEY);
33
+ const stored = readStorage(STORAGE_KEY);
33
34
  const value = stored === null ? true : stored === "true";
34
35
  set({ isExpanded: value, _hydrated: true });
35
36
  },
@@ -1,12 +1,10 @@
1
1
  import { create } from "zustand";
2
+ import { readStorage, writeStorage } from "../utils/browser/safe-storage";
2
3
 
3
4
  const STORAGE_KEY = "md-sidebar-expanded";
4
5
 
5
6
  function getInitialState(): boolean {
6
- if (typeof window === "undefined") return false;
7
- const stored = localStorage.getItem(STORAGE_KEY);
8
- if (stored === null) return false;
9
- return stored === "true";
7
+ return readStorage(STORAGE_KEY) === "true";
10
8
  }
11
9
 
12
10
  type MdSidebarStore = {
@@ -20,15 +18,15 @@ export const useMdSidebarStore = create<MdSidebarStore>((set) => ({
20
18
  isExpanded: getInitialState(),
21
19
  toggle: () => set((state) => {
22
20
  const next = !state.isExpanded;
23
- localStorage.setItem(STORAGE_KEY, String(next));
21
+ writeStorage(STORAGE_KEY, String(next));
24
22
  return { isExpanded: next };
25
23
  }),
26
24
  expand: () => {
27
- localStorage.setItem(STORAGE_KEY, "true");
25
+ writeStorage(STORAGE_KEY, "true");
28
26
  return set({ isExpanded: true });
29
27
  },
30
28
  collapse: () => {
31
- localStorage.setItem(STORAGE_KEY, "false");
29
+ writeStorage(STORAGE_KEY, "false");
32
30
  return set({ isExpanded: false });
33
31
  },
34
32
  }));
@@ -0,0 +1,20 @@
1
+ // Alguns webviews in-app (Google/Meta, Android com storage desabilitado) tanto podem
2
+ // devolver `window.localStorage === null` quanto lançar SecurityError ao simplesmente
3
+ // acessar o getter. `typeof window === 'undefined'` não cobre nenhum dos dois casos.
4
+ export function readStorage(key: string): string | null {
5
+ if (typeof window === 'undefined') return null;
6
+ try {
7
+ return window.localStorage?.getItem(key) ?? null;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+
13
+ export function writeStorage(key: string, value: string): void {
14
+ if (typeof window === 'undefined') return;
15
+ try {
16
+ window.localStorage?.setItem(key, value);
17
+ } catch {
18
+ // storage indisponível — segue só com o estado em memória
19
+ }
20
+ }