@greatapps/common 1.1.744 → 1.1.746
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/modals/AccountFrozenModal.mjs +69 -0
- package/dist/components/modals/AccountFrozenModal.mjs.map +1 -0
- package/dist/components/navigation/FreezeWarningBanner.mjs +47 -0
- package/dist/components/navigation/FreezeWarningBanner.mjs.map +1 -0
- package/dist/components/navigation/FrozenAccountBanner.mjs +38 -0
- package/dist/components/navigation/FrozenAccountBanner.mjs.map +1 -0
- package/dist/components/navigation/SubscriptionBanner.mjs +20 -0
- package/dist/components/navigation/SubscriptionBanner.mjs.map +1 -1
- package/dist/handlers.mjs +2 -0
- package/dist/handlers.mjs.map +1 -1
- package/dist/i18n/messages/en-us.mjs +31 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +31 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +33 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/index.mjs +59 -37
- package/dist/index.mjs.map +1 -1
- package/dist/modules/auth/utils/assert-account-not-frozen.mjs +26 -0
- package/dist/modules/auth/utils/assert-account-not-frozen.mjs.map +1 -0
- package/dist/modules/projects/services/projects.service.mjs +3 -0
- package/dist/modules/projects/services/projects.service.mjs.map +1 -1
- package/dist/modules/subscriptions/actions/ack-freeze-notice.action.mjs +10 -0
- package/dist/modules/subscriptions/actions/ack-freeze-notice.action.mjs.map +1 -0
- package/dist/modules/subscriptions/constants/query-keys.constants.mjs +2 -0
- package/dist/modules/subscriptions/constants/query-keys.constants.mjs.map +1 -1
- package/dist/modules/subscriptions/constants/subscription.constants.mjs +6 -0
- package/dist/modules/subscriptions/constants/subscription.constants.mjs.map +1 -1
- package/dist/modules/subscriptions/handlers/freeze-notice.handler.mjs +12 -0
- package/dist/modules/subscriptions/handlers/freeze-notice.handler.mjs.map +1 -0
- package/dist/modules/subscriptions/hooks/use-account-freeze.hook.mjs +42 -0
- package/dist/modules/subscriptions/hooks/use-account-freeze.hook.mjs.map +1 -0
- package/dist/modules/subscriptions/hooks/use-freeze-notice.hook.mjs +33 -0
- package/dist/modules/subscriptions/hooks/use-freeze-notice.hook.mjs.map +1 -0
- package/dist/modules/subscriptions/services/subscriptions.service.mjs +29 -0
- package/dist/modules/subscriptions/services/subscriptions.service.mjs.map +1 -1
- package/dist/modules/subscriptions/types/freeze-notice.type.mjs +1 -0
- package/dist/modules/subscriptions/types/freeze-notice.type.mjs.map +1 -0
- package/dist/modules/subscriptions/utils/get-account-freeze-date.mjs +19 -0
- package/dist/modules/subscriptions/utils/get-account-freeze-date.mjs.map +1 -0
- package/dist/modules/subscriptions/utils/get-account-freeze-state.mjs +21 -0
- package/dist/modules/subscriptions/utils/get-account-freeze-state.mjs.map +1 -0
- package/dist/server.mjs +4 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/modals/AccountFrozenModal.tsx +106 -0
- package/src/components/navigation/FreezeWarningBanner.tsx +57 -0
- package/src/components/navigation/FrozenAccountBanner.tsx +49 -0
- package/src/components/navigation/SubscriptionBanner.tsx +30 -0
- package/src/handlers.ts +1 -0
- package/src/i18n/messages/en-us.ts +31 -0
- package/src/i18n/messages/es-es.ts +31 -0
- package/src/i18n/messages/pt-br.ts +34 -0
- package/src/index.ts +14 -0
- package/src/modules/auth/utils/assert-account-not-frozen.ts +38 -0
- package/src/modules/projects/services/projects.service.ts +3 -0
- package/src/modules/subscriptions/actions/ack-freeze-notice.action.ts +8 -0
- package/src/modules/subscriptions/constants/query-keys.constants.ts +2 -0
- package/src/modules/subscriptions/constants/subscription.constants.ts +8 -0
- package/src/modules/subscriptions/handlers/freeze-notice.handler.ts +10 -0
- package/src/modules/subscriptions/hooks/use-account-freeze.hook.ts +44 -0
- package/src/modules/subscriptions/hooks/use-freeze-notice.hook.ts +42 -0
- package/src/modules/subscriptions/services/subscriptions.service.ts +39 -0
- package/src/modules/subscriptions/types/freeze-notice.type.ts +5 -0
- package/src/modules/subscriptions/utils/get-account-freeze-date.ts +40 -0
- package/src/modules/subscriptions/utils/get-account-freeze-state.ts +43 -0
- package/src/server.ts +2 -0
|
@@ -432,6 +432,37 @@ const messages = {
|
|
|
432
432
|
knowPlans: 'View plans',
|
|
433
433
|
},
|
|
434
434
|
},
|
|
435
|
+
subscription: {
|
|
436
|
+
frozen: {
|
|
437
|
+
banner: {
|
|
438
|
+
message: 'Your account resources are blocked due to missing payment.',
|
|
439
|
+
regularize: 'Update subscription',
|
|
440
|
+
},
|
|
441
|
+
warningBanner: {
|
|
442
|
+
message: 'Payment overdue: your account resources will be blocked on {freezeDate}. Pay now to keep your access.',
|
|
443
|
+
details: 'Details',
|
|
444
|
+
},
|
|
445
|
+
modal: {
|
|
446
|
+
title: 'Your account resources are blocked',
|
|
447
|
+
description: 'We could not identify your payment, so these resources are blocked:',
|
|
448
|
+
warningTitle: 'Your resources will be blocked on {freezeDate}',
|
|
449
|
+
warningDescription: 'Your payment is overdue. If we do not identify it by then, these resources will be blocked:',
|
|
450
|
+
keepsWorking: 'Your published pages stay online and your leads keep coming in. Everything is restored automatically once the payment goes through.',
|
|
451
|
+
resources: {
|
|
452
|
+
pages: 'Publishing, creating and editing pages',
|
|
453
|
+
projects: 'Creating and editing projects',
|
|
454
|
+
leadsExport: 'Exporting leads',
|
|
455
|
+
pageSettings: 'Page settings, including lead notifications',
|
|
456
|
+
domains: 'Connecting new domains',
|
|
457
|
+
users: 'Inviting users and changing team permissions',
|
|
458
|
+
abTests: 'Creating and editing A/B tests',
|
|
459
|
+
uploads: 'Uploading images, PDFs and other files',
|
|
460
|
+
integrations: 'Managing integrations',
|
|
461
|
+
},
|
|
462
|
+
cta: 'Update subscription',
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
},
|
|
435
466
|
forms: {
|
|
436
467
|
combobox: {
|
|
437
468
|
placeholder: 'Select an option',
|
|
@@ -433,6 +433,37 @@ const messages = {
|
|
|
433
433
|
knowPlans: 'Conoce nuestros planes',
|
|
434
434
|
},
|
|
435
435
|
},
|
|
436
|
+
subscription: {
|
|
437
|
+
frozen: {
|
|
438
|
+
banner: {
|
|
439
|
+
message: 'Los recursos de tu cuenta están bloqueados por falta de pago.',
|
|
440
|
+
regularize: 'Regularizar suscripción',
|
|
441
|
+
},
|
|
442
|
+
warningBanner: {
|
|
443
|
+
message: 'Pago atrasado: los recursos de tu cuenta se bloquearán el {freezeDate}. Regulariza para no perder el acceso.',
|
|
444
|
+
details: 'Detalles',
|
|
445
|
+
},
|
|
446
|
+
modal: {
|
|
447
|
+
title: 'Los recursos de tu cuenta están bloqueados',
|
|
448
|
+
description: 'No identificamos el pago, así que estos recursos están bloqueados:',
|
|
449
|
+
warningTitle: 'Tus recursos se bloquearán el {freezeDate}',
|
|
450
|
+
warningDescription: 'El pago está atrasado. Si no lo identificamos hasta entonces, estos recursos se bloquearán:',
|
|
451
|
+
keepsWorking: 'Tus páginas publicadas siguen en línea y tus leads se siguen capturando. Todo vuelve automáticamente en cuanto se identifique el pago.',
|
|
452
|
+
resources: {
|
|
453
|
+
pages: 'Publicar, crear y editar páginas',
|
|
454
|
+
projects: 'Crear y editar proyectos',
|
|
455
|
+
leadsExport: 'Exportar leads',
|
|
456
|
+
pageSettings: 'Configuración de la página, incluidas las notificaciones de leads',
|
|
457
|
+
domains: 'Conectar nuevos dominios',
|
|
458
|
+
users: 'Invitar usuarios y cambiar permisos del equipo',
|
|
459
|
+
abTests: 'Crear y editar pruebas A/B',
|
|
460
|
+
uploads: 'Subir imágenes, PDF y otros archivos',
|
|
461
|
+
integrations: 'Gestionar integraciones',
|
|
462
|
+
},
|
|
463
|
+
cta: 'Regularizar suscripción',
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
436
467
|
forms: {
|
|
437
468
|
combobox: {
|
|
438
469
|
placeholder: 'Selecciona una opción',
|
|
@@ -448,6 +448,40 @@ const messages = {
|
|
|
448
448
|
},
|
|
449
449
|
},
|
|
450
450
|
|
|
451
|
+
// Congelamento por falta de pagamento (date_due + 7d) — banner, aviso de
|
|
452
|
+
// véspera e modal única entre as 3 aplicações.
|
|
453
|
+
subscription: {
|
|
454
|
+
frozen: {
|
|
455
|
+
banner: {
|
|
456
|
+
message: 'Os recursos da sua conta estão bloqueados por falta de pagamento.',
|
|
457
|
+
regularize: 'Regularizar assinatura',
|
|
458
|
+
},
|
|
459
|
+
warningBanner: {
|
|
460
|
+
message: 'Pagamento em atraso: os recursos da sua conta serão bloqueados em {freezeDate}. Regularize para não perder o acesso.',
|
|
461
|
+
details: 'Detalhes',
|
|
462
|
+
},
|
|
463
|
+
modal: {
|
|
464
|
+
title: 'Recursos da sua conta bloqueados',
|
|
465
|
+
description: 'O pagamento não foi identificado, então estes recursos estão bloqueados:',
|
|
466
|
+
warningTitle: 'Seus recursos serão bloqueados em {freezeDate}',
|
|
467
|
+
warningDescription: 'O pagamento está em atraso. Se não for identificado até lá, estes recursos serão bloqueados:',
|
|
468
|
+
keepsWorking: 'Suas páginas publicadas continuam no ar e seus leads seguem sendo capturados. Assim que o pagamento for identificado, tudo volta automaticamente.',
|
|
469
|
+
resources: {
|
|
470
|
+
pages: 'Publicar, criar e editar páginas',
|
|
471
|
+
projects: 'Criar e editar projetos',
|
|
472
|
+
leadsExport: 'Exportar leads',
|
|
473
|
+
pageSettings: 'Configurações da página, incluindo notificações de leads',
|
|
474
|
+
domains: 'Conectar novos domínios',
|
|
475
|
+
users: 'Convidar usuários e alterar permissões da equipe',
|
|
476
|
+
abTests: 'Criar e editar testes A/B',
|
|
477
|
+
uploads: 'Enviar imagens, PDFs e outros arquivos',
|
|
478
|
+
integrations: 'Gerenciar integrações',
|
|
479
|
+
},
|
|
480
|
+
cta: 'Regularizar assinatura',
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
|
|
451
485
|
// Nomes dos idiomas exibidos no seletor.
|
|
452
486
|
// Planos: features e main features (mapApiPlanToUiPlan).
|
|
453
487
|
plans: {
|
package/src/index.ts
CHANGED
|
@@ -84,9 +84,12 @@ export { useSubscriptions } from "./modules/subscriptions/hooks/list-subscriptio
|
|
|
84
84
|
export {
|
|
85
85
|
SUBSCRIPTIONS_QUERY_KEY,
|
|
86
86
|
CHARGES_QUERY_KEY,
|
|
87
|
+
FREEZE_NOTICE_QUERY_KEY,
|
|
87
88
|
} from "./modules/subscriptions/constants/query-keys.constants";
|
|
88
89
|
export { useActiveSubscription } from "./modules/subscriptions/hooks/find-active-subscription.hook";
|
|
89
90
|
export { useCancelledSubscriptionGuard } from "./modules/subscriptions/hooks/use-cancelled-subscription-guard";
|
|
91
|
+
export { useAccountFreeze } from "./modules/subscriptions/hooks/use-account-freeze.hook";
|
|
92
|
+
export { useFreezeNotice } from "./modules/subscriptions/hooks/use-freeze-notice.hook";
|
|
90
93
|
export { usePlanById } from "./modules/plans/hooks/use-plan-by-id.hook";
|
|
91
94
|
export { useHasPlanAddon } from "./modules/plans/hooks/use-has-plan-addon.hook";
|
|
92
95
|
export { useCalculateSubscription } from "./modules/subscriptions/hooks/calculate-subscription.hook";
|
|
@@ -199,6 +202,14 @@ export {
|
|
|
199
202
|
export { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "./modules/subscriptions/constants/subscription.constants";
|
|
200
203
|
export { getSubscriptionCancellationState } from "./modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
201
204
|
export type { SubscriptionCancellationState } from "./modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
205
|
+
export {
|
|
206
|
+
ACCOUNT_FREEZE_AFTER_DAYS,
|
|
207
|
+
FREEZE_WARNING_DAYS_BEFORE,
|
|
208
|
+
} from "./modules/subscriptions/constants/subscription.constants";
|
|
209
|
+
export { getAccountFreezeState } from "./modules/subscriptions/utils/get-account-freeze-state";
|
|
210
|
+
export type { AccountFreezeState } from "./modules/subscriptions/utils/get-account-freeze-state";
|
|
211
|
+
export { getAccountFreezeDate } from "./modules/subscriptions/utils/get-account-freeze-date";
|
|
212
|
+
export type { FreezeNotice } from "./modules/subscriptions/types/freeze-notice.type";
|
|
202
213
|
export {
|
|
203
214
|
useCountPages,
|
|
204
215
|
COUNT_PAGES_QUERY_KEY,
|
|
@@ -216,6 +227,7 @@ export { useAccountDeletionWarningModal } from "./store/useAccountDeletionWarnin
|
|
|
216
227
|
export { default as BuyCreditsModal } from "./components/modals/BuyCreditsModal";
|
|
217
228
|
export { default as CreditsDisabledModal } from "./components/modals/CreditsDisabledModal";
|
|
218
229
|
export { default as PaidPlanRequiredModal } from "./components/modals/PaidPlanRequiredModal";
|
|
230
|
+
export { default as AccountFrozenModal } from "./components/modals/AccountFrozenModal";
|
|
219
231
|
export { default as AccountDeletionWarningModal } from "./components/modals/AccountDeletionWarningModal";
|
|
220
232
|
export { default as RequiredBillingDataModal } from "./components/modals/billing/RequiredBillingDataModal";
|
|
221
233
|
export { default as AddCardModal } from "./components/modals/cards/AddCardModal";
|
|
@@ -294,6 +306,8 @@ export { SubscriptionBanner } from "./components/navigation/SubscriptionBanner";
|
|
|
294
306
|
export { OverdueInvoiceBanner } from "./components/navigation/OverdueInvoiceBanner";
|
|
295
307
|
export { UpcomingInvoiceBanner } from "./components/navigation/UpcomingInvoiceBanner";
|
|
296
308
|
export { TrialBanner } from "./components/navigation/TrialBanner";
|
|
309
|
+
export { FrozenAccountBanner } from "./components/navigation/FrozenAccountBanner";
|
|
310
|
+
export { FreezeWarningBanner } from "./components/navigation/FreezeWarningBanner";
|
|
297
311
|
export type { NavItemConfig } from "./components/navigation/subcomponents/NavItems";
|
|
298
312
|
export { useMobileNavbarSheet } from "./store/useMobileNavbarSheet";
|
|
299
313
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
|
|
3
|
+
import { ApiError } from "../../../infra/api/types";
|
|
4
|
+
import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
|
|
5
|
+
import { getAccountFreezeState } from "../../subscriptions/utils/get-account-freeze-state";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Bloqueia mutações de conta congelada por falta de pagamento (date_due + 7d).
|
|
9
|
+
*
|
|
10
|
+
* Fail-OPEN na leitura: falha de rede/payload ao consultar billing não pode
|
|
11
|
+
* travar cliente adimplente. Diferente do assertManagementSubscription
|
|
12
|
+
* (default-deny) porque ali o risco é liberar entitlement não contratado; aqui
|
|
13
|
+
* o gate primário já é este server action, e o caso vencido (expired) continua
|
|
14
|
+
* coberto pelo assertManagementSubscription rodando ao lado.
|
|
15
|
+
*/
|
|
16
|
+
export async function assertAccountNotFrozen(): Promise<void> {
|
|
17
|
+
const result = await listSubscriptionsAction({ active: true });
|
|
18
|
+
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
console.error(
|
|
21
|
+
"[assertAccountNotFrozen] failed to read subscription context:",
|
|
22
|
+
result.error,
|
|
23
|
+
);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const subscription = result.data?.data?.[0];
|
|
28
|
+
|
|
29
|
+
if (!subscription) return;
|
|
30
|
+
|
|
31
|
+
if (getAccountFreezeState(subscription) !== "frozen") return;
|
|
32
|
+
|
|
33
|
+
throw new ApiError(
|
|
34
|
+
"Sua conta está com os recursos bloqueados por falta de pagamento. Regularize a assinatura para liberar.",
|
|
35
|
+
"ACCOUNT_FROZEN",
|
|
36
|
+
403,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -5,6 +5,7 @@ import { ApiError } from '../../../infra/api/types';
|
|
|
5
5
|
import { buildQueryParams } from '../../../infra/utils/params';
|
|
6
6
|
import { getUserContext } from '../../auth/utils/get-user-context';
|
|
7
7
|
import { assertManagementSubscription } from '../../auth/utils/assert-management-subscription';
|
|
8
|
+
import { assertAccountNotFrozen } from '../../auth/utils/assert-account-not-frozen';
|
|
8
9
|
import {
|
|
9
10
|
Project,
|
|
10
11
|
ProjectSchema,
|
|
@@ -80,6 +81,7 @@ class ProjectsService {
|
|
|
80
81
|
|
|
81
82
|
async create(data: CreateProjectRequest): Promise<Project> {
|
|
82
83
|
await assertManagementSubscription();
|
|
84
|
+
await assertAccountNotFrozen();
|
|
83
85
|
const { id_account, id_user } = await getUserContext();
|
|
84
86
|
const url = `/account/${id_account}/user/${id_user}/projects`;
|
|
85
87
|
|
|
@@ -102,6 +104,7 @@ class ProjectsService {
|
|
|
102
104
|
|
|
103
105
|
async update(projectId: number, data: UpdateProjectRequest): Promise<Project> {
|
|
104
106
|
await assertManagementSubscription();
|
|
107
|
+
await assertAccountNotFrozen();
|
|
105
108
|
const { id_account, id_user } = await getUserContext();
|
|
106
109
|
const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;
|
|
107
110
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { safeMutationAction } from '../../../utils/safeMutationAction';
|
|
4
|
+
import { subscriptionsService } from '../services/subscriptions.service';
|
|
5
|
+
|
|
6
|
+
export async function ackFreezeNoticeAction(cycle: string) {
|
|
7
|
+
return safeMutationAction(() => subscriptionsService.ackFreezeNotice(cycle));
|
|
8
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export const SUBSCRIPTION_GRACE_PERIOD_DAYS = 15;
|
|
2
2
|
|
|
3
|
+
export const ACCOUNT_FREEZE_AFTER_DAYS = 7;
|
|
4
|
+
export const FREEZE_WARNING_DAYS_BEFORE = 1;
|
|
5
|
+
|
|
6
|
+
/* Congelamento só vale para a wl 1: o aviso de véspera e o e-mail de bloqueio saem do
|
|
7
|
+
* cron `dailyNotifications` do gapps-r3-api, que só roda para ela. Congelar as demais
|
|
8
|
+
* seria bloquear sem avisar. Espelhado nos gates dos dois backends. */
|
|
9
|
+
export const FREEZE_ELIGIBLE_WHITELABEL_ID = 1;
|
|
10
|
+
|
|
3
11
|
/* Stripe Smart Retries (dunning): até 8 tentativas de cobrança no cartão antes
|
|
4
12
|
* do cancelamento automático. `tentativas restantes = MAX - attempt_count`. */
|
|
5
13
|
export const MAX_PAYMENT_ATTEMPTS = 8;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
|
|
3
|
+
import { safeRouteHandler } from '../../../infra/route/safe-route-handler';
|
|
4
|
+
import { subscriptionsService } from '../services/subscriptions.service';
|
|
5
|
+
|
|
6
|
+
export function createFreezeNoticeHandler() {
|
|
7
|
+
return async function GET() {
|
|
8
|
+
return safeRouteHandler(() => subscriptionsService.getFreezeNotice());
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { UserProfile } from '../../users/schema';
|
|
5
|
+
import { useAuth } from '../../../providers/auth.provider';
|
|
6
|
+
import { useModalManager } from '../../../store/useModalManager';
|
|
7
|
+
import { useActiveSubscription } from './find-active-subscription.hook';
|
|
8
|
+
import { getAccountFreezeState } from '../utils/get-account-freeze-state';
|
|
9
|
+
import { getAccountFreezeDate } from '../utils/get-account-freeze-date';
|
|
10
|
+
|
|
11
|
+
export function useAccountFreeze() {
|
|
12
|
+
const { user } = useAuth();
|
|
13
|
+
const { openModal } = useModalManager();
|
|
14
|
+
const {
|
|
15
|
+
data: { data: [subscription] = [] } = {},
|
|
16
|
+
} = useActiveSubscription();
|
|
17
|
+
|
|
18
|
+
const isViewer = user?.profile === UserProfile.viewer;
|
|
19
|
+
const state = getAccountFreezeState(subscription);
|
|
20
|
+
const isFrozen = state === 'frozen';
|
|
21
|
+
const isWarning = state === 'warning';
|
|
22
|
+
const freezeDate = getAccountFreezeDate(subscription);
|
|
23
|
+
|
|
24
|
+
const guardAction = useCallback(
|
|
25
|
+
(action?: () => void) => {
|
|
26
|
+
if (!isViewer && isFrozen) {
|
|
27
|
+
openModal('accountFrozenModal');
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
action?.();
|
|
32
|
+
return true;
|
|
33
|
+
},
|
|
34
|
+
[openModal, isViewer, isFrozen]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
state,
|
|
39
|
+
isFrozen,
|
|
40
|
+
isWarning,
|
|
41
|
+
freezeDate,
|
|
42
|
+
guardAction,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
5
|
+
import { apiFetch } from '../../../infra/http/api-fetch';
|
|
6
|
+
import { withAction } from '../../../utils/withAction';
|
|
7
|
+
import { ackFreezeNoticeAction } from '../actions/ack-freeze-notice.action';
|
|
8
|
+
import { FREEZE_NOTICE_QUERY_KEY } from '../constants/query-keys.constants';
|
|
9
|
+
import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
|
|
10
|
+
import type { FreezeNotice } from '../types/freeze-notice.type';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Modal única do congelamento (D4): pending quando `frozen && !acknowledged`.
|
|
14
|
+
* ack() marca o episódio (id_subscription + date_due) como visto no gapps-r3-api
|
|
15
|
+
* — reaparece só se um novo ciclo congelar de novo.
|
|
16
|
+
*/
|
|
17
|
+
export function useFreezeNotice() {
|
|
18
|
+
const queryClient = useQueryClient();
|
|
19
|
+
const queryKey = useAuthQueryKey([...FREEZE_NOTICE_QUERY_KEY]);
|
|
20
|
+
|
|
21
|
+
const query = useQuery({
|
|
22
|
+
queryKey,
|
|
23
|
+
queryFn: ({ signal }) => apiFetch<FreezeNotice>('/api/notices/freeze', { signal }),
|
|
24
|
+
staleTime: 10_000,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const mutation = useMutation({
|
|
28
|
+
mutationFn: (cycle: string) => withAction(ackFreezeNoticeAction)(cycle),
|
|
29
|
+
onSuccess: () => {
|
|
30
|
+
queryClient.invalidateQueries({ queryKey });
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const pending = !!query.data?.frozen && !query.data?.acknowledged;
|
|
35
|
+
const cycle = query.data?.cycle ?? null;
|
|
36
|
+
|
|
37
|
+
const ack = useCallback(() => {
|
|
38
|
+
if (cycle) mutation.mutate(cycle);
|
|
39
|
+
}, [cycle, mutation]);
|
|
40
|
+
|
|
41
|
+
return { pending, cycle, ack };
|
|
42
|
+
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
SubscriptionPendingPixResponseSchema,
|
|
27
27
|
type SubscriptionPendingPixResponse,
|
|
28
28
|
} from "../types/pix-pending.type";
|
|
29
|
+
import type { FreezeNotice } from "../types/freeze-notice.type";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Timeout das mutations de assinatura que ESCREVEM (action/plan e action/payment).
|
|
@@ -181,6 +182,44 @@ class SubscriptionsService {
|
|
|
181
182
|
|
|
182
183
|
return { success: true };
|
|
183
184
|
}
|
|
185
|
+
|
|
186
|
+
async getFreezeNotice(): Promise<FreezeNotice> {
|
|
187
|
+
const { id_account } = await getUserContext();
|
|
188
|
+
|
|
189
|
+
const response = await api.apps.get<ApiActionResult<FreezeNotice>>(
|
|
190
|
+
`/accounts/${id_account}/notices/freeze`,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (response.status === 0 || !response.data) {
|
|
194
|
+
throw new ApiError(
|
|
195
|
+
(response.status === 0 ? response.message : "") ||
|
|
196
|
+
"Erro ao consultar aviso de congelamento",
|
|
197
|
+
"GET_FREEZE_NOTICE_FAILED",
|
|
198
|
+
400,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return response.data;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async ackFreezeNotice(cycle: string): Promise<SuccessResult<void>> {
|
|
206
|
+
const { id_account } = await getUserContext();
|
|
207
|
+
|
|
208
|
+
const response = await api.apps.post<ApiActionResult<void>>(
|
|
209
|
+
`/accounts/${id_account}/notices/freeze/ack`,
|
|
210
|
+
{ cycle },
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (response.status === 0) {
|
|
214
|
+
throw new ApiError(
|
|
215
|
+
response.message || "Erro ao confirmar aviso de congelamento",
|
|
216
|
+
"ACK_FREEZE_NOTICE_FAILED",
|
|
217
|
+
400,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { success: true };
|
|
222
|
+
}
|
|
184
223
|
}
|
|
185
224
|
|
|
186
225
|
export const subscriptionsService = new SubscriptionsService();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCOUNT_FREEZE_AFTER_DAYS,
|
|
3
|
+
FREEZE_ELIGIBLE_WHITELABEL_ID,
|
|
4
|
+
} from "../constants/subscription.constants";
|
|
5
|
+
import { toCalendarDate } from "../../../infra/utils/date";
|
|
6
|
+
import type { Subscription } from "../types/subscription.type";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Data em que a conta é congelada por falta de pagamento: date_due + ACCOUNT_FREEZE_AFTER_DAYS.
|
|
10
|
+
*
|
|
11
|
+
* Só congela assinatura `client` da whitelabel 1 — MESMA régua do
|
|
12
|
+
* `isSubscriptionFrozen` (gapps-r3-api) e do `isAccountFrozen` (gpages-r3-api).
|
|
13
|
+
* Se as três divergirem, o usuário é bloqueado por uma camada e liberado por outra.
|
|
14
|
+
*
|
|
15
|
+
* - `type !== "client"` cobre free, trial, partner e whitelabel de uma vez (a coluna
|
|
16
|
+
* aceita mais valores do que a lista que o SubscriptionBanner enumera).
|
|
17
|
+
* - `id_wl !== 1`: o pipeline de aviso (cron `dailyNotifications` do gapps-r3-api) só
|
|
18
|
+
* existe para a wl 1. Congelar conta de whitelabel seria bloquear sem nunca avisar —
|
|
19
|
+
* o defeito do ID-5029 que este card existe para não repetir.
|
|
20
|
+
*/
|
|
21
|
+
export function getAccountFreezeDate(
|
|
22
|
+
subscription:
|
|
23
|
+
| Pick<Subscription, "type" | "date_due" | "id_wl">
|
|
24
|
+
| undefined
|
|
25
|
+
| null,
|
|
26
|
+
): Date | null {
|
|
27
|
+
if (!subscription) return null;
|
|
28
|
+
|
|
29
|
+
if (subscription.type !== "client") return null;
|
|
30
|
+
|
|
31
|
+
if (Number(subscription.id_wl) !== FREEZE_ELIGIBLE_WHITELABEL_ID) return null;
|
|
32
|
+
|
|
33
|
+
const dueDate = toCalendarDate(subscription.date_due);
|
|
34
|
+
|
|
35
|
+
if (!dueDate) return null;
|
|
36
|
+
|
|
37
|
+
const result = new Date(dueDate);
|
|
38
|
+
result.setDate(result.getDate() + ACCOUNT_FREEZE_AFTER_DAYS);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FREEZE_WARNING_DAYS_BEFORE } from "../constants/subscription.constants";
|
|
2
|
+
import { toCalendarDate } from "../../../infra/utils/date";
|
|
3
|
+
import { getAccountFreezeDate } from "./get-account-freeze-date";
|
|
4
|
+
import { getSubscriptionCancellationState } from "./get-subscription-cancellation-state";
|
|
5
|
+
import type { Subscription } from "../types/subscription.type";
|
|
6
|
+
|
|
7
|
+
export type AccountFreezeState = "none" | "warning" | "frozen";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Estado de congelamento por falta de pagamento (date_due + ACCOUNT_FREEZE_AFTER_DAYS).
|
|
11
|
+
*
|
|
12
|
+
* Conta cancelada/pendente de cancelamento é tratada pelos ramos de cancelamento
|
|
13
|
+
* (mesma ordem do SubscriptionBanner) — aqui sempre "none" nesse caso, pra não
|
|
14
|
+
* disputar mensagem com o CancelledSubscriptionBanner/PendingCancellationBanner.
|
|
15
|
+
*/
|
|
16
|
+
export function getAccountFreezeState(
|
|
17
|
+
subscription:
|
|
18
|
+
| Pick<
|
|
19
|
+
Subscription,
|
|
20
|
+
"type" | "active" | "date_due" | "date_cancellation" | "id_wl"
|
|
21
|
+
>
|
|
22
|
+
| undefined
|
|
23
|
+
| null,
|
|
24
|
+
): AccountFreezeState {
|
|
25
|
+
if (!subscription) return "none";
|
|
26
|
+
|
|
27
|
+
if (getSubscriptionCancellationState(subscription) !== "none") return "none";
|
|
28
|
+
|
|
29
|
+
const freezeDate = getAccountFreezeDate(subscription);
|
|
30
|
+
if (!freezeDate) return "none";
|
|
31
|
+
|
|
32
|
+
const today = toCalendarDate(new Date());
|
|
33
|
+
if (!today) return "none";
|
|
34
|
+
|
|
35
|
+
if (today.getTime() >= freezeDate.getTime()) return "frozen";
|
|
36
|
+
|
|
37
|
+
const warningDate = new Date(freezeDate);
|
|
38
|
+
warningDate.setDate(warningDate.getDate() - FREEZE_WARNING_DAYS_BEFORE);
|
|
39
|
+
|
|
40
|
+
if (today.getTime() >= warningDate.getTime()) return "warning";
|
|
41
|
+
|
|
42
|
+
return "none";
|
|
43
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { assertManagementPermission } from './modules/auth/utils/assert-manageme
|
|
|
11
11
|
export { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';
|
|
12
12
|
export { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';
|
|
13
13
|
export { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';
|
|
14
|
+
export { assertAccountNotFrozen } from './modules/auth/utils/assert-account-not-frozen';
|
|
14
15
|
export { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';
|
|
15
16
|
export { plansService } from './modules/plans/services/plans.service';
|
|
16
17
|
export { cardsService } from './modules/cards/services/cards.service';
|
|
@@ -27,6 +28,7 @@ export { findUserById } from './modules/users/action/find-user-by-id.action';
|
|
|
27
28
|
export { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';
|
|
28
29
|
export { getAccountTokenAction } from './modules/accounts/actions/get-account-token.action';
|
|
29
30
|
export { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';
|
|
31
|
+
export { ackFreezeNoticeAction } from './modules/subscriptions/actions/ack-freeze-notice.action';
|
|
30
32
|
export { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';
|
|
31
33
|
export { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';
|
|
32
34
|
export { updateSubscriptionPaymentAction } from './modules/subscriptions/actions/update-subscription-payment.action';
|