@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
@@ -612,39 +612,6 @@ const messages = {
612
612
  'en-us': 'English (US)',
613
613
  'es-es': 'Spanish',
614
614
  },
615
-
616
- // Modais promocionais compartilhados entre app/accounts/editor.
617
- promo: {
618
- // Componente: AiCreditsOfferModal.
619
- aiCreditsOffer: {
620
- eyebrow: 'First purchase pack',
621
- title: '{credits} AI credits <break></break>at {discount}',
622
- bullets: {
623
- first: 'Build a full page from one prompt.',
624
- second: 'Generate copy and images in the editor.',
625
- third: 'One-time credits: your plan stays as is.',
626
- },
627
- discountBadge: '{percent}% OFF',
628
- cta: 'Get my credits at {discount}',
629
- ctaAddCard: 'Add card and lock in the discount',
630
- later: 'Not now',
631
- editPayment: 'Edit payment details',
632
- close: 'Close',
633
- successToast: 'Done! {credits} AI credits are now in your account.',
634
- errorToast: 'We could not complete the purchase. Please try again.',
635
- cardVerification: 'Your card asked for an extra verification. Try another card.',
636
- illustration: {
637
- credits: 'credits',
638
- },
639
- exit: {
640
- title: 'Sure you want to lose {discount}?',
641
- description: 'This screen will not come back. Leave now and the {credits} credits go back to {price}.',
642
- descriptionNoPrice: 'This screen will not come back. Leave now and the {credits} credits go back to full price.',
643
- keep: 'Back to the offer',
644
- lose: 'Lose my offer',
645
- },
646
- },
647
- },
648
615
  } as const;
649
616
 
650
617
  export default messages;
@@ -613,39 +613,6 @@ const messages = {
613
613
  'en-us': 'Inglés (EE. UU.)',
614
614
  'es-es': 'Español',
615
615
  },
616
-
617
- // Modais promocionais compartilhados entre app/accounts/editor.
618
- promo: {
619
- // Componente: AiCreditsOfferModal.
620
- aiCreditsOffer: {
621
- eyebrow: 'Paquete primera compra',
622
- title: '{credits} créditos de IA <break></break>con {discount}',
623
- bullets: {
624
- first: 'Crea una página entera con un prompt.',
625
- second: 'Genera textos e imágenes en el editor.',
626
- third: 'Créditos sueltos: no tocan tu plan.',
627
- },
628
- discountBadge: '{percent}% OFF',
629
- cta: 'Quiero mis créditos con {discount}',
630
- ctaAddCard: 'Agregar tarjeta y asegurar el descuento',
631
- later: 'Ahora no',
632
- editPayment: 'Editar datos de pago',
633
- close: 'Cerrar',
634
- successToast: '¡Listo! {credits} créditos de IA ya están en tu cuenta.',
635
- errorToast: 'No pudimos completar la compra. Inténtalo de nuevo.',
636
- cardVerification: 'Tu tarjeta pidió una verificación extra. Prueba con otra tarjeta.',
637
- illustration: {
638
- credits: 'créditos',
639
- },
640
- exit: {
641
- title: '¿Seguro que quieres perder {discount}?',
642
- description: 'Esta pantalla no vuelve. Si sales ahora, los {credits} créditos vuelven a costar {price}.',
643
- descriptionNoPrice: 'Esta pantalla no vuelve. Si sales ahora, los {credits} créditos vuelven al precio completo.',
644
- keep: 'Volver a la oferta',
645
- lose: 'Perder mi oferta',
646
- },
647
- },
648
- },
649
616
  } as const;
650
617
 
651
618
  export default messages;
@@ -641,39 +641,6 @@ const messages = {
641
641
  description: 'A página que você está procurando não existe. Verifique se digitou corretamente o link que está procurando.',
642
642
  backToHome: 'Voltar para o início',
643
643
  },
