@greatapps/common 1.1.752 → 1.1.754
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/form/ComboboxField.mjs.map +1 -1
- package/dist/modules/plans/services/plans.service.mjs +2 -2
- package/dist/modules/plans/services/plans.service.mjs.map +1 -1
- package/dist/modules/plans/types/plan.type.mjs.map +1 -1
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs +1 -0
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
- package/dist/modules/subscriptions/types/calculate-subscription.type.mjs +10 -0
- package/dist/modules/subscriptions/types/calculate-subscription.type.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/ui/form/ComboboxField.tsx +7 -1
- package/src/modules/plans/services/plans.service.ts +3 -3
- package/src/modules/plans/types/plan.type.ts +1 -0
- package/src/modules/plans/utils/map-api-plan-to-ui.ts +1 -0
- package/src/modules/subscriptions/types/calculate-subscription.type.ts +10 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/ui/form/ComboboxField.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useTranslations } from 'next-intl';\nimport { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-react';\nimport { Popover, PopoverContent, PopoverTrigger } from '../overlay/Popover';\nimport { cn } from '../../../infra/utils/clsx';\n\nexport type ComboboxOption = {\n value: string;\n label: string;\n displayValue?: string;\n};\n\ninterface ComboboxFieldProps {\n options: ComboboxOption[];\n label?: string;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyMessage?: string;\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n icon?: React.ReactNode;\n className?: string;\n containerClassName?: string;\n}\n\nexport function ComboboxField({\n options,\n label,\n placeholder,\n searchPlaceholder,\n emptyMessage,\n value,\n onChange,\n disabled = false,\n icon,\n className,\n containerClassName,\n}: ComboboxFieldProps) {\n const translate = useTranslations();\n const resolvedPlaceholder = placeholder ?? translate('common.forms.combobox.placeholder');\n const resolvedSearchPlaceholder = searchPlaceholder ?? translate('common.forms.combobox.searchPlaceholder');\n const resolvedEmptyMessage = emptyMessage ?? translate('common.forms.combobox.emptyMessage');\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n\n const selectedOption = useMemo(\n () => options.find((o) => o.value === value),\n [options, value]\n );\n\n const filteredOptions = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return options;\n return options.filter((o) => o.label.toLowerCase().includes(q));\n }, [options, search]);\n\n const handleSelect = (optionValue: string) => {\n onChange?.(optionValue);\n setOpen(false);\n setSearch('');\n };\n\n return (\n <div className={cn('flex flex-col gap-1.5', containerClassName)}>\n {label && (\n <div className=\"flex items-center gap-1 paragraph-xsmall-semibold\">\n <span className=\"text-gray-600\">{label}</span>\n </div>\n )}\n\n {/* `modal` é obrigatório para o combobox funcionar dentro de Dialog modal\n * (ex.: Preferências): sem ele, o FocusScope do Dialog rouba o foco no\n * pointerdown da opção (o conteúdo do Popover é portalado pra fora do Dialog)\n * e o Popover fecha antes do click — a opção nunca é selecionada. Com `modal`,\n * o Popover entra na pilha de focus scopes do Radix e pausa o do Dialog. */}\n <Popover\n modal\n open={open}\n onOpenChange={(v) => {\n if (!disabled) {\n setOpen(v);\n if (!v) setSearch('');\n }\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'paragraph-small-medium w-full h-10 text-gray-950 px-4 transition-colors',\n 'flex items-center justify-between gap-2 rounded-md border bg-transparent',\n 'border-gray-200 hover:border-gray-400 focus:border-gray-950 focus:outline-none',\n disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',\n className\n )}\n >\n <span className={cn('truncate text-left', !selectedOption && 'text-gray-400')}>\n {selectedOption\n ? (selectedOption.displayValue ?? selectedOption.label)\n : resolvedPlaceholder}\n </span>\n {icon ?? (\n <IconChevronDown\n className={cn(\n 'size-4 opacity-50 shrink-0 transition-all duration-200',\n open && 'rotate-180 opacity-100 text-gray-950'\n )}\n />\n )}\n </button>\n </PopoverTrigger>\n\n <PopoverContent\n className=\"p-0 overflow-hidden rounded-md border shadow-md\"\n align=\"start\"\n sideOffset={4}\n style={{ width: 'var(--radix-popover-trigger-width)' }}\n onWheel={(e) => e.stopPropagation()}\n >\n <div className=\"flex items-center gap-2 border-b border-gray-100 px-3 h-9\">\n <IconSearch className=\"size-4 shrink-0 opacity-50 text-gray-500\" />\n <input\n autoFocus\n placeholder={resolvedSearchPlaceholder}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"flex h-full w-full bg-transparent text-sm outline-none placeholder:text-gray-400 paragraph-small-medium text-gray-950\"\n />\n </div>\n\n <div className=\"overflow-y-auto max-h-[280px] p-1\">\n {filteredOptions.length === 0 ? (\n <p className=\"py-6 text-center paragraph-small-medium text-gray-500\">{resolvedEmptyMessage}</p>\n ) : (\n filteredOptions.map((option) => {\n const isSelected = value === option.value;\n return (\n <div\n key={option.value}\n className={cn(\n 'flex items-center gap-2 rounded-sm px-2 h-9 cursor-pointer select-none',\n 'paragraph-small-medium text-gray-950',\n 'hover:bg-gray-100',\n isSelected && 'bg-gray-50'\n )}\n onClick={() => handleSelect(option.value)}\n >\n <span className=\"flex-1 truncate\">{option.label}</span>\n {isSelected && <IconCheck className=\"size-4 shrink-0 text-pages-600\" />}\n </div>\n );\n })\n )}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n\nexport default ComboboxField;\n"],"mappings":";AAqEU,
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/ui/form/ComboboxField.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useMemo } from 'react';\nimport { useTranslations } from 'next-intl';\nimport { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-react';\nimport { Popover, PopoverContent, PopoverTrigger } from '../overlay/Popover';\nimport { cn } from '../../../infra/utils/clsx';\n\nexport type ComboboxOption = {\n value: string;\n label: string;\n displayValue?: string;\n};\n\ninterface ComboboxFieldProps {\n options: ComboboxOption[];\n label?: string;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyMessage?: string;\n value?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n icon?: React.ReactNode;\n className?: string;\n containerClassName?: string;\n}\n\nexport function ComboboxField({\n options,\n label,\n placeholder,\n searchPlaceholder,\n emptyMessage,\n value,\n onChange,\n disabled = false,\n icon,\n className,\n containerClassName,\n}: ComboboxFieldProps) {\n const translate = useTranslations();\n const resolvedPlaceholder = placeholder ?? translate('common.forms.combobox.placeholder');\n const resolvedSearchPlaceholder = searchPlaceholder ?? translate('common.forms.combobox.searchPlaceholder');\n const resolvedEmptyMessage = emptyMessage ?? translate('common.forms.combobox.emptyMessage');\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n\n const selectedOption = useMemo(\n () => options.find((o) => o.value === value),\n [options, value]\n );\n\n const filteredOptions = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return options;\n return options.filter((o) => o.label.toLowerCase().includes(q));\n }, [options, search]);\n\n const handleSelect = (optionValue: string) => {\n onChange?.(optionValue);\n setOpen(false);\n setSearch('');\n };\n\n return (\n <div className={cn('flex flex-col gap-1.5', containerClassName)}>\n {label && (\n <div className=\"flex items-center gap-1 paragraph-xsmall-semibold\">\n <span className=\"text-gray-600\">{label}</span>\n </div>\n )}\n\n {/* `modal` é obrigatório para o combobox funcionar dentro de Dialog modal\n * (ex.: Preferências): sem ele, o FocusScope do Dialog rouba o foco no\n * pointerdown da opção (o conteúdo do Popover é portalado pra fora do Dialog)\n * e o Popover fecha antes do click — a opção nunca é selecionada. Com `modal`,\n * o Popover entra na pilha de focus scopes do Radix e pausa o do Dialog.\n *\n * ⚠️ Essa pilha é um singleton POR INSTÂNCIA de @radix-ui/react-focus-scope:\n * react-dialog e react-popover precisam resolver para a MESMA versão dele\n * (manter os dois pins do package.json na mesma linha de release). Com duas\n * cópias, o trap do Dialog não é pausado e rouba o foco do input de busca a\n * cada clique — impossível digitar. */}\n <Popover\n modal\n open={open}\n onOpenChange={(v) => {\n if (!disabled) {\n setOpen(v);\n if (!v) setSearch('');\n }\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'paragraph-small-medium w-full h-10 text-gray-950 px-4 transition-colors',\n 'flex items-center justify-between gap-2 rounded-md border bg-transparent',\n 'border-gray-200 hover:border-gray-400 focus:border-gray-950 focus:outline-none',\n disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',\n className\n )}\n >\n <span className={cn('truncate text-left', !selectedOption && 'text-gray-400')}>\n {selectedOption\n ? (selectedOption.displayValue ?? selectedOption.label)\n : resolvedPlaceholder}\n </span>\n {icon ?? (\n <IconChevronDown\n className={cn(\n 'size-4 opacity-50 shrink-0 transition-all duration-200',\n open && 'rotate-180 opacity-100 text-gray-950'\n )}\n />\n )}\n </button>\n </PopoverTrigger>\n\n <PopoverContent\n className=\"p-0 overflow-hidden rounded-md border shadow-md\"\n align=\"start\"\n sideOffset={4}\n style={{ width: 'var(--radix-popover-trigger-width)' }}\n onWheel={(e) => e.stopPropagation()}\n >\n <div className=\"flex items-center gap-2 border-b border-gray-100 px-3 h-9\">\n <IconSearch className=\"size-4 shrink-0 opacity-50 text-gray-500\" />\n <input\n autoFocus\n placeholder={resolvedSearchPlaceholder}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"flex h-full w-full bg-transparent text-sm outline-none placeholder:text-gray-400 paragraph-small-medium text-gray-950\"\n />\n </div>\n\n <div className=\"overflow-y-auto max-h-[280px] p-1\">\n {filteredOptions.length === 0 ? (\n <p className=\"py-6 text-center paragraph-small-medium text-gray-500\">{resolvedEmptyMessage}</p>\n ) : (\n filteredOptions.map((option) => {\n const isSelected = value === option.value;\n return (\n <div\n key={option.value}\n className={cn(\n 'flex items-center gap-2 rounded-sm px-2 h-9 cursor-pointer select-none',\n 'paragraph-small-medium text-gray-950',\n 'hover:bg-gray-100',\n isSelected && 'bg-gray-50'\n )}\n onClick={() => handleSelect(option.value)}\n >\n <span className=\"flex-1 truncate\">{option.label}</span>\n {isSelected && <IconCheck className=\"size-4 shrink-0 text-pages-600\" />}\n </div>\n );\n })\n )}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n\nexport default ComboboxField;\n"],"mappings":"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","names":[]}
|
|
@@ -16,7 +16,7 @@ class PlansService {
|
|
|
16
16
|
const sort = params?.sort ?? "id:ASC";
|
|
17
17
|
const active = params?.active ?? "";
|
|
18
18
|
const search = params?.search ?? "";
|
|
19
|
-
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-
|
|
19
|
+
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v7`;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Lista planos do whitelabel.
|
|
@@ -61,7 +61,7 @@ class PlansService {
|
|
|
61
61
|
}
|
|
62
62
|
async findById(idPlan) {
|
|
63
63
|
const { id_wl, id_account } = await getUserContext();
|
|
64
|
-
const cacheKey = this.buildCacheKey(`plan-${idPlan}-
|
|
64
|
+
const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {
|
|
65
65
|
id_wl,
|
|
66
66
|
id_account: String(id_account)
|
|
67
67
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import \"server-only\";\n\nimport { api, getUserContext } from \"@greatapps/common/server\";\nimport { ApiError, buildQueryParams, PlanSchema } from \"@greatapps/common\";\nimport type {\n ApiPaginatedActionResult,\n PaginatedSuccessResult,\n Plan,\n SuccessResult,\n} from \"@greatapps/common\";\nimport greatCache from \"@greatapps/cache\";\n\nexport type ListPlansParams = {\n sort?: string;\n active?: boolean;\n search?: string;\n};\n\nconst PLANS_CACHE_TTL = 21600;\n\nclass PlansService {\n private cache = new greatCache({\n service: \"plans\",\n version: \"1.0\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n\n private buildCacheKey(key: string, params?: Record<string, unknown>): string {\n const idWl = params?.id_wl ?? \"\";\n const idAccount = params?.id_account ?? \"\";\n const sort = params?.sort ?? \"id:ASC\";\n const active = params?.active ?? \"\";\n const search = params?.search ?? \"\";\n //
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/services/plans.service.ts"],"sourcesContent":["import \"server-only\";\n\nimport { api, getUserContext } from \"@greatapps/common/server\";\nimport { ApiError, buildQueryParams, PlanSchema } from \"@greatapps/common\";\nimport type {\n ApiPaginatedActionResult,\n PaginatedSuccessResult,\n Plan,\n SuccessResult,\n} from \"@greatapps/common\";\nimport greatCache from \"@greatapps/cache\";\n\nexport type ListPlansParams = {\n sort?: string;\n active?: boolean;\n search?: string;\n};\n\nconst PLANS_CACHE_TTL = 21600;\n\nclass PlansService {\n private cache = new greatCache({\n service: \"plans\",\n version: \"1.0\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n\n private buildCacheKey(key: string, params?: Record<string, unknown>): string {\n const idWl = params?.id_wl ?? \"\";\n const idAccount = params?.id_account ?? \"\";\n const sort = params?.sort ?? \"id:ASC\";\n const active = params?.active ?? \"\";\n const search = params?.search ?? \"\";\n // v7: descarta as entradas v6 que não têm o campo pricing (ancoragem de preço)\n return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v7`;\n }\n\n /**\n * Lista planos do whitelabel.\n * Exemplo:\n * GET /{id_wl}/plans?active=true&search=client&sort=id:ASC\n */\n async listPlans(\n params?: ListPlansParams,\n ): Promise<PaginatedSuccessResult<Plan>> {\n const { id_wl, id_account } = await getUserContext();\n const cacheKey = this.buildCacheKey(\"plans\", {\n ...params,\n id_wl,\n id_account,\n });\n\n const cachedData = await this.cache.select(cacheKey);\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan[];\n return { data, total: data.length, success: true };\n }\n\n const query = buildQueryParams({\n sort: params?.sort ?? \"id:ASC\",\n active: params?.active,\n search: params?.search,\n id_account: id_account,\n });\n const url = `/plans${query ? `?${query}` : \"\"}`;\n\n const response = await api.apps.get<ApiPaginatedActionResult<Plan[]>>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n (response as { message?: string }).message || \"Erro ao listar planos\",\n \"LIST_PLANS_FAILED\",\n 400,\n );\n }\n\n const rawData = (response as { data?: unknown }).data;\n const data = Array.isArray(rawData)\n ? rawData.map((item) => PlanSchema.parse(item))\n : [];\n\n await this.cache.insert(cacheKey, JSON.stringify(data), PLANS_CACHE_TTL);\n\n return {\n data,\n total: response.total,\n success: true,\n } satisfies PaginatedSuccessResult<Plan>;\n }\n\n async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {\n const { id_wl, id_account } = await getUserContext();\n const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {\n id_wl,\n id_account: String(id_account),\n });\n\n const cachedData = await this.cache.select(cacheKey);\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const data = JSON.parse(cachedData.data) as Plan;\n return { data, success: true };\n }\n\n const query = buildQueryParams({ type: \"client\", id_account });\n\n const response = await api.apps.get<ApiPaginatedActionResult<Plan>>(\n `/plans/${idPlan}?${query}`,\n );\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || \"Erro ao buscar plano\",\n \"FIND_PLAN_FAILED\",\n 400,\n );\n }\n\n if (!response.data?.length) {\n throw new ApiError(\"Plano não encontrado\", \"PLAN_NOT_FOUND\", 404);\n }\n\n const plan = PlanSchema.parse(response.data[0]);\n\n await this.cache.insert(cacheKey, JSON.stringify(plan), PLANS_CACHE_TTL);\n\n return {\n success: true,\n data: plan,\n };\n }\n}\n\nexport const plansService = new PlansService();\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,KAAK,sBAAsB;AACpC,SAAS,UAAU,kBAAkB,kBAAkB;AAOvD,OAAO,gBAAgB;AAQvB,MAAM,kBAAkB;AAExB,MAAM,aAAa;AAAA,EACT,QAAQ,IAAI,WAAW;AAAA,IAC7B,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS,QAAQ,IAAI,YAAY;AAAA,EACnC,CAAC;AAAA,EAEO,cAAc,KAAa,QAA0C;AAC3E,UAAM,OAAO,QAAQ,SAAS;AAC9B,UAAM,YAAY,QAAQ,cAAc;AACxC,UAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,SAAS,QAAQ,UAAU;AAEjC,WAAO,GAAG,GAAG,IAAI,IAAI,IAAI,SAAS,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UACJ,QACuC;AACvC,UAAM,EAAE,OAAO,WAAW,IAAI,MAAM,eAAe;AACnD,UAAM,WAAW,KAAK,cAAc,SAAS;AAAA,MAC3C,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,aAAa,MAAM,KAAK,MAAM,OAAO,QAAQ;AAEnD,QAAI,WAAW,UAAU,KAAK,UAAU,cAAc,WAAW,MAAM;AACrE,YAAMA,QAAO,KAAK,MAAM,WAAW,IAAI;AACvC,aAAO,EAAE,MAAAA,OAAM,OAAOA,MAAK,QAAQ,SAAS,KAAK;AAAA,IACnD;AAEA,UAAM,QAAQ,iBAAiB;AAAA,MAC7B,MAAM,QAAQ,QAAQ;AAAA,MACtB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB;AAAA,IACF,CAAC;AACD,UAAM,MAAM,SAAS,QAAQ,IAAI,KAAK,KAAK,EAAE;AAE7C,UAAM,WAAW,MAAM,IAAI,KAAK,IAAsC,GAAG;AAEzE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACP,SAAkC,WAAW;AAAA,QAC9C;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,UAAW,SAAgC;AACjD,UAAM,OAAO,MAAM,QAAQ,OAAO,IAC9B,QAAQ,IAAI,CAAC,SAAS,WAAW,MAAM,IAAI,CAAC,IAC5C,CAAC;AAEL,UAAM,KAAK,MAAM,OAAO,UAAU,KAAK,UAAU,IAAI,GAAG,eAAe;AAEvE,WAAO;AAAA,MACL;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,QAAuD;AACpE,UAAM,EAAE,OAAO,WAAW,IAAI,MAAM,eAAe;AACnD,UAAM,WAAW,KAAK,cAAc,QAAQ,MAAM,OAAO;AAAA,MACvD;AAAA,MACA,YAAY,OAAO,UAAU;AAAA,IAC/B,CAAC;AAED,UAAM,aAAa,MAAM,KAAK,MAAM,OAAO,QAAQ;AAEnD,QAAI,WAAW,UAAU,KAAK,UAAU,cAAc,WAAW,MAAM;AACrE,YAAM,OAAO,KAAK,MAAM,WAAW,IAAI;AACvC,aAAO,EAAE,MAAM,SAAS,KAAK;AAAA,IAC/B;AAEA,UAAM,QAAQ,iBAAiB,EAAE,MAAM,UAAU,WAAW,CAAC;AAE7D,UAAM,WAAW,MAAM,IAAI,KAAK;AAAA,MAC9B,UAAU,MAAM,IAAI,KAAK;AAAA,IAC3B;AAEA,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,MAAM,QAAQ;AAC1B,YAAM,IAAI,SAAS,2BAAwB,kBAAkB,GAAG;AAAA,IAClE;AAEA,UAAM,OAAO,WAAW,MAAM,SAAS,KAAK,CAAC,CAAC;AAE9C,UAAM,KAAK,MAAM,OAAO,UAAU,KAAK,UAAU,IAAI,GAAG,eAAe;AAEvE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAEO,MAAM,eAAe,IAAI,aAAa;","names":["data"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/plans/types/plan.type.ts"],"sourcesContent":["import z from 'zod';\nimport type { BillingPeriod } from '../../subscriptions/types/billing-period.type';\n\nexport const PlanItemSchema = z.object({\n id_addon: z.number(),\n quantity: z.number(),\n value: z.number(),\n value_usd: z.number().nullish(),\n // Maior valor positivo por addon = preço base/original (demais linhas são promos).\n // Derivado por heurística no backend (não há coluna no banco). Ausente em payloads\n // de cache antigos → optional.\n base: z.boolean().optional(),\n});\n\nexport type PlanItem = z.infer<typeof PlanItemSchema>;\n\nexport const PlanPeriodPricingSchema = z.object({\n charged: z.number(),\n list: z.number(),\n discount: z.number(),\n discount_percent: z.number(),\n});\n\nexport const PlanApiPricingSchema = z.object({\n currency: z.enum(['brl', 'usd']),\n monthly: PlanPeriodPricingSchema,\n semester: PlanPeriodPricingSchema,\n annual: PlanPeriodPricingSchema,\n});\n\nexport type PlanApiPricing = z.infer<typeof PlanApiPricingSchema>;\n\nexport const PlanSchema = z.object({\n id: z.number(),\n id_plan: z.number().optional(),\n name: z.string(),\n value: z.number().nullable().default(0),\n // Preço em USD (conta stripe_usd). Operador cadastra no backend; ausente em planos\n // sem suporte internacional. Backend agora resolve por moeda da conta (customer),\n // omitindo value_usd para customer requests e adicionando currency. Admin paths\n // ainda recebem value_usd como número (sem .coerce — backend joga agora como Number).\n value_usd: z.number().nullish(),\n currency: z.enum(['brl', 'usd']).optional(),\n type: z.string().optional(),\n discount_semester: z.number().default(0),\n discount_annual: z.number().default(0),\n pricing: PlanApiPricingSchema.optional(),\n items: z.array(PlanItemSchema).optional().default([]),\n});\n\nexport type Plan = z.infer<typeof PlanSchema>;\n\n// ── UI Plan types (used by list-plans action and consumers) ──\n\nexport type PlanFeature = {\n icon: 'check' | 'x';\n text: string;\n disabled?: boolean;\n tooltip?: keyof PlanTooltips;\n};\n\nexport type PlanMainFeatures = {\n pages: string;\n domains: string;\n users: string;\n};\n\n/** Preços mensais equivalentes por período de cobrança (já com desconto aplicado). */\nexport type PlanPricingByPeriod = {\n monthly: number;\n semiannual: number;\n annual: number;\n};\n\nexport type UiPlan = {\n planId: number;\n name: string;\n isPopular?: boolean;\n isCurrentPlan?: boolean;\n currentPeriodicity?: BillingPeriod | null;\n mainFeatures: PlanMainFeatures;\n features: PlanFeature[];\n originalPrice: string;\n price: string;\n pricingByPeriod?: PlanPricingByPeriod;\n buttonVariant?: 'brand' | 'default';\n priceUnavailable?: boolean;\n items: PlanItem[];\n};\n\nexport type PlanTooltips = {\n domains: string;\n users: string;\n sharePages: string;\n projectManagement: string;\n};\n"],"mappings":"AAAA,OAAO,OAAO;AAGP,MAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,UAAU,EAAE,OAAO;AAAA,EACnB,UAAU,EAAE,OAAO;AAAA,EACnB,OAAO,EAAE,OAAO;AAAA,EAChB,WAAW,EAAE,OAAO,EAAE,QAAQ;AAAA;AAAA;AAAA;AAAA,EAI9B,MAAM,EAAE,QAAQ,EAAE,SAAS;AAC7B,CAAC;AAIM,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,SAAS,EAAE,OAAO;AAAA,EAClB,MAAM,EAAE,OAAO;AAAA,EACf,UAAU,EAAE,OAAO;AAAA,EACnB,kBAAkB,EAAE,OAAO;AAC7B,CAAC;AAEM,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,UAAU,EAAE,KAAK,CAAC,OAAO,KAAK,CAAC;AAAA,EAC/B,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AACV,CAAC;AAIM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKtC,WAAW,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC9B,UAAU,EAAE,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,SAAS;AAAA,EAC1C,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,mBAAmB,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACvC,iBAAiB,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACrC,SAAS,qBAAqB,SAAS;AAAA,EACvC,OAAO,EAAE,MAAM,cAAc,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/types/plan.type.ts"],"sourcesContent":["import z from 'zod';\nimport type { BillingPeriod } from '../../subscriptions/types/billing-period.type';\n\nexport const PlanItemSchema = z.object({\n id_addon: z.number(),\n quantity: z.number(),\n value: z.number(),\n value_usd: z.number().nullish(),\n // Maior valor positivo por addon = preço base/original (demais linhas são promos).\n // Derivado por heurística no backend (não há coluna no banco). Ausente em payloads\n // de cache antigos → optional.\n base: z.boolean().optional(),\n});\n\nexport type PlanItem = z.infer<typeof PlanItemSchema>;\n\nexport const PlanPeriodPricingSchema = z.object({\n charged: z.number(),\n list: z.number(),\n discount: z.number(),\n discount_percent: z.number(),\n});\n\nexport const PlanApiPricingSchema = z.object({\n currency: z.enum(['brl', 'usd']),\n monthly: PlanPeriodPricingSchema,\n semester: PlanPeriodPricingSchema,\n annual: PlanPeriodPricingSchema,\n});\n\nexport type PlanApiPricing = z.infer<typeof PlanApiPricingSchema>;\n\nexport const PlanSchema = z.object({\n id: z.number(),\n id_plan: z.number().optional(),\n name: z.string(),\n value: z.number().nullable().default(0),\n // Preço em USD (conta stripe_usd). Operador cadastra no backend; ausente em planos\n // sem suporte internacional. Backend agora resolve por moeda da conta (customer),\n // omitindo value_usd para customer requests e adicionando currency. Admin paths\n // ainda recebem value_usd como número (sem .coerce — backend joga agora como Number).\n value_usd: z.number().nullish(),\n currency: z.enum(['brl', 'usd']).optional(),\n type: z.string().optional(),\n discount_semester: z.number().default(0),\n discount_annual: z.number().default(0),\n pricing: PlanApiPricingSchema.optional(),\n items: z.array(PlanItemSchema).optional().default([]),\n});\n\nexport type Plan = z.infer<typeof PlanSchema>;\n\n// ── UI Plan types (used by list-plans action and consumers) ──\n\nexport type PlanFeature = {\n icon: 'check' | 'x';\n text: string;\n disabled?: boolean;\n tooltip?: keyof PlanTooltips;\n};\n\nexport type PlanMainFeatures = {\n pages: string;\n domains: string;\n users: string;\n};\n\n/** Preços mensais equivalentes por período de cobrança (já com desconto aplicado). */\nexport type PlanPricingByPeriod = {\n monthly: number;\n semiannual: number;\n annual: number;\n};\n\nexport type UiPlan = {\n planId: number;\n name: string;\n isPopular?: boolean;\n isCurrentPlan?: boolean;\n currentPeriodicity?: BillingPeriod | null;\n mainFeatures: PlanMainFeatures;\n features: PlanFeature[];\n originalPrice: string;\n price: string;\n pricingByPeriod?: PlanPricingByPeriod;\n pricing?: PlanApiPricing;\n buttonVariant?: 'brand' | 'default';\n priceUnavailable?: boolean;\n items: PlanItem[];\n};\n\nexport type PlanTooltips = {\n domains: string;\n users: string;\n sharePages: string;\n projectManagement: string;\n};\n"],"mappings":"AAAA,OAAO,OAAO;AAGP,MAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,UAAU,EAAE,OAAO;AAAA,EACnB,UAAU,EAAE,OAAO;AAAA,EACnB,OAAO,EAAE,OAAO;AAAA,EAChB,WAAW,EAAE,OAAO,EAAE,QAAQ;AAAA;AAAA;AAAA;AAAA,EAI9B,MAAM,EAAE,QAAQ,EAAE,SAAS;AAC7B,CAAC;AAIM,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,SAAS,EAAE,OAAO;AAAA,EAClB,MAAM,EAAE,OAAO;AAAA,EACf,UAAU,EAAE,OAAO;AAAA,EACnB,kBAAkB,EAAE,OAAO;AAC7B,CAAC;AAEM,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,UAAU,EAAE,KAAK,CAAC,OAAO,KAAK,CAAC;AAAA,EAC/B,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AACV,CAAC;AAIM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKtC,WAAW,EAAE,OAAO,EAAE,QAAQ;AAAA,EAC9B,UAAU,EAAE,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,SAAS;AAAA,EAC1C,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,mBAAmB,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACvC,iBAAiB,EAAE,OAAO,EAAE,QAAQ,CAAC;AAAA,EACrC,SAAS,qBAAqB,SAAS;AAAA,EACvC,OAAO,EAAE,MAAM,cAAc,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;","names":[]}
|
|
@@ -109,6 +109,7 @@ function buildUiPlan(plan, currency, translate) {
|
|
|
109
109
|
originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
110
110
|
price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
111
111
|
pricingByPeriod,
|
|
112
|
+
pricing: plan.pricing,
|
|
112
113
|
priceUnavailable,
|
|
113
114
|
items: currencyAwareItems
|
|
114
115
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/plans/utils/map-api-plan-to-ui.ts"],"sourcesContent":["import type {\n Plan,\n PlanFeature,\n PlanMainFeatures,\n PlanPricingByPeriod,\n UiPlan,\n} from \"../types/plan.type\";\nimport { ADDON_IDS } from \"../../subscriptions/constants/addons.constants\";\nimport {\n formatCurrencyNumber,\n type CurrencyCode,\n} from \"../../../utils/format/currency\";\n\nexport type PlanBillingPeriod = \"monthly\" | \"semiannual\" | \"annual\";\n\nconst STARTER_PLAN_ID = 415;\nconst AGENCY_PLAN_ID = 3;\n\ntype TranslateFn = (key: any, values?: Record<string, string | number>) => string;\n\nconst planIdOf = (plan: Plan): number => plan.id_plan ?? plan.id;\n\nfunction resolvePlanName(plan: Plan, translate?: TranslateFn): string {\n if (!translate) return plan.name;\n const key = `common.plans.names.${planIdOf(plan)}`;\n try {\n const translated = translate(key);\n return translated && translated !== key ? translated : plan.name;\n } catch {\n return plan.name;\n }\n}\n\nconst BASE_FEATURES_KEYS = [\n { icon: \"check\", key: \"plans.features.unlimitedVisits\", fallback: \"Visitas ilimitadas\" },\n { icon: \"check\", key: \"plans.features.unlimitedLeads\", fallback: \"Leads ilimitadas\" },\n { icon: \"check\", key: \"plans.features.sharePages\", fallback: \"Compartilhar páginas\", tooltip: \"sharePages\" },\n { icon: \"check\", key: \"plans.features.hosting\", fallback: \"Hospedagem inclusa\" },\n // SSL (HTTPS) + CDN — brand/acronym: never translated\n { icon: \"check\", key: null, fallback: \"SSL (HTTPS) + CDN\" },\n { icon: \"check\", key: \"plans.features.freeTemplates\", fallback: \"Templates gratuitos\" },\n] as const;\n\nfunction buildBaseFeatures(translate?: TranslateFn): PlanFeature[] {\n return BASE_FEATURES_KEYS.map(({ icon, key, fallback, ...rest }) => {\n const text = key && translate ? translate(`common.${key}`) : fallback;\n const feature: PlanFeature = { icon, text };\n if (\"tooltip\" in rest && rest.tooltip) feature.tooltip = rest.tooltip;\n return feature;\n });\n}\n\nfunction buildMainFeaturesFromItems(\n items: Plan[\"items\"],\n translate?: TranslateFn\n): PlanMainFeatures {\n const pagesItem = items.find((i) => i.id_addon === ADDON_IDS.PAGES);\n const domainsItem = items.find((i) => i.id_addon === ADDON_IDS.DOMAINS);\n\n const pagesQty = pagesItem?.quantity ?? 0;\n const domainsQty = domainsItem?.quantity ?? 1;\n\n let pages: string;\n if (pagesQty === 0) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesUnlimited\")\n : \"Páginas ilimitadas\";\n } else if (pagesQty === 1) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pageSingular\")\n : \"1 página\";\n } else {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesPlural\", { count: pagesQty })\n : `${pagesQty} páginas`;\n }\n\n let domains: string;\n if (domainsQty >= 999) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsUnlimited\")\n : \"Domínios ilimitados\";\n } else if (domainsQty === 1) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainSingular\")\n : \"1 domínio externo\";\n } else {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsPlural\", { count: domainsQty })\n : `${domainsQty} domínios externos`;\n }\n\n const users = translate\n ? translate(\"common.plans.mainFeatures.usersUnlimited\")\n : \"Usuários ilimitados\";\n\n return { pages, domains, users };\n}\n\nfunction computePricingByPeriod(apiPlan: Plan): PlanPricingByPeriod {\n const monthly = apiPlan.value;\n if (monthly == null) {\n return { monthly: 0, semiannual: 0, annual: 0 };\n }\n const round2 = (n: number) => Math.round(n * 100) / 100;\n\n const pricing = apiPlan.pricing;\n if (pricing) {\n return {\n monthly,\n semiannual: round2(pricing.semester.charged / 6),\n annual: round2(pricing.annual.charged / 12),\n };\n }\n\n return {\n monthly,\n semiannual: round2(monthly * (1 - apiPlan.discount_semester / 100)),\n annual: round2(monthly * (1 - apiPlan.discount_annual / 100)),\n };\n}\n\nfunction buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn): UiPlan {\n const popular = plan.id === 2 || plan.id_plan === 2;\n const buttonVariant = popular ? \"brand\" : \"default\";\n\n const pricingByPeriod = computePricingByPeriod(plan);\n const monthlyFinal = pricingByPeriod.monthly;\n\n const isStarterPlan = plan.id === STARTER_PLAN_ID || plan.id_plan === STARTER_PLAN_ID;\n const hasProjectAndOnboarding = planIdOf(plan) === AGENCY_PLAN_ID;\n\n const onboardingText = translate\n ? translate(\"common.plans.features.onboarding\")\n : \"Reunião de Onboarding\";\n const projectMgmtText = translate\n ? translate(\"common.plans.features.projectManagement\")\n : \"Gestão de projetos\";\n const sharePagesText = translate\n ? translate(\"common.plans.features.sharePages\")\n : \"Compartilhar páginas\";\n\n const features: PlanFeature[] = [\n ...buildBaseFeatures(translate).map((feature) =>\n feature.text === sharePagesText && isStarterPlan\n ? { ...feature, icon: \"x\" as const, disabled: true }\n : feature\n ),\n hasProjectAndOnboarding\n ? { icon: \"check\", text: onboardingText }\n : { icon: \"x\", text: onboardingText, disabled: true },\n hasProjectAndOnboarding\n ? { icon: \"check\", text: projectMgmtText, tooltip: \"projectManagement\" }\n : { icon: \"x\", text: projectMgmtText, disabled: true, tooltip: \"projectManagement\" },\n ];\n\n const currencyAwareItems = plan.items.map((item) => ({\n ...item,\n value: item.value ?? 0,\n }));\n\n const priceUnavailable = plan.value == null;\n const unavailableLabel = translate ? translate(\"common.plans.usdNotConfigured\") : \"USD não configurado\";\n\n return {\n planId: plan.id,\n name: resolvePlanName(plan, translate),\n isPopular: popular,\n buttonVariant,\n mainFeatures: buildMainFeaturesFromItems(plan.items, translate),\n features,\n originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n pricingByPeriod,\n priceUnavailable,\n items: currencyAwareItems,\n };\n}\n\n/**\n * Mapeia um plano da API para UiPlan em BRL.\n * Assinatura de 1 argumento — seguro em `plans.map(mapApiPlanToUiPlan)`.\n * Passa `translate` opcional para i18n; sem ele cai nos literais pt-br (back-compat).\n */\nexport function mapApiPlanToUiPlan(plan: Plan, translate?: TranslateFn): UiPlan {\n return buildUiPlan(plan, \"brl\", translate);\n}\n\n/**\n * Variante currency-aware: a moeda vem do gateway da conta (getCurrencyForGateway).\n * Fábrica para uso ergonômico em `.map`: `plans.map(mapApiPlanToUiPlanForCurrency(currency, translate))`.\n * Mantida separada de mapApiPlanToUiPlan para não quebrar callers que passam a fn direto pro `.map`.\n */\nexport const mapApiPlanToUiPlanForCurrency =\n (currency: CurrencyCode, translate?: TranslateFn) =>\n (plan: Plan): UiPlan =>\n buildUiPlan(plan, currency, translate);\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/plans/utils/map-api-plan-to-ui.ts"],"sourcesContent":["import type {\n Plan,\n PlanFeature,\n PlanMainFeatures,\n PlanPricingByPeriod,\n UiPlan,\n} from \"../types/plan.type\";\nimport { ADDON_IDS } from \"../../subscriptions/constants/addons.constants\";\nimport {\n formatCurrencyNumber,\n type CurrencyCode,\n} from \"../../../utils/format/currency\";\n\nexport type PlanBillingPeriod = \"monthly\" | \"semiannual\" | \"annual\";\n\nconst STARTER_PLAN_ID = 415;\nconst AGENCY_PLAN_ID = 3;\n\ntype TranslateFn = (key: any, values?: Record<string, string | number>) => string;\n\nconst planIdOf = (plan: Plan): number => plan.id_plan ?? plan.id;\n\nfunction resolvePlanName(plan: Plan, translate?: TranslateFn): string {\n if (!translate) return plan.name;\n const key = `common.plans.names.${planIdOf(plan)}`;\n try {\n const translated = translate(key);\n return translated && translated !== key ? translated : plan.name;\n } catch {\n return plan.name;\n }\n}\n\nconst BASE_FEATURES_KEYS = [\n { icon: \"check\", key: \"plans.features.unlimitedVisits\", fallback: \"Visitas ilimitadas\" },\n { icon: \"check\", key: \"plans.features.unlimitedLeads\", fallback: \"Leads ilimitadas\" },\n { icon: \"check\", key: \"plans.features.sharePages\", fallback: \"Compartilhar páginas\", tooltip: \"sharePages\" },\n { icon: \"check\", key: \"plans.features.hosting\", fallback: \"Hospedagem inclusa\" },\n // SSL (HTTPS) + CDN — brand/acronym: never translated\n { icon: \"check\", key: null, fallback: \"SSL (HTTPS) + CDN\" },\n { icon: \"check\", key: \"plans.features.freeTemplates\", fallback: \"Templates gratuitos\" },\n] as const;\n\nfunction buildBaseFeatures(translate?: TranslateFn): PlanFeature[] {\n return BASE_FEATURES_KEYS.map(({ icon, key, fallback, ...rest }) => {\n const text = key && translate ? translate(`common.${key}`) : fallback;\n const feature: PlanFeature = { icon, text };\n if (\"tooltip\" in rest && rest.tooltip) feature.tooltip = rest.tooltip;\n return feature;\n });\n}\n\nfunction buildMainFeaturesFromItems(\n items: Plan[\"items\"],\n translate?: TranslateFn\n): PlanMainFeatures {\n const pagesItem = items.find((i) => i.id_addon === ADDON_IDS.PAGES);\n const domainsItem = items.find((i) => i.id_addon === ADDON_IDS.DOMAINS);\n\n const pagesQty = pagesItem?.quantity ?? 0;\n const domainsQty = domainsItem?.quantity ?? 1;\n\n let pages: string;\n if (pagesQty === 0) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesUnlimited\")\n : \"Páginas ilimitadas\";\n } else if (pagesQty === 1) {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pageSingular\")\n : \"1 página\";\n } else {\n pages = translate\n ? translate(\"common.plans.mainFeatures.pagesPlural\", { count: pagesQty })\n : `${pagesQty} páginas`;\n }\n\n let domains: string;\n if (domainsQty >= 999) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsUnlimited\")\n : \"Domínios ilimitados\";\n } else if (domainsQty === 1) {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainSingular\")\n : \"1 domínio externo\";\n } else {\n domains = translate\n ? translate(\"common.plans.mainFeatures.domainsPlural\", { count: domainsQty })\n : `${domainsQty} domínios externos`;\n }\n\n const users = translate\n ? translate(\"common.plans.mainFeatures.usersUnlimited\")\n : \"Usuários ilimitados\";\n\n return { pages, domains, users };\n}\n\nfunction computePricingByPeriod(apiPlan: Plan): PlanPricingByPeriod {\n const monthly = apiPlan.value;\n if (monthly == null) {\n return { monthly: 0, semiannual: 0, annual: 0 };\n }\n const round2 = (n: number) => Math.round(n * 100) / 100;\n\n const pricing = apiPlan.pricing;\n if (pricing) {\n return {\n monthly,\n semiannual: round2(pricing.semester.charged / 6),\n annual: round2(pricing.annual.charged / 12),\n };\n }\n\n return {\n monthly,\n semiannual: round2(monthly * (1 - apiPlan.discount_semester / 100)),\n annual: round2(monthly * (1 - apiPlan.discount_annual / 100)),\n };\n}\n\nfunction buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn): UiPlan {\n const popular = plan.id === 2 || plan.id_plan === 2;\n const buttonVariant = popular ? \"brand\" : \"default\";\n\n const pricingByPeriod = computePricingByPeriod(plan);\n const monthlyFinal = pricingByPeriod.monthly;\n\n const isStarterPlan = plan.id === STARTER_PLAN_ID || plan.id_plan === STARTER_PLAN_ID;\n const hasProjectAndOnboarding = planIdOf(plan) === AGENCY_PLAN_ID;\n\n const onboardingText = translate\n ? translate(\"common.plans.features.onboarding\")\n : \"Reunião de Onboarding\";\n const projectMgmtText = translate\n ? translate(\"common.plans.features.projectManagement\")\n : \"Gestão de projetos\";\n const sharePagesText = translate\n ? translate(\"common.plans.features.sharePages\")\n : \"Compartilhar páginas\";\n\n const features: PlanFeature[] = [\n ...buildBaseFeatures(translate).map((feature) =>\n feature.text === sharePagesText && isStarterPlan\n ? { ...feature, icon: \"x\" as const, disabled: true }\n : feature\n ),\n hasProjectAndOnboarding\n ? { icon: \"check\", text: onboardingText }\n : { icon: \"x\", text: onboardingText, disabled: true },\n hasProjectAndOnboarding\n ? { icon: \"check\", text: projectMgmtText, tooltip: \"projectManagement\" }\n : { icon: \"x\", text: projectMgmtText, disabled: true, tooltip: \"projectManagement\" },\n ];\n\n const currencyAwareItems = plan.items.map((item) => ({\n ...item,\n value: item.value ?? 0,\n }));\n\n const priceUnavailable = plan.value == null;\n const unavailableLabel = translate ? translate(\"common.plans.usdNotConfigured\") : \"USD não configurado\";\n\n return {\n planId: plan.id,\n name: resolvePlanName(plan, translate),\n isPopular: popular,\n buttonVariant,\n mainFeatures: buildMainFeaturesFromItems(plan.items, translate),\n features,\n originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),\n pricingByPeriod,\n pricing: plan.pricing,\n priceUnavailable,\n items: currencyAwareItems,\n };\n}\n\n/**\n * Mapeia um plano da API para UiPlan em BRL.\n * Assinatura de 1 argumento — seguro em `plans.map(mapApiPlanToUiPlan)`.\n * Passa `translate` opcional para i18n; sem ele cai nos literais pt-br (back-compat).\n */\nexport function mapApiPlanToUiPlan(plan: Plan, translate?: TranslateFn): UiPlan {\n return buildUiPlan(plan, \"brl\", translate);\n}\n\n/**\n * Variante currency-aware: a moeda vem do gateway da conta (getCurrencyForGateway).\n * Fábrica para uso ergonômico em `.map`: `plans.map(mapApiPlanToUiPlanForCurrency(currency, translate))`.\n * Mantida separada de mapApiPlanToUiPlan para não quebrar callers que passam a fn direto pro `.map`.\n */\nexport const mapApiPlanToUiPlanForCurrency =\n (currency: CurrencyCode, translate?: TranslateFn) =>\n (plan: Plan): UiPlan =>\n buildUiPlan(plan, currency, translate);\n"],"mappings":"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","names":[]}
|
|
@@ -55,6 +55,12 @@ const CalculateSubscriptionResponseSchema = z.object({
|
|
|
55
55
|
month_original: z.number(),
|
|
56
56
|
month_original_discount: z.number().optional().default(0),
|
|
57
57
|
month_original_discount_cupom: z.number().optional().default(0),
|
|
58
|
+
// Preço "de" (âncora riscada) e badge de desconto da vitrine. O backend SEMPRE envia
|
|
59
|
+
// os dois: sem ancoragem, *_list cai no total cobrado e o badge no desconto real de
|
|
60
|
+
// catálogo. Presença do campo não significa âncora — quem exibe compara list x charged.
|
|
61
|
+
// Opcionais só por causa de payload antigo em cache.
|
|
62
|
+
month_list: z.number().optional(),
|
|
63
|
+
month_badge_percent: z.number().optional(),
|
|
58
64
|
semmester: z.number(),
|
|
59
65
|
semmester_price: z.number().optional().default(0),
|
|
60
66
|
semmester_discount: z.number(),
|
|
@@ -62,12 +68,16 @@ const CalculateSubscriptionResponseSchema = z.object({
|
|
|
62
68
|
semmester_original: z.number(),
|
|
63
69
|
semmester_original_discount: z.number().optional().default(0),
|
|
64
70
|
semmester_original_discount_cupom: z.number().optional().default(0),
|
|
71
|
+
semmester_list: z.number().optional(),
|
|
72
|
+
semmester_badge_percent: z.number().optional(),
|
|
65
73
|
year: z.number(),
|
|
66
74
|
year_discount: z.number(),
|
|
67
75
|
year_discount_coupom: z.number().optional().default(0),
|
|
68
76
|
year_original: z.number(),
|
|
69
77
|
year_original_discount: z.number().optional().default(0),
|
|
70
78
|
year_original_discount_cupom: z.number().optional().default(0),
|
|
79
|
+
year_list: z.number().optional(),
|
|
80
|
+
year_badge_percent: z.number().optional(),
|
|
71
81
|
plan_extras: z.array(CalculateSubscriptionPlanExtraResponseSchema),
|
|
72
82
|
target: CalculateSubscriptionTargetSchema.optional().nullable()
|
|
73
83
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/subscriptions/types/calculate-subscription.type.ts"],"sourcesContent":["import z from \"zod\";\n\nexport const CalculateSubscriptionPlanExtraRequestSchema = z.object({\n id_addon: z.number(),\n value: z.number(),\n quantity: z.number(),\n currency: z.string(),\n});\n\nexport const CalculateSubscriptionRequestSchema = z.object({\n id_product: z.number(),\n id_plan: z.number().optional(),\n id_subscription: z.union([z.number(), z.string()]).optional(),\n id_coupon: z.number().optional(),\n periodicity: z.number().optional(),\n plan_extras: z\n .array(\n CalculateSubscriptionPlanExtraRequestSchema.partial({\n value: true,\n currency: true,\n }),\n )\n .optional(),\n});\n\nexport type CalculateSubscriptionRequest = z.infer<\n typeof CalculateSubscriptionRequestSchema\n>;\n\nexport const CalculateSubscriptionPlanExtraResponseSchema = z.object({\n id_addon: z.number(),\n value: z.number(),\n value_original: z.number().optional().default(0),\n value_discount: z.number().optional().default(0),\n quantity: z.number(),\n currency: z.string(),\n});\n\nconst ImmediateChargeItemSchema = z.object({\n type: z.string(),\n description: z.string(),\n value: z.number(),\n id_addon: z.number().optional(),\n quantity: z.number().optional(),\n});\n\nconst ImmediateChargeSchema = z.object({\n total: z.number(),\n items: z.array(ImmediateChargeItemSchema),\n});\n\nconst CalculateSubscriptionTargetSchema = z.object({\n id_subscription: z.union([z.number(), z.string()]).optional(),\n current_plan_id: z.number().optional(),\n current_plan_name: z.string().optional(),\n current_periodicity: z.number().optional(),\n current_date_due: z.string().optional(),\n days_remaining: z.number().optional(),\n days_total: z.number().optional(),\n immediate_charge: ImmediateChargeSchema.optional(),\n});\n\nexport const CalculateSubscriptionResponseSchema = z.object({\n name: z.string(),\n month: z.number(),\n month_discount: z.number(),\n month_discount_coupom: z.number().optional().default(0),\n month_original: z.number(),\n month_original_discount: z.number().optional().default(0),\n month_original_discount_cupom: z.number().optional().default(0),\n semmester: z.number(),\n semmester_price: z.number().optional().default(0),\n semmester_discount: z.number(),\n semmester_discount_coupom: z.number().optional().default(0),\n semmester_original: z.number(),\n semmester_original_discount: z.number().optional().default(0),\n semmester_original_discount_cupom: z.number().optional().default(0),\n year: z.number(),\n year_discount: z.number(),\n year_discount_coupom: z.number().optional().default(0),\n year_original: z.number(),\n year_original_discount: z.number().optional().default(0),\n year_original_discount_cupom: z.number().optional().default(0),\n plan_extras: z.array(CalculateSubscriptionPlanExtraResponseSchema),\n target: CalculateSubscriptionTargetSchema.optional().nullable(),\n});\n\nexport type CalculateSubscriptionResponse = z.infer<\n typeof CalculateSubscriptionResponseSchema\n>;\n\nexport const UpdateSubscriptionPlanExtraSchema = z.object({\n id_addon: z.number(),\n value: z.number().optional(),\n quantity: z.number(),\n price: z.string().optional(),\n trial: z.boolean().optional(),\n trial_days: z.number().optional(),\n});\n\nexport const UpdateSubscriptionPlanRequestSchema = z.object({\n id_plan: z.number().optional(),\n id_coupon: z.union([z.number(), z.null()]).optional(),\n date_due: z.string().optional(),\n type: z.string().optional(),\n active: z.boolean().optional(),\n periodicity: z.number().optional(),\n plan_extras: z.array(UpdateSubscriptionPlanExtraSchema).optional(),\n enable_charge: z.boolean().optional(),\n payment_method: z.number().optional(),\n id_card: z.union([z.number(), z.string()]).optional(),\n});\n\nexport type UpdateSubscriptionPlanRequest = z.infer<\n typeof UpdateSubscriptionPlanRequestSchema\n>;\n"],"mappings":"AAAA,OAAO,OAAO;AAEP,MAAM,8CAA8C,EAAE,OAAO;AAAA,EAClE,UAAU,EAAE,OAAO;AAAA,EACnB,OAAO,EAAE,OAAO;AAAA,EAChB,UAAU,EAAE,OAAO;AAAA,EACnB,UAAU,EAAE,OAAO;AACrB,CAAC;AAEM,MAAM,qCAAqC,EAAE,OAAO;AAAA,EACzD,YAAY,EAAE,OAAO;AAAA,EACrB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,iBAAiB,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,EAC5D,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,aAAa,EACV;AAAA,IACC,4CAA4C,QAAQ;AAAA,MAClD,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,EACC,SAAS;AACd,CAAC;AAMM,MAAM,+CAA+C,EAAE,OAAO;AAAA,EACnE,UAAU,EAAE,OAAO;AAAA,EACnB,OAAO,EAAE,OAAO;AAAA,EAChB,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EAC/C,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EAC/C,UAAU,EAAE,OAAO;AAAA,EACnB,UAAU,EAAE,OAAO;AACrB,CAAC;AAED,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,MAAM,EAAE,OAAO;AAAA,EACf,aAAa,EAAE,OAAO;AAAA,EACtB,OAAO,EAAE,OAAO;AAAA,EAChB,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,UAAU,EAAE,OAAO,EAAE,SAAS;AAChC,CAAC;AAED,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,OAAO,EAAE,OAAO;AAAA,EAChB,OAAO,EAAE,MAAM,yBAAyB;AAC1C,CAAC;AAED,MAAM,oCAAoC,EAAE,OAAO;AAAA,EACjD,iBAAiB,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,EAC5D,iBAAiB,EAAE,OAAO,EAAE,SAAS;AAAA,EACrC,mBAAmB,EAAE,OAAO,EAAE,SAAS;AAAA,EACvC,qBAAqB,EAAE,OAAO,EAAE,SAAS;AAAA,EACzC,kBAAkB,EAAE,OAAO,EAAE,SAAS;AAAA,EACtC,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,EACpC,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,kBAAkB,sBAAsB,SAAS;AACnD,CAAC;AAEM,MAAM,sCAAsC,EAAE,OAAO;AAAA,EAC1D,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,gBAAgB,EAAE,OAAO;AAAA,EACzB,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EACtD,gBAAgB,EAAE,OAAO;AAAA,EACzB,yBAAyB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,EACxD,+BAA+B,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/types/calculate-subscription.type.ts"],"sourcesContent":["import z from \"zod\";\n\nexport const CalculateSubscriptionPlanExtraRequestSchema = z.object({\n id_addon: z.number(),\n value: z.number(),\n quantity: z.number(),\n currency: z.string(),\n});\n\nexport const CalculateSubscriptionRequestSchema = z.object({\n id_product: z.number(),\n id_plan: z.number().optional(),\n id_subscription: z.union([z.number(), z.string()]).optional(),\n id_coupon: z.number().optional(),\n periodicity: z.number().optional(),\n plan_extras: z\n .array(\n CalculateSubscriptionPlanExtraRequestSchema.partial({\n value: true,\n currency: true,\n }),\n )\n .optional(),\n});\n\nexport type CalculateSubscriptionRequest = z.infer<\n typeof CalculateSubscriptionRequestSchema\n>;\n\nexport const CalculateSubscriptionPlanExtraResponseSchema = z.object({\n id_addon: z.number(),\n value: z.number(),\n value_original: z.number().optional().default(0),\n value_discount: z.number().optional().default(0),\n quantity: z.number(),\n currency: z.string(),\n});\n\nconst ImmediateChargeItemSchema = z.object({\n type: z.string(),\n description: z.string(),\n value: z.number(),\n id_addon: z.number().optional(),\n quantity: z.number().optional(),\n});\n\nconst ImmediateChargeSchema = z.object({\n total: z.number(),\n items: z.array(ImmediateChargeItemSchema),\n});\n\nconst CalculateSubscriptionTargetSchema = z.object({\n id_subscription: z.union([z.number(), z.string()]).optional(),\n current_plan_id: z.number().optional(),\n current_plan_name: z.string().optional(),\n current_periodicity: z.number().optional(),\n current_date_due: z.string().optional(),\n days_remaining: z.number().optional(),\n days_total: z.number().optional(),\n immediate_charge: ImmediateChargeSchema.optional(),\n});\n\nexport const CalculateSubscriptionResponseSchema = z.object({\n name: z.string(),\n month: z.number(),\n month_discount: z.number(),\n month_discount_coupom: z.number().optional().default(0),\n month_original: z.number(),\n month_original_discount: z.number().optional().default(0),\n month_original_discount_cupom: z.number().optional().default(0),\n // Preço \"de\" (âncora riscada) e badge de desconto da vitrine. O backend SEMPRE envia\n // os dois: sem ancoragem, *_list cai no total cobrado e o badge no desconto real de\n // catálogo. Presença do campo não significa âncora — quem exibe compara list x charged.\n // Opcionais só por causa de payload antigo em cache.\n month_list: z.number().optional(),\n month_badge_percent: z.number().optional(),\n semmester: z.number(),\n semmester_price: z.number().optional().default(0),\n semmester_discount: z.number(),\n semmester_discount_coupom: z.number().optional().default(0),\n semmester_original: z.number(),\n semmester_original_discount: z.number().optional().default(0),\n semmester_original_discount_cupom: z.number().optional().default(0),\n semmester_list: z.number().optional(),\n semmester_badge_percent: z.number().optional(),\n year: z.number(),\n year_discount: z.number(),\n year_discount_coupom: z.number().optional().default(0),\n year_original: z.number(),\n year_original_discount: z.number().optional().default(0),\n year_original_discount_cupom: z.number().optional().default(0),\n year_list: z.number().optional(),\n year_badge_percent: z.number().optional(),\n plan_extras: z.array(CalculateSubscriptionPlanExtraResponseSchema),\n target: CalculateSubscriptionTargetSchema.optional().nullable(),\n});\n\nexport type CalculateSubscriptionResponse = z.infer<\n typeof CalculateSubscriptionResponseSchema\n>;\n\nexport const UpdateSubscriptionPlanExtraSchema = z.object({\n id_addon: z.number(),\n value: z.number().optional(),\n quantity: z.number(),\n price: z.string().optional(),\n trial: z.boolean().optional(),\n trial_days: z.number().optional(),\n});\n\nexport const UpdateSubscriptionPlanRequestSchema = z.object({\n id_plan: z.number().optional(),\n id_coupon: z.union([z.number(), z.null()]).optional(),\n date_due: z.string().optional(),\n type: z.string().optional(),\n active: z.boolean().optional(),\n periodicity: z.number().optional(),\n plan_extras: z.array(UpdateSubscriptionPlanExtraSchema).optional(),\n enable_charge: z.boolean().optional(),\n payment_method: z.number().optional(),\n id_card: z.union([z.number(), z.string()]).optional(),\n});\n\nexport type UpdateSubscriptionPlanRequest = z.infer<\n typeof UpdateSubscriptionPlanRequestSchema\n>;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@greatapps/common",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.754",
|
|
4
4
|
"description": "Shared library for GreatApps frontend applications",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@greatapps/jwt": "1.0.12",
|
|
62
62
|
"@radix-ui/react-accordion": "1.2.12",
|
|
63
63
|
"@radix-ui/react-checkbox": "1.3.3",
|
|
64
|
-
"@radix-ui/react-dialog": "1.1.
|
|
64
|
+
"@radix-ui/react-dialog": "1.1.15",
|
|
65
65
|
"@radix-ui/react-popover": "1.1.15",
|
|
66
66
|
"@radix-ui/react-progress": "1.1.8",
|
|
67
67
|
"@radix-ui/react-radio-group": "1.3.8",
|
|
@@ -75,7 +75,13 @@ export function ComboboxField({
|
|
|
75
75
|
* (ex.: Preferências): sem ele, o FocusScope do Dialog rouba o foco no
|
|
76
76
|
* pointerdown da opção (o conteúdo do Popover é portalado pra fora do Dialog)
|
|
77
77
|
* e o Popover fecha antes do click — a opção nunca é selecionada. Com `modal`,
|
|
78
|
-
* o Popover entra na pilha de focus scopes do Radix e pausa o do Dialog.
|
|
78
|
+
* o Popover entra na pilha de focus scopes do Radix e pausa o do Dialog.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ Essa pilha é um singleton POR INSTÂNCIA de @radix-ui/react-focus-scope:
|
|
81
|
+
* react-dialog e react-popover precisam resolver para a MESMA versão dele
|
|
82
|
+
* (manter os dois pins do package.json na mesma linha de release). Com duas
|
|
83
|
+
* cópias, o trap do Dialog não é pausado e rouba o foco do input de busca a
|
|
84
|
+
* cada clique — impossível digitar. */}
|
|
79
85
|
<Popover
|
|
80
86
|
modal
|
|
81
87
|
open={open}
|
|
@@ -32,8 +32,8 @@ class PlansService {
|
|
|
32
32
|
const sort = params?.sort ?? "id:ASC";
|
|
33
33
|
const active = params?.active ?? "";
|
|
34
34
|
const search = params?.search ?? "";
|
|
35
|
-
//
|
|
36
|
-
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-
|
|
35
|
+
// v7: descarta as entradas v6 que não têm o campo pricing (ancoragem de preço)
|
|
36
|
+
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v7`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -92,7 +92,7 @@ class PlansService {
|
|
|
92
92
|
|
|
93
93
|
async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {
|
|
94
94
|
const { id_wl, id_account } = await getUserContext();
|
|
95
|
-
const cacheKey = this.buildCacheKey(`plan-${idPlan}-
|
|
95
|
+
const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {
|
|
96
96
|
id_wl,
|
|
97
97
|
id_account: String(id_account),
|
|
98
98
|
});
|
|
@@ -172,6 +172,7 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn
|
|
|
172
172
|
originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
173
173
|
price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
174
174
|
pricingByPeriod,
|
|
175
|
+
pricing: plan.pricing,
|
|
175
176
|
priceUnavailable,
|
|
176
177
|
items: currencyAwareItems,
|
|
177
178
|
};
|
|
@@ -68,6 +68,12 @@ export const CalculateSubscriptionResponseSchema = z.object({
|
|
|
68
68
|
month_original: z.number(),
|
|
69
69
|
month_original_discount: z.number().optional().default(0),
|
|
70
70
|
month_original_discount_cupom: z.number().optional().default(0),
|
|
71
|
+
// Preço "de" (âncora riscada) e badge de desconto da vitrine. O backend SEMPRE envia
|
|
72
|
+
// os dois: sem ancoragem, *_list cai no total cobrado e o badge no desconto real de
|
|
73
|
+
// catálogo. Presença do campo não significa âncora — quem exibe compara list x charged.
|
|
74
|
+
// Opcionais só por causa de payload antigo em cache.
|
|
75
|
+
month_list: z.number().optional(),
|
|
76
|
+
month_badge_percent: z.number().optional(),
|
|
71
77
|
semmester: z.number(),
|
|
72
78
|
semmester_price: z.number().optional().default(0),
|
|
73
79
|
semmester_discount: z.number(),
|
|
@@ -75,12 +81,16 @@ export const CalculateSubscriptionResponseSchema = z.object({
|
|
|
75
81
|
semmester_original: z.number(),
|
|
76
82
|
semmester_original_discount: z.number().optional().default(0),
|
|
77
83
|
semmester_original_discount_cupom: z.number().optional().default(0),
|
|
84
|
+
semmester_list: z.number().optional(),
|
|
85
|
+
semmester_badge_percent: z.number().optional(),
|
|
78
86
|
year: z.number(),
|
|
79
87
|
year_discount: z.number(),
|
|
80
88
|
year_discount_coupom: z.number().optional().default(0),
|
|
81
89
|
year_original: z.number(),
|
|
82
90
|
year_original_discount: z.number().optional().default(0),
|
|
83
91
|
year_original_discount_cupom: z.number().optional().default(0),
|
|
92
|
+
year_list: z.number().optional(),
|
|
93
|
+
year_badge_percent: z.number().optional(),
|
|
84
94
|
plan_extras: z.array(CalculateSubscriptionPlanExtraResponseSchema),
|
|
85
95
|
target: CalculateSubscriptionTargetSchema.optional().nullable(),
|
|
86
96
|
});
|