@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.
- package/dist/handlers.mjs +1 -5
- package/dist/handlers.mjs.map +1 -1
- package/dist/i18n/messages/en-us.mjs +0 -32
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +0 -32
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +0 -32
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/index.mjs +43 -71
- package/dist/index.mjs.map +1 -1
- package/dist/modules/auth/utils/get-user-context.mjs +1 -2
- package/dist/modules/auth/utils/get-user-context.mjs.map +1 -1
- package/dist/modules/cards/hooks/cards.hook.mjs +2 -3
- package/dist/modules/cards/hooks/cards.hook.mjs.map +1 -1
- package/dist/modules/ia-credits/services/ia-credits.service.mjs +0 -67
- package/dist/modules/ia-credits/services/ia-credits.service.mjs.map +1 -1
- package/dist/modules/plans/services/plans.service.mjs +2 -2
- package/dist/modules/plans/services/plans.service.mjs.map +1 -1
- package/dist/modules/plans/types/plan.type.mjs.map +1 -1
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs +1 -0
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
- package/dist/modules/subscriptions/types/calculate-subscription.type.mjs +8 -0
- package/dist/modules/subscriptions/types/calculate-subscription.type.mjs.map +1 -1
- package/package.json +1 -3
- package/src/handlers.ts +0 -2
- package/src/i18n/messages/en-us.ts +0 -33
- package/src/i18n/messages/es-es.ts +0 -33
- package/src/i18n/messages/pt-br.ts +0 -33
- package/src/index.ts +0 -25
- package/src/modules/auth/utils/get-user-context.ts +0 -3
- package/src/modules/cards/hooks/cards.hook.ts +1 -6
- package/src/modules/ia-credits/services/ia-credits.service.ts +0 -92
- package/src/modules/plans/services/plans.service.ts +3 -3
- package/src/modules/plans/types/plan.type.ts +1 -0
- package/src/modules/plans/utils/map-api-plan-to-ui.ts +1 -0
- package/src/modules/subscriptions/types/calculate-subscription.type.ts +8 -0
- package/dist/components/layouts/AiCreditsOfferGate.mjs +0 -92
- package/dist/components/layouts/AiCreditsOfferGate.mjs.map +0 -1
- package/dist/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.mjs +0 -275
- package/dist/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.mjs.map +0 -1
- package/dist/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.mjs +0 -55
- package/dist/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.mjs.map +0 -1
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.mjs +0 -78
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.mjs.map +0 -1
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.mjs +0 -60
- package/dist/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.mjs.map +0 -1
- package/dist/components/modals/promo/AiCreditsOfferModal/index.mjs +0 -5
- package/dist/components/modals/promo/AiCreditsOfferModal/index.mjs.map +0 -1
- package/dist/modules/ia-credits/constants/ai-credits-offer.constants.mjs +0 -26
- package/dist/modules/ia-credits/constants/ai-credits-offer.constants.mjs.map +0 -1
- package/dist/modules/ia-credits/handlers/get-offer-eligibility.handler.mjs +0 -19
- package/dist/modules/ia-credits/handlers/get-offer-eligibility.handler.mjs.map +0 -1
- package/dist/modules/ia-credits/handlers/purchase-ia-credits.handler.mjs +0 -33
- package/dist/modules/ia-credits/handlers/purchase-ia-credits.handler.mjs.map +0 -1
- package/dist/modules/ia-credits/hooks/purchase-ia-credits.hook.mjs +0 -29
- package/dist/modules/ia-credits/hooks/purchase-ia-credits.hook.mjs.map +0 -1
- package/dist/modules/ia-credits/hooks/use-offer-eligibility.hook.mjs +0 -18
- package/dist/modules/ia-credits/hooks/use-offer-eligibility.hook.mjs.map +0 -1
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs +0 -34
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs.map +0 -1
- package/dist/modules/ia-credits/utils/offer-stage-cookie.mjs +0 -41
- package/dist/modules/ia-credits/utils/offer-stage-cookie.mjs.map +0 -1
- package/dist/store/useAiCreditsOfferModal.mjs +0 -14
- package/dist/store/useAiCreditsOfferModal.mjs.map +0 -1
- package/src/components/layouts/AiCreditsOfferGate.tsx +0 -171
- package/src/components/modals/promo/AiCreditsOfferModal/AiCreditsOfferModal.tsx +0 -400
- package/src/components/modals/promo/AiCreditsOfferModal/components/LoseOfferDialog.tsx +0 -97
- package/src/components/modals/promo/AiCreditsOfferModal/components/OfferIllustration.tsx +0 -121
- package/src/components/modals/promo/AiCreditsOfferModal/components/OfferPaymentField.tsx +0 -91
- package/src/components/modals/promo/AiCreditsOfferModal/index.ts +0 -1
- package/src/modules/ia-credits/constants/ai-credits-offer.constants.ts +0 -33
- package/src/modules/ia-credits/handlers/get-offer-eligibility.handler.ts +0 -24
- package/src/modules/ia-credits/handlers/purchase-ia-credits.handler.ts +0 -42
- package/src/modules/ia-credits/hooks/purchase-ia-credits.hook.ts +0 -43
- package/src/modules/ia-credits/hooks/use-offer-eligibility.hook.ts +0 -28
- package/src/modules/ia-credits/types/ai-credits-offer.type.ts +0 -87
- package/src/modules/ia-credits/utils/offer-stage-cookie.ts +0 -66
- 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
|
|
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
|
-
//
|
|
36
|
-
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-
|
|
35
|
+
// v7: descarta as entradas v6 que não têm o campo pricing (ancoragem de preço)
|
|
36
|
+
return `${key}-${idWl}-${idAccount}-${sort}-${active}-${search}-v7`;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -92,7 +92,7 @@ class PlansService {
|
|
|
92
92
|
|
|
93
93
|
async findById(idPlan: number | string): Promise<SuccessResult<Plan>> {
|
|
94
94
|
const { id_wl, id_account } = await getUserContext();
|
|
95
|
-
const cacheKey = this.buildCacheKey(`plan-${idPlan}-
|
|
95
|
+
const cacheKey = this.buildCacheKey(`plan-${idPlan}-v3`, {
|
|
96
96
|
id_wl,
|
|
97
97
|
id_account: String(id_account),
|
|
98
98
|
});
|
|
@@ -172,6 +172,7 @@ function buildUiPlan(plan: Plan, currency: CurrencyCode, translate?: TranslateFn
|
|
|
172
172
|
originalPrice: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
173
173
|
price: priceUnavailable ? unavailableLabel : formatCurrencyNumber(monthlyFinal, 2, currency),
|
|
174
174
|
pricingByPeriod,
|
|
175
|
+
pricing: plan.pricing,
|
|
175
176
|
priceUnavailable,
|
|
176
177
|
items: currencyAwareItems,
|
|
177
178
|
};
|
|
@@ -68,6 +68,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":[]}
|