@greatapps/common 1.1.743 → 1.1.745
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 +48 -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 +17 -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 +23 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +23 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +25 -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/plans/utils/map-api-plan-to-ui.mjs +14 -2
- package/dist/modules/plans/utils/map-api-plan-to-ui.mjs.map +1 -1
- 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 +58 -0
- package/src/components/navigation/FreezeWarningBanner.tsx +57 -0
- package/src/components/navigation/FrozenAccountBanner.tsx +49 -0
- package/src/components/navigation/SubscriptionBanner.tsx +19 -0
- package/src/handlers.ts +1 -0
- package/src/i18n/messages/en-us.ts +23 -0
- package/src/i18n/messages/es-es.ts +23 -0
- package/src/i18n/messages/pt-br.ts +26 -0
- package/src/index.ts +14 -0
- package/src/modules/auth/utils/assert-account-not-frozen.ts +38 -0
- package/src/modules/plans/utils/map-api-plan-to-ui.ts +16 -3
- 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/projects/services/projects.service.ts"],"sourcesContent":["import 'server-only';\n\nimport { api } from '../../../infra/api/client';\nimport { ApiError } from '../../../infra/api/types';\nimport { buildQueryParams } from '../../../infra/utils/params';\nimport { getUserContext } from '../../auth/utils/get-user-context';\nimport { assertManagementSubscription } from '../../auth/utils/assert-management-subscription';\nimport {\n Project,\n ProjectSchema,\n ProjectsPage,\n CreateProjectRequest,\n UpdateProjectRequest,\n} from '../types';\n\ninterface ApiListResponse<T> {\n data: T[];\n total: number;\n status: number;\n message?: string;\n}\n\ninterface ApiMutationResponse {\n status: number;\n message?: string;\n data?: Project[];\n}\n\ninterface ListProjectsParams {\n page?: number;\n limit?: number;\n sort?: string;\n search?: string;\n deleted?: number;\n}\n\nclass ProjectsService {\n async list(params: ListProjectsParams = {}): Promise<ProjectsPage> {\n const { id_account, id_user } = await getUserContext();\n const query = buildQueryParams(params as Record<string, string | number | boolean | undefined>);\n const url = `/account/${id_account}/user/${id_user}/projects${query ? `?${query}` : ''}`;\n\n const response = await api.apps.get<ApiListResponse<Project>>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao listar projetos',\n 'LIST_PROJECTS_FAILED',\n 400\n );\n }\n\n return {\n data: (response.data || []).map((item) => ProjectSchema.parse(item)),\n total: response.total || 0,\n page: params.page ?? 1,\n };\n }\n\n async findById(projectId: number): Promise<Project> {\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;\n\n const response = await api.apps.get<ApiMutationResponse>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao buscar projeto',\n 'FIND_PROJECT_FAILED',\n 400\n );\n }\n\n if (!response.data || response.data.length === 0) {\n throw new ApiError('Projeto não encontrado', 'PROJECT_NOT_FOUND', 404);\n }\n\n return ProjectSchema.parse(response.data[0]);\n }\n\n async create(data: CreateProjectRequest): Promise<Project> {\n await assertManagementSubscription();\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects`;\n\n const response = await api.apps.post<ApiMutationResponse>(url, data);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao criar projeto',\n 'CREATE_PROJECT_FAILED',\n 400\n );\n }\n\n if (!response.data || response.data.length === 0) {\n throw new ApiError('Resposta inválida ao criar projeto', 'INVALID_RESPONSE', 500);\n }\n\n return ProjectSchema.parse(response.data[0]);\n }\n\n async update(projectId: number, data: UpdateProjectRequest): Promise<Project> {\n await assertManagementSubscription();\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;\n\n const response = await api.apps.put<ApiMutationResponse>(url, data);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao atualizar projeto',\n 'UPDATE_PROJECT_FAILED',\n 400\n );\n }\n\n if (!response.data || response.data.length === 0) {\n throw new ApiError('Resposta inválida ao atualizar projeto', 'INVALID_RESPONSE', 500);\n }\n\n return ProjectSchema.parse(response.data[0]);\n }\n\n async delete(projectId: number, moveToProjectId: number): Promise<void> {\n await assertManagementSubscription();\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;\n\n const response = await api.apps.delete<ApiMutationResponse>(url, {\n body: JSON.stringify({ id_project_to_move: moveToProjectId }),\n });\n\n // Alguns backends respondem `null` no delete quando a exclusão foi bem-sucedida.\n // Nesse caso não há payload para validar e o fluxo deve seguir sem erro.\n if (response == null) {\n console.warn('[ProjectsService.delete] null response treated as success', {\n id_account,\n id_user,\n projectId,\n moveToProjectId,\n });\n return;\n }\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao deletar projeto',\n 'DELETE_PROJECT_FAILED',\n 400\n );\n }\n }\n}\n\nexport const projectsService = new ProjectsService();\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/projects/services/projects.service.ts"],"sourcesContent":["import 'server-only';\n\nimport { api } from '../../../infra/api/client';\nimport { ApiError } from '../../../infra/api/types';\nimport { buildQueryParams } from '../../../infra/utils/params';\nimport { getUserContext } from '../../auth/utils/get-user-context';\nimport { assertManagementSubscription } from '../../auth/utils/assert-management-subscription';\nimport { assertAccountNotFrozen } from '../../auth/utils/assert-account-not-frozen';\nimport {\n Project,\n ProjectSchema,\n ProjectsPage,\n CreateProjectRequest,\n UpdateProjectRequest,\n} from '../types';\n\ninterface ApiListResponse<T> {\n data: T[];\n total: number;\n status: number;\n message?: string;\n}\n\ninterface ApiMutationResponse {\n status: number;\n message?: string;\n data?: Project[];\n}\n\ninterface ListProjectsParams {\n page?: number;\n limit?: number;\n sort?: string;\n search?: string;\n deleted?: number;\n}\n\nclass ProjectsService {\n async list(params: ListProjectsParams = {}): Promise<ProjectsPage> {\n const { id_account, id_user } = await getUserContext();\n const query = buildQueryParams(params as Record<string, string | number | boolean | undefined>);\n const url = `/account/${id_account}/user/${id_user}/projects${query ? `?${query}` : ''}`;\n\n const response = await api.apps.get<ApiListResponse<Project>>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao listar projetos',\n 'LIST_PROJECTS_FAILED',\n 400\n );\n }\n\n return {\n data: (response.data || []).map((item) => ProjectSchema.parse(item)),\n total: response.total || 0,\n page: params.page ?? 1,\n };\n }\n\n async findById(projectId: number): Promise<Project> {\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;\n\n const response = await api.apps.get<ApiMutationResponse>(url);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao buscar projeto',\n 'FIND_PROJECT_FAILED',\n 400\n );\n }\n\n if (!response.data || response.data.length === 0) {\n throw new ApiError('Projeto não encontrado', 'PROJECT_NOT_FOUND', 404);\n }\n\n return ProjectSchema.parse(response.data[0]);\n }\n\n async create(data: CreateProjectRequest): Promise<Project> {\n await assertManagementSubscription();\n await assertAccountNotFrozen();\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects`;\n\n const response = await api.apps.post<ApiMutationResponse>(url, data);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao criar projeto',\n 'CREATE_PROJECT_FAILED',\n 400\n );\n }\n\n if (!response.data || response.data.length === 0) {\n throw new ApiError('Resposta inválida ao criar projeto', 'INVALID_RESPONSE', 500);\n }\n\n return ProjectSchema.parse(response.data[0]);\n }\n\n async update(projectId: number, data: UpdateProjectRequest): Promise<Project> {\n await assertManagementSubscription();\n await assertAccountNotFrozen();\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;\n\n const response = await api.apps.put<ApiMutationResponse>(url, data);\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao atualizar projeto',\n 'UPDATE_PROJECT_FAILED',\n 400\n );\n }\n\n if (!response.data || response.data.length === 0) {\n throw new ApiError('Resposta inválida ao atualizar projeto', 'INVALID_RESPONSE', 500);\n }\n\n return ProjectSchema.parse(response.data[0]);\n }\n\n async delete(projectId: number, moveToProjectId: number): Promise<void> {\n await assertManagementSubscription();\n const { id_account, id_user } = await getUserContext();\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;\n\n const response = await api.apps.delete<ApiMutationResponse>(url, {\n body: JSON.stringify({ id_project_to_move: moveToProjectId }),\n });\n\n // Alguns backends respondem `null` no delete quando a exclusão foi bem-sucedida.\n // Nesse caso não há payload para validar e o fluxo deve seguir sem erro.\n if (response == null) {\n console.warn('[ProjectsService.delete] null response treated as success', {\n id_account,\n id_user,\n projectId,\n moveToProjectId,\n });\n return;\n }\n\n if (response.status === 0) {\n throw new ApiError(\n response.message || 'Erro ao deletar projeto',\n 'DELETE_PROJECT_FAILED',\n 400\n );\n }\n }\n}\n\nexport const projectsService = new ProjectsService();\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { safeMutationAction } from "../../../utils/safeMutationAction";
|
|
3
|
+
import { subscriptionsService } from "../services/subscriptions.service";
|
|
4
|
+
async function ackFreezeNoticeAction(cycle) {
|
|
5
|
+
return safeMutationAction(() => subscriptionsService.ackFreezeNotice(cycle));
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
ackFreezeNoticeAction
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=ack-freeze-notice.action.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/actions/ack-freeze-notice.action.ts"],"sourcesContent":["'use server';\n\nimport { safeMutationAction } from '../../../utils/safeMutationAction';\nimport { subscriptionsService } from '../services/subscriptions.service';\n\nexport async function ackFreezeNoticeAction(cycle: string) {\n return safeMutationAction(() => subscriptionsService.ackFreezeNotice(cycle));\n}\n"],"mappings":";AAEA,SAAS,0BAA0B;AACnC,SAAS,4BAA4B;AAErC,eAAsB,sBAAsB,OAAe;AACzD,SAAO,mBAAmB,MAAM,qBAAqB,gBAAgB,KAAK,CAAC;AAC7E;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const SUBSCRIPTIONS_QUERY_KEY = ["subscriptions"];
|
|
2
2
|
const CHARGES_QUERY_KEY = ["charges"];
|
|
3
|
+
const FREEZE_NOTICE_QUERY_KEY = ["freeze-notice"];
|
|
3
4
|
export {
|
|
4
5
|
CHARGES_QUERY_KEY,
|
|
6
|
+
FREEZE_NOTICE_QUERY_KEY,
|
|
5
7
|
SUBSCRIPTIONS_QUERY_KEY
|
|
6
8
|
};
|
|
7
9
|
//# sourceMappingURL=query-keys.constants.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/subscriptions/constants/query-keys.constants.ts"],"sourcesContent":["export const SUBSCRIPTIONS_QUERY_KEY = ['subscriptions'] as const;\n\nexport const CHARGES_QUERY_KEY = ['charges'] as const;\n"],"mappings":"AAAO,MAAM,0BAA0B,CAAC,eAAe;AAEhD,MAAM,oBAAoB,CAAC,SAAS;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/constants/query-keys.constants.ts"],"sourcesContent":["export const SUBSCRIPTIONS_QUERY_KEY = ['subscriptions'] as const;\n\nexport const CHARGES_QUERY_KEY = ['charges'] as const;\n\nexport const FREEZE_NOTICE_QUERY_KEY = ['freeze-notice'] as const;\n"],"mappings":"AAAO,MAAM,0BAA0B,CAAC,eAAe;AAEhD,MAAM,oBAAoB,CAAC,SAAS;AAEpC,MAAM,0BAA0B,CAAC,eAAe;","names":[]}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
const SUBSCRIPTION_GRACE_PERIOD_DAYS = 15;
|
|
2
|
+
const ACCOUNT_FREEZE_AFTER_DAYS = 7;
|
|
3
|
+
const FREEZE_WARNING_DAYS_BEFORE = 1;
|
|
4
|
+
const FREEZE_ELIGIBLE_WHITELABEL_ID = 1;
|
|
2
5
|
const MAX_PAYMENT_ATTEMPTS = 8;
|
|
3
6
|
export {
|
|
7
|
+
ACCOUNT_FREEZE_AFTER_DAYS,
|
|
8
|
+
FREEZE_ELIGIBLE_WHITELABEL_ID,
|
|
9
|
+
FREEZE_WARNING_DAYS_BEFORE,
|
|
4
10
|
MAX_PAYMENT_ATTEMPTS,
|
|
5
11
|
SUBSCRIPTION_GRACE_PERIOD_DAYS
|
|
6
12
|
};
|
|
@@ -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,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { IconLock } from '@tabler/icons-react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
5
|
+
import { Button } from '../ui/buttons/Button';
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
} from '../ui/overlay/Dialog';
|
|
13
|
+
import { useWhitelabelUrls, useExternalContracting } from '../../providers/whitelabel.provider';
|
|
14
|
+
import { useModalManager } from '../../store/useModalManager';
|
|
15
|
+
|
|
16
|
+
export default function AccountFrozenModal() {
|
|
17
|
+
const translate = useTranslations();
|
|
18
|
+
const { activeModal, closeModal } = useModalManager();
|
|
19
|
+
const isOpen = activeModal === 'accountFrozenModal';
|
|
20
|
+
const { accountsUrl } = useWhitelabelUrls();
|
|
21
|
+
const { redirectToExternal } = useExternalContracting();
|
|
22
|
+
|
|
23
|
+
const title = translate('common.subscription.frozen.modal.title');
|
|
24
|
+
const description = translate('common.subscription.frozen.modal.description');
|
|
25
|
+
|
|
26
|
+
const handleRegularize = () => {
|
|
27
|
+
if (redirectToExternal('plans')) return;
|
|
28
|
+
window.location.href = `${accountsUrl}/subscriptions`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Dialog open={isOpen} onOpenChange={() => closeModal()}>
|
|
33
|
+
<DialogContent className="flex flex-col p-0 gap-0 w-full md:w-[410px] lg:w-[410px]">
|
|
34
|
+
<DialogHeader className="gap-3 text-left p-5">
|
|
35
|
+
<div className="flex items-center justify-center w-10 h-10 bg-zinc-50 rounded-lg">
|
|
36
|
+
<IconLock size={24} className="text-zinc-950" />
|
|
37
|
+
</div>
|
|
38
|
+
<div className="flex flex-col gap-2">
|
|
39
|
+
<DialogTitle className="paragraph-medium-semibold text-zinc-950">
|
|
40
|
+
{title}
|
|
41
|
+
</DialogTitle>
|
|
42
|
+
<DialogDescription className="paragraph-small-regular text-zinc-600">
|
|
43
|
+
{description}
|
|
44
|
+
</DialogDescription>
|
|
45
|
+
</div>
|
|
46
|
+
</DialogHeader>
|
|
47
|
+
<div className="flex items-center gap-2 p-5 border-t border-zinc-200">
|
|
48
|
+
<Button
|
|
49
|
+
className="w-fit h-10!"
|
|
50
|
+
onClick={handleRegularize}
|
|
51
|
+
>
|
|
52
|
+
{translate('common.subscription.frozen.modal.cta')}
|
|
53
|
+
</Button>
|
|
54
|
+
</div>
|
|
55
|
+
</DialogContent>
|
|
56
|
+
</Dialog>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -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
|
+
}
|