@greatapps/common 1.1.749 → 1.1.751

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.
Files changed (78) hide show
  1. package/dist/handlers.mjs +1 -5
  2. package/dist/handlers.mjs.map +1 -1
  3. package/dist/i18n/messages/en-us.mjs +0 -32
  4. package/dist/i18n/messages/en-us.mjs.map +1 -1
  5. package/dist/i18n/messages/es-es.mjs +0 -32
  6. package/dist/i18n/messages/es-es.mjs.map +1 -1
  7. package/dist/i18n/messages/pt-br.mjs +0 -32
  8. package/dist/i18n/messages/pt-br.mjs.map +1 -1
  9. package/dist/index.mjs +43 -71
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/modules/auth/utils/get-user-context.mjs +1 -2
  12. package/dist/modules/auth/utils/get-user-context.mjs.map +1 -1
  13. package/dist/modules/cards/hooks/cards.hook.mjs +2 -3
  14. package/dist/modules/cards/hooks/cards.hook.mjs.map +1 -1
  15. package/dist/modules/ia-credits/services/ia-credits.service.mjs +0 -67
  16. package/dist/modules/ia-credits/services/ia-credits.service.mjs.map +1 -1
  17. package/dist/modules/plans/services/plans.service.mjs +2 -2
  18. package/dist/modules/plans/services/plans.service.mjs.map +1 -1
  19. package/dist/modules/plans/types/plan.type.mjs.map +1 -1
  20. package/dist/modules/plans/utils/map-api-plan-to-ui.mjs +1 -0
  21. package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
  22. package/dist/modules/subscriptions/types/calculate-subscription.type.mjs +8 -0
  23. package/dist/modules/subscriptions/types/calculate-subscription.type.mjs.map +1 -1
  24. package/package.json +1 -3
  25. package/src/handlers.ts +0 -2
  26. package/src/i18n/messages/en-us.ts +0 -33
  27. package/src/i18n/messages/es-es.ts +0 -33
  28. package/src/i18n/messages/pt-br.ts +0 -33
  29. package/src/index.ts +0 -25
  30. package/src/modules/auth/utils/get-user-context.ts +0 -3
  31. package/src/modules/cards/hooks/cards.hook.ts +1 -6
  32. package/src/modules/ia-credits/services/ia-credits.service.ts +0 -92
  33. package/src/modules/plans/services/plans.service.ts +3 -3
  34. package/src/modules/plans/types/plan.type.ts +1 -0
  35. package/src/modules/plans/utils/map-api-plan-to-ui.ts +1 -0
  36. package/src/modules/subscriptions/types/calculate-subscription.type.ts +8 -0
  37. package/dist/components/layouts/AiCreditsOfferGate.mjs +0 -92
  38. package/dist/components/layouts/AiCreditsOfferGate.mjs.map +0 -1
  39. package/dist/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.mjs +0 -275
  40. package/dist/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.mjs.map +0 -1
  41. package/dist/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.mjs +0 -55
  42. package/dist/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.mjs.map +0 -1
  43. package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.mjs +0 -78
  44. package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.mjs.map +0 -1
  45. package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.mjs +0 -60
  46. package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.mjs.map +0 -1
  47. package/dist/components/modals/promo/AiCreditsOfferModal/index.mjs +0 -5
  48. package/dist/components/modals/promo/AiCreditsOfferModal/index.mjs.map +0 -1
  49. package/dist/modules/ia-credits/constants/ai-credits-offer.constants.mjs +0 -26
  50. package/dist/modules/ia-credits/constants/ai-credits-offer.constants.mjs.map +0 -1
  51. package/dist/modules/ia-credits/handlers/get-offer-eligibility.handler.mjs +0 -19
  52. package/dist/modules/ia-credits/handlers/get-offer-eligibility.handler.mjs.map +0 -1
  53. package/dist/modules/ia-credits/handlers/purchase-ia-credits.handler.mjs +0 -33
  54. package/dist/modules/ia-credits/handlers/purchase-ia-credits.handler.mjs.map +0 -1
  55. package/dist/modules/ia-credits/hooks/purchase-ia-credits.hook.mjs +0 -29
  56. package/dist/modules/ia-credits/hooks/purchase-ia-credits.hook.mjs.map +0 -1
  57. package/dist/modules/ia-credits/hooks/use-offer-eligibility.hook.mjs +0 -18
  58. package/dist/modules/ia-credits/hooks/use-offer-eligibility.hook.mjs.map +0 -1
  59. package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs +0 -34
  60. package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs.map +0 -1
  61. package/dist/modules/ia-credits/utils/offer-stage-cookie.mjs +0 -41
  62. package/dist/modules/ia-credits/utils/offer-stage-cookie.mjs.map +0 -1
  63. package/dist/store/useAiCreditsOfferModal.mjs +0 -14
  64. package/dist/store/useAiCreditsOfferModal.mjs.map +0 -1
  65. package/src/components/layouts/AiCreditsOfferGate.tsx +0 -171
  66. package/src/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.tsx +0 -400
  67. package/src/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.tsx +0 -97
  68. package/src/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.tsx +0 -121
  69. package/src/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.tsx +0 -91
  70. package/src/components/modals/promo/AiCreditsOfferModal/index.ts +0 -1
  71. package/src/modules/ia-credits/constants/ai-credits-offer.constants.ts +0 -33
  72. package/src/modules/ia-credits/handlers/get-offer-eligibility.handler.ts +0 -24
  73. package/src/modules/ia-credits/handlers/purchase-ia-credits.handler.ts +0 -42
  74. package/src/modules/ia-credits/hooks/purchase-ia-credits.hook.ts +0 -43
  75. package/src/modules/ia-credits/hooks/use-offer-eligibility.hook.ts +0 -28
  76. package/src/modules/ia-credits/types/ai-credits-offer.type.ts +0 -87
  77. package/src/modules/ia-credits/utils/offer-stage-cookie.ts +0 -66
  78. package/src/store/useAiCreditsOfferModal.ts +0 -24
