@greatapps/common 1.1.681 → 1.1.683
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/navigation/CancelledSubscriptionBanner.mjs +2 -6
- package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
- package/dist/components/navigation/SubscriptionBanner.mjs +4 -5
- package/dist/components/navigation/SubscriptionBanner.mjs.map +1 -1
- package/dist/modules/subscriptions/utils/get-subscription-cancellation-state.mjs +15 -0
- package/dist/modules/subscriptions/utils/get-subscription-cancellation-state.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/navigation/CancelledSubscriptionBanner.tsx +5 -8
- package/src/components/navigation/SubscriptionBanner.tsx +8 -9
- package/src/modules/subscriptions/utils/get-subscription-cancellation-state.ts +37 -0
|
@@ -3,8 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useEffect, useCallback } from "react";
|
|
4
4
|
import { IconAlertOctagon, IconX } from "@tabler/icons-react";
|
|
5
5
|
import { useActiveSubscription } from "../../modules/subscriptions/hooks/find-active-subscription.hook";
|
|
6
|
-
import {
|
|
7
|
-
import { getSubscriptionExpiryDate } from "../../modules/subscriptions/utils/get-subscription-expiry-date";
|
|
6
|
+
import { getSubscriptionCancellationState } from "../../modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
8
7
|
import { useTranslations } from "next-intl";
|
|
9
8
|
function CancelledSubscriptionBanner({ onReactivate }) {
|
|
10
9
|
const translate = useTranslations();
|
|
@@ -18,10 +17,7 @@ function CancelledSubscriptionBanner({ onReactivate }) {
|
|
|
18
17
|
const dismiss = useCallback(() => setVisible(false), []);
|
|
19
18
|
if (!visible) return null;
|
|
20
19
|
if (subscription?.type === "free" || subscription?.type === "trial") return null;
|
|
21
|
-
|
|
22
|
-
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
23
|
-
if (!today || !expiryDate || !subscription?.date_cancellation) return null;
|
|
24
|
-
if (today.getTime() < expiryDate.getTime()) return null;
|
|
20
|
+
if (getSubscriptionCancellationState(subscription) !== "cancelled") return null;
|
|
25
21
|
return /* @__PURE__ */ jsx("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", children: /* @__PURE__ */ jsxs(
|
|
26
22
|
"div",
|
|
27
23
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/navigation/CancelledSubscriptionBanner.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { IconAlertOctagon, IconX } from '@tabler/icons-react';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/components/navigation/CancelledSubscriptionBanner.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { IconAlertOctagon, IconX } from '@tabler/icons-react';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { getSubscriptionCancellationState } from '../../modules/subscriptions/utils/get-subscription-cancellation-state';\nimport { useTranslations } from 'next-intl';\n\ninterface CancelledSubscriptionBannerProps {\n onReactivate?: () => void;\n}\n\nexport function CancelledSubscriptionBanner({ onReactivate }: CancelledSubscriptionBannerProps) {\n const translate = useTranslations();\n const { data: { data: [subscription] = [] } = {} } = useActiveSubscription();\n const [visible, setVisible] = useState(true);\n const [animated, setAnimated] = useState(false);\n\n useEffect(() => {\n const timer = setTimeout(() => setAnimated(true), 50);\n return () => clearTimeout(timer);\n }, []);\n\n const dismiss = useCallback(() => setVisible(false), []);\n\n if (!visible) return null;\n if (subscription?.type === 'free' || subscription?.type === 'trial') return null;\n\n // Mesma fonte da verdade do SubscriptionBanner: páginas fora do ar quando a\n // assinatura está inativa (ex.: baixa por falha de pagamento) OU o cancelamento\n // explícito já passou do período (com grace, no caso de client).\n if (getSubscriptionCancellationState(subscription) !== 'cancelled') return null;\n\n return (\n <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\">\n <div\n 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'}`}\n >\n <div className=\"flex items-center gap-2 flex-1 w-full sm:w-auto\">\n <IconAlertOctagon size={16} className=\"text-white shrink-0\" />\n <span className=\"paragraph-xsmall-semibold text-white\">\n {translate('common.navigation.banners.cancelled.message')}\n </span>\n </div>\n <div className=\"flex items-center gap-1.5 w-full sm:w-auto justify-between sm:justify-start\">\n <button\n type=\"button\"\n className=\"flex items-center justify-center p-1.5 cursor-pointer\"\n onClick={onReactivate}\n >\n <span className=\"paragraph-xsmall-semibold text-white underline\">\n {translate('common.navigation.banners.cancelled.choosePlan')}\n </span>\n </button>\n <button type=\"button\" className=\"cursor-pointer shrink-0\" onClick={dismiss}>\n <IconX size={18} className=\"text-red-200\" />\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";AAsCQ,SACE,KADF;AApCR,SAAS,UAAU,WAAW,mBAAmB;AACjD,SAAS,kBAAkB,aAAa;AACxC,SAAS,6BAA6B;AACtC,SAAS,wCAAwC;AACjD,SAAS,uBAAuB;AAMzB,SAAS,4BAA4B,EAAE,aAAa,GAAqC;AAC9F,QAAM,YAAY,gBAAgB;AAClC,QAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,sBAAsB;AAC3E,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,YAAU,MAAM;AACd,UAAM,QAAQ,WAAW,MAAM,YAAY,IAAI,GAAG,EAAE;AACpD,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,YAAY,MAAM,WAAW,KAAK,GAAG,CAAC,CAAC;AAEvD,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,cAAc,SAAS,UAAU,cAAc,SAAS,QAAS,QAAO;AAK5E,MAAI,iCAAiC,YAAY,MAAM,YAAa,QAAO;AAE3E,SACE,oBAAC,SAAI,WAAU,wGACb;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,8NAA8N,WAAW,mCAAmC,mBAAmB;AAAA,MAE1S;AAAA,6BAAC,SAAI,WAAU,mDACb;AAAA,8BAAC,oBAAiB,MAAM,IAAI,WAAU,uBAAsB;AAAA,UAC5D,oBAAC,UAAK,WAAU,wCACb,oBAAU,6CAA6C,GAC1D;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,+EACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cAET,8BAAC,UAAK,WAAU,kDACb,oBAAU,gDAAgD,GAC7D;AAAA;AAAA,UACF;AAAA,UACA,oBAAC,YAAO,MAAK,UAAS,WAAU,2BAA0B,SAAS,SACjE,8BAAC,SAAM,MAAM,IAAI,WAAU,gBAAe,GAC5C;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}
|
|
@@ -4,6 +4,7 @@ import { useActiveSubscription } from "../../modules/subscriptions/hooks/find-ac
|
|
|
4
4
|
import { useCharges } from "../../modules/charges/hooks/charges.hook";
|
|
5
5
|
import { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "../../modules/subscriptions/constants/subscription.constants";
|
|
6
6
|
import { getSubscriptionExpiryDate } from "../../modules/subscriptions/utils/get-subscription-expiry-date";
|
|
7
|
+
import { getSubscriptionCancellationState } from "../../modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
7
8
|
import { toCalendarDate, daysBetween } from "../../infra/utils/date";
|
|
8
9
|
import { OverdueInvoiceBanner } from "./OverdueInvoiceBanner";
|
|
9
10
|
import { UpcomingInvoiceBanner } from "./UpcomingInvoiceBanner";
|
|
@@ -32,13 +33,11 @@ function SubscriptionBanner({
|
|
|
32
33
|
const hasPendingCharge = !!pendingChargesData?.data?.[0];
|
|
33
34
|
const isBoleto = subscription.payment_method === PAYMENT_METHOD_BOLETO;
|
|
34
35
|
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
const isPendingCancellation = hasCancellation && !!expiryDate && today.getTime() < expiryDate.getTime();
|
|
38
|
-
if (isEffectivelyCancelled) {
|
|
36
|
+
const cancellationState = getSubscriptionCancellationState(subscription);
|
|
37
|
+
if (cancellationState === "cancelled") {
|
|
39
38
|
return /* @__PURE__ */ jsx(CancelledSubscriptionBanner, { onReactivate });
|
|
40
39
|
}
|
|
41
|
-
if (
|
|
40
|
+
if (cancellationState === "pending") {
|
|
42
41
|
return /* @__PURE__ */ jsx(
|
|
43
42
|
PendingCancellationBanner,
|
|
44
43
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/navigation/SubscriptionBanner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useActiveSubscription } from \"../../modules/subscriptions/hooks/find-active-subscription.hook\";\nimport { useCharges } from \"../../modules/charges/hooks/charges.hook\";\nimport { SUBSCRIPTION_GRACE_PERIOD_DAYS } from \"../../modules/subscriptions/constants/subscription.constants\";\nimport { getSubscriptionExpiryDate } from \"../../modules/subscriptions/utils/get-subscription-expiry-date\";\nimport { toCalendarDate, daysBetween } from \"../../infra/utils/date\";\nimport { OverdueInvoiceBanner } from \"./OverdueInvoiceBanner\";\nimport { UpcomingInvoiceBanner } from \"./UpcomingInvoiceBanner\";\nimport { CancelledSubscriptionBanner } from \"./CancelledSubscriptionBanner\";\nimport { PendingCancellationBanner } from \"./PendingCancellationBanner\";\n\nconst UPCOMING_WINDOW_DAYS = 7;\nconst PAYMENT_METHOD_BOLETO = 2;\n\ninterface SubscriptionBannerProps {\n onReactivate?: () => void;\n onDetails?: () => void;\n}\n\nexport function SubscriptionBanner({\n onReactivate,\n onDetails,\n}: SubscriptionBannerProps) {\n const { data: { data: [subscription] = [] } = {} } = useActiveSubscription();\n const { data: pendingChargesData, isLoading: isLoadingCharges } = useCharges({\n page: 1,\n limit: 1,\n status: [0],\n });\n\n if (!subscription?.date_due) return null;\n if (subscription.type === \"free\" || subscription.type === \"trial\")\n return null;\n\n const dueDate = toCalendarDate(subscription.date_due);\n const today = toCalendarDate(new Date());\n if (!dueDate || !today) return null;\n\n const daysSinceDue = daysBetween(dueDate, today);\n const daysToDue = daysBetween(today, dueDate);\n const hasPendingCharge = !!pendingChargesData?.data?.[0];\n const isBoleto = subscription.payment_method === PAYMENT_METHOD_BOLETO;\n\n //
|
|
1
|
+
{"version":3,"sources":["../../../src/components/navigation/SubscriptionBanner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useActiveSubscription } from \"../../modules/subscriptions/hooks/find-active-subscription.hook\";\nimport { useCharges } from \"../../modules/charges/hooks/charges.hook\";\nimport { SUBSCRIPTION_GRACE_PERIOD_DAYS } from \"../../modules/subscriptions/constants/subscription.constants\";\nimport { getSubscriptionExpiryDate } from \"../../modules/subscriptions/utils/get-subscription-expiry-date\";\nimport { getSubscriptionCancellationState } from \"../../modules/subscriptions/utils/get-subscription-cancellation-state\";\nimport { toCalendarDate, daysBetween } from \"../../infra/utils/date\";\nimport { OverdueInvoiceBanner } from \"./OverdueInvoiceBanner\";\nimport { UpcomingInvoiceBanner } from \"./UpcomingInvoiceBanner\";\nimport { CancelledSubscriptionBanner } from \"./CancelledSubscriptionBanner\";\nimport { PendingCancellationBanner } from \"./PendingCancellationBanner\";\n\nconst UPCOMING_WINDOW_DAYS = 7;\nconst PAYMENT_METHOD_BOLETO = 2;\n\ninterface SubscriptionBannerProps {\n onReactivate?: () => void;\n onDetails?: () => void;\n}\n\nexport function SubscriptionBanner({\n onReactivate,\n onDetails,\n}: SubscriptionBannerProps) {\n const { data: { data: [subscription] = [] } = {} } = useActiveSubscription();\n const { data: pendingChargesData, isLoading: isLoadingCharges } = useCharges({\n page: 1,\n limit: 1,\n status: [0],\n });\n\n if (!subscription?.date_due) return null;\n if (subscription.type === \"free\" || subscription.type === \"trial\")\n return null;\n\n const dueDate = toCalendarDate(subscription.date_due);\n const today = toCalendarDate(new Date());\n if (!dueDate || !today) return null;\n\n const daysSinceDue = daysBetween(dueDate, today);\n const daysToDue = daysBetween(today, dueDate);\n const hasPendingCharge = !!pendingChargesData?.data?.[0];\n const isBoleto = subscription.payment_method === PAYMENT_METHOD_BOLETO;\n\n // Estado de cancelamento (fonte da verdade única, compartilhada com o\n // CancelledSubscriptionBanner). `active === false` tem prioridade e cai em\n // \"cancelled\" mesmo com date_due no futuro — é o caso da baixa por falha de\n // pagamento, onde date_due aponta pra uma fatura não paga.\n const expiryDate = getSubscriptionExpiryDate(subscription);\n const cancellationState = getSubscriptionCancellationState(subscription);\n\n if (cancellationState === \"cancelled\") {\n return <CancelledSubscriptionBanner onReactivate={onReactivate} />;\n }\n\n if (cancellationState === \"pending\") {\n return (\n <PendingCancellationBanner\n limitDate={expiryDate}\n onReactivate={onReactivate}\n />\n );\n }\n\n if (subscription.type === \"whitelabel\") return null;\n\n if (daysSinceDue > 0 && daysSinceDue <= SUBSCRIPTION_GRACE_PERIOD_DAYS) {\n return <OverdueInvoiceBanner onDetails={onDetails} />;\n }\n\n if (\n isBoleto &&\n !isLoadingCharges &&\n hasPendingCharge &&\n daysToDue >= 0 &&\n daysToDue <= UPCOMING_WINDOW_DAYS\n ) {\n return <UpcomingInvoiceBanner dueDate={dueDate} onDetails={onDetails} />;\n }\n\n return null;\n}\n"],"mappings":";AAqDW;AAnDX,SAAS,6BAA6B;AACtC,SAAS,kBAAkB;AAC3B,SAAS,sCAAsC;AAC/C,SAAS,iCAAiC;AAC1C,SAAS,wCAAwC;AACjD,SAAS,gBAAgB,mBAAmB;AAC5C,SAAS,4BAA4B;AACrC,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAC5C,SAAS,iCAAiC;AAE1C,MAAM,uBAAuB;AAC7B,MAAM,wBAAwB;AAOvB,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAA4B;AAC1B,QAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,sBAAsB;AAC3E,QAAM,EAAE,MAAM,oBAAoB,WAAW,iBAAiB,IAAI,WAAW;AAAA,IAC3E,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ,CAAC,CAAC;AAAA,EACZ,CAAC;AAED,MAAI,CAAC,cAAc,SAAU,QAAO;AACpC,MAAI,aAAa,SAAS,UAAU,aAAa,SAAS;AACxD,WAAO;AAET,QAAM,UAAU,eAAe,aAAa,QAAQ;AACpD,QAAM,QAAQ,eAAe,oBAAI,KAAK,CAAC;AACvC,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO;AAE/B,QAAM,eAAe,YAAY,SAAS,KAAK;AAC/C,QAAM,YAAY,YAAY,OAAO,OAAO;AAC5C,QAAM,mBAAmB,CAAC,CAAC,oBAAoB,OAAO,CAAC;AACvD,QAAM,WAAW,aAAa,mBAAmB;AAMjD,QAAM,aAAa,0BAA0B,YAAY;AACzD,QAAM,oBAAoB,iCAAiC,YAAY;AAEvE,MAAI,sBAAsB,aAAa;AACrC,WAAO,oBAAC,+BAA4B,cAA4B;AAAA,EAClE;AAEA,MAAI,sBAAsB,WAAW;AACnC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,QACX;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,MAAI,aAAa,SAAS,aAAc,QAAO;AAE/C,MAAI,eAAe,KAAK,gBAAgB,gCAAgC;AACtE,WAAO,oBAAC,wBAAqB,WAAsB;AAAA,EACrD;AAEA,MACE,YACA,CAAC,oBACD,oBACA,aAAa,KACb,aAAa,sBACb;AACA,WAAO,oBAAC,yBAAsB,SAAkB,WAAsB;AAAA,EACxE;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { toCalendarDate } from "../../../infra/utils/date";
|
|
2
|
+
import { getSubscriptionExpiryDate } from "./get-subscription-expiry-date";
|
|
3
|
+
function getSubscriptionCancellationState(subscription) {
|
|
4
|
+
if (!subscription) return "none";
|
|
5
|
+
if (!subscription.active) return "cancelled";
|
|
6
|
+
if (!subscription.date_cancellation) return "none";
|
|
7
|
+
const today = toCalendarDate(/* @__PURE__ */ new Date());
|
|
8
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
9
|
+
if (!today || !expiryDate) return "none";
|
|
10
|
+
return today.getTime() >= expiryDate.getTime() ? "cancelled" : "pending";
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
getSubscriptionCancellationState
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=get-subscription-cancellation-state.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/utils/get-subscription-cancellation-state.ts"],"sourcesContent":["import { toCalendarDate } from \"../../../infra/utils/date\";\nimport { getSubscriptionExpiryDate } from \"./get-subscription-expiry-date\";\nimport type { Subscription } from \"../types/subscription.type\";\n\nexport type SubscriptionCancellationState = \"none\" | \"pending\" | \"cancelled\";\n\n/**\n * Fonte da verdade do estado de cancelamento, compartilhada pelo SubscriptionBanner\n * (orquestrador) e pelo CancelledSubscriptionBanner (que tem guarda própria).\n *\n * Prioridade:\n * - `active === false` → `cancelled`: o serviço já baixou. Cobre a baixa por falha\n * de pagamento, onde `date_due` aponta pra uma fatura NÃO paga e portanto não pode\n * ser usado como prazo (\"você tem até X\"). Vale com ou sem `date_cancellation`.\n * - cancelamento explícito cujo período já terminou (`hoje >= expiryDate`; p/ client\n * = `date_due`, p/ whitelabel = `date_cancellation`) → `cancelled`.\n * - cancelamento ainda dentro do período (`hoje < expiryDate`) → `pending`.\n * - sem cancelamento e ativo → `none`.\n */\nexport function getSubscriptionCancellationState(\n subscription:\n | Pick<Subscription, \"type\" | \"active\" | \"date_due\" | \"date_cancellation\">\n | undefined\n | null,\n): SubscriptionCancellationState {\n if (!subscription) return \"none\";\n\n if (!subscription.active) return \"cancelled\";\n\n if (!subscription.date_cancellation) return \"none\";\n\n const today = toCalendarDate(new Date());\n const expiryDate = getSubscriptionExpiryDate(subscription);\n if (!today || !expiryDate) return \"none\";\n\n return today.getTime() >= expiryDate.getTime() ? \"cancelled\" : \"pending\";\n}\n"],"mappings":"AAAA,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAkBnC,SAAS,iCACd,cAI+B;AAC/B,MAAI,CAAC,aAAc,QAAO;AAE1B,MAAI,CAAC,aAAa,OAAQ,QAAO;AAEjC,MAAI,CAAC,aAAa,kBAAmB,QAAO;AAE5C,QAAM,QAAQ,eAAe,oBAAI,KAAK,CAAC;AACvC,QAAM,aAAa,0BAA0B,YAAY;AACzD,MAAI,CAAC,SAAS,CAAC,WAAY,QAAO;AAElC,SAAO,MAAM,QAAQ,KAAK,WAAW,QAAQ,IAAI,cAAc;AACjE;","names":[]}
|
package/package.json
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
4
|
import { IconAlertOctagon, IconX } from '@tabler/icons-react';
|
|
5
5
|
import { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';
|
|
6
|
-
import {
|
|
7
|
-
import { getSubscriptionExpiryDate } from '../../modules/subscriptions/utils/get-subscription-expiry-date';
|
|
6
|
+
import { getSubscriptionCancellationState } from '../../modules/subscriptions/utils/get-subscription-cancellation-state';
|
|
8
7
|
import { useTranslations } from 'next-intl';
|
|
9
8
|
|
|
10
9
|
interface CancelledSubscriptionBannerProps {
|
|
@@ -27,12 +26,10 @@ export function CancelledSubscriptionBanner({ onReactivate }: CancelledSubscript
|
|
|
27
26
|
if (!visible) return null;
|
|
28
27
|
if (subscription?.type === 'free' || subscription?.type === 'trial') return null;
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!today || !expiryDate || !subscription?.date_cancellation) return null;
|
|
35
|
-
if (today.getTime() < expiryDate.getTime()) return null;
|
|
29
|
+
// Mesma fonte da verdade do SubscriptionBanner: páginas fora do ar quando a
|
|
30
|
+
// assinatura está inativa (ex.: baixa por falha de pagamento) OU o cancelamento
|
|
31
|
+
// explícito já passou do período (com grace, no caso de client).
|
|
32
|
+
if (getSubscriptionCancellationState(subscription) !== 'cancelled') return null;
|
|
36
33
|
|
|
37
34
|
return (
|
|
38
35
|
<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">
|
|
@@ -4,6 +4,7 @@ import { useActiveSubscription } from "../../modules/subscriptions/hooks/find-ac
|
|
|
4
4
|
import { useCharges } from "../../modules/charges/hooks/charges.hook";
|
|
5
5
|
import { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "../../modules/subscriptions/constants/subscription.constants";
|
|
6
6
|
import { getSubscriptionExpiryDate } from "../../modules/subscriptions/utils/get-subscription-expiry-date";
|
|
7
|
+
import { getSubscriptionCancellationState } from "../../modules/subscriptions/utils/get-subscription-cancellation-state";
|
|
7
8
|
import { toCalendarDate, daysBetween } from "../../infra/utils/date";
|
|
8
9
|
import { OverdueInvoiceBanner } from "./OverdueInvoiceBanner";
|
|
9
10
|
import { UpcomingInvoiceBanner } from "./UpcomingInvoiceBanner";
|
|
@@ -42,20 +43,18 @@ export function SubscriptionBanner({
|
|
|
42
43
|
const hasPendingCharge = !!pendingChargesData?.data?.[0];
|
|
43
44
|
const isBoleto = subscription.payment_method === PAYMENT_METHOD_BOLETO;
|
|
44
45
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
46
|
+
// Estado de cancelamento (fonte da verdade única, compartilhada com o
|
|
47
|
+
// CancelledSubscriptionBanner). `active === false` tem prioridade e cai em
|
|
48
|
+
// "cancelled" mesmo com date_due no futuro — é o caso da baixa por falha de
|
|
49
|
+
// pagamento, onde date_due aponta pra uma fatura não paga.
|
|
47
50
|
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
48
|
-
const
|
|
49
|
-
const isEffectivelyCancelled =
|
|
50
|
-
hasCancellation && !!expiryDate && today.getTime() >= expiryDate.getTime();
|
|
51
|
-
const isPendingCancellation =
|
|
52
|
-
hasCancellation && !!expiryDate && today.getTime() < expiryDate.getTime();
|
|
51
|
+
const cancellationState = getSubscriptionCancellationState(subscription);
|
|
53
52
|
|
|
54
|
-
if (
|
|
53
|
+
if (cancellationState === "cancelled") {
|
|
55
54
|
return <CancelledSubscriptionBanner onReactivate={onReactivate} />;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
if (
|
|
57
|
+
if (cancellationState === "pending") {
|
|
59
58
|
return (
|
|
60
59
|
<PendingCancellationBanner
|
|
61
60
|
limitDate={expiryDate}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { toCalendarDate } from "../../../infra/utils/date";
|
|
2
|
+
import { getSubscriptionExpiryDate } from "./get-subscription-expiry-date";
|
|
3
|
+
import type { Subscription } from "../types/subscription.type";
|
|
4
|
+
|
|
5
|
+
export type SubscriptionCancellationState = "none" | "pending" | "cancelled";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fonte da verdade do estado de cancelamento, compartilhada pelo SubscriptionBanner
|
|
9
|
+
* (orquestrador) e pelo CancelledSubscriptionBanner (que tem guarda própria).
|
|
10
|
+
*
|
|
11
|
+
* Prioridade:
|
|
12
|
+
* - `active === false` → `cancelled`: o serviço já baixou. Cobre a baixa por falha
|
|
13
|
+
* de pagamento, onde `date_due` aponta pra uma fatura NÃO paga e portanto não pode
|
|
14
|
+
* ser usado como prazo ("você tem até X"). Vale com ou sem `date_cancellation`.
|
|
15
|
+
* - cancelamento explícito cujo período já terminou (`hoje >= expiryDate`; p/ client
|
|
16
|
+
* = `date_due`, p/ whitelabel = `date_cancellation`) → `cancelled`.
|
|
17
|
+
* - cancelamento ainda dentro do período (`hoje < expiryDate`) → `pending`.
|
|
18
|
+
* - sem cancelamento e ativo → `none`.
|
|
19
|
+
*/
|
|
20
|
+
export function getSubscriptionCancellationState(
|
|
21
|
+
subscription:
|
|
22
|
+
| Pick<Subscription, "type" | "active" | "date_due" | "date_cancellation">
|
|
23
|
+
| undefined
|
|
24
|
+
| null,
|
|
25
|
+
): SubscriptionCancellationState {
|
|
26
|
+
if (!subscription) return "none";
|
|
27
|
+
|
|
28
|
+
if (!subscription.active) return "cancelled";
|
|
29
|
+
|
|
30
|
+
if (!subscription.date_cancellation) return "none";
|
|
31
|
+
|
|
32
|
+
const today = toCalendarDate(new Date());
|
|
33
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
34
|
+
if (!today || !expiryDate) return "none";
|
|
35
|
+
|
|
36
|
+
return today.getTime() >= expiryDate.getTime() ? "cancelled" : "pending";
|
|
37
|
+
}
|