644
-
645
- // Modais promocionais compartilhados entre app/accounts/editor.
646
- promo: {
647
- // Componente: AiCreditsOfferModal (movido verbatim do gpages — pendência de copy: ver PR).
648
- aiCreditsOffer: {
649
- eyebrow: 'Pacote primeira compra',
650
- title: '{credits} créditos de IA <break></break>com {discount}',
651
- bullets: {
652
- first: 'Crie uma página inteira com um prompt.',
653
- second: 'Gere textos e imagens direto no editor.',
654
- third: 'Créditos avulsos: não mexem no seu plano.',
655
- },
656
- discountBadge: '{percent}% OFF',
657
- cta: 'Quero meus créditos com {discount}',
658
- ctaAddCard: 'Cadastrar cartão e garantir o desconto',
659
- later: 'Agora não',
660
- editPayment: 'Editar informações de pagamento',
661
- close: 'Fechar',
662
- successToast: 'Pronto! {credits} créditos de IA entraram na sua conta.',
663
- errorToast: 'Não foi possível concluir a compra. Tente novamente.',
664
- cardVerification: 'Seu cartão pediu uma verificação extra. Tente outro cartão.',
665
- illustration: {
666
- credits: 'créditos',
667
- },
668
- exit: {
669
- title: 'Quer mesmo perder {discount}?',
670
- description: 'Esta tela não volta. Saindo agora, os {credits} créditos voltam a custar {price}.',
671
- descriptionNoPrice: 'Esta tela não volta. Saindo agora, os {credits} créditos voltam ao preço cheio.',
672
- keep: 'Voltar para a oferta',
673
- lose: 'Perder minha oferta',
674
- },
675
- },
676
- },
677
644
  } as const;
678
645
 
679
646
  export default messages;
package/src/index.ts CHANGED
@@ -114,27 +114,6 @@ export type {
114
114
  IaCreditAddBatch,
115
115
  IaCreditOperation,
116
116
  } from "./modules/ia-credits/types";