@@ -1,91 +0,0 @@
1
- 'use client';
2
-
3
- import { useTranslations } from 'next-intl';
4
- import { CardBrandIcon } from '../../../../ui/data-display/CardBrandIcons';
5
- import { Tooltip, TooltipContent, TooltipTrigger } from '../../../../ui/overlay/Tooltip';
6
- import { useCards } from '../../../../../modules/cards/hooks/cards.hook';
7
- import { useModalManager } from '../../../../../store/useModalManager';
8
- import { IconPencil, IconPlus } from '@tabler/icons-react';
9
-
10
- interface OfferPaymentFieldProps {
11
- /** Cartão em uso (padrão da conta ou o que acabou de ser cadastrado). */
12
- cardId: string;
13
- /** Recebe o id do cartão cadastrado no `addCardModal`. */
14
- onCardAdded: (cardId: string) => void;
15
- }
16
-
17
- /**
18
- * Campo de pagamento da oferta — versão de **um cartão só**.
19
- *
20
- * Nasceu do `PaymentInfoCard` (mesma moldura, mesma linha "Pagamento", mesmo `CardBrandIcon`,
21
- * mesmas chaves de tradução `common.cards.*`), mas sem o seletor e sem o "Nova forma de
22
- * pagamento": aqui a pessoa está numa compra única de primeira vez, então ou ela ainda não tem
23
- * cartão (e o campo é só o botão de cadastrar) ou já tem (e o campo é só o cartão). Oferecer uma
24
- * lista de cartões numa tela cujo objetivo é UM clique era ruído.
25
- *
26
- * Com cartão, o lápis fica sempre visível e abre o `addCardModal` — o mesmo fluxo padrão da
27
- * plataforma, que é por onde se troca a forma de pagamento (não existe edição de cartão: o
28
- * gateway guarda o dado, então "editar" é cadastrar outro). Nada de formulário próprio.
29
- */
30
- export function OfferPaymentField({ cardId, onCardAdded }: OfferPaymentFieldProps) {
31
- const translate = useTranslations();
32
- const { openModal } = useModalManager();
33
- const { data: cardsData } = useCards();
34
-
35
- const card = cardsData?.data?.find((item) => item.id.toString() === cardId.toString());
36
-
37
- const handleOpenCardModal = () => {
38
- openModal('addCardModal', { onCardAdded });
39
- };
40
-
41
- return (
42
- <div className="overflow-hidden rounded-lg border border-zinc-200">
43
- <div className="flex h-[60px] items-center gap-3 px-4">
44
- <span className="paragraph-small-medium w-[100px] text-zinc-500">
45
- {translate('common.cards.paymentInfo.payment')}
46
- </span>
47
-
48
- {card ? (
49
- <>
50
- <div className="flex min-w-0 flex-1 flex-col gap-0.5">
51
- <span className="paragraph-small-semibold truncate text-zinc-950">{card.name}</span>
52
- <span className="paragraph-small-medium text-zinc-950">
53
- •••• {card.number?.replaceAll(/\D/g, '')?.slice(-4)}
54
- </span>
55
- </div>
56
-
57
- <div className="flex shrink-0 items-center gap-2">
58
- <CardBrandIcon brand={card.brand} />
59
- {/* `z-[1002]`: o tooltip nasce num portal e precisa passar por cima do
60
- `DialogContent` (z-1001), senão fica escondido atrás do modal. */}
61
- <Tooltip>
62
- <TooltipTrigger asChild>
63
- <button
64
- type="button"
65
- onClick={handleOpenCardModal}
66
- aria-label={translate('common.promo.aiCreditsOffer.editPayment')}
67
- className="flex size-8 cursor-pointer items-center justify-center rounded-lg text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-950"
68
- >
69
- <IconPencil size={18} />
70
- </button>
71
- </TooltipTrigger>
72
- <TooltipContent side="top" sideOffset={6} className="z-[1002]">
73
- {translate('common.promo.aiCreditsOffer.editPayment')}
74
- </TooltipContent>
75
- </Tooltip>
76
- </div>
77
- </>
78
- ) : (
79
- <button
80
- type="button"
81
- onClick={handleOpenCardModal}
82
- className="paragraph-small-semibold flex h-8 w-fit cursor-pointer items-center rounded-md border border-zinc-200 px-2 text-zinc-950 transition-colors hover:border-zinc-400"
83
- >
84
- <IconPlus className="size-[18px]" />
85
- <span className="px-1.5">{translate('common.cards.paymentInfo.addCard')}</span>
86
- </button>
87
- )}
88
- </div>
89
- </div>
90
- );
91
- }
@@ -1 +0,0 @@
1
- export { default } from './AiCreditsOfferModal';
@@ -1,33 +0,0 @@
1
- /**
2
- * Oferta de primeira compra de créditos de IA — o slug é o contrato inteiro com o backend
3
- * (gapps-r3-api): ele resolve pacote/preço/elegibilidade a partir dele, o front nunca manda preço.
4
- * `credits`/`price` aqui são só o que a UI precisa ANTES do primeiro fetch de elegibilidade (rótulo
5
- * do CTA, contador da ilustração) — o preço de tabela ("de R$ 19,90") é a `anchorCents` que vem do
6
- * endpoint de elegibilidade, nunca hardcoded.
7
- */
8
- export const AI_CREDITS_FIRST_PURCHASE_OFFER = {
9
- slug: 'first-purchase-ia-credits',
10
- credits: 500,
11
- price: 4.9,
12
- } as const;
13
-
14
- /** 1 = cartão, 3 = pix — mesmos códigos do gapps-r3-api. A oferta só aceita cartão. */
15
- export const PAYMENT_METHOD_CARD = 1;
16
- export const PAYMENT_METHOD_PIX = 3;
17
-
18
- /**
19
- * Escada de estágios da oferta, persistida no cookie `ai-credits-offer-stage`. A ordem importa: o
20
- * gate só abre para um estágio MAIOR que o gravado (ver `AiCreditsOfferGate`) — CONVERTED é
21
- * terminal, nunca reabre depois dele.
22
- */
23
- export enum AiCreditsOfferStage {
24
- NONE = 0,
25
- CAMPAIGN_SHOWN = 1,
26
- FIRST_USE_SHOWN = 2,
27
- HALF_SHOWN = 3,
28
- EXHAUSTED_SHOWN = 4,
29
- ZERO_SHOWN = 5,
30
- CONVERTED = 6,
31
- }
32
-
33
- export const AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY = ['ai-credits-offer-eligibility'] as const;
@@ -1,24 +0,0 @@
1
- import 'server-only';
2
-
3
- import { safeRouteHandler } from '../../../infra/route/safe-route-handler';
4
- import { getUserContext } from '../../auth/utils/get-user-context';
5
- import { iaCreditsService } from '../services/ia-credits.service';
6
-
7
- /**
8
- * `GET /api/ai-credits-offer/eligibility` — proxy da elegibilidade do gapps-r3-api mais o
9
- * `sessionIssuedAt` (o `iat` do JWT), que o `AiCreditsOfferGate` usa pra distinguir "mesma sessão de
10
- * login" de "sessão nova" nos gatilhos de campanha/zero-créditos. O `iat` é decodificado aqui (rota,
11
- * dono do cookie httpOnly) e não no service, que só fala com o backend de créditos.
12
- */
13
- export function createGetAiCreditsOfferEligibilityHandler() {
14
- return async function GET() {
15
- return safeRouteHandler(async () => {
16
- const [{ iat }, eligibility] = await Promise.all([
17
- getUserContext(),
18
- iaCreditsService.offerEligibility(),
19
- ]);
20
-
21
- return { ...eligibility, sessionIssuedAt: iat };
22
- });
23
- };
24
- }
@@ -1,42 +0,0 @@
1
- import 'server-only';
2
-
3
- import type { NextRequest } from 'next/server';
4
- import { ApiError } from '../../../infra/api/types';
5
- import { PurchaseIaCreditsRequestBodySchema } from '../types/ai-credits-offer.type';
6
- import { iaCreditsService } from '../services/ia-credits.service';
7
-
8
- /**
9
- * `POST /api/subscriptions/:id/credits/purchase` — compra avulsa de créditos de IA, comum ou
10
- * (com `offer`) a de primeira compra. Desvio deliberado de `safeRouteHandler`: o `BuyOneTimeCreditsModal`
11
- * do editor e o `AiCreditsOfferModal` desta lib esperam o envelope CRU do gapps-r3-api direto na
12
- * raiz da resposta (`{ status: 1 | 2 | 0, client_secret?, message?, data? }`), inclusive pra
13
- * distinguir pago (1), 3DS pendente (2) e recusado (0) — embrulhar em `{ success, data }` quebraria
14
- * os dois.
15
- */
16
- export function createPurchaseIaCreditsHandler() {
17
- return async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
18
- const { id } = await params;
19
- const parsedBody = PurchaseIaCreditsRequestBodySchema.safeParse(await req.json().catch(() => null));
20
-
21
- if (!parsedBody.success) {
22
- return Response.json(
23
- { status: 0, message: 'Erro ao comprar créditos de IA' },
24
- { status: 400 }
25
- );
26
- }
27
-
28
- try {
29
- const result = await iaCreditsService.purchase(id, {
30
- quantity: parsedBody.data.quantity,
31
- paymentMethod: parsedBody.data.payment_method,
32
- idCard: parsedBody.data.id_card,
33
- offer: parsedBody.data.offer,
34
- });
35
- return Response.json(result);
36
- } catch (error) {
37
- const message = error instanceof Error ? error.message : 'Erro ao comprar créditos de IA';
38
- const status = error instanceof ApiError ? error.statusCode : 500;
39
- return Response.json({ status: 0, message }, { status });
40
- }
41
- };
42
- }
@@ -1,43 +0,0 @@
1
- 'use client';
2
-
3
- import { useMutation, useQueryClient } from '@tanstack/react-query';
4
- import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
5
- import { PurchaseIaCreditsResultSchema } from '../types/ai-credits-offer.type';
6
- import type { PurchaseIaCreditsInput, PurchaseIaCreditsResult } from '../types/ai-credits-offer.type';
7
-
8
- /**
9
- * Compra de créditos de IA — avulsa comum OU a oferta de primeira compra (`offer` no input). POSTa
10
- * na rota local `/api/subscriptions/:id/credits/purchase` (`createPurchaseIaCreditsHandler`), que
11
- * repassa CRU pro gapps-r3-api. A resposta:
12
- * - `{ status: 1, data: [{ credited }] }` — cartão pago, créditos concedidos na hora.
13
- * - `{ status: 2, client_secret }` — cartão pediu 3DS (confirmar com `confirmPaymentIfRequired`
14
- * e re-postar o MESMO input: a chave de idempotência do backend devolve a mesma cobrança já paga).
15
- * - `{ status: 0, message }` — recusado ou inelegível.
16
- *
17
- * Um hook só pros dois fluxos (avulsa e oferta): quem recusa PIX na oferta é o backend, o front
18
- * não precisa saber a regra.
19
- */
20
- export function usePurchaseIaCredits() {
21
- const queryClient = useQueryClient();
22
- const iaCreditsKey = useAuthQueryKey(['ia-credits']);
23
-
24
- return useMutation({
25
- mutationFn: async (
26
- input: PurchaseIaCreditsInput & { subscriptionId: number | string }
27
- ): Promise<PurchaseIaCreditsResult> => {
28
- const response = await fetch(`/api/subscriptions/${input.subscriptionId}/credits/purchase`, {
29
- method: 'POST',
30
- headers: { 'Content-Type': 'application/json' },
31
- body: JSON.stringify({
32
- ...(input.offer ? { offer: input.offer } : { quantity: input.quantity }),
33
- payment_method: input.paymentMethod,
34
- ...(input.idCard ? { id_card: input.idCard } : {}),
35
- }),
36
- });
37
- return PurchaseIaCreditsResultSchema.parse(await response.json());
38
- },
39
- onSettled: () => {
40
- queryClient.invalidateQueries({ queryKey: iaCreditsKey });
41
- },
42
- });
43
- }
@@ -1,28 +0,0 @@
1
- 'use client';
2
-
3
- import { useQuery } from '@tanstack/react-query';
4
- import { apiFetch } from '../../../infra/http/api-fetch';
5
- import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
6
- import { AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY } from '../constants/ai-credits-offer.constants';
7
- import type { AiCreditsOfferEligibilityResponse } from '../types/ai-credits-offer.type';
8
-
9
- export interface UseAiCreditsOfferEligibilityOptions {
10
- enabled?: boolean;
11
- }
12
-
13
- /**
14
- * Elegibilidade da oferta de primeira compra de créditos de IA, resolvida SEMPRE no servidor
15
- * (`GET /api/ai-credits-offer/eligibility`) — o `AiCreditsOfferGate` trata erro/timeout como
16
- * inelegível (fail closed): sem confirmação do backend, a oferta não abre.
17
- */
18
- export function useAiCreditsOfferEligibility(options: UseAiCreditsOfferEligibilityOptions = {}) {
19
- const queryKey = useAuthQueryKey([...AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY]);
20
-
21
- return useQuery({
22
- queryKey,
23
- queryFn: ({ signal }) =>
24
- apiFetch<AiCreditsOfferEligibilityResponse>('/api/ai-credits-offer/eligibility', { signal }),
25
- enabled: options.enabled ?? true,
26
- staleTime: 30_000,
27
- });
28
- }
@@ -1,87 +0,0 @@
1
- import z from 'zod';
2
- import { PAYMENT_METHOD_CARD, PAYMENT_METHOD_PIX } from '../constants/ai-credits-offer.constants';
3
-
4
- /**
5
- * `reason` é sempre um código de máquina do backend — nunca renderizar como texto: o modal usa uma
6
- * mensagem de erro genérica já existente no catálogo (ver `AiCreditsOfferModal`).
7
- */
8
- export type AiCreditsOfferIneligibilityReason =
9
- | 'not_default_wl'
10
- | 'gateway_not_supported'
11
- | 'no_active_subscription'
12
- | 'already_charged'
13
- | 'has_paid_plan'
14
- | 'has_card'
15
- | 'offer_consumed'
16
- | 'offer_card_only'
17
- | null;
18
-
19
- export interface AiCreditsOfferEligibility {
20
- eligible: boolean;
21
- reason: AiCreditsOfferIneligibilityReason;
22
- credits: number;
23
- /** Preço promocional em centavos. `null` quando `eligible` é `false` sem cálculo de preço. */
24
- priceCents: number | null;
25
- /** Preço de tabela em centavos, derivado de `getAddonUnitPrice` no backend — nunca hardcoded. */
26
- anchorCents: number | null;
27
- }
28
-
29
- /** Resposta do route handler local — a elegibilidade do backend + o carimbo da sessão de login. */
30
- export interface AiCreditsOfferEligibilityResponse extends AiCreditsOfferEligibility {
31
- /** `iat` do JWT no momento da consulta — usado pelo gate para saber se é uma sessão nova. */
32
- sessionIssuedAt: number;
33
- }
34
-
35
- export interface PurchaseIaCreditsInput {
36
- /** Ignorado pelo backend quando `offer` é enviado (a oferta força a quantidade do pacote). */
37
- quantity?: number;
38
- paymentMethod: number;
39
- idCard?: string;
40
- /** Slug da oferta (`AI_CREDITS_FIRST_PURCHASE_OFFER.slug`). Ausente = compra avulsa comum. */
41
- offer?: string;
42
- }
43
-
44
- /**
45
- * Corpo aceito pela rota local de compra (`createPurchaseIaCreditsHandler`) — validado no handler
46
- * porque o JSON do request não é confiável: `payment_method` fora do union (1 = cartão, 3 = pix,
47
- * únicos métodos que o gapps-r3-api reconhece) nem chega a virar chamada de backend.
48
- */
49
- export const PurchaseIaCreditsRequestBodySchema = z.object({
50
- quantity: z.number().optional(),
51
- payment_method: z.union([z.literal(PAYMENT_METHOD_CARD), z.literal(PAYMENT_METHOD_PIX)]),
52
- id_card: z.string().optional(),
53
- offer: z.string().optional(),
54
- });
55
- export type PurchaseIaCreditsRequestBody = z.infer<typeof PurchaseIaCreditsRequestBodySchema>;
56
-
57
- /** Envelope PIX dentro de `data`, presente só quando a compra foi feita com `payment_method: 3`. */
58
- const PurchaseIaCreditsPixDataSchema = z.object({
59
- correlation_id: z.string().optional(),
60
- qr_code: z.string().optional(),
61
- qr_code_image: z.string().optional(),
62
- payment_link: z.string().optional(),
63
- expires_at: z.string().optional(),
64
- });
65
-
66
- /**
67
- * Envelope CRU do gapps-r3-api — status 1 (pago), 2 (3DS pendente) ou 0 (recusado). Devolvido
68
- * sem reembrulho pelo `createPurchaseIaCreditsHandler` (ver o comentário no handler); o hook valida
69
- * a resposta contra este schema em vez de confiar cegamente no `fetch`.
70
- */
71
- export const PurchaseIaCreditsResultSchema = z.object({
72
- status: z.number(),
73
- message: z.string().optional(),
74
- client_secret: z.string().optional(),
75
- data: z
76
- .array(
77
- z.object({
78
- credited: z.number().optional(),
79
- invoice_id: z.string().optional(),
80
- pending: z.number().optional(),
81
- payment_method: z.number().optional(),
82
- pix: PurchaseIaCreditsPixDataSchema.optional(),
83
- })
84
- )
85
- .optional(),
86
- });
87
- export type PurchaseIaCreditsResult = z.infer<typeof PurchaseIaCreditsResultSchema>;
@@ -1,66 +0,0 @@
1
- import { cookieDomainFromDomain } from '../../../utils/intl/cookie-domain';
2
- import { AiCreditsOfferStage } from '../constants/ai-credits-offer.constants';
3
-
4
- const COOKIE_NAME = 'ai-credits-offer-stage';
5
- const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
6
-
7
- export interface OfferStageRecord {
8
- stage: AiCreditsOfferStage;
9
- /** `iat` do JWT na sessão em que este estágio foi gravado — é o que diferencia "sessão nova". */
10
- lastSessionIat: number;
11
- }
12
-
13
- const EMPTY_RECORD: OfferStageRecord = {
14
- stage: AiCreditsOfferStage.NONE,
15
- lastSessionIat: 0,
16
- };
17
-
18
- /**
19
- * Cookie `ai-credits-offer-stage`, no domínio pai da whitelabel (`cookieDomainFromDomain` — mesmo
20
- * mecanismo do cookie de locale, que por sua vez espelha o cookie de auth `greatapps`): sustenta a
21
- * escada de estágios entre app/accounts/editor, 1 ano de validade.
22
- *
23
- * Valor: `${idAccount}:${stage}:${lastSessionIat}`. Registro de OUTRA conta
24
- * (id diferente do gravado) lê como estágio zerado — last-writer-wins, aceito porque o cookie é por
25
- * navegador e trocar de conta paga no mesmo device durante a janela da oferta é evento raro.
26
- */
27
- export function readOfferStageCookie(idAccount: number): OfferStageRecord {
28
- if (typeof document === 'undefined') return EMPTY_RECORD;
29
-
30
- const match = new RegExp(`(?:^|;\\s*)${COOKIE_NAME}=([^;]+)`).exec(document.cookie);
31
- if (!match) return EMPTY_RECORD;
32
-
33
- const raw = decodeURIComponent(match[1]);
34
- const [rawAccount, rawStage, rawIat] = raw.split(':');
35
-
36
- if (Number(rawAccount) !== idAccount) return EMPTY_RECORD;
37
-
38
- const stage = Number(rawStage);
39
- if (!(stage in AiCreditsOfferStage)) return EMPTY_RECORD;
40
-
41
- return {
42
- stage,
43
- lastSessionIat: Number(rawIat) || 0,
44
- };
45
- }
46
-
47
- export function writeOfferStageCookie(
48
- idAccount: number,
49
- record: OfferStageRecord,
50
- whitelabelDomain?: string | null
51
- ): void {
52
- if (typeof document === 'undefined') return;
53
-
54
- const value = `${idAccount}:${record.stage}:${record.lastSessionIat}`;
55
- const domain = cookieDomainFromDomain(whitelabelDomain);
56
- const parts = [
57
- `${COOKIE_NAME}=${encodeURIComponent(value)}`,
58
- 'path=/',
59
- `max-age=${ONE_YEAR_SECONDS}`,
60
- 'samesite=lax',
61
- domain ? `domain=${domain}` : '',
62
- typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'secure' : '',
63
- ].filter(Boolean);
64
-
65
- document.cookie = parts.join('; ');
66
- }
@@ -1,24 +0,0 @@
1
- import { useCallback } from 'react';
2
- import { useModalManager } from './useModalManager';
3
-
4
- /**
5
- * Oferta de primeira compra de créditos de IA. Quem abre é o `AiCreditsOfferGate` (1s depois do
6
- * gatilho decidir que é a vez dela) — daí não existir payload: a oferta é sempre a mesma.
7
- *
8
- * Os callbacks vão em `useCallback` (as ações do zustand já são estáveis): o gate arma um
9
- * `setTimeout` num efeito que tem `openModal` nas dependências, e uma identidade nova a cada render
10
- * limparia o timer antes de ele disparar — a oferta nunca abriria.
11
- *
12
- * O fechamento usa `closeModalByKey` e não `closeModal`: o modal abre o `addCardModal` por cima (a
13
- * pilha do `useModalManager` empilha), e um `closeModal` disparado com o cartão em cima derrubaria
14
- * o topo da pilha, não a oferta.
15
- */
16
- export const useAiCreditsOfferModal = () => {
17
- const { activeModal, openModal, closeModalByKey } = useModalManager();
18
-
19
- return {
20
- open: activeModal === 'aiCreditsOfferModal',
21
- openModal: useCallback(() => openModal('aiCreditsOfferModal'), [openModal]),
22
- closeModal: useCallback(() => closeModalByKey('aiCreditsOfferModal'), [closeModalByKey]),
23
- };
24
- };