@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/subscriptions/constants/subscription.constants.ts"],"sourcesContent":["export const SUBSCRIPTION_GRACE_PERIOD_DAYS = 15;\n\n/* Stripe Smart Retries (dunning): até 8 tentativas de cobrança no cartão antes\n * do cancelamento automático. `tentativas restantes = MAX - attempt_count`. */\nexport const MAX_PAYMENT_ATTEMPTS = 8;\n"],"mappings":"AAAO,MAAM,iCAAiC;
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/constants/subscription.constants.ts"],"sourcesContent":["export const SUBSCRIPTION_GRACE_PERIOD_DAYS = 15;\n\nexport const ACCOUNT_FREEZE_AFTER_DAYS = 7;\nexport const FREEZE_WARNING_DAYS_BEFORE = 1;\n\n/* Congelamento só vale para a wl 1: o aviso de véspera e o e-mail de bloqueio saem do\n * cron `dailyNotifications` do gapps-r3-api, que só roda para ela. Congelar as demais\n * seria bloquear sem avisar. Espelhado nos gates dos dois backends. */\nexport const FREEZE_ELIGIBLE_WHITELABEL_ID = 1;\n\n/* Stripe Smart Retries (dunning): até 8 tentativas de cobrança no cartão antes\n * do cancelamento automático. `tentativas restantes = MAX - attempt_count`. */\nexport const MAX_PAYMENT_ATTEMPTS = 8;\n"],"mappings":"AAAO,MAAM,iCAAiC;AAEvC,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;AAKnC,MAAM,gCAAgC;AAItC,MAAM,uBAAuB;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "server-only";
|
|
2
|
+
import { safeRouteHandler } from "../../../infra/route/safe-route-handler";
|
|
3
|
+
import { subscriptionsService } from "../services/subscriptions.service";
|
|
4
|
+
function createFreezeNoticeHandler() {
|
|
5
|
+
return async function GET() {
|
|
6
|
+
return safeRouteHandler(() => subscriptionsService.getFreezeNotice());
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
createFreezeNoticeHandler
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=freeze-notice.handler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/handlers/freeze-notice.handler.ts"],"sourcesContent":["import 'server-only';\n\nimport { safeRouteHandler } from '../../../infra/route/safe-route-handler';\nimport { subscriptionsService } from '../services/subscriptions.service';\n\nexport function createFreezeNoticeHandler() {\n return async function GET() {\n return safeRouteHandler(() => subscriptionsService.getFreezeNotice());\n };\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,wBAAwB;AACjC,SAAS,4BAA4B;AAE9B,SAAS,4BAA4B;AAC1C,SAAO,eAAe,MAAM;AAC1B,WAAO,iBAAiB,MAAM,qBAAqB,gBAAgB,CAAC;AAAA,EACtE;AACF;","names":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { UserProfile } from "../../users/schema";
|
|
4
|
+
import { useAuth } from "../../../providers/auth.provider";
|
|
5
|
+
import { useModalManager } from "../../../store/useModalManager";
|
|
6
|
+
import { useActiveSubscription } from "./find-active-subscription.hook";
|
|
7
|
+
import { getAccountFreezeState } from "../utils/get-account-freeze-state";
|
|
8
|
+
import { getAccountFreezeDate } from "../utils/get-account-freeze-date";
|
|
9
|
+
function useAccountFreeze() {
|
|
10
|
+
const { user } = useAuth();
|
|
11
|
+
const { openModal } = useModalManager();
|
|
12
|
+
const {
|
|
13
|
+
data: { data: [subscription] = [] } = {}
|
|
14
|
+
} = useActiveSubscription();
|
|
15
|
+
const isViewer = user?.profile === UserProfile.viewer;
|
|
16
|
+
const state = getAccountFreezeState(subscription);
|
|
17
|
+
const isFrozen = state === "frozen";
|
|
18
|
+
const isWarning = state === "warning";
|
|
19
|
+
const freezeDate = getAccountFreezeDate(subscription);
|
|
20
|
+
const guardAction = useCallback(
|
|
21
|
+
(action) => {
|
|
22
|
+
if (!isViewer && isFrozen) {
|
|
23
|
+
openModal("accountFrozenModal");
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
action?.();
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
29
|
+
[openModal, isViewer, isFrozen]
|
|
30
|
+
);
|
|
31
|
+
return {
|
|
32
|
+
state,
|
|
33
|
+
isFrozen,
|
|
34
|
+
isWarning,
|
|
35
|
+
freezeDate,
|
|
36
|
+
guardAction
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
useAccountFreeze
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=use-account-freeze.hook.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/hooks/use-account-freeze.hook.ts"],"sourcesContent":["'use client';\n\nimport { useCallback } from 'react';\nimport { UserProfile } from '../../users/schema';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useModalManager } from '../../../store/useModalManager';\nimport { useActiveSubscription } from './find-active-subscription.hook';\nimport { getAccountFreezeState } from '../utils/get-account-freeze-state';\nimport { getAccountFreezeDate } from '../utils/get-account-freeze-date';\n\nexport function useAccountFreeze() {\n const { user } = useAuth();\n const { openModal } = useModalManager();\n const {\n data: { data: [subscription] = [] } = {},\n } = useActiveSubscription();\n\n const isViewer = user?.profile === UserProfile.viewer;\n const state = getAccountFreezeState(subscription);\n const isFrozen = state === 'frozen';\n const isWarning = state === 'warning';\n const freezeDate = getAccountFreezeDate(subscription);\n\n const guardAction = useCallback(\n (action?: () => void) => {\n if (!isViewer && isFrozen) {\n openModal('accountFrozenModal');\n return false;\n }\n\n action?.();\n return true;\n },\n [openModal, isViewer, isFrozen]\n );\n\n return {\n state,\n isFrozen,\n isWarning,\n freezeDate,\n guardAction,\n };\n}\n"],"mappings":";AAEA,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AACtC,SAAS,4BAA4B;AAE9B,SAAS,mBAAmB;AACjC,QAAM,EAAE,KAAK,IAAI,QAAQ;AACzB,QAAM,EAAE,UAAU,IAAI,gBAAgB;AACtC,QAAM;AAAA,IACJ,MAAM,EAAE,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,EACzC,IAAI,sBAAsB;AAE1B,QAAM,WAAW,MAAM,YAAY,YAAY;AAC/C,QAAM,QAAQ,sBAAsB,YAAY;AAChD,QAAM,WAAW,UAAU;AAC3B,QAAM,YAAY,UAAU;AAC5B,QAAM,aAAa,qBAAqB,YAAY;AAEpD,QAAM,cAAc;AAAA,IAClB,CAAC,WAAwB;AACvB,UAAI,CAAC,YAAY,UAAU;AACzB,kBAAU,oBAAoB;AAC9B,eAAO;AAAA,MACT;AAEA,eAAS;AACT,aAAO;AAAA,IACT;AAAA,IACA,CAAC,WAAW,UAAU,QAAQ;AAAA,EAChC;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { apiFetch } from "../../../infra/http/api-fetch";
|
|
5
|
+
import { withAction } from "../../../utils/withAction";
|
|
6
|
+
import { ackFreezeNoticeAction } from "../actions/ack-freeze-notice.action";
|
|
7
|
+
import { FREEZE_NOTICE_QUERY_KEY } from "../constants/query-keys.constants";
|
|
8
|
+
import { useAuthQueryKey } from "../../../hooks/useAuthQueryKey";
|
|
9
|
+
function useFreezeNotice() {
|
|
10
|
+
const queryClient = useQueryClient();
|
|
11
|
+
const queryKey = useAuthQueryKey([...FREEZE_NOTICE_QUERY_KEY]);
|
|
12
|
+
const query = useQuery({
|
|
13
|
+
queryKey,
|
|
14
|
+
queryFn: ({ signal }) => apiFetch("/api/notices/freeze", { signal }),
|
|
15
|
+
staleTime: 1e4
|
|
16
|
+
});
|
|
17
|
+
const mutation = useMutation({
|
|
18
|
+
mutationFn: (cycle2) => withAction(ackFreezeNoticeAction)(cycle2),
|
|
19
|
+
onSuccess: () => {
|
|
20
|
+
queryClient.invalidateQueries({ queryKey });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const pending = !!query.data?.frozen && !query.data?.acknowledged;
|
|
24
|
+
const cycle = query.data?.cycle ?? null;
|
|
25
|
+
const ack = useCallback(() => {
|
|
26
|
+
if (cycle) mutation.mutate(cycle);
|
|
27
|
+
}, [cycle, mutation]);
|
|
28
|
+
return { pending, cycle, ack };
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
useFreezeNotice
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=use-freeze-notice.hook.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/hooks/use-freeze-notice.hook.ts"],"sourcesContent":["'use client';\n\nimport { useCallback } from 'react';\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';\nimport { apiFetch } from '../../../infra/http/api-fetch';\nimport { withAction } from '../../../utils/withAction';\nimport { ackFreezeNoticeAction } from '../actions/ack-freeze-notice.action';\nimport { FREEZE_NOTICE_QUERY_KEY } from '../constants/query-keys.constants';\nimport { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';\nimport type { FreezeNotice } from '../types/freeze-notice.type';\n\n/**\n * Modal única do congelamento (D4): pending quando `frozen && !acknowledged`.\n * ack() marca o episódio (id_subscription + date_due) como visto no gapps-r3-api\n * — reaparece só se um novo ciclo congelar de novo.\n */\nexport function useFreezeNotice() {\n const queryClient = useQueryClient();\n const queryKey = useAuthQueryKey([...FREEZE_NOTICE_QUERY_KEY]);\n\n const query = useQuery({\n queryKey,\n queryFn: ({ signal }) => apiFetch<FreezeNotice>('/api/notices/freeze', { signal }),\n staleTime: 10_000,\n });\n\n const mutation = useMutation({\n mutationFn: (cycle: string) => withAction(ackFreezeNoticeAction)(cycle),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey });\n },\n });\n\n const pending = !!query.data?.frozen && !query.data?.acknowledged;\n const cycle = query.data?.cycle ?? null;\n\n const ack = useCallback(() => {\n if (cycle) mutation.mutate(cycle);\n }, [cycle, mutation]);\n\n return { pending, cycle, ack };\n}\n"],"mappings":";AAEA,SAAS,mBAAmB;AAC5B,SAAS,UAAU,aAAa,sBAAsB;AACtD,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AAQzB,SAAS,kBAAkB;AAChC,QAAM,cAAc,eAAe;AACnC,QAAM,WAAW,gBAAgB,CAAC,GAAG,uBAAuB,CAAC;AAE7D,QAAM,QAAQ,SAAS;AAAA,IACrB;AAAA,IACA,SAAS,CAAC,EAAE,OAAO,MAAM,SAAuB,uBAAuB,EAAE,OAAO,CAAC;AAAA,IACjF,WAAW;AAAA,EACb,CAAC;AAED,QAAM,WAAW,YAAY;AAAA,IAC3B,YAAY,CAACA,WAAkB,WAAW,qBAAqB,EAAEA,MAAK;AAAA,IACtE,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,SAAS,CAAC;AAAA,IAC5C;AAAA,EACF,CAAC;AAED,QAAM,UAAU,CAAC,CAAC,MAAM,MAAM,UAAU,CAAC,MAAM,MAAM;AACrD,QAAM,QAAQ,MAAM,MAAM,SAAS;AAEnC,QAAM,MAAM,YAAY,MAAM;AAC5B,QAAI,MAAO,UAAS,OAAO,KAAK;AAAA,EAClC,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEpB,SAAO,EAAE,SAAS,OAAO,IAAI;AAC/B;","names":["cycle"]}
|
|
@@ -101,6 +101,35 @@ class SubscriptionsService {
|
|
|
101
101
|
}
|
|
102
102
|
return { success: true };
|
|
103
103
|
}
|
|
104
|
+
async getFreezeNotice() {
|
|
105
|
+
const { id_account } = await getUserContext();
|
|
106
|
+
const response = await api.apps.get(
|
|
107
|
+
`/accounts/${id_account}/notices/freeze`
|
|
108
|
+
);
|
|
109
|
+
if (response.status === 0 || !response.data) {
|
|
110
|
+
throw new ApiError(
|
|
111
|
+
(response.status === 0 ? response.message : "") || "Erro ao consultar aviso de congelamento",
|
|
112
|
+
"GET_FREEZE_NOTICE_FAILED",
|
|
113
|
+
400
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return response.data;
|
|
117
|
+
}
|
|
118
|
+
async ackFreezeNotice(cycle) {
|
|
119
|
+
const { id_account } = await getUserContext();
|
|
120
|
+
const response = await api.apps.post(
|
|
121
|
+
`/accounts/${id_account}/notices/freeze/ack`,
|
|
122
|
+
{ cycle }
|
|
123
|
+
);
|
|
124
|
+
if (response.status === 0) {
|
|
125
|
+
throw new ApiError(
|
|
126
|
+
response.message || "Erro ao confirmar aviso de congelamento",
|
|
127
|
+
"ACK_FREEZE_NOTICE_FAILED",
|
|
128
|
+
400
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return { success: true };
|
|
132
|
+
}
|
|
104
133
|
}
|
|
105
134
|
const subscriptionsService = new SubscriptionsService();
|
|
106
135
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/subscriptions/services/subscriptions.service.ts"],"sourcesContent":["import \"server-only\";\r\n\r\nimport { api } from \"../../../infra/api/client\";\r\nimport {\r\n ApiError,\r\n ApiActionResult,\r\n ApiPaginatedActionResult,\r\n PaginatedSuccessResult,\r\n SuccessResult,\r\n} from \"../../../infra/api/types\";\r\nimport { buildQueryParams } from \"../../../infra/utils/params\";\r\nimport { assertManagementPermission } from \"../../auth/utils/assert-management-permission\";\r\nimport { getUserContext } from \"../../auth/utils/get-user-context\";\r\nimport {\r\n Subscription,\r\n SubscriptionSchema,\r\n FindSubscriptionsParams,\r\n} from \"../types/subscription.type\";\r\nimport {\r\n type CalculateSubscriptionRequest,\r\n type CalculateSubscriptionResponse,\r\n type UpdateSubscriptionPlanRequest,\r\n CalculateSubscriptionResponseSchema,\r\n} from \"../types/calculate-subscription.type\";\r\nimport {\r\n SubscriptionPendingPixResponseSchema,\r\n type SubscriptionPendingPixResponse,\r\n} from \"../types/pix-pending.type\";\r\n\r\n/**\r\n * Timeout das mutations de assinatura que ESCREVEM (action/plan e action/payment).\r\n *\r\n * O default do ApiClient é 30s. Essas duas rotas caem no mesmo motor pesado do\r\n * backend (planEdit delega pra paymentEdit) — troca de método de pagamento, mandato\r\n * PIX recorrente Woovi, void-and-replace de fatura — e passam de 30s com alguma\r\n * frequência: em produção elas aparecem como `outcome=canceled` com wallTime ~29.9s.\r\n *\r\n * O problema não é o usuário ver \"Tempo excedido\": é que o abort MATA o worker no\r\n * meio da saga. Se o corte cai depois de criar o mandato Woovi e suprimir a fatura\r\n * de ciclo, sobra mandato órfão + fatura sem dunning, o usuário não recebe QR nenhum\r\n * e a próxima tentativa cria um segundo mandato. Deixar a request terminar converte\r\n * uma escrita parcial numa escrita completa.\r\n *\r\n * Não é a solução definitiva — a rota deveria ser assíncrona (202 + polling no\r\n * correlation_id). É o teto que dá folga enquanto isso.\r\n *\r\n * Só nas mutations: calculateSubscription é idempotente (não escreve nada), então\r\n * um timeout nela não deixa rastro e não precisa esperar mais.\r\n */\r\nconst SUBSCRIPTION_WRITE_TIMEOUT_MS = 60000;\r\n\r\nexport type UpdateSubscriptionPlanResponse =\r\n | Subscription\r\n | SubscriptionPendingPixResponse;\r\n\r\nclass SubscriptionsService {\r\n async listSubscriptions(\r\n params?: Partial<FindSubscriptionsParams>,\r\n ): Promise<PaginatedSuccessResult<Subscription>> {\r\n const { id_account } = await getUserContext();\r\n\r\n const query = buildQueryParams(\r\n params as Record<string, string | boolean | undefined>,\r\n );\r\n const url = `/accounts/${id_account}/subscriptions${query ? `?${query}` : \"\"}`;\r\n\r\n const response =\r\n await api.apps.get<ApiPaginatedActionResult<Subscription>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao listar assinaturas\",\r\n \"LIST_SUBSCRIPTIONS_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n const data = response.data.map((item) => SubscriptionSchema.parse(item));\r\n\r\n return {\r\n data,\r\n total: response.total,\r\n success: true,\r\n } satisfies PaginatedSuccessResult<Subscription>;\r\n }\r\n\r\n async calculateSubscription(\r\n data: CalculateSubscriptionRequest,\r\n ): Promise<SuccessResult<CalculateSubscriptionResponse>> {\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.post<\r\n ApiActionResult<CalculateSubscriptionResponse>\r\n >(`/accounts/${id_account}/subscriptions/action/calculate`, data);\r\n\r\n console.log(\"Calculate subscription response:\", {\r\n response,\r\n data: JSON.stringify(data, null, 2),\r\n });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao calcular assinatura\",\r\n \"CALCULATE_SUBSCRIPTION_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n return {\r\n success: true,\r\n data: CalculateSubscriptionResponseSchema.parse(response.data),\r\n };\r\n }\r\n\r\n async updateSubscriptionPlan(\r\n subscriptionId: number | string,\r\n data: UpdateSubscriptionPlanRequest,\r\n ): Promise<\r\n SuccessResult<UpdateSubscriptionPlanResponse | null> & { client_secret?: string }\r\n > {\r\n await assertManagementPermission('manage_subscriptions');\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.put<\r\n ApiActionResult<UpdateSubscriptionPlanResponse | UpdateSubscriptionPlanResponse[]> & {\r\n client_secret?: string;\r\n }\r\n >(\r\n `/accounts/${id_account}/subscriptions/${subscriptionId}/action/plan`,\r\n data,\r\n { timeout: SUBSCRIPTION_WRITE_TIMEOUT_MS },\r\n );\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao atualizar plano\",\r\n \"UPDATE_SUBSCRIPTION_PLAN_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n const raw = Array.isArray(response.data) ? response.data[0] : response.data;\r\n\r\n // Preserva resposta pix-pending (id: null, pending: true, pix: {...})\r\n // pra clientes poderem rotear o user pra fluxo dedicado de pix em vez\r\n // de tratar como subscription comum. Caso contrário, parseia como\r\n // Subscription (compatível com o comportamento antigo de devolver { id }).\r\n let parsed: UpdateSubscriptionPlanResponse | null = null;\r\n if (raw) {\r\n const pixResult = SubscriptionPendingPixResponseSchema.safeParse(raw);\r\n parsed = pixResult.success ? pixResult.data : SubscriptionSchema.parse(raw);\r\n }\r\n\r\n return {\r\n success: true,\r\n data: parsed,\r\n ...(response.client_secret ? { client_secret: response.client_secret } : {}),\r\n };\r\n }\r\n\r\n async updateSubscriptionPayment(\r\n subscriptionId: number | string,\r\n data: { id_card: string; payment_method: number },\r\n ): Promise<SuccessResult<void>> {\r\n await assertManagementPermission(\"manage_subscriptions\");\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.put<ApiActionResult<void>>(\r\n `/accounts/${id_account}/subscriptions/${subscriptionId}/action/payment`,\r\n data,\r\n { timeout: SUBSCRIPTION_WRITE_TIMEOUT_MS },\r\n );\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao atualizar método de pagamento\",\r\n \"UPDATE_SUBSCRIPTION_PAYMENT_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n return { success: true };\r\n }\r\n}\r\n\r\nexport const subscriptionsService = new SubscriptionsService();\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/services/subscriptions.service.ts"],"sourcesContent":["import \"server-only\";\r\n\r\nimport { api } from \"../../../infra/api/client\";\r\nimport {\r\n ApiError,\r\n ApiActionResult,\r\n ApiPaginatedActionResult,\r\n PaginatedSuccessResult,\r\n SuccessResult,\r\n} from \"../../../infra/api/types\";\r\nimport { buildQueryParams } from \"../../../infra/utils/params\";\r\nimport { assertManagementPermission } from \"../../auth/utils/assert-management-permission\";\r\nimport { getUserContext } from \"../../auth/utils/get-user-context\";\r\nimport {\r\n Subscription,\r\n SubscriptionSchema,\r\n FindSubscriptionsParams,\r\n} from \"../types/subscription.type\";\r\nimport {\r\n type CalculateSubscriptionRequest,\r\n type CalculateSubscriptionResponse,\r\n type UpdateSubscriptionPlanRequest,\r\n CalculateSubscriptionResponseSchema,\r\n} from \"../types/calculate-subscription.type\";\r\nimport {\r\n SubscriptionPendingPixResponseSchema,\r\n type SubscriptionPendingPixResponse,\r\n} from \"../types/pix-pending.type\";\r\nimport type { FreezeNotice } from \"../types/freeze-notice.type\";\r\n\r\n/**\r\n * Timeout das mutations de assinatura que ESCREVEM (action/plan e action/payment).\r\n *\r\n * O default do ApiClient é 30s. Essas duas rotas caem no mesmo motor pesado do\r\n * backend (planEdit delega pra paymentEdit) — troca de método de pagamento, mandato\r\n * PIX recorrente Woovi, void-and-replace de fatura — e passam de 30s com alguma\r\n * frequência: em produção elas aparecem como `outcome=canceled` com wallTime ~29.9s.\r\n *\r\n * O problema não é o usuário ver \"Tempo excedido\": é que o abort MATA o worker no\r\n * meio da saga. Se o corte cai depois de criar o mandato Woovi e suprimir a fatura\r\n * de ciclo, sobra mandato órfão + fatura sem dunning, o usuário não recebe QR nenhum\r\n * e a próxima tentativa cria um segundo mandato. Deixar a request terminar converte\r\n * uma escrita parcial numa escrita completa.\r\n *\r\n * Não é a solução definitiva — a rota deveria ser assíncrona (202 + polling no\r\n * correlation_id). É o teto que dá folga enquanto isso.\r\n *\r\n * Só nas mutations: calculateSubscription é idempotente (não escreve nada), então\r\n * um timeout nela não deixa rastro e não precisa esperar mais.\r\n */\r\nconst SUBSCRIPTION_WRITE_TIMEOUT_MS = 60000;\r\n\r\nexport type UpdateSubscriptionPlanResponse =\r\n | Subscription\r\n | SubscriptionPendingPixResponse;\r\n\r\nclass SubscriptionsService {\r\n async listSubscriptions(\r\n params?: Partial<FindSubscriptionsParams>,\r\n ): Promise<PaginatedSuccessResult<Subscription>> {\r\n const { id_account } = await getUserContext();\r\n\r\n const query = buildQueryParams(\r\n params as Record<string, string | boolean | undefined>,\r\n );\r\n const url = `/accounts/${id_account}/subscriptions${query ? `?${query}` : \"\"}`;\r\n\r\n const response =\r\n await api.apps.get<ApiPaginatedActionResult<Subscription>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao listar assinaturas\",\r\n \"LIST_SUBSCRIPTIONS_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n const data = response.data.map((item) => SubscriptionSchema.parse(item));\r\n\r\n return {\r\n data,\r\n total: response.total,\r\n success: true,\r\n } satisfies PaginatedSuccessResult<Subscription>;\r\n }\r\n\r\n async calculateSubscription(\r\n data: CalculateSubscriptionRequest,\r\n ): Promise<SuccessResult<CalculateSubscriptionResponse>> {\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.post<\r\n ApiActionResult<CalculateSubscriptionResponse>\r\n >(`/accounts/${id_account}/subscriptions/action/calculate`, data);\r\n\r\n console.log(\"Calculate subscription response:\", {\r\n response,\r\n data: JSON.stringify(data, null, 2),\r\n });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao calcular assinatura\",\r\n \"CALCULATE_SUBSCRIPTION_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n return {\r\n success: true,\r\n data: CalculateSubscriptionResponseSchema.parse(response.data),\r\n };\r\n }\r\n\r\n async updateSubscriptionPlan(\r\n subscriptionId: number | string,\r\n data: UpdateSubscriptionPlanRequest,\r\n ): Promise<\r\n SuccessResult<UpdateSubscriptionPlanResponse | null> & { client_secret?: string }\r\n > {\r\n await assertManagementPermission('manage_subscriptions');\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.put<\r\n ApiActionResult<UpdateSubscriptionPlanResponse | UpdateSubscriptionPlanResponse[]> & {\r\n client_secret?: string;\r\n }\r\n >(\r\n `/accounts/${id_account}/subscriptions/${subscriptionId}/action/plan`,\r\n data,\r\n { timeout: SUBSCRIPTION_WRITE_TIMEOUT_MS },\r\n );\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao atualizar plano\",\r\n \"UPDATE_SUBSCRIPTION_PLAN_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n const raw = Array.isArray(response.data) ? response.data[0] : response.data;\r\n\r\n // Preserva resposta pix-pending (id: null, pending: true, pix: {...})\r\n // pra clientes poderem rotear o user pra fluxo dedicado de pix em vez\r\n // de tratar como subscription comum. Caso contrário, parseia como\r\n // Subscription (compatível com o comportamento antigo de devolver { id }).\r\n let parsed: UpdateSubscriptionPlanResponse | null = null;\r\n if (raw) {\r\n const pixResult = SubscriptionPendingPixResponseSchema.safeParse(raw);\r\n parsed = pixResult.success ? pixResult.data : SubscriptionSchema.parse(raw);\r\n }\r\n\r\n return {\r\n success: true,\r\n data: parsed,\r\n ...(response.client_secret ? { client_secret: response.client_secret } : {}),\r\n };\r\n }\r\n\r\n async updateSubscriptionPayment(\r\n subscriptionId: number | string,\r\n data: { id_card: string; payment_method: number },\r\n ): Promise<SuccessResult<void>> {\r\n await assertManagementPermission(\"manage_subscriptions\");\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.put<ApiActionResult<void>>(\r\n `/accounts/${id_account}/subscriptions/${subscriptionId}/action/payment`,\r\n data,\r\n { timeout: SUBSCRIPTION_WRITE_TIMEOUT_MS },\r\n );\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao atualizar método de pagamento\",\r\n \"UPDATE_SUBSCRIPTION_PAYMENT_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n return { success: true };\r\n }\r\n\r\n async getFreezeNotice(): Promise<FreezeNotice> {\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.get<ApiActionResult<FreezeNotice>>(\r\n `/accounts/${id_account}/notices/freeze`,\r\n );\r\n\r\n if (response.status === 0 || !response.data) {\r\n throw new ApiError(\r\n (response.status === 0 ? response.message : \"\") ||\r\n \"Erro ao consultar aviso de congelamento\",\r\n \"GET_FREEZE_NOTICE_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n return response.data;\r\n }\r\n\r\n async ackFreezeNotice(cycle: string): Promise<SuccessResult<void>> {\r\n const { id_account } = await getUserContext();\r\n\r\n const response = await api.apps.post<ApiActionResult<void>>(\r\n `/accounts/${id_account}/notices/freeze/ack`,\r\n { cycle },\r\n );\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || \"Erro ao confirmar aviso de congelamento\",\r\n \"ACK_FREEZE_NOTICE_FAILED\",\r\n 400,\r\n );\r\n }\r\n\r\n return { success: true };\r\n }\r\n}\r\n\r\nexport const subscriptionsService = new SubscriptionsService();\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=freeze-notice.type.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
function getAccountFreezeDate(subscription) {
|
|
7
|
+
if (!subscription) return null;
|
|
8
|
+
if (subscription.type !== "client") return null;
|
|
9
|
+
if (Number(subscription.id_wl) !== FREEZE_ELIGIBLE_WHITELABEL_ID) return null;
|
|
10
|
+
const dueDate = toCalendarDate(subscription.date_due);
|
|
11
|
+
if (!dueDate) return null;
|
|
12
|
+
const result = new Date(dueDate);
|
|
13
|
+
result.setDate(result.getDate() + ACCOUNT_FREEZE_AFTER_DAYS);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
getAccountFreezeDate
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=get-account-freeze-date.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/utils/get-account-freeze-date.ts"],"sourcesContent":["import {\n ACCOUNT_FREEZE_AFTER_DAYS,\n FREEZE_ELIGIBLE_WHITELABEL_ID,\n} from \"../constants/subscription.constants\";\nimport { toCalendarDate } from \"../../../infra/utils/date\";\nimport type { Subscription } from \"../types/subscription.type\";\n\n/**\n * Data em que a conta é congelada por falta de pagamento: date_due + ACCOUNT_FREEZE_AFTER_DAYS.\n *\n * Só congela assinatura `client` da whitelabel 1 — MESMA régua do\n * `isSubscriptionFrozen` (gapps-r3-api) e do `isAccountFrozen` (gpages-r3-api).\n * Se as três divergirem, o usuário é bloqueado por uma camada e liberado por outra.\n *\n * - `type !== \"client\"` cobre free, trial, partner e whitelabel de uma vez (a coluna\n * aceita mais valores do que a lista que o SubscriptionBanner enumera).\n * - `id_wl !== 1`: o pipeline de aviso (cron `dailyNotifications` do gapps-r3-api) só\n * existe para a wl 1. Congelar conta de whitelabel seria bloquear sem nunca avisar —\n * o defeito do ID-5029 que este card existe para não repetir.\n */\nexport function getAccountFreezeDate(\n subscription:\n | Pick<Subscription, \"type\" | \"date_due\" | \"id_wl\">\n | undefined\n | null,\n): Date | null {\n if (!subscription) return null;\n\n if (subscription.type !== \"client\") return null;\n\n if (Number(subscription.id_wl) !== FREEZE_ELIGIBLE_WHITELABEL_ID) return null;\n\n const dueDate = toCalendarDate(subscription.date_due);\n\n if (!dueDate) return null;\n\n const result = new Date(dueDate);\n result.setDate(result.getDate() + ACCOUNT_FREEZE_AFTER_DAYS);\n return result;\n}\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAgBxB,SAAS,qBACd,cAIa;AACb,MAAI,CAAC,aAAc,QAAO;AAE1B,MAAI,aAAa,SAAS,SAAU,QAAO;AAE3C,MAAI,OAAO,aAAa,KAAK,MAAM,8BAA+B,QAAO;AAEzE,QAAM,UAAU,eAAe,aAAa,QAAQ;AAEpD,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,IAAI,KAAK,OAAO;AAC/B,SAAO,QAAQ,OAAO,QAAQ,IAAI,yBAAyB;AAC3D,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
function getAccountFreezeState(subscription) {
|
|
6
|
+
if (!subscription) return "none";
|
|
7
|
+
if (getSubscriptionCancellationState(subscription) !== "none") return "none";
|
|
8
|
+
const freezeDate = getAccountFreezeDate(subscription);
|
|
9
|
+
if (!freezeDate) return "none";
|
|
10
|
+
const today = toCalendarDate(/* @__PURE__ */ new Date());
|
|
11
|
+
if (!today) return "none";
|
|
12
|
+
if (today.getTime() >= freezeDate.getTime()) return "frozen";
|
|
13
|
+
const warningDate = new Date(freezeDate);
|
|
14
|
+
warningDate.setDate(warningDate.getDate() - FREEZE_WARNING_DAYS_BEFORE);
|
|
15
|
+
if (today.getTime() >= warningDate.getTime()) return "warning";
|
|
16
|
+
return "none";
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
getAccountFreezeState
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=get-account-freeze-state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/utils/get-account-freeze-state.ts"],"sourcesContent":["import { FREEZE_WARNING_DAYS_BEFORE } from \"../constants/subscription.constants\";\nimport { toCalendarDate } from \"../../../infra/utils/date\";\nimport { getAccountFreezeDate } from \"./get-account-freeze-date\";\nimport { getSubscriptionCancellationState } from \"./get-subscription-cancellation-state\";\nimport type { Subscription } from \"../types/subscription.type\";\n\nexport type AccountFreezeState = \"none\" | \"warning\" | \"frozen\";\n\n/**\n * Estado de congelamento por falta de pagamento (date_due + ACCOUNT_FREEZE_AFTER_DAYS).\n *\n * Conta cancelada/pendente de cancelamento é tratada pelos ramos de cancelamento\n * (mesma ordem do SubscriptionBanner) — aqui sempre \"none\" nesse caso, pra não\n * disputar mensagem com o CancelledSubscriptionBanner/PendingCancellationBanner.\n */\nexport function getAccountFreezeState(\n subscription:\n | Pick<\n Subscription,\n \"type\" | \"active\" | \"date_due\" | \"date_cancellation\" | \"id_wl\"\n >\n | undefined\n | null,\n): AccountFreezeState {\n if (!subscription) return \"none\";\n\n if (getSubscriptionCancellationState(subscription) !== \"none\") return \"none\";\n\n const freezeDate = getAccountFreezeDate(subscription);\n if (!freezeDate) return \"none\";\n\n const today = toCalendarDate(new Date());\n if (!today) return \"none\";\n\n if (today.getTime() >= freezeDate.getTime()) return \"frozen\";\n\n const warningDate = new Date(freezeDate);\n warningDate.setDate(warningDate.getDate() - FREEZE_WARNING_DAYS_BEFORE);\n\n if (today.getTime() >= warningDate.getTime()) return \"warning\";\n\n return \"none\";\n}\n"],"mappings":"AAAA,SAAS,kCAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,4BAA4B;AACrC,SAAS,wCAAwC;AAY1C,SAAS,sBACd,cAOoB;AACpB,MAAI,CAAC,aAAc,QAAO;AAE1B,MAAI,iCAAiC,YAAY,MAAM,OAAQ,QAAO;AAEtE,QAAM,aAAa,qBAAqB,YAAY;AACpD,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,QAAQ,eAAe,oBAAI,KAAK,CAAC;AACvC,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,MAAM,QAAQ,KAAK,WAAW,QAAQ,EAAG,QAAO;AAEpD,QAAM,cAAc,IAAI,KAAK,UAAU;AACvC,cAAY,QAAQ,YAAY,QAAQ,IAAI,0BAA0B;AAEtE,MAAI,MAAM,QAAQ,KAAK,YAAY,QAAQ,EAAG,QAAO;AAErD,SAAO;AACT;","names":[]}
|
package/dist/server.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { assertManagementPermission } from "./modules/auth/utils/assert-manageme
|
|
|
7
7
|
import { assertManagementSubscription } from "./modules/auth/utils/assert-management-subscription";
|
|
8
8
|
import { assertSubscriptionAddon } from "./modules/auth/utils/assert-subscription-addon";
|
|
9
9
|
import { assertPaidSubscription } from "./modules/auth/utils/assert-paid-subscription";
|
|
10
|
+
import { assertAccountNotFrozen } from "./modules/auth/utils/assert-account-not-frozen";
|
|
10
11
|
import { subscriptionsService } from "./modules/subscriptions/services/subscriptions.service";
|
|
11
12
|
import { plansService } from "./modules/plans/services/plans.service";
|
|
12
13
|
import { cardsService } from "./modules/cards/services/cards.service";
|
|
@@ -21,6 +22,7 @@ import { findUserById } from "./modules/users/action/find-user-by-id.action";
|
|
|
21
22
|
import { findCurrentAccount } from "./modules/accounts/actions/find-current-account.action";
|
|
22
23
|
import { getAccountTokenAction } from "./modules/accounts/actions/get-account-token.action";
|
|
23
24
|
import { listSubscriptionsAction } from "./modules/subscriptions/actions/list-subscriptions.action";
|
|
25
|
+
import { ackFreezeNoticeAction } from "./modules/subscriptions/actions/ack-freeze-notice.action";
|
|
24
26
|
import { calculateSubscriptionAction } from "./modules/subscriptions/actions/calculate-subscription.action";
|
|
25
27
|
import { updateSubscriptionPlanAction } from "./modules/subscriptions/actions/update-subscription-plan.action";
|
|
26
28
|
import { updateSubscriptionPaymentAction } from "./modules/subscriptions/actions/update-subscription-payment.action";
|
|
@@ -73,9 +75,11 @@ import { processImage } from "./modules/images/services/image-processing.service
|
|
|
73
75
|
export {
|
|
74
76
|
ApiClient,
|
|
75
77
|
accountService,
|
|
78
|
+
ackFreezeNoticeAction,
|
|
76
79
|
addProjectUserAction,
|
|
77
80
|
api,
|
|
78
81
|
apiClient,
|
|
82
|
+
assertAccountNotFrozen,
|
|
79
83
|
assertManagementPermission,
|
|
80
84
|
assertManagementSubscription,
|
|
81
85
|
assertPaidSubscription,
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { revalidateWhitelabelAction } from './modules/whitelabel/actions/revalidate-whitelabel.action';\r\nexport { assertManagementPermission } from './modules/auth/utils/assert-management-permission';\r\nexport { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';\r\nexport { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';\r\nexport { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { plansService } from './modules/plans/services/plans.service';\r\nexport { cardsService } from './modules/cards/services/cards.service';\r\nexport { chargesService } from './modules/charges/services/charges.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { socialGoogleLoginAction } from './modules/auth/actions/social-google-login.action';\r\nexport { socialGoogleOnboardingAction } from './modules/auth/actions/social-google-onboarding.action';\r\nexport { unlinkGoogleAction } from './modules/auth/actions/unlink-google.action';\r\nexport { findUserById } from './modules/users/action/find-user-by-id.action';\r\nexport { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';\r\nexport { getAccountTokenAction } from './modules/accounts/actions/get-account-token.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';\r\nexport { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';\r\nexport { updateSubscriptionPaymentAction } from './modules/subscriptions/actions/update-subscription-payment.action';\r\nexport { createCardAction } from './modules/cards/actions/create-card.action';\r\nexport { createSetupIntentAction } from './modules/cards/actions/create-setup-intent.action';\r\nexport { chargeActionAction } from './modules/charges/actions/charge-action.action';\r\nexport { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';\r\nexport { hasSubscriptionExpired } from './modules/subscriptions/utils/has-subscription-expired';\r\nexport { hasActivePaidSubscription } from './modules/subscriptions/utils/has-active-paid-subscription';\r\n\r\n// Project Services & Actions (server-only)\r\nexport { projectsService } from './modules/projects/services/projects.service';\r\nexport { listProjectsAction } from './modules/projects/actions/list-projects.action';\r\nexport { findProjectAction } from './modules/projects/actions/find-project.action';\r\nexport { createProjectAction } from './modules/projects/actions/create-project.action';\r\nexport { updateProjectAction } from './modules/projects/actions/update-project.action';\r\nexport { deleteProjectAction } from './modules/projects/actions/delete-project.action';\r\n\r\n// Project Users Services & Actions (server-only)\r\nexport { projectUsersService } from './modules/projects/services/project-users.service';\r\nexport { listProjectUsersAction } from './modules/projects/actions/list-project-users.action';\r\nexport { addProjectUserAction } from './modules/projects/actions/add-project-user.action';\r\nexport { removeProjectUserAction } from './modules/projects/actions/remove-project-user.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n updateBillingDataAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\nexport { resolveLocale } from './i18n/resolve-locale';\r\nexport type { ResolveLocaleOptions } from './i18n/resolve-locale';\r\nexport { requireValidSession } from './modules/auth/utils/require-valid-session';\r\nexport { requireTokenNotExpired } from './modules/auth/utils/require-token-not-expired';\r\nexport { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';\r\nexport { safeServerAction } from './utils/safeServerAction';\r\nexport { safeMutationAction } from './utils/safeMutationAction';\r\n\r\n// Image Processing (server-side)\r\nexport { processImageAction } from './modules/images/actions/process-image.action';\r\nexport { processImage } from './modules/images/services/image-processing.service';\r\nexport type {\r\n ProcessImageInput,\r\n ProcessImageOutput,\r\n ProcessImageActionInput,\r\n ProcessImageActionOutput,\r\n} from './modules/images/types/image.type';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAC3C,SAAS,kCAAkC;AAC3C,SAAS,oCAAoC;AAC7C,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,oCAAoC;AAC7C,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,uCAAuC;AAChD,SAAS,wBAAwB;AACjC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,iCAAiC;AAG1C,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AAGxC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAE9B,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AAGnC,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import 'server-only';\r\n\r\n// API Client\r\nexport { ApiClient, api, apiClient } from './infra/api/client';\r\n\r\n// Services\r\nexport { authService } from './modules/auth/services/auth.service';\r\nexport { whitelabelService } from './modules/whitelabel/services/whitelabel.service';\r\nexport { revalidateWhitelabelAction } from './modules/whitelabel/actions/revalidate-whitelabel.action';\r\nexport { assertManagementPermission } from './modules/auth/utils/assert-management-permission';\r\nexport { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';\r\nexport { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';\r\nexport { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';\r\nexport { assertAccountNotFrozen } from './modules/auth/utils/assert-account-not-frozen';\r\nexport { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';\r\nexport { plansService } from './modules/plans/services/plans.service';\r\nexport { cardsService } from './modules/cards/services/cards.service';\r\nexport { chargesService } from './modules/charges/services/charges.service';\r\nexport { iaCreditsService } from './modules/ia-credits/services/ia-credits.service';\r\n\r\n// Actions\r\nexport { findWhitelabel } from './modules/whitelabel/actions/find-whitelabel.action';\r\nexport { validateSessionAction } from './modules/auth/actions/validate-session.action';\r\nexport { socialGoogleLoginAction } from './modules/auth/actions/social-google-login.action';\r\nexport { socialGoogleOnboardingAction } from './modules/auth/actions/social-google-onboarding.action';\r\nexport { unlinkGoogleAction } from './modules/auth/actions/unlink-google.action';\r\nexport { findUserById } from './modules/users/action/find-user-by-id.action';\r\nexport { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';\r\nexport { getAccountTokenAction } from './modules/accounts/actions/get-account-token.action';\r\nexport { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';\r\nexport { ackFreezeNoticeAction } from './modules/subscriptions/actions/ack-freeze-notice.action';\r\nexport { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';\r\nexport { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';\r\nexport { updateSubscriptionPaymentAction } from './modules/subscriptions/actions/update-subscription-payment.action';\r\nexport { createCardAction } from './modules/cards/actions/create-card.action';\r\nexport { createSetupIntentAction } from './modules/cards/actions/create-setup-intent.action';\r\nexport { chargeActionAction } from './modules/charges/actions/charge-action.action';\r\nexport { resolvePlanExtrasPrices } from './modules/subscriptions/utils/resolve-plan-extras-prices';\r\nexport { hasSubscriptionExpired } from './modules/subscriptions/utils/has-subscription-expired';\r\nexport { hasActivePaidSubscription } from './modules/subscriptions/utils/has-active-paid-subscription';\r\n\r\n// Project Services & Actions (server-only)\r\nexport { projectsService } from './modules/projects/services/projects.service';\r\nexport { listProjectsAction } from './modules/projects/actions/list-projects.action';\r\nexport { findProjectAction } from './modules/projects/actions/find-project.action';\r\nexport { createProjectAction } from './modules/projects/actions/create-project.action';\r\nexport { updateProjectAction } from './modules/projects/actions/update-project.action';\r\nexport { deleteProjectAction } from './modules/projects/actions/delete-project.action';\r\n\r\n// Project Users Services & Actions (server-only)\r\nexport { projectUsersService } from './modules/projects/services/project-users.service';\r\nexport { listProjectUsersAction } from './modules/projects/actions/list-project-users.action';\r\nexport { addProjectUserAction } from './modules/projects/actions/add-project-user.action';\r\nexport { removeProjectUserAction } from './modules/projects/actions/remove-project-user.action';\r\n\r\n// Account Services & Actions (server-only)\r\nexport { accountService } from './modules/accounts/services/account.service';\r\nexport { twoFactorService } from './modules/accounts/services/two-factor.service';\r\nexport {\r\n listAccountUsersAction,\r\n updateUserAction,\r\n updateAccountAction,\r\n updateAccountUserByIdAction,\r\n updateBillingDataAction,\r\n deleteAccountUserAction,\r\n deleteAccountAction,\r\n createAccountUserAction,\r\n changePasswordAction,\r\n requestEmailChangeAction,\r\n confirmEmailChangeAction,\r\n requestPhoneChangeAction,\r\n confirmPhoneChangeAction,\r\n generateTwoFactorAction,\r\n confirmTwoFactorAction,\r\n disableTwoFactorAction,\r\n} from './modules/accounts/actions/account-management.action';\r\n\r\n// Server Utils\r\nexport { getClientInfoFromRequest } from './infra/utils/client-info';\r\nexport { getUserContext } from './modules/auth/utils/get-user-context';\r\nexport { resolveLocale } from './i18n/resolve-locale';\r\nexport type { ResolveLocaleOptions } from './i18n/resolve-locale';\r\nexport { requireValidSession } from './modules/auth/utils/require-valid-session';\r\nexport { requireTokenNotExpired } from './modules/auth/utils/require-token-not-expired';\r\nexport { buildWlOverride, buildWlOverrideFromJwt } from './modules/auth/utils/build-wl-override';\r\nexport { safeServerAction } from './utils/safeServerAction';\r\nexport { safeMutationAction } from './utils/safeMutationAction';\r\n\r\n// Image Processing (server-side)\r\nexport { processImageAction } from './modules/images/actions/process-image.action';\r\nexport { processImage } from './modules/images/services/image-processing.service';\r\nexport type {\r\n ProcessImageInput,\r\n ProcessImageOutput,\r\n ProcessImageActionInput,\r\n ProcessImageActionOutput,\r\n} from './modules/images/types/image.type';\r\n"],"mappings":"AAAA,OAAO;AAGP,SAAS,WAAW,KAAK,iBAAiB;AAG1C,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,kCAAkC;AAC3C,SAAS,kCAAkC;AAC3C,SAAS,oCAAoC;AAC7C,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,oCAAoC;AAC7C,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,SAAS,+BAA+B;AACxC,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAC5C,SAAS,oCAAoC;AAC7C,SAAS,uCAAuC;AAChD,SAAS,wBAAwB;AACjC,SAAS,+BAA+B;AACxC,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,iCAAiC;AAG1C,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,+BAA+B;AAGxC,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAE9B,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AAGnC,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;","names":[]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { IconLock, IconAlertTriangle } from '@tabler/icons-react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
import { Button } from '../ui/buttons/Button';
|
|
6
|
+
import { Separator } from '../ui/data-display/Separator';
|
|
7
|
+
import {
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogDescription,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from '../ui/overlay/Dialog';
|
|
14
|
+
import { useWhitelabelUrls, useExternalContracting } from '../../providers/whitelabel.provider';
|
|
15
|
+
import { useModalManager } from '../../store/useModalManager';
|
|
16
|
+
import { formatShortDate } from '../../infra/utils/date';
|
|
17
|
+
|
|
18
|
+
/* Lista do que o congelamento tira, na ordem do card. O texto mora no catálogo. */
|
|
19
|
+
const BLOCKED_RESOURCE_KEYS = [
|
|
20
|
+
'pages',
|
|
21
|
+
'projects',
|
|
22
|
+
'leadsExport',
|
|
23
|
+
'pageSettings',
|
|
24
|
+
'domains',
|
|
25
|
+
'users',
|
|
26
|
+
'abTests',
|
|
27
|
+
'uploads',
|
|
28
|
+
'integrations',
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
/* `freezeDate` no data = ainda VAI congelar (véspera); sem ele = já congelado. As duas
|
|
32
|
+
* variantes listam os mesmos recursos: o ponto do "Detalhes" no banner é o usuário saber o
|
|
33
|
+
* que perde ANTES de perder. */
|
|
34
|
+
interface FrozenModalData {
|
|
35
|
+
freezeDate?: Date | string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function AccountFrozenModal() {
|
|
39
|
+
const translate = useTranslations();
|
|
40
|
+
const { activeModal, closeModal, modalData } = useModalManager();
|
|
41
|
+
const isOpen = activeModal === 'accountFrozenModal';
|
|
42
|
+
const { accountsUrl } = useWhitelabelUrls();
|
|
43
|
+
const { redirectToExternal } = useExternalContracting();
|
|
44
|
+
|
|
45
|
+
const freezeDate = (modalData as FrozenModalData | undefined)?.freezeDate ?? null;
|
|
46
|
+
const isWarning = Boolean(freezeDate);
|
|
47
|
+
|
|
48
|
+
const title = isWarning
|
|
49
|
+
? translate('common.subscription.frozen.modal.warningTitle', {
|
|
50
|
+
freezeDate: formatShortDate(freezeDate ? new Date(freezeDate) : null),
|
|
51
|
+
})
|
|
52
|
+
: translate('common.subscription.frozen.modal.title');
|
|
53
|
+
|
|
54
|
+
const description = isWarning
|
|
55
|
+
? translate('common.subscription.frozen.modal.warningDescription')
|
|
56
|
+
: translate('common.subscription.frozen.modal.description');
|
|
57
|
+
|
|
58
|
+
const handleRegularize = () => {
|
|
59
|
+
if (redirectToExternal('plans')) return;
|
|
60
|
+
window.location.href = `${accountsUrl}/subscriptions`;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Dialog open={isOpen} onOpenChange={() => closeModal()}>
|
|
65
|
+
<DialogContent className="flex flex-col p-0 gap-0 w-full md:w-[410px] lg:w-[410px]">
|
|
66
|
+
<DialogHeader className="gap-3 text-left p-5">
|
|
67
|
+
<div
|
|
68
|
+
className={`flex items-center justify-center w-10 h-10 rounded-lg ${isWarning ? 'bg-orange-50' : 'bg-zinc-50'}`}
|
|
69
|
+
>
|
|
70
|
+
{isWarning ? (
|
|
71
|
+
<IconAlertTriangle size={20} className="text-orange-500" />
|
|
72
|
+
) : (
|
|
73
|
+
<IconLock size={24} className="text-zinc-950" />
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
<div className="flex flex-col gap-2">
|
|
77
|
+
<DialogTitle className="paragraph-medium-semibold text-zinc-950">
|
|
78
|
+
{title}
|
|
79
|
+
</DialogTitle>
|
|
80
|
+
<DialogDescription className="paragraph-small-regular text-zinc-600" asChild>
|
|
81
|
+
<div className="flex flex-col gap-3">
|
|
82
|
+
<p>{description}</p>
|
|
83
|
+
<ul className="flex flex-col gap-1.5 list-disc pl-4">
|
|
84
|
+
{BLOCKED_RESOURCE_KEYS.map((key) => (
|
|
85
|
+
<li key={key}>
|
|
86
|
+
{translate(`common.subscription.frozen.modal.resources.${key}`)}
|
|
87
|
+
</li>
|
|
88
|
+
))}
|
|
89
|
+
</ul>
|
|
90
|
+
<p>{translate('common.subscription.frozen.modal.keepsWorking')}</p>
|
|
91
|
+
</div>
|
|
92
|
+
</DialogDescription>
|
|
93
|
+
</div>
|
|
94
|
+
</DialogHeader>
|
|
95
|
+
|
|
96
|
+
<Separator />
|
|
97
|
+
|
|
98
|
+
<div className="flex items-center gap-2 p-5">
|
|
99
|
+
<Button className="w-fit h-10!" onClick={handleRegularize}>
|
|
100
|
+
{translate('common.subscription.frozen.modal.cta')}
|
|
101
|
+
</Button>
|
|
102
|
+
</div>
|
|
103
|
+
</DialogContent>
|
|
104
|
+
</Dialog>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { IconAlertTriangle, IconX } from '@tabler/icons-react';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
6
|
+
import { formatShortDate } from '../../infra/utils/date';
|
|
7
|
+
|
|
8
|
+
interface FreezeWarningBannerProps {
|
|
9
|
+
freezeDate: Date | null;
|
|
10
|
+
onDetails?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function FreezeWarningBanner({ freezeDate, onDetails }: FreezeWarningBannerProps) {
|
|
14
|
+
const translate = useTranslations();
|
|
15
|
+
const [visible, setVisible] = useState(true);
|
|
16
|
+
const [animated, setAnimated] = useState(false);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const timer = setTimeout(() => setAnimated(true), 50);
|
|
20
|
+
return () => clearTimeout(timer);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
const dismiss = useCallback(() => setVisible(false), []);
|
|
24
|
+
|
|
25
|
+
if (!visible) return null;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="fixed top-[80px] md:top-0 left-0 right-0 z-[60] flex justify-center pointer-events-none px-4 lg:px-0">
|
|
29
|
+
<div
|
|
30
|
+
className={`flex flex-col sm:flex-row items-start sm:items-center justify-between bg-orange-500 rounded-lg px-3 py-2 sm:py-1 w-full lg:w-[805px] max-w-full pointer-events-auto transition-transform duration-500 ease-out gap-2 sm:gap-0 ${animated ? 'translate-y-0 md:translate-y-9' : '-translate-y-full'}`}
|
|
31
|
+
>
|
|
32
|
+
<div className="flex items-center gap-2 flex-1 w-full sm:w-auto">
|
|
33
|
+
<IconAlertTriangle size={16} className="text-white shrink-0" />
|
|
34
|
+
<span className="paragraph-xsmall-semibold text-white">
|
|
35
|
+
{translate('common.subscription.frozen.warningBanner.message', {
|
|
36
|
+
freezeDate: formatShortDate(freezeDate),
|
|
37
|
+
})}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="flex items-center gap-1.5 w-full sm:w-auto justify-between sm:justify-start">
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
className="flex items-center justify-center p-1.5 cursor-pointer"
|
|
44
|
+
onClick={onDetails}
|
|
45
|
+
>
|
|
46
|
+
<span className="paragraph-xsmall-semibold text-white underline">
|
|
47
|
+
{translate('common.subscription.frozen.warningBanner.details')}
|
|
48
|
+
</span>
|
|
49
|
+
</button>
|
|
50
|
+
<button type="button" className="cursor-pointer shrink-0" onClick={dismiss}>
|
|
51
|
+
<IconX size={18} className="text-orange-200" />
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { IconAlertTriangle } from '@tabler/icons-react';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
6
|
+
|
|
7
|
+
interface FrozenAccountBannerProps {
|
|
8
|
+
onDetails?: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Banner permanente (D1): congelamento bloqueia publish/update/sync — sem
|
|
13
|
+
* botão de dismiss, ao contrário do OverdueInvoiceBanner.
|
|
14
|
+
*/
|
|
15
|
+
export function FrozenAccountBanner({ onDetails }: FrozenAccountBannerProps) {
|
|
16
|
+
const translate = useTranslations();
|
|
17
|
+
const [animated, setAnimated] = useState(false);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const timer = setTimeout(() => setAnimated(true), 50);
|
|
21
|
+
return () => clearTimeout(timer);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="fixed top-[80px] md:top-0 left-0 right-0 z-[60] flex justify-center pointer-events-none px-4 lg:px-0">
|
|
26
|
+
<div
|
|
27
|
+
className={`flex flex-col sm:flex-row items-start sm:items-center justify-between bg-red-500 rounded-lg px-3 py-2 sm:py-1 w-full lg:w-[805px] max-w-full pointer-events-auto transition-transform duration-500 ease-out gap-2 sm:gap-0 ${animated ? 'translate-y-0 md:translate-y-9' : '-translate-y-full'}`}
|
|
28
|
+
>
|
|
29
|
+
<div className="flex items-center gap-2 flex-1 w-full sm:w-auto">
|
|
30
|
+
<IconAlertTriangle size={16} className="text-white shrink-0" />
|
|
31
|
+
<span className="paragraph-xsmall-semibold text-white">
|
|
32
|
+
{translate('common.subscription.frozen.banner.message')}
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div className="flex items-center gap-1.5 w-full sm:w-auto justify-between sm:justify-start">
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
className="flex items-center justify-center p-1.5 cursor-pointer"
|
|
39
|
+
onClick={onDetails}
|
|
40
|
+
>
|
|
41
|
+
<span className="paragraph-xsmall-semibold text-white underline">
|
|
42
|
+
{translate('common.subscription.frozen.banner.regularize')}
|
|
43
|
+
</span>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -5,12 +5,17 @@ import { useCharges } from "../../modules/charges/hooks/charges.hook";
|
|
|
5
5
|
import { SUBSCRIPTION_GRACE_PERIOD_DAYS, MAX_PAYMENT_ATTEMPTS } from "../../modules/subscriptions/constants/subscription.constants";
|
|
6
6
|
import { getSubscriptionExpiryDate } from "../../modules/subscriptions/utils/get-subscription-expiry-date";
|
|
7
7
|
import { getSubscriptionCancellationState } from "../../modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
8
|
+
import { getAccountFreezeState } from "../../modules/subscriptions/utils/get-account-freeze-state";
|
|
9
|
+
import { getAccountFreezeDate } from "../../modules/subscriptions/utils/get-account-freeze-date";
|
|
8
10
|
import { toCalendarDate, daysBetween } from "../../infra/utils/date";
|
|
9
11
|
import { OverdueInvoiceBanner } from "./OverdueInvoiceBanner";
|
|
10
12
|
import { UpcomingInvoiceBanner } from "./UpcomingInvoiceBanner";
|
|
11
13
|
import { CancelledSubscriptionBanner } from "./CancelledSubscriptionBanner";
|
|
12
14
|
import { PendingCancellationBanner } from "./PendingCancellationBanner";
|
|
13
15
|
import { TrialBanner } from "./TrialBanner";
|
|
16
|
+
import { FrozenAccountBanner } from "./FrozenAccountBanner";
|
|
17
|
+
import { FreezeWarningBanner } from "./FreezeWarningBanner";
|
|
18
|
+
import { useModalManager } from "../../store/useModalManager";
|
|
14
19
|
|
|
15
20
|
const UPCOMING_WINDOW_DAYS = 7;
|
|
16
21
|
const PAYMENT_METHOD_BOLETO = 2;
|
|
@@ -25,6 +30,7 @@ export function SubscriptionBanner({
|
|
|
25
30
|
onDetails,
|
|
26
31
|
}: SubscriptionBannerProps) {
|
|
27
32
|
const { data: { data: [subscription] = [] } = {} } = useActiveSubscription();
|
|
33
|
+
const { openModal } = useModalManager();
|
|
28
34
|
const { data: pendingChargesData, isLoading: isLoadingCharges } = useCharges({
|
|
29
35
|
page: 1,
|
|
30
36
|
limit: 1,
|
|
@@ -76,6 +82,30 @@ export function SubscriptionBanner({
|
|
|
76
82
|
|
|
77
83
|
if (subscription.type === "whitelabel") return null;
|
|
78
84
|
|
|
85
|
+
// Congelamento (date_due + ACCOUNT_FREEZE_AFTER_DAYS): entra antes do dunning
|
|
86
|
+
// e do aviso genérico de atraso, que hoje engolem essa janela.
|
|
87
|
+
const freezeState = getAccountFreezeState(subscription);
|
|
88
|
+
// "Detalhes" abre a MESMA modal do bloqueio, com a lista do que o congelamento tira: no
|
|
89
|
+
// aviso o usuário precisa ver o que vai perder antes de perder. O `freezeDate` no data é
|
|
90
|
+
// o que faz a modal renderizar a variante de véspera.
|
|
91
|
+
//
|
|
92
|
+
// IGNORA o `onDetails` do host de propósito: nos apps ele abre os detalhes da COBRANÇA,
|
|
93
|
+
// que responde outra pergunta. Aqui "Detalhes" significa "o que eu perco".
|
|
94
|
+
if (freezeState === "frozen") {
|
|
95
|
+
return (
|
|
96
|
+
<FrozenAccountBanner onDetails={() => openModal("accountFrozenModal")} />
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (freezeState === "warning") {
|
|
100
|
+
const freezeDate = getAccountFreezeDate(subscription);
|
|
101
|
+
return (
|
|
102
|
+
<FreezeWarningBanner
|
|
103
|
+
freezeDate={freezeDate}
|
|
104
|
+
onDetails={() => openModal("accountFrozenModal", { freezeDate })}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
// Cartão em dunning (Stripe Smart Retries): comunica as tentativas de cobrança
|
|
80
110
|
// restantes antes do cancelamento automático — não mais por tolerância fixa de
|
|
81
111
|
// dias. attempt_count vive no invoice (payment_retry); só cartão tem retries.
|
package/src/handlers.ts
CHANGED
|
@@ -18,5 +18,6 @@ export { createListAvailableUsersHandler } from './modules/projects/handlers/lis
|
|
|
18
18
|
export { createListAllAccountUsersHandler } from './modules/projects/handlers/list-all-account-users.handler';
|
|
19
19
|
|
|
20
20
|
export { createListSubscriptionsHandler } from './modules/subscriptions/handlers/list-subscriptions.handler';
|
|
21
|
+
export { createFreezeNoticeHandler } from './modules/subscriptions/handlers/freeze-notice.handler';
|
|
21
22
|
|
|
22
23
|
export { createListMessagesHandler } from './modules/users/handlers/list-messages.handler';
|