@greatapps/common 1.1.751 → 1.1.752
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/layouts/AiCreditsOfferGate.mjs +105 -0
- package/dist/components/layouts/AiCreditsOfferGate.mjs.map +1 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.mjs +276 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.mjs.map +1 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.mjs +55 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.mjs.map +1 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.mjs +78 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.mjs.map +1 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.mjs +60 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.mjs.map +1 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/index.mjs +5 -0
- package/dist/components/modals/promo/AiCreditsOfferModal/index.mjs.map +1 -0
- package/dist/components/ui/form/ComboboxField.mjs +1 -0
- package/dist/components/ui/form/ComboboxField.mjs.map +1 -1
- package/dist/handlers.mjs +5 -1
- package/dist/handlers.mjs.map +1 -1
- package/dist/i18n/messages/en-us.mjs +32 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +32 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +32 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/index.mjs +73 -43
- package/dist/index.mjs.map +1 -1
- package/dist/modules/auth/utils/get-user-context.mjs +2 -1
- package/dist/modules/auth/utils/get-user-context.mjs.map +1 -1
- package/dist/modules/cards/hooks/cards.hook.mjs +3 -2
- package/dist/modules/cards/hooks/cards.hook.mjs.map +1 -1
- package/dist/modules/ia-credits/constants/ai-credits-offer.constants.mjs +28 -0
- package/dist/modules/ia-credits/constants/ai-credits-offer.constants.mjs.map +1 -0
- package/dist/modules/ia-credits/handlers/get-offer-eligibility.handler.mjs +19 -0
- package/dist/modules/ia-credits/handlers/get-offer-eligibility.handler.mjs.map +1 -0
- package/dist/modules/ia-credits/handlers/purchase-ia-credits.handler.mjs +33 -0
- package/dist/modules/ia-credits/handlers/purchase-ia-credits.handler.mjs.map +1 -0
- package/dist/modules/ia-credits/hooks/purchase-ia-credits.hook.mjs +29 -0
- package/dist/modules/ia-credits/hooks/purchase-ia-credits.hook.mjs.map +1 -0
- package/dist/modules/ia-credits/hooks/use-offer-eligibility.hook.mjs +18 -0
- package/dist/modules/ia-credits/hooks/use-offer-eligibility.hook.mjs.map +1 -0
- package/dist/modules/ia-credits/services/ia-credits.service.mjs +67 -0
- package/dist/modules/ia-credits/services/ia-credits.service.mjs.map +1 -1
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs +34 -0
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs.map +1 -0
- package/dist/modules/ia-credits/utils/offer-stage-cookie.mjs +43 -0
- package/dist/modules/ia-credits/utils/offer-stage-cookie.mjs.map +1 -0
- 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 +0 -1
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
- package/dist/modules/subscriptions/types/calculate-subscription.type.mjs +0 -8
- package/dist/modules/subscriptions/types/calculate-subscription.type.mjs.map +1 -1
- package/dist/store/useAiCreditsOfferModal.mjs +14 -0
- package/dist/store/useAiCreditsOfferModal.mjs.map +1 -0
- package/package.json +3 -1
- package/src/components/layouts/AiCreditsOfferGate.tsx +199 -0
- package/src/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.tsx +401 -0
- package/src/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.tsx +97 -0
- package/src/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.tsx +121 -0
- package/src/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.tsx +91 -0
- package/src/components/modals/promo/AiCreditsOfferModal/index.ts +1 -0
- package/src/components/ui/form/ComboboxField.tsx +6 -0
- package/src/handlers.ts +2 -0
- package/src/i18n/messages/en-us.ts +33 -0
- package/src/i18n/messages/es-es.ts +33 -0
- package/src/i18n/messages/pt-br.ts +33 -0
- package/src/index.ts +26 -0
- package/src/modules/auth/utils/get-user-context.ts +3 -0
- package/src/modules/cards/hooks/cards.hook.ts +6 -1
- package/src/modules/ia-credits/constants/ai-credits-offer.constants.ts +41 -0
- package/src/modules/ia-credits/handlers/get-offer-eligibility.handler.ts +24 -0
- package/src/modules/ia-credits/handlers/purchase-ia-credits.handler.ts +42 -0
- package/src/modules/ia-credits/hooks/purchase-ia-credits.hook.ts +43 -0
- package/src/modules/ia-credits/hooks/use-offer-eligibility.hook.ts +28 -0
- package/src/modules/ia-credits/services/ia-credits.service.ts +92 -0
- package/src/modules/ia-credits/types/ai-credits-offer.type.ts +87 -0
- package/src/modules/ia-credits/utils/offer-stage-cookie.ts +70 -0
- package/src/modules/plans/services/plans.service.ts +3 -3
- package/src/modules/plans/types/plan.type.ts +0 -1
- package/src/modules/plans/utils/map-api-plan-to-ui.ts +0 -1
- package/src/modules/subscriptions/types/calculate-subscription.type.ts +0 -8
- package/src/store/useAiCreditsOfferModal.ts +24 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import { ApiError } from "../../../infra/api/types";
|
|
3
|
+
import { PurchaseIaCreditsRequestBodySchema } from "../types/ai-credits-offer.type";
|
|
4
|
+
import { iaCreditsService } from "../services/ia-credits.service";
|
|
5
|
+
function createPurchaseIaCreditsHandler() {
|
|
6
|
+
return async function POST(req, { params }) {
|
|
7
|
+
const { id } = await params;
|
|
8
|
+
const parsedBody = PurchaseIaCreditsRequestBodySchema.safeParse(await req.json().catch(() => null));
|
|
9
|
+
if (!parsedBody.success) {
|
|
10
|
+
return Response.json(
|
|
11
|
+
{ status: 0, message: "Erro ao comprar cr\xE9ditos de IA" },
|
|
12
|
+
{ status: 400 }
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const result = await iaCreditsService.purchase(id, {
|
|
17
|
+
quantity: parsedBody.data.quantity,
|
|
18
|
+
paymentMethod: parsedBody.data.payment_method,
|
|
19
|
+
idCard: parsedBody.data.id_card,
|
|
20
|
+
offer: parsedBody.data.offer
|
|
21
|
+
});
|
|
22
|
+
return Response.json(result);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const message = error instanceof Error ? error.message : "Erro ao comprar cr\xE9ditos de IA";
|
|
25
|
+
const status = error instanceof ApiError ? error.statusCode : 500;
|
|
26
|
+
return Response.json({ status: 0, message }, { status });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
createPurchaseIaCreditsHandler
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=purchase-ia-credits.handler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/ia-credits/handlers/purchase-ia-credits.handler.ts"],"sourcesContent":["import 'server-only';\n\nimport type { NextRequest } from 'next/server';\nimport { ApiError } from '../../../infra/api/types';\nimport { PurchaseIaCreditsRequestBodySchema } from '../types/ai-credits-offer.type';\nimport { iaCreditsService } from '../services/ia-credits.service';\n\n/**\n * `POST /api/subscriptions/:id/credits/purchase` — compra avulsa de créditos de IA, comum ou\n * (com `offer`) a de primeira compra. Desvio deliberado de `safeRouteHandler`: o `BuyOneTimeCreditsModal`\n * do editor e o `AiCreditsOfferModal` desta lib esperam o envelope CRU do gapps-r3-api direto na\n * raiz da resposta (`{ status: 1 | 2 | 0, client_secret?, message?, data? }`), inclusive pra\n * distinguir pago (1), 3DS pendente (2) e recusado (0) — embrulhar em `{ success, data }` quebraria\n * os dois.\n */\nexport function createPurchaseIaCreditsHandler() {\n return async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {\n const { id } = await params;\n const parsedBody = PurchaseIaCreditsRequestBodySchema.safeParse(await req.json().catch(() => null));\n\n if (!parsedBody.success) {\n return Response.json(\n { status: 0, message: 'Erro ao comprar créditos de IA' },\n { status: 400 }\n );\n }\n\n try {\n const result = await iaCreditsService.purchase(id, {\n quantity: parsedBody.data.quantity,\n paymentMethod: parsedBody.data.payment_method,\n idCard: parsedBody.data.id_card,\n offer: parsedBody.data.offer,\n });\n return Response.json(result);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Erro ao comprar créditos de IA';\n const status = error instanceof ApiError ? error.statusCode : 500;\n return Response.json({ status: 0, message }, { status });\n }\n };\n}\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,gBAAgB;AACzB,SAAS,0CAA0C;AACnD,SAAS,wBAAwB;AAU1B,SAAS,iCAAiC;AAC/C,SAAO,eAAe,KAAK,KAAkB,EAAE,OAAO,GAAwC;AAC5F,UAAM,EAAE,GAAG,IAAI,MAAM;AACrB,UAAM,aAAa,mCAAmC,UAAU,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,CAAC;AAElG,QAAI,CAAC,WAAW,SAAS;AACvB,aAAO,SAAS;AAAA,QACd,EAAE,QAAQ,GAAG,SAAS,oCAAiC;AAAA,QACvD,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,iBAAiB,SAAS,IAAI;AAAA,QACjD,UAAU,WAAW,KAAK;AAAA,QAC1B,eAAe,WAAW,KAAK;AAAA,QAC/B,QAAQ,WAAW,KAAK;AAAA,QACxB,OAAO,WAAW,KAAK;AAAA,MACzB,CAAC;AACD,aAAO,SAAS,KAAK,MAAM;AAAA,IAC7B,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,YAAM,SAAS,iBAAiB,WAAW,MAAM,aAAa;AAC9D,aAAO,SAAS,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,EAAE,OAAO,CAAC;AAAA,IACzD;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { useAuthQueryKey } from "../../../hooks/useAuthQueryKey";
|
|
4
|
+
import { PurchaseIaCreditsResultSchema } from "../types/ai-credits-offer.type";
|
|
5
|
+
function usePurchaseIaCredits() {
|
|
6
|
+
const queryClient = useQueryClient();
|
|
7
|
+
const iaCreditsKey = useAuthQueryKey(["ia-credits"]);
|
|
8
|
+
return useMutation({
|
|
9
|
+
mutationFn: async (input) => {
|
|
10
|
+
const response = await fetch(`/api/subscriptions/${input.subscriptionId}/credits/purchase`, {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: { "Content-Type": "application/json" },
|
|
13
|
+
body: JSON.stringify({
|
|
14
|
+
...input.offer ? { offer: input.offer } : { quantity: input.quantity },
|
|
15
|
+
payment_method: input.paymentMethod,
|
|
16
|
+
...input.idCard ? { id_card: input.idCard } : {}
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
return PurchaseIaCreditsResultSchema.parse(await response.json());
|
|
20
|
+
},
|
|
21
|
+
onSettled: () => {
|
|
22
|
+
queryClient.invalidateQueries({ queryKey: iaCreditsKey });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
usePurchaseIaCredits
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=purchase-ia-credits.hook.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/ia-credits/hooks/purchase-ia-credits.hook.ts"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';\nimport { PurchaseIaCreditsResultSchema } from '../types/ai-credits-offer.type';\nimport type { PurchaseIaCreditsInput, PurchaseIaCreditsResult } from '../types/ai-credits-offer.type';\n\n/**\n * Compra de créditos de IA — avulsa comum OU a oferta de primeira compra (`offer` no input). POSTa\n * na rota local `/api/subscriptions/:id/credits/purchase` (`createPurchaseIaCreditsHandler`), que\n * repassa CRU pro gapps-r3-api. A resposta:\n * - `{ status: 1, data: [{ credited }] }` — cartão pago, créditos concedidos na hora.\n * - `{ status: 2, client_secret }` — cartão pediu 3DS (confirmar com `confirmPaymentIfRequired`\n * e re-postar o MESMO input: a chave de idempotência do backend devolve a mesma cobrança já paga).\n * - `{ status: 0, message }` — recusado ou inelegível.\n *\n * Um hook só pros dois fluxos (avulsa e oferta): quem recusa PIX na oferta é o backend, o front\n * não precisa saber a regra.\n */\nexport function usePurchaseIaCredits() {\n const queryClient = useQueryClient();\n const iaCreditsKey = useAuthQueryKey(['ia-credits']);\n\n return useMutation({\n mutationFn: async (\n input: PurchaseIaCreditsInput & { subscriptionId: number | string }\n ): Promise<PurchaseIaCreditsResult> => {\n const response = await fetch(`/api/subscriptions/${input.subscriptionId}/credits/purchase`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...(input.offer ? { offer: input.offer } : { quantity: input.quantity }),\n payment_method: input.paymentMethod,\n ...(input.idCard ? { id_card: input.idCard } : {}),\n }),\n });\n return PurchaseIaCreditsResultSchema.parse(await response.json());\n },\n onSettled: () => {\n queryClient.invalidateQueries({ queryKey: iaCreditsKey });\n },\n });\n}\n"],"mappings":";AAEA,SAAS,aAAa,sBAAsB;AAC5C,SAAS,uBAAuB;AAChC,SAAS,qCAAqC;AAevC,SAAS,uBAAuB;AACrC,QAAM,cAAc,eAAe;AACnC,QAAM,eAAe,gBAAgB,CAAC,YAAY,CAAC;AAEnD,SAAO,YAAY;AAAA,IACjB,YAAY,OACV,UACqC;AACrC,YAAM,WAAW,MAAM,MAAM,sBAAsB,MAAM,cAAc,qBAAqB;AAAA,QAC1F,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,GAAI,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI,EAAE,UAAU,MAAM,SAAS;AAAA,UACtE,gBAAgB,MAAM;AAAA,UACtB,GAAI,MAAM,SAAS,EAAE,SAAS,MAAM,OAAO,IAAI,CAAC;AAAA,QAClD,CAAC;AAAA,MACH,CAAC;AACD,aAAO,8BAA8B,MAAM,MAAM,SAAS,KAAK,CAAC;AAAA,IAClE;AAAA,IACA,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,aAAa,CAAC;AAAA,IAC1D;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { apiFetch } from "../../../infra/http/api-fetch";
|
|
4
|
+
import { useAuthQueryKey } from "../../../hooks/useAuthQueryKey";
|
|
5
|
+
import { AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY } from "../constants/ai-credits-offer.constants";
|
|
6
|
+
function useAiCreditsOfferEligibility(options = {}) {
|
|
7
|
+
const queryKey = useAuthQueryKey([...AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY]);
|
|
8
|
+
return useQuery({
|
|
9
|
+
queryKey,
|
|
10
|
+
queryFn: ({ signal }) => apiFetch("/api/ai-credits-offer/eligibility", { signal }),
|
|
11
|
+
enabled: options.enabled ?? true,
|
|
12
|
+
staleTime: 3e4
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
useAiCreditsOfferEligibility
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=use-offer-eligibility.hook.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/ia-credits/hooks/use-offer-eligibility.hook.ts"],"sourcesContent":["'use client';\n\nimport { useQuery } from '@tanstack/react-query';\nimport { apiFetch } from '../../../infra/http/api-fetch';\nimport { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';\nimport { AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY } from '../constants/ai-credits-offer.constants';\nimport type { AiCreditsOfferEligibilityResponse } from '../types/ai-credits-offer.type';\n\nexport interface UseAiCreditsOfferEligibilityOptions {\n enabled?: boolean;\n}\n\n/**\n * Elegibilidade da oferta de primeira compra de créditos de IA, resolvida SEMPRE no servidor\n * (`GET /api/ai-credits-offer/eligibility`) — o `AiCreditsOfferGate` trata erro/timeout como\n * inelegível (fail closed): sem confirmação do backend, a oferta não abre.\n */\nexport function useAiCreditsOfferEligibility(options: UseAiCreditsOfferEligibilityOptions = {}) {\n const queryKey = useAuthQueryKey([...AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY]);\n\n return useQuery({\n queryKey,\n queryFn: ({ signal }) =>\n apiFetch<AiCreditsOfferEligibilityResponse>('/api/ai-credits-offer/eligibility', { signal }),\n enabled: options.enabled ?? true,\n staleTime: 30_000,\n });\n}\n"],"mappings":";AAEA,SAAS,gBAAgB;AACzB,SAAS,gBAAgB;AACzB,SAAS,uBAAuB;AAChC,SAAS,8CAA8C;AAYhD,SAAS,6BAA6B,UAA+C,CAAC,GAAG;AAC9F,QAAM,WAAW,gBAAgB,CAAC,GAAG,sCAAsC,CAAC;AAE5E,SAAO,SAAS;AAAA,IACd;AAAA,IACA,SAAS,CAAC,EAAE,OAAO,MACjB,SAA4C,qCAAqC,EAAE,OAAO,CAAC;AAAA,IAC7F,SAAS,QAAQ,WAAW;AAAA,IAC5B,WAAW;AAAA,EACb,CAAC;AACH;","names":[]}
|
|
@@ -2,9 +2,11 @@ import "server-only";
|
|
|
2
2
|
import { api } from "../../../infra/api/client";
|
|
3
3
|
import { ApiError } from "../../../infra/api/types";
|
|
4
4
|
import { getUserContext } from "../../auth/utils/get-user-context";
|
|
5
|
+
import { subscriptionsService } from "../../subscriptions/services/subscriptions.service";
|
|
5
6
|
import {
|
|
6
7
|
IaCreditAddBatchSchema
|
|
7
8
|
} from "../types";
|
|
9
|
+
import { AI_CREDITS_FIRST_PURCHASE_OFFER } from "../constants/ai-credits-offer.constants";
|
|
8
10
|
class IaCreditsService {
|
|
9
11
|
async getSummaryData(subscriptionId) {
|
|
10
12
|
const { id_account } = await getUserContext();
|
|
@@ -45,6 +47,71 @@ class IaCreditsService {
|
|
|
45
47
|
const raw = response.data ?? [];
|
|
46
48
|
return raw.map((item) => IaCreditAddBatchSchema.parse(item));
|
|
47
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Elegibilidade da oferta de primeira compra, pra assinatura ATIVA da conta — resolvida aqui (e
|
|
52
|
+
* não recebida por parâmetro) porque o consumidor (`createGetAiCreditsOfferEligibilityHandler`)
|
|
53
|
+
* não tem id de assinatura: a rota é `/api/ai-credits-offer/eligibility`, sem `:id`.
|
|
54
|
+
*
|
|
55
|
+
* Sem assinatura ativa a resposta já sai inelegível — nem vale bater no backend, que exige
|
|
56
|
+
* `id_subscription` na URL.
|
|
57
|
+
*/
|
|
58
|
+
async offerEligibility() {
|
|
59
|
+
const { id_account } = await getUserContext();
|
|
60
|
+
const { data: subscriptions } = await subscriptionsService.listSubscriptions({ limit: 1 });
|
|
61
|
+
const subscription = subscriptions[0];
|
|
62
|
+
if (!subscription) {
|
|
63
|
+
return {
|
|
64
|
+
eligible: false,
|
|
65
|
+
reason: "no_active_subscription",
|
|
66
|
+
credits: AI_CREDITS_FIRST_PURCHASE_OFFER.credits,
|
|
67
|
+
priceCents: null,
|
|
68
|
+
anchorCents: null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const endpoint = `/accounts/${id_account}/subscriptions/${subscription.id}/items/balance/purchase-offer/${AI_CREDITS_FIRST_PURCHASE_OFFER.slug}`;
|
|
72
|
+
const response = await api.apps.get(
|
|
73
|
+
endpoint
|
|
74
|
+
);
|
|
75
|
+
if (response.status === 0) {
|
|
76
|
+
throw new ApiError(
|
|
77
|
+
response.message || "Erro ao consultar elegibilidade da oferta de cr\xE9ditos de IA",
|
|
78
|
+
"GET_OFFER_ELIGIBILITY_FAILED",
|
|
79
|
+
400
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const raw = response.data[0];
|
|
83
|
+
if (!raw) {
|
|
84
|
+
throw new ApiError(
|
|
85
|
+
"Resposta vazia ao consultar elegibilidade da oferta de cr\xE9ditos de IA",
|
|
86
|
+
"GET_OFFER_ELIGIBILITY_EMPTY",
|
|
87
|
+
502
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
eligible: raw.eligible,
|
|
92
|
+
reason: raw.reason,
|
|
93
|
+
credits: raw.credits,
|
|
94
|
+
priceCents: raw.price_cents,
|
|
95
|
+
anchorCents: raw.anchor_cents
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compra de créditos de IA — avulsa comum (`offer` ausente) OU a oferta de primeira compra
|
|
100
|
+
* (`offer: AI_CREDITS_FIRST_PURCHASE_OFFER.slug`). Devolve o envelope CRU do gapps-r3-api
|
|
101
|
+
* (`status` 1/2/0), sem reembrulhar em `SuccessResult`: quem consome é sempre o hook de compra,
|
|
102
|
+
* que já sabe interpretar esses três status (ver `usePurchaseIaCredits`).
|
|
103
|
+
*/
|
|
104
|
+
async purchase(subscriptionId, input) {
|
|
105
|
+
const { id_account } = await getUserContext();
|
|
106
|
+
return api.apps.post(
|
|
107
|
+
`/accounts/${id_account}/subscriptions/${subscriptionId}/items/balance/purchase`,
|
|
108
|
+
{
|
|
109
|
+
...input.offer ? { offer: input.offer } : { quantity: input.quantity },
|
|
110
|
+
payment_method: input.paymentMethod,
|
|
111
|
+
...input.idCard ? { id_card: input.idCard } : {}
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
48
115
|
}
|
|
49
116
|
const iaCreditsService = new IaCreditsService();
|
|
50
117
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/ia-credits/services/ia-credits.service.ts"],"sourcesContent":["import 'server-only';\n\nimport { api } from '../../../infra/api/client';\nimport { ApiError, ApiPaginatedActionResult, SuccessResult } from '../../../infra/api/types';\nimport { getUserContext } from '../../auth/utils/get-user-context';\nimport {\n IaCreditAddBatch,\n IaCreditAddBatchSchema,\n IaCreditOperation,\n IaCreditsSummaryData,\n} from '../types';\n\nclass IaCreditsService {\n async getSummaryData(\n subscriptionId: number | string\n ): Promise<SuccessResult<IaCreditsSummaryData>> {\n const { id_account } = await getUserContext();\n\n const [availableCredits, addBatches] = await Promise.all([\n this.fetchCurrentBalance(id_account, subscriptionId),\n this.fetchAddBatches(id_account, subscriptionId),\n ]);\n\n return { success: true, data: { availableCredits, addBatches } };\n }\n\n // Saldo atual = balance_after_operation da última operação do extrato\n // (sort id:DESC, limit=1). Já é o saldo do usuário, não precisa recalcular.\n private async fetchCurrentBalance(\n idAccount: number | string,\n subscriptionId: number | string\n ): Promise<number> {\n const endpoint = `/accounts/${idAccount}/subscriptions/${subscriptionId}/items/balance/list?page=1&limit=1&sort=id:DESC`;\n\n const response = await api.apps.get<ApiPaginatedActionResult<IaCreditOperation>>(endpoint);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao consultar saldo de créditos de IA',\n 'GET_IA_CREDITS_BALANCE_FAILED',\n 400\n );\n }\n\n const balance = (response.data ?? [])[0]?.balance_after_operation;\n return balance == null ? 0 : Number(balance);\n }\n\n // Lotes de adição com added/consumed/remaining já agregados pelo backend.\n // sort id:DESC garante que os lotes mais recentes (não expirados) entram\n // primeiro no limite; lotes expirados são filtrados no hook.\n private async fetchAddBatches(\n idAccount: number | string,\n subscriptionId: number | string\n ): Promise<IaCreditAddBatch[]> {\n const endpoint = `/accounts/${idAccount}/subscriptions/${subscriptionId}/items/balance/list/add?page=1&limit=100&sort=id:DESC`;\n\n const response = await api.apps.get<ApiPaginatedActionResult<IaCreditAddBatch>>(endpoint);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao listar lotes de créditos de IA',\n 'LIST_IA_CREDITS_ADDS_FAILED',\n 400\n );\n }\n\n const raw = response.data ?? [];\n return raw.map((item) => IaCreditAddBatchSchema.parse(item));\n }\n}\n\nexport const iaCreditsService = new IaCreditsService();\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,WAAW;AACpB,SAAS,gBAAyD;AAClE,SAAS,sBAAsB;AAC/B;AAAA,EAEE;AAAA,OAGK;
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/ia-credits/services/ia-credits.service.ts"],"sourcesContent":["import 'server-only';\n\nimport { api } from '../../../infra/api/client';\nimport { ApiError, ApiPaginatedActionResult, SuccessResult } from '../../../infra/api/types';\nimport { getUserContext } from '../../auth/utils/get-user-context';\nimport { subscriptionsService } from '../../subscriptions/services/subscriptions.service';\nimport {\n IaCreditAddBatch,\n IaCreditAddBatchSchema,\n IaCreditOperation,\n IaCreditsSummaryData,\n} from '../types';\nimport { AI_CREDITS_FIRST_PURCHASE_OFFER } from '../constants/ai-credits-offer.constants';\nimport type {\n AiCreditsOfferEligibility,\n PurchaseIaCreditsInput,\n PurchaseIaCreditsResult,\n} from '../types/ai-credits-offer.type';\n\n/** Shape da API pra elegibilidade — `price_cents`/`anchor_cents` viram camelCase ao sair do service. */\ninterface AiCreditsOfferEligibilityApiItem {\n eligible: boolean;\n reason: AiCreditsOfferEligibility['reason'];\n credits: number;\n price_cents: number | null;\n anchor_cents: number | null;\n}\n\nclass IaCreditsService {\n async getSummaryData(\n subscriptionId: number | string\n ): Promise<SuccessResult<IaCreditsSummaryData>> {\n const { id_account } = await getUserContext();\n\n const [availableCredits, addBatches] = await Promise.all([\n this.fetchCurrentBalance(id_account, subscriptionId),\n this.fetchAddBatches(id_account, subscriptionId),\n ]);\n\n return { success: true, data: { availableCredits, addBatches } };\n }\n\n // Saldo atual = balance_after_operation da última operação do extrato\n // (sort id:DESC, limit=1). Já é o saldo do usuário, não precisa recalcular.\n private async fetchCurrentBalance(\n idAccount: number | string,\n subscriptionId: number | string\n ): Promise<number> {\n const endpoint = `/accounts/${idAccount}/subscriptions/${subscriptionId}/items/balance/list?page=1&limit=1&sort=id:DESC`;\n\n const response = await api.apps.get<ApiPaginatedActionResult<IaCreditOperation>>(endpoint);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao consultar saldo de créditos de IA',\n 'GET_IA_CREDITS_BALANCE_FAILED',\n 400\n );\n }\n\n const balance = (response.data ?? [])[0]?.balance_after_operation;\n return balance == null ? 0 : Number(balance);\n }\n\n // Lotes de adição com added/consumed/remaining já agregados pelo backend.\n // sort id:DESC garante que os lotes mais recentes (não expirados) entram\n // primeiro no limite; lotes expirados são filtrados no hook.\n private async fetchAddBatches(\n idAccount: number | string,\n subscriptionId: number | string\n ): Promise<IaCreditAddBatch[]> {\n const endpoint = `/accounts/${idAccount}/subscriptions/${subscriptionId}/items/balance/list/add?page=1&limit=100&sort=id:DESC`;\n\n const response = await api.apps.get<ApiPaginatedActionResult<IaCreditAddBatch>>(endpoint);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao listar lotes de créditos de IA',\n 'LIST_IA_CREDITS_ADDS_FAILED',\n 400\n );\n }\n\n const raw = response.data ?? [];\n return raw.map((item) => IaCreditAddBatchSchema.parse(item));\n }\n\n /**\n * Elegibilidade da oferta de primeira compra, pra assinatura ATIVA da conta — resolvida aqui (e\n * não recebida por parâmetro) porque o consumidor (`createGetAiCreditsOfferEligibilityHandler`)\n * não tem id de assinatura: a rota é `/api/ai-credits-offer/eligibility`, sem `:id`.\n *\n * Sem assinatura ativa a resposta já sai inelegível — nem vale bater no backend, que exige\n * `id_subscription` na URL.\n */\n async offerEligibility(): Promise<AiCreditsOfferEligibility> {\n const { id_account } = await getUserContext();\n const { data: subscriptions } = await subscriptionsService.listSubscriptions({ limit: 1 });\n const subscription = subscriptions[0];\n\n if (!subscription) {\n return {\n eligible: false,\n reason: 'no_active_subscription',\n credits: AI_CREDITS_FIRST_PURCHASE_OFFER.credits,\n priceCents: null,\n anchorCents: null,\n };\n }\n\n const endpoint = `/accounts/${id_account}/subscriptions/${subscription.id}/items/balance/purchase-offer/${AI_CREDITS_FIRST_PURCHASE_OFFER.slug}`;\n const response = await api.apps.get<ApiPaginatedActionResult<AiCreditsOfferEligibilityApiItem>>(\n endpoint\n );\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao consultar elegibilidade da oferta de créditos de IA',\n 'GET_OFFER_ELIGIBILITY_FAILED',\n 400\n );\n }\n\n const raw = response.data[0];\n if (!raw) {\n throw new ApiError(\n 'Resposta vazia ao consultar elegibilidade da oferta de créditos de IA',\n 'GET_OFFER_ELIGIBILITY_EMPTY',\n 502\n );\n }\n\n return {\n eligible: raw.eligible,\n reason: raw.reason,\n credits: raw.credits,\n priceCents: raw.price_cents,\n anchorCents: raw.anchor_cents,\n };\n }\n\n /**\n * Compra de créditos de IA — avulsa comum (`offer` ausente) OU a oferta de primeira compra\n * (`offer: AI_CREDITS_FIRST_PURCHASE_OFFER.slug`). Devolve o envelope CRU do gapps-r3-api\n * (`status` 1/2/0), sem reembrulhar em `SuccessResult`: quem consome é sempre o hook de compra,\n * que já sabe interpretar esses três status (ver `usePurchaseIaCredits`).\n */\n async purchase(\n subscriptionId: number | string,\n input: PurchaseIaCreditsInput\n ): Promise<PurchaseIaCreditsResult> {\n const { id_account } = await getUserContext();\n\n return api.apps.post<PurchaseIaCreditsResult>(\n `/accounts/${id_account}/subscriptions/${subscriptionId}/items/balance/purchase`,\n {\n ...(input.offer ? { offer: input.offer } : { quantity: input.quantity }),\n payment_method: input.paymentMethod,\n ...(input.idCard ? { id_card: input.idCard } : {}),\n }\n );\n }\n}\n\nexport const iaCreditsService = new IaCreditsService();\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { PAYMENT_METHOD_CARD, PAYMENT_METHOD_PIX } from "../constants/ai-credits-offer.constants";
|
|
3
|
+
const PurchaseIaCreditsRequestBodySchema = z.object({
|
|
4
|
+
quantity: z.number().optional(),
|
|
5
|
+
payment_method: z.union([z.literal(PAYMENT_METHOD_CARD), z.literal(PAYMENT_METHOD_PIX)]),
|
|
6
|
+
id_card: z.string().optional(),
|
|
7
|
+
offer: z.string().optional()
|
|
8
|
+
});
|
|
9
|
+
const PurchaseIaCreditsPixDataSchema = z.object({
|
|
10
|
+
correlation_id: z.string().optional(),
|
|
11
|
+
qr_code: z.string().optional(),
|
|
12
|
+
qr_code_image: z.string().optional(),
|
|
13
|
+
payment_link: z.string().optional(),
|
|
14
|
+
expires_at: z.string().optional()
|
|
15
|
+
});
|
|
16
|
+
const PurchaseIaCreditsResultSchema = z.object({
|
|
17
|
+
status: z.number(),
|
|
18
|
+
message: z.string().optional(),
|
|
19
|
+
client_secret: z.string().optional(),
|
|
20
|
+
data: z.array(
|
|
21
|
+
z.object({
|
|
22
|
+
credited: z.number().optional(),
|
|
23
|
+
invoice_id: z.string().optional(),
|
|
24
|
+
pending: z.number().optional(),
|
|
25
|
+
payment_method: z.number().optional(),
|
|
26
|
+
pix: PurchaseIaCreditsPixDataSchema.optional()
|
|
27
|
+
})
|
|
28
|
+
).optional()
|
|
29
|
+
});
|
|
30
|
+
export {
|
|
31
|
+
PurchaseIaCreditsRequestBodySchema,
|
|
32
|
+
PurchaseIaCreditsResultSchema
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=ai-credits-offer.type.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/ia-credits/types/ai-credits-offer.type.ts"],"sourcesContent":["import z from 'zod';\nimport { PAYMENT_METHOD_CARD, PAYMENT_METHOD_PIX } from '../constants/ai-credits-offer.constants';\n\n/**\n * `reason` é sempre um código de máquina do backend — nunca renderizar como texto: o modal usa uma\n * mensagem de erro genérica já existente no catálogo (ver `AiCreditsOfferModal`).\n */\nexport type AiCreditsOfferIneligibilityReason =\n | 'not_default_wl'\n | 'gateway_not_supported'\n | 'no_active_subscription'\n | 'already_charged'\n | 'has_paid_plan'\n | 'has_card'\n | 'offer_consumed'\n | 'offer_card_only'\n | null;\n\nexport interface AiCreditsOfferEligibility {\n eligible: boolean;\n reason: AiCreditsOfferIneligibilityReason;\n credits: number;\n /** Preço promocional em centavos. `null` quando `eligible` é `false` sem cálculo de preço. */\n priceCents: number | null;\n /** Preço de tabela em centavos, derivado de `getAddonUnitPrice` no backend — nunca hardcoded. */\n anchorCents: number | null;\n}\n\n/** Resposta do route handler local — a elegibilidade do backend + o carimbo da sessão de login. */\nexport interface AiCreditsOfferEligibilityResponse extends AiCreditsOfferEligibility {\n /** `iat` do JWT no momento da consulta — usado pelo gate para saber se é uma sessão nova. */\n sessionIssuedAt: number;\n}\n\nexport interface PurchaseIaCreditsInput {\n /** Ignorado pelo backend quando `offer` é enviado (a oferta força a quantidade do pacote). */\n quantity?: number;\n paymentMethod: number;\n idCard?: string;\n /** Slug da oferta (`AI_CREDITS_FIRST_PURCHASE_OFFER.slug`). Ausente = compra avulsa comum. */\n offer?: string;\n}\n\n/**\n * Corpo aceito pela rota local de compra (`createPurchaseIaCreditsHandler`) — validado no handler\n * porque o JSON do request não é confiável: `payment_method` fora do union (1 = cartão, 3 = pix,\n * únicos métodos que o gapps-r3-api reconhece) nem chega a virar chamada de backend.\n */\nexport const PurchaseIaCreditsRequestBodySchema = z.object({\n quantity: z.number().optional(),\n payment_method: z.union([z.literal(PAYMENT_METHOD_CARD), z.literal(PAYMENT_METHOD_PIX)]),\n id_card: z.string().optional(),\n offer: z.string().optional(),\n});\nexport type PurchaseIaCreditsRequestBody = z.infer<typeof PurchaseIaCreditsRequestBodySchema>;\n\n/** Envelope PIX dentro de `data`, presente só quando a compra foi feita com `payment_method: 3`. */\nconst PurchaseIaCreditsPixDataSchema = z.object({\n correlation_id: z.string().optional(),\n qr_code: z.string().optional(),\n qr_code_image: z.string().optional(),\n payment_link: z.string().optional(),\n expires_at: z.string().optional(),\n});\n\n/**\n * Envelope CRU do gapps-r3-api — status 1 (pago), 2 (3DS pendente) ou 0 (recusado). Devolvido\n * sem reembrulho pelo `createPurchaseIaCreditsHandler` (ver o comentário no handler); o hook valida\n * a resposta contra este schema em vez de confiar cegamente no `fetch`.\n */\nexport const PurchaseIaCreditsResultSchema = z.object({\n status: z.number(),\n message: z.string().optional(),\n client_secret: z.string().optional(),\n data: z\n .array(\n z.object({\n credited: z.number().optional(),\n invoice_id: z.string().optional(),\n pending: z.number().optional(),\n payment_method: z.number().optional(),\n pix: PurchaseIaCreditsPixDataSchema.optional(),\n })\n )\n .optional(),\n});\nexport type PurchaseIaCreditsResult = z.infer<typeof PurchaseIaCreditsResultSchema>;\n"],"mappings":"AAAA,OAAO,OAAO;AACd,SAAS,qBAAqB,0BAA0B;AA+CjD,MAAM,qCAAqC,EAAE,OAAO;AAAA,EACzD,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,gBAAgB,EAAE,MAAM,CAAC,EAAE,QAAQ,mBAAmB,GAAG,EAAE,QAAQ,kBAAkB,CAAC,CAAC;AAAA,EACvF,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAID,MAAM,iCAAiC,EAAE,OAAO;AAAA,EAC9C,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,EACpC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,SAAS;AAClC,CAAC;AAOM,MAAM,gCAAgC,EAAE,OAAO;AAAA,EACpD,QAAQ,EAAE,OAAO;AAAA,EACjB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,MAAM,EACH;AAAA,IACC,EAAE,OAAO;AAAA,MACP,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,MAC9B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,MAChC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,MACpC,KAAK,+BAA+B,SAAS;AAAA,IAC/C,CAAC;AAAA,EACH,EACC,SAAS;AACd,CAAC;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cookieDomainFromDomain } from "../../../utils/intl/cookie-domain";
|
|
2
|
+
import { AiCreditsOfferStage } from "../constants/ai-credits-offer.constants";
|
|
3
|
+
const COOKIE_NAME = "ai-credits-offer-stage";
|
|
4
|
+
const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
|
|
5
|
+
const EMPTY_RECORD = {
|
|
6
|
+
stage: AiCreditsOfferStage.NONE,
|
|
7
|
+
lastSessionIat: 0,
|
|
8
|
+
repeatCount: 0
|
|
9
|
+
};
|
|
10
|
+
function readOfferStageCookie(idAccount) {
|
|
11
|
+
if (typeof document === "undefined") return EMPTY_RECORD;
|
|
12
|
+
const match = new RegExp(`(?:^|;\\s*)${COOKIE_NAME}=([^;]+)`).exec(document.cookie);
|
|
13
|
+
if (!match) return EMPTY_RECORD;
|
|
14
|
+
const raw = decodeURIComponent(match[1]);
|
|
15
|
+
const [rawAccount, rawStage, rawIat, rawRepeat] = raw.split(":");
|
|
16
|
+
if (Number(rawAccount) !== idAccount) return EMPTY_RECORD;
|
|
17
|
+
const stage = Number(rawStage);
|
|
18
|
+
if (!(stage in AiCreditsOfferStage)) return EMPTY_RECORD;
|
|
19
|
+
return {
|
|
20
|
+
stage,
|
|
21
|
+
lastSessionIat: Number(rawIat) || 0,
|
|
22
|
+
repeatCount: Number(rawRepeat) || 0
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function writeOfferStageCookie(idAccount, record, whitelabelDomain) {
|
|
26
|
+
if (typeof document === "undefined") return;
|
|
27
|
+
const value = `${idAccount}:${record.stage}:${record.lastSessionIat}:${record.repeatCount}`;
|
|
28
|
+
const domain = cookieDomainFromDomain(whitelabelDomain);
|
|
29
|
+
const parts = [
|
|
30
|
+
`${COOKIE_NAME}=${encodeURIComponent(value)}`,
|
|
31
|
+
"path=/",
|
|
32
|
+
`max-age=${ONE_YEAR_SECONDS}`,
|
|
33
|
+
"samesite=lax",
|
|
34
|
+
domain ? `domain=${domain}` : "",
|
|
35
|
+
typeof window !== "undefined" && window.location.protocol === "https:" ? "secure" : ""
|
|
36
|
+
].filter(Boolean);
|
|
37
|
+
document.cookie = parts.join("; ");
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
readOfferStageCookie,
|
|
41
|
+
writeOfferStageCookie
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=offer-stage-cookie.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/ia-credits/utils/offer-stage-cookie.ts"],"sourcesContent":["import { cookieDomainFromDomain } from '../../../utils/intl/cookie-domain';\nimport { AiCreditsOfferStage } from '../constants/ai-credits-offer.constants';\n\nconst COOKIE_NAME = 'ai-credits-offer-stage';\nconst ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;\n\nexport interface OfferStageRecord {\n stage: AiCreditsOfferStage;\n /** `iat` do JWT na sessão em que este estágio foi gravado — é o que diferencia \"sessão nova\". */\n lastSessionIat: number;\n /** Quantas vezes um estágio REPETÍVEL (campanha / sem crédito) já foi apresentado. */\n repeatCount: number;\n}\n\nconst EMPTY_RECORD: OfferStageRecord = {\n stage: AiCreditsOfferStage.NONE,\n lastSessionIat: 0,\n repeatCount: 0,\n};\n\n/**\n * Cookie `ai-credits-offer-stage`, no domínio pai da whitelabel (`cookieDomainFromDomain` — mesmo\n * mecanismo do cookie de locale, que por sua vez espelha o cookie de auth `greatapps`): sustenta a\n * escada de estágios entre app/accounts/editor, 1 ano de validade.\n *\n * Valor: `${idAccount}:${stage}:${lastSessionIat}:${repeatCount}`. Registro de OUTRA conta\n * (id diferente do gravado) lê como estágio zerado — last-writer-wins, aceito porque o cookie é por\n * navegador e trocar de conta paga no mesmo device durante a janela da oferta é evento raro.\n */\nexport function readOfferStageCookie(idAccount: number): OfferStageRecord {\n if (typeof document === 'undefined') return EMPTY_RECORD;\n\n const match = new RegExp(`(?:^|;\\\\s*)${COOKIE_NAME}=([^;]+)`).exec(document.cookie);\n if (!match) return EMPTY_RECORD;\n\n const raw = decodeURIComponent(match[1]);\n const [rawAccount, rawStage, rawIat, rawRepeat] = raw.split(':');\n\n if (Number(rawAccount) !== idAccount) return EMPTY_RECORD;\n\n const stage = Number(rawStage);\n if (!(stage in AiCreditsOfferStage)) return EMPTY_RECORD;\n\n return {\n stage,\n lastSessionIat: Number(rawIat) || 0,\n repeatCount: Number(rawRepeat) || 0,\n };\n}\n\nexport function writeOfferStageCookie(\n idAccount: number,\n record: OfferStageRecord,\n whitelabelDomain?: string | null\n): void {\n if (typeof document === 'undefined') return;\n\n const value = `${idAccount}:${record.stage}:${record.lastSessionIat}:${record.repeatCount}`;\n const domain = cookieDomainFromDomain(whitelabelDomain);\n const parts = [\n `${COOKIE_NAME}=${encodeURIComponent(value)}`,\n 'path=/',\n `max-age=${ONE_YEAR_SECONDS}`,\n 'samesite=lax',\n domain ? `domain=${domain}` : '',\n typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'secure' : '',\n ].filter(Boolean);\n\n document.cookie = parts.join('; ');\n}\n"],"mappings":"AAAA,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AAEpC,MAAM,cAAc;AACpB,MAAM,mBAAmB,KAAK,KAAK,KAAK;AAUxC,MAAM,eAAiC;AAAA,EACrC,OAAO,oBAAoB;AAAA,EAC3B,gBAAgB;AAAA,EAChB,aAAa;AACf;AAWO,SAAS,qBAAqB,WAAqC;AACxE,MAAI,OAAO,aAAa,YAAa,QAAO;AAE5C,QAAM,QAAQ,IAAI,OAAO,cAAc,WAAW,UAAU,EAAE,KAAK,SAAS,MAAM;AAClF,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,MAAM,mBAAmB,MAAM,CAAC,CAAC;AACvC,QAAM,CAAC,YAAY,UAAU,QAAQ,SAAS,IAAI,IAAI,MAAM,GAAG;AAE/D,MAAI,OAAO,UAAU,MAAM,UAAW,QAAO;AAE7C,QAAM,QAAQ,OAAO,QAAQ;AAC7B,MAAI,EAAE,SAAS,qBAAsB,QAAO;AAE5C,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,OAAO,MAAM,KAAK;AAAA,IAClC,aAAa,OAAO,SAAS,KAAK;AAAA,EACpC;AACF;AAEO,SAAS,sBACd,WACA,QACA,kBACM;AACN,MAAI,OAAO,aAAa,YAAa;AAErC,QAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,KAAK,IAAI,OAAO,cAAc,IAAI,OAAO,WAAW;AACzF,QAAM,SAAS,uBAAuB,gBAAgB;AACtD,QAAM,QAAQ;AAAA,IACZ,GAAG,WAAW,IAAI,mBAAmB,KAAK,CAAC;AAAA,IAC3C;AAAA,IACA,WAAW,gBAAgB;AAAA,IAC3B;AAAA,IACA,SAAS,UAAU,MAAM,KAAK;AAAA,IAC9B,OAAO,WAAW,eAAe,OAAO,SAAS,aAAa,WAAW,WAAW;AAAA,EACtF,EAAE,OAAO,OAAO;AAEhB,WAAS,SAAS,MAAM,KAAK,IAAI;AACnC;","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}-v6`;
|
|
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}-v2`, {
|
|
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 // v6: descarta as entradas v5 que ficaram vivas durante a janela sem cache\n return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v6`;\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}-v2`, {\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":"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","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
|
|
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":[]}
|
|
@@ -109,7 +109,6 @@ 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,
|
|
113
112
|
priceUnavailable,
|
|
114
113
|
items: currencyAwareItems
|
|
115
114
|
};
|
|
@@ -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 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":[]}
|
|
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":[]}
|
|
@@ -55,10 +55,6 @@ 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 declarado da vitrine. Ausentes
|
|
59
|
-
// quando não há ancoragem (whitelabel != 1, USD, grandfathered) — ver CONTRATO DE CAMPOS.
|
|
60
|
-
month_list: z.number().optional(),
|
|
61
|
-
month_badge_percent: z.number().optional(),
|
|
62
58
|
semmester: z.number(),
|
|
63
59
|
semmester_price: z.number().optional().default(0),
|
|
64
60
|
semmester_discount: z.number(),
|
|
@@ -66,16 +62,12 @@ const CalculateSubscriptionResponseSchema = z.object({
|
|
|
66
62
|
semmester_original: z.number(),
|
|
67
63
|
semmester_original_discount: z.number().optional().default(0),
|
|
68
64
|
semmester_original_discount_cupom: z.number().optional().default(0),
|
|
69
|
-
semmester_list: z.number().optional(),
|
|
70
|
-
semmester_badge_percent: z.number().optional(),
|
|
71
65
|
year: z.number(),
|
|
72
66
|
year_discount: z.number(),
|
|
73
67
|
year_discount_coupom: z.number().optional().default(0),
|
|
74
68
|
year_original: z.number(),
|
|
75
69
|
year_original_discount: z.number().optional().default(0),
|
|
76
70
|
year_original_discount_cupom: z.number().optional().default(0),
|
|
77
|
-
year_list: z.number().optional(),
|
|
78
|
-
year_badge_percent: z.number().optional(),
|
|
79
71
|
plan_extras: z.array(CalculateSubscriptionPlanExtraResponseSchema),
|
|
80
72
|
target: CalculateSubscriptionTargetSchema.optional().nullable()
|
|
81
73
|
});
|
|
@@ -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
|
|
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":"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","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useModalManager } from "./useModalManager";
|
|
3
|
+
const useAiCreditsOfferModal = () => {
|
|
4
|
+
const { activeModal, openModal, closeModalByKey } = useModalManager();
|
|
5
|
+
return {
|
|
6
|
+
open: activeModal === "aiCreditsOfferModal",
|
|
7
|
+
openModal: useCallback(() => openModal("aiCreditsOfferModal"), [openModal]),
|
|
8
|
+
closeModal: useCallback(() => closeModalByKey("aiCreditsOfferModal"), [closeModalByKey])
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export {
|
|
12
|
+
useAiCreditsOfferModal
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=useAiCreditsOfferModal.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/store/useAiCreditsOfferModal.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useModalManager } from './useModalManager';\n\n/**\n * Oferta de primeira compra de créditos de IA. Quem abre é o `AiCreditsOfferGate` (1s depois do\n * gatilho decidir que é a vez dela) — daí não existir payload: a oferta é sempre a mesma.\n *\n * Os callbacks vão em `useCallback` (as ações do zustand já são estáveis): o gate arma um\n * `setTimeout` num efeito que tem `openModal` nas dependências, e uma identidade nova a cada render\n * limparia o timer antes de ele disparar — a oferta nunca abriria.\n *\n * O fechamento usa `closeModalByKey` e não `closeModal`: o modal abre o `addCardModal` por cima (a\n * pilha do `useModalManager` empilha), e um `closeModal` disparado com o cartão em cima derrubaria\n * o topo da pilha, não a oferta.\n */\nexport const useAiCreditsOfferModal = () => {\n const { activeModal, openModal, closeModalByKey } = useModalManager();\n\n return {\n open: activeModal === 'aiCreditsOfferModal',\n openModal: useCallback(() => openModal('aiCreditsOfferModal'), [openModal]),\n closeModal: useCallback(() => closeModalByKey('aiCreditsOfferModal'), [closeModalByKey]),\n };\n};\n"],"mappings":"AAAA,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAczB,MAAM,yBAAyB,MAAM;AAC1C,QAAM,EAAE,aAAa,WAAW,gBAAgB,IAAI,gBAAgB;AAEpE,SAAO;AAAA,IACL,MAAM,gBAAgB;AAAA,IACtB,WAAW,YAAY,MAAM,UAAU,qBAAqB,GAAG,CAAC,SAAS,CAAC;AAAA,IAC1E,YAAY,YAAY,MAAM,gBAAgB,qBAAqB,GAAG,CAAC,eAAe,CAAC;AAAA,EACzF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@greatapps/common",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.752",
|
|
4
4
|
"description": "Shared library for GreatApps frontend applications",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -61,12 +61,14 @@
|
|
|
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.23",
|
|
64
65
|
"@radix-ui/react-popover": "1.1.15",
|
|
65
66
|
"@radix-ui/react-progress": "1.1.8",
|
|
66
67
|
"@radix-ui/react-radio-group": "1.3.8",
|
|
67
68
|
"@radix-ui/react-scroll-area": "1.2.10",
|
|
68
69
|
"@radix-ui/react-select": "2.2.6",
|
|
69
70
|
"@radix-ui/react-separator": "1.1.8",
|
|
71
|
+
"@radix-ui/react-slot": "1.3.3",
|
|
70
72
|
"@radix-ui/react-switch": "1.2.6",
|
|
71
73
|
"@radix-ui/react-tabs": "1.1.13",
|
|
72
74
|
"@radix-ui/react-tooltip": "1.2.8",
|