117
- export { usePurchaseIaCredits } from "./modules/ia-credits/hooks/purchase-ia-credits.hook";
118
- export { useAiCreditsOfferEligibility } from "./modules/ia-credits/hooks/use-offer-eligibility.hook";
119
- export {
120
- AI_CREDITS_FIRST_PURCHASE_OFFER,
121
- AI_CREDITS_OFFER_ELIGIBILITY_QUERY_KEY,
122
- AiCreditsOfferStage,
123
- PAYMENT_METHOD_CARD,
124
- PAYMENT_METHOD_PIX,
125
- } from "./modules/ia-credits/constants/ai-credits-offer.constants";
126
- export type {
127
- AiCreditsOfferEligibility,
128
- AiCreditsOfferEligibilityResponse,
129
- AiCreditsOfferIneligibilityReason,
130
- PurchaseIaCreditsInput,
131
- PurchaseIaCreditsResult,
132
- } from "./modules/ia-credits/types/ai-credits-offer.type";
133
- export {
134
- readOfferStageCookie,
135
- writeOfferStageCookie,
136
- } from "./modules/ia-credits/utils/offer-stage-cookie";
137
- export type { OfferStageRecord } from "./modules/ia-credits/utils/offer-stage-cookie";
138
117
  export type {
139
118
  Subscription,
140
119
  SubscriptionItem,
@@ -245,11 +224,7 @@ export { useBuyCreditsModal } from "./store/useBuyCreditsModal";
245
224
  export { useCreditsDisabledModal } from "./store/useCreditsDisabledModal";
246
225
  export { usePaidPlanRequiredModal } from "./store/usePaidPlanRequiredModal";
247
226
  export { useAccountDeletionWarningModal } from "./store/useAccountDeletionWarningModal";
248
- export { useAiCreditsOfferModal } from "./store/useAiCreditsOfferModal";
249
227
  export { default as BuyCreditsModal } from "./components/modals/BuyCreditsModal";
250
- export { default as AiCreditsOfferModal } from "./components/modals/promo/AiCreditsOfferModal";
251
- export { AiCreditsOfferGate } from "./components/layouts/AiCreditsOfferGate";
252
- export type { AiCreditsOfferGateProps } from "./components/layouts/AiCreditsOfferGate";
253
228
  export { default as CreditsDisabledModal } from "./components/modals/CreditsDisabledModal";
254
229
  export { default as PaidPlanRequiredModal } from "./components/modals/PaidPlanRequiredModal";
255
230
  export { default as AccountFrozenModal } from "./components/modals/AccountFrozenModal";
@@ -11,8 +11,6 @@ export async function getUserContext(): Promise<{
11
11
  id_wl: number;
12
12
  id_account: number;
13
13
  id_user: number;
14
- /** `iat` do JWT — carimbo da sessão de login atual (renovado a cada novo login, não a cada request). */
15
- iat: number;
16
14
  }> {
17
15
  const cookieStore = await cookies();
18
16
  const token = process.env.DUMMY_AUTH_TOKEN || cookieStore.get(AUTH_COOKIE_NAME)?.value;
@@ -28,7 +26,6 @@ export async function getUserContext(): Promise<{
28
26
  id_wl: Number(payload.id_wl),
29
27
  id_account: payload.id_account,
30
28
  id_user: payload.id_user,
31
- iat: payload.iat,
32
29
  };
33
30
  } catch {
34
31
  throw new ApiError('Token inválido', 'INVALID_TOKEN', 401);
@@ -8,11 +8,7 @@ import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
8
8
 
9
9
  export const CARDS_QUERY_KEY = ['cards'] as const;
10
10
 
11
- export interface UseCardsOptions {
12
- enabled?: boolean;
13
- }
14
-
15
- export function useCards(options: UseCardsOptions = {}) {
11
+ export function useCards() {
16
12
  const queryKey = useAuthQueryKey([...CARDS_QUERY_KEY]);
17
13
 
18
14
  return useQuery({
@@ -22,6 +18,5 @@ export function useCards(options: UseCardsOptions = {}) {
22
18
  ...result,
23
19
  data: result.data.filter((card) => !card.deleted),
24
20
  }),
25
- enabled: options.enabled ?? true,
26
21
  });
27
22
  }
@@ -3,28 +3,12 @@ import 'server-only';
3
3
  import { api } from '../../../infra/api/client';
4
4
  import { ApiError, ApiPaginatedActionResult, SuccessResult } from '../../../infra/api/types';
5
5
  import { getUserContext } from '../../auth/utils/get-user-context';
6
- import { subscriptionsService } from '../../subscriptions/services/subscriptions.service';
7
6
  import {
8
7
  IaCreditAddBatch,
9
8
  IaCreditAddBatchSchema,
10
9
  IaCreditOperation,
11
10
  IaCreditsSummaryData,
12
11
  } from '../types';
13
- import { AI_CREDITS_FIRST_PURCHASE_OFFER } from '../constants/ai-credits-offer.constants';
14
- import type {
15
- AiCreditsOfferEligibility,
16
- PurchaseIaCreditsInput,
17
- PurchaseIaCreditsResult,
18
- } from '../types/ai-credits-offer.type';
19
-
20
- /** Shape da API pra elegibilidade — `price_cents`/`anchor_cents` viram camelCase ao sair do service. */
21
- interface AiCreditsOfferEligibilityApiItem {
22
- eligible: boolean;
23
- reason: AiCreditsOfferEligibility['reason'];
24
- credits: number;
25
- price_cents: number | null;
26
- anchor_cents: number | null;
27
- }
28
12
 
29
13
  class IaCreditsService {
30
14
  async getSummaryData(
@@ -84,82 +68,6 @@ class IaCreditsService {
84
68
  const raw = response.data ?? [];
85
69
  return raw.map((item) => IaCreditAddBatchSchema.parse(item));
86
70
  }
87
-
88
- /**
89
- * Elegibilidade da oferta de primeira compra, pra assinatura ATIVA da conta — resolvida aqui (e
90
- * não recebida por parâmetro) porque o consumidor (`createGetAiCreditsOfferEligibilityHandler`)
91
- * não tem id de assinatura: a rota é `/api/ai-credits-offer/eligibility`, sem `:id`.
92
- *
93
- * Sem assinatura ativa a resposta já sai inelegível — nem vale bater no backend, que exige
94
- * `id_subscription` na URL.
95
- */
96
- async offerEligibility(): Promise<AiCreditsOfferEligibility> {
97
- const { id_account } = await getUserContext();
98
- const { data: subscriptions } = await subscriptionsService.listSubscriptions({ limit: 1 });
99
- const subscription = subscriptions[0];
100
-
101
- if (!subscription) {
102
- return {
103
- eligible: false,
104
- reason: 'no_active_subscription',
105
- credits: AI_CREDITS_FIRST_PURCHASE_OFFER.credits,
106
- priceCents: null,
107
- anchorCents: null,
108
- };
109
- }
110
-
111
- const endpoint = `/accounts/${id_account}/subscriptions/${subscription.id}/items/balance/purchase-offer/${AI_CREDITS_FIRST_PURCHASE_OFFER.slug}`;
112
- const response = await api.apps.get<ApiPaginatedActionResult<AiCreditsOfferEligibilityApiItem>>(
113
- endpoint
114
- );
115
-
116
- if (response.status === 0) {
117
- throw new ApiError(
118
- response.message || 'Erro ao consultar elegibilidade da oferta de créditos de IA',
119
- 'GET_OFFER_ELIGIBILITY_FAILED',
120
- 400
121
- );
122
- }
123
-
124
- const raw = response.data[0];
125
- if (!raw) {
126
- throw new ApiError(
127
- 'Resposta vazia ao consultar elegibilidade da oferta de créditos de IA',
128
- 'GET_OFFER_ELIGIBILITY_EMPTY',
129
- 502
130
- );
131
- }
132
-
133
- return {
134
- eligible: raw.eligible,
135
- reason: raw.reason,
136
- credits: raw.credits,
137
- priceCents: raw.price_cents,
138
- anchorCents: raw.anchor_cents,
139
- };
140
- }
141
-
142
- /**
143
- * Compra de créditos de IA — avulsa comum (`offer` ausente) OU a oferta de primeira compra
144
- * (`offer: AI_CREDITS_FIRST_PURCHASE_OFFER.slug`). Devolve o envelope CRU do gapps-r3-api
145
- * (`status` 1/2/0), sem reembrulhar em `SuccessResult`: quem consome é sempre o hook de compra,
146
- * que já sabe interpretar esses três status (ver `usePurchaseIaCredits`).
147
- */
148
- async purchase(
149
- subscriptionId: number | string,
150
- input: PurchaseIaCreditsInput
151
- ): Promise<PurchaseIaCreditsResult> {
152
- const { id_account } = await getUserContext();
153
-
154
- return api.apps.post<PurchaseIaCreditsResult>(
155
- `/accounts/${id_account}/subscriptions/${subscriptionId}/items/balance/purchase`,
156
- {
157
- ...(input.offer ? { offer: input.offer } : { quantity: input.quantity }),
158
- payment_method: input.paymentMethod,
159
- ...(input.idCard ? { id_card: input.idCard } : {}),
160
- }
161
- );
162
- }
163
71
  }
164
72
 
165
73
  export const iaCreditsService = new IaCreditsService();
@@ -32,8 +32,8 @@ class PlansService {
32
32
  const sort = params?.sort ?? "id:ASC";
33
33
  const active = params?.active ?? "";
34
34
  const search = params?.search ?? "";
35
- // v6: descarta as entradas v5 que ficaram vivas durante a janela sem cache
36
- return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v6`;
35
+ // v7: descarta as entradas v6 que não têm o campo pricing (ancoragem de preço)
36
+ return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v7`;
37
37
  }
38
38
 
39
39
  /**
@@ -92,7 +92,7 @@ class PlansService {
92
92
 
93
93
  async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {
94
94
  const { id_wl, id_account } = await getUserContext();
95
- const cacheKey = this.buildCacheKey(`plan-${idPlan}-v2`, {
95
+ const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {
96
96
  id_wl,
97
97
  id_account: String(id_account),
98
98
  });
@@ -83,6 +83,7 @@ export type UiPlan = {
83
83
  originalPrice: string;
84
84
  price: string;
85
85
  pricingByPeriod?: PlanPricingByPeriod;
86
+ pricing?: PlanApiPricing;
86
87
  buttonVariant?: 'brand' | 'default';
87
88
  priceUnavailable?: boolean;
88
89
  items: PlanItem[];
@@ -172,6 +172,7 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn
172
172
  originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
173
173
  price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
174
174
  pricingByPeriod,
175
+ pricing: plan.pricing,
175
176
  priceUnavailable,
176
177
  items: currencyAwareItems,
177
178
  };
@@ -68,6 +68,10 @@ export const CalculateSubscriptionResponseSchema = z.object({
68
68
  month_original: z.number(),
69
69
  month_original_discount: z.number().optional().default(0),
70
70
  month_original_discount_cupom: z.number().optional().default(0),
71
+ // Preço "de" (âncora riscada) e badge de desconto declarado da vitrine. Ausentes
72
+ // quando não há ancoragem (whitelabel != 1, USD, grandfathered) — ver CONTRATO DE CAMPOS.
73
+ month_list: z.number().optional(),
74
+ month_badge_percent: z.number().optional(),
71
75
  semmester: z.number(),
72
76
  semmester_price: z.number().optional().default(0),
73
77
  semmester_discount: z.number(),
@@ -75,12 +79,16 @@ export const CalculateSubscriptionResponseSchema = z.object({
75
79
  semmester_original: z.number(),
76
80
  semmester_original_discount: z.number().optional().default(0),
77
81
  semmester_original_discount_cupom: z.number().optional().default(0),
82
+ semmester_list: z.number().optional(),
83
+ semmester_badge_percent: z.number().optional(),
78
84
  year: z.number(),
79
85
  year_discount: z.number(),
80
86
  year_discount_coupom: z.number().optional().default(0),
81
87
  year_original: z.number(),
82
88
  year_original_discount: z.number().optional().default(0),
83
89
  year_original_discount_cupom: z.number().optional().default(0),
90
+ year_list: z.number().optional(),
91
+ year_badge_percent: z.number().optional(),
84
92
  plan_extras: z.array(CalculateSubscriptionPlanExtraResponseSchema),
85
93
  target: CalculateSubscriptionTargetSchema.optional().nullable(),
86
94
  });
@@ -1,92 +0,0 @@
1
- "use client";
2
- import { useEffect, useRef } from "react";
3
- import { useAuth } from "../../providers/auth.provider";
4
- import { useIsDefaultWhitelabel, useWhitelabel } from "../../providers/whitelabel.provider";
5
- import { useActiveSubscription } from "../../modules/subscriptions/hooks/find-active-subscription.hook";
6
- import { hasActivePaidSubscription } from "../../modules/subscriptions/utils/has-active-paid-subscription";
7
- import { ADDON_IDS } from "../../modules/subscriptions/constants/addons.constants";
8
- import { useCurrencyFormatter } from "../../modules/accounts/hooks/use-currency-formatter.hook";
9
- import { useCards } from "../../modules/cards/hooks/cards.hook";
10
- import { useIaCredits } from "../../modules/ia-credits/hooks/ia-credits.hook";
11
- import { useAiCreditsOfferEligibility } from "../../modules/ia-credits/hooks/use-offer-eligibility.hook";
12
- import {
13
- AI_CREDITS_FIRST_PURCHASE_OFFER,
14
- AiCreditsOfferStage
15
- } from "../../modules/ia-credits/constants/ai-credits-offer.constants";
16
- import {
17
- readOfferStageCookie,
18
- writeOfferStageCookie
19
- } from "../../modules/ia-credits/utils/offer-stage-cookie";
20
- import { useAiCreditsOfferModal } from "../../store/useAiCreditsOfferModal";
21
- import { useModalManager } from "../../store/useModalManager";
22
- const OPEN_DELAY_MS = 1e3;
23
- function resolveCandidateStage(summary) {
24
- if (summary.availableCredits === 0 || summary.totalCredits === 0) {
25
- return AiCreditsOfferStage.ZERO_SHOWN;
26
- }
27
- const usedRatio = summary.usedCredits / summary.totalCredits;
28
- if (usedRatio >= 0.9) return AiCreditsOfferStage.EXHAUSTED_SHOWN;
29
- if (usedRatio >= 0.5) return AiCreditsOfferStage.HALF_SHOWN;
30
- if (summary.usedCredits >= 1) return AiCreditsOfferStage.FIRST_USE_SHOWN;
31
- return AiCreditsOfferStage.CAMPAIGN_SHOWN;
32
- }
33
- function AiCreditsOfferGate({ suppressWhen = false }) {
34
- const { activeModal } = useModalManager();
35
- const { openModal } = useAiCreditsOfferModal();
36
- const { user } = useAuth();
37
- const isDefaultWl = useIsDefaultWhitelabel();
38
- const { whitelabel } = useWhitelabel();
39
- const { data: subscriptionData } = useActiveSubscription();
40
- const { currency } = useCurrencyFormatter();
41
- const { summary, isLoading: isCreditsLoading } = useIaCredits();
42
- const hasArmedRef = useRef(false);
43
- const subscription = subscriptionData?.data?.[0] ?? null;
44
- const accountId = user?.id_account;
45
- const canManageBilling = user?.profile === "owner" || user?.profile === "admin";
46
- const currentAiCredits = subscription?.items?.find((item) => item.id_addon === ADDON_IDS.AI)?.quantity ?? 0;
47
- const isFirstPurchase = !hasActivePaidSubscription(subscription) && !subscription?.charged;
48
- const passesClientCutoff = isDefaultWl && currency === "brl" && canManageBilling && accountId != null && isFirstPurchase && currentAiCredits < AI_CREDITS_FIRST_PURCHASE_OFFER.credits;
49
- const { data: eligibilityData } = useAiCreditsOfferEligibility({ enabled: passesClientCutoff });
50
- const cardsQuery = useCards({ enabled: passesClientCutoff });
51
- useEffect(() => {
52
- if (hasArmedRef.current) return;
53
- if (suppressWhen || activeModal) return;
54
- if (!passesClientCutoff || isCreditsLoading) return;
55
- if (!eligibilityData || !eligibilityData.eligible || accountId == null) return;
56
- if (!cardsQuery.isSuccess || cardsQuery.data.data.length > 0) return;
57
- const candidateStage = resolveCandidateStage(summary);
58
- const cookie = readOfferStageCookie(accountId);
59
- const alreadyShownThisSession = cookie.lastSessionIat === eligibilityData.sessionIssuedAt;
60
- if (alreadyShownThisSession) return;
61
- const isRepeatableStage = candidateStage === AiCreditsOfferStage.CAMPAIGN_SHOWN || candidateStage === AiCreditsOfferStage.ZERO_SHOWN;
62
- const shouldOpen = candidateStage > cookie.stage || isRepeatableStage && candidateStage === cookie.stage;
63
- if (!shouldOpen) return;
64
- hasArmedRef.current = true;
65
- const timer = setTimeout(() => {
66
- writeOfferStageCookie(
67
- accountId,
68
- { stage: candidateStage, lastSessionIat: eligibilityData.sessionIssuedAt },
69
- whitelabel?.domain
70
- );
71
- openModal();
72
- }, OPEN_DELAY_MS);
73
- return () => clearTimeout(timer);
74
- }, [
75
- accountId,
76
- activeModal,
77
- cardsQuery.data,
78
- cardsQuery.isSuccess,
79
- eligibilityData,
80
- isCreditsLoading,
81
- openModal,
82
- passesClientCutoff,
83
- suppressWhen,
84
- summary,
85
- whitelabel?.domain
86
- ]);
87
- return null;
88
- }
89
- export {
90
- AiCreditsOfferGate
91
- };
92
- //# sourceMappingURL=AiCreditsOfferGate.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/components/layouts/AiCreditsOfferGate.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useAuth } from '../../providers/auth.provider';\nimport { useIsDefaultWhitelabel, useWhitelabel } from '../../providers/whitelabel.provider';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { hasActivePaidSubscription } from '../../modules/subscriptions/utils/has-active-paid-subscription';\nimport { ADDON_IDS } from '../../modules/subscriptions/constants/addons.constants';\nimport { useCurrencyFormatter } from '../../modules/accounts/hooks/use-currency-formatter.hook';\nimport { useCards } from '../../modules/cards/hooks/cards.hook';\nimport { useIaCredits } from '../../modules/ia-credits/hooks/ia-credits.hook';\nimport { useAiCreditsOfferEligibility } from '../../modules/ia-credits/hooks/use-offer-eligibility.hook';\nimport {\n AI_CREDITS_FIRST_PURCHASE_OFFER,\n AiCreditsOfferStage,\n} from '../../modules/ia-credits/constants/ai-credits-offer.constants';\nimport {\n readOfferStageCookie,\n writeOfferStageCookie,\n} from '../../modules/ia-credits/utils/offer-stage-cookie';\nimport { useAiCreditsOfferModal } from '../../store/useAiCreditsOfferModal';\nimport { useModalManager } from '../../store/useModalManager';\nimport type { IaCreditsSummary } from '../../modules/ia-credits/types';\n\n/** Espera pedida pelo produto: a oferta entra 1s depois de o gatilho decidir, não junto. */\nconst OPEN_DELAY_MS = 1000;\n\n/**\n * Estágio que os créditos ATUAIS da conta sustentam, na escada do Diogo — sempre o mais alto que os\n * números batem (ordem de checagem: ZERO > EXHAUSTED > HALF > FIRST_USE > CAMPAIGN).\n *\n * `totalCredits === 0` (lote mensal expirado, ainda sem renovar) cai em ZERO, não em divisão por\n * zero — mesma régua do `availableCredits === 0`.\n */\nfunction resolveCandidateStage(\n summary: Pick<IaCreditsSummary, 'totalCredits' | 'usedCredits' | 'availableCredits'>\n): AiCreditsOfferStage {\n if (summary.availableCredits === 0 || summary.totalCredits === 0) {\n return AiCreditsOfferStage.ZERO_SHOWN;\n }\n const usedRatio = summary.usedCredits / summary.totalCredits;\n if (usedRatio >= 0.9) return AiCreditsOfferStage.EXHAUSTED_SHOWN;\n if (usedRatio >= 0.5) return AiCreditsOfferStage.HALF_SHOWN;\n if (summary.usedCredits >= 1) return AiCreditsOfferStage.FIRST_USE_SHOWN;\n return AiCreditsOfferStage.CAMPAIGN_SHOWN;\n}\n\nexport interface AiCreditsOfferGateProps {\n /**\n * Overlay local do host, fora do `useModalManager` compartilhado (ex.: geração de IA em\n * progresso, `InsufficientCreditsModal` do editor) — a oferta não empilha em cima nem espera a\n * vez enquanto `true`; ela volta a tentar no próximo render em que a condição cair, porque o\n * estágio só é gravado quando o modal realmente abre.\n */\n suppressWhen?: boolean;\n}\n\n/**\n * Quem decide se a oferta de primeira compra de créditos de IA aparece, e em qual estágio (gatilhos\n * do Diogo — ver Asana ID-1217333331953496). Mora no root layout de cada app, ao lado dos outros\n * gates de modal.\n *\n * Precisa passar por, nesta ordem:\n *\n * 1. **Corte barato do cliente.** Whitelabel padrão + conta em BRL + `owner`/`admin` + primeira\n * compra de verdade (sem assinatura paga vigente, sem NENHUMA cobrança no histórico) + menos de\n * 500 créditos já contratados. Evita bater no backend pra quem obviamente não se qualifica.\n * 2. **Elegibilidade do servidor**, com FAIL CLOSED: erro, timeout ou ainda carregando conta como\n * inelegível — sem confirmação do backend não existe oferta (ele é quem sabe se a conta já\n * consumiu a oferta em QUALQUER assinatura, cross-cancelamento incluso).\n * 2.1. **Cartão salvo, também FAIL CLOSED.** O card exige as duas condições juntas — plano\n * elegível E ainda sem cartão. O backend valida isso na elegibilidade, mas o gate confere de\n * novo com `useCards`: sem confirmação de que a lista veio vazia, não abre (evita a corrida em\n * que alguém cadastra o cartão pelo CTA e desiste antes de confirmar, e o cache local de\n * elegibilidade ainda não invalidou).\n * 3. **Estágio candidato**, derivado dos créditos atuais (`resolveCandidateStage`).\n * 4. **Escada monotônica do cookie `ai-credits-offer-stage`**: só abre se o candidato é MAIOR que o\n * estágio gravado, OU se o candidato é CAMPAIGN/ZERO — os dois únicos que reapresentam, e só\n * numa sessão de login nova (`sessionIssuedAt` ≠ `lastSessionIat`). Nenhum dos dois tem teto de\n * repetições: o Diogo pediu literalmente \"mostrar sempre que iniciar sessão nova\" para a\n * campanha e \"pode ficar mostrando\" para quem está sem crédito (Asana ID-1217333331953496).\n * 5. **Máx. 1 apresentação por sessão de login.** Sai de graça da regra acima: se o cookie já tem\n * `lastSessionIat` igual ao da sessão atual, NADA abre — nem uma escalada de estágio dentro da\n * mesma sessão (ex.: FIRST_USE → HALF → EXHAUSTED na mesma geração, no Free de 10 créditos).\n */\nexport function AiCreditsOfferGate({ suppressWhen = false }: AiCreditsOfferGateProps) {\n const { activeModal } = useModalManager();\n const { openModal } = useAiCreditsOfferModal();\n const { user } = useAuth();\n const isDefaultWl = useIsDefaultWhitelabel();\n const { whitelabel } = useWhitelabel();\n const { data: subscriptionData } = useActiveSubscription();\n const { currency } = useCurrencyFormatter();\n const { summary, isLoading: isCreditsLoading } = useIaCredits();\n const hasArmedRef = useRef(false);\n\n const subscription = subscriptionData?.data?.[0] ?? null;\n const accountId = user?.id_account;\n const canManageBilling = user?.profile === 'owner' || user?.profile === 'admin';\n const currentAiCredits =\n subscription?.items?.find((item) => item.id_addon === ADDON_IDS.AI)?.quantity ?? 0;\n const isFirstPurchase = !hasActivePaidSubscription(subscription) && !subscription?.charged;\n\n const passesClientCutoff =\n isDefaultWl &&\n currency === 'brl' &&\n canManageBilling &&\n accountId != null &&\n isFirstPurchase &&\n currentAiCredits < AI_CREDITS_FIRST_PURCHASE_OFFER.credits;\n\n // Só consulta o servidor depois do corte barato: quem não passa aqui nunca veria a oferta, e a\n // consulta custa duas chamadas no gapps-r3-api.\n const { data: eligibilityData } = useAiCreditsOfferEligibility({ enabled: passesClientCutoff });\n const cardsQuery = useCards({ enabled: passesClientCutoff });\n\n useEffect(() => {\n if (hasArmedRef.current) return;\n if (suppressWhen || activeModal) return;\n if (!passesClientCutoff || isCreditsLoading) return;\n // Fail closed: sem resposta do servidor (carregando, erro, ou reason preenchido), não abre.\n if (!eligibilityData || !eligibilityData.eligible || accountId == null) return;\n // Fail closed: sem confirmação de que a lista de cartões carregou vazia, não abre — o backend\n // já valida \"sem cartão\" na elegibilidade, isto é a segunda camada do lado do cliente.\n if (!cardsQuery.isSuccess || cardsQuery.data.data.length > 0) return;\n\n const candidateStage = resolveCandidateStage(summary);\n const cookie = readOfferStageCookie(accountId);\n\n const alreadyShownThisSession = cookie.lastSessionIat === eligibilityData.sessionIssuedAt;\n if (alreadyShownThisSession) return;\n\n const isRepeatableStage =\n candidateStage === AiCreditsOfferStage.CAMPAIGN_SHOWN ||\n candidateStage === AiCreditsOfferStage.ZERO_SHOWN;\n\n const shouldOpen =\n candidateStage > cookie.stage || (isRepeatableStage && candidateStage === cookie.stage);\n if (!shouldOpen) return;\n\n // Arma uma única vez por montagem: sem isso, qualquer re-render limparia o timer pendente.\n hasArmedRef.current = true;\n\n const timer = setTimeout(() => {\n // O estágio é gravado junto com a abertura (e não antes): fechar a aba durante o delay não\n // pode queimar a apresentação.\n writeOfferStageCookie(\n accountId,\n { stage: candidateStage, lastSessionIat: eligibilityData.sessionIssuedAt },\n whitelabel?.domain\n );\n openModal();\n }, OPEN_DELAY_MS);\n\n return () => clearTimeout(timer);\n }, [\n accountId,\n activeModal,\n cardsQuery.data,\n cardsQuery.isSuccess,\n eligibilityData,\n isCreditsLoading,\n openModal,\n passesClientCutoff,\n suppressWhen,\n summary,\n whitelabel?.domain,\n ]);\n\n return null;\n}\n"],"mappings":"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","names":[]}