@greatapps/common 1.1.644 → 1.1.647
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 +4 -3
- package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
- package/dist/components/navigation/SubscriptionBanner.mjs +9 -8
- package/dist/components/navigation/SubscriptionBanner.mjs.map +1 -1
- package/dist/modules/auth/utils/assert-management-subscription.mjs +3 -3
- package/dist/modules/auth/utils/assert-management-subscription.mjs.map +1 -1
- package/dist/modules/auth/utils/assert-paid-subscription.mjs +3 -3
- package/dist/modules/auth/utils/assert-paid-subscription.mjs.map +1 -1
- package/dist/modules/subscriptions/hooks/use-cancelled-subscription-guard.mjs +3 -3
- package/dist/modules/subscriptions/hooks/use-cancelled-subscription-guard.mjs.map +1 -1
- package/dist/modules/subscriptions/utils/get-subscription-expiry-date.mjs +18 -0
- package/dist/modules/subscriptions/utils/get-subscription-expiry-date.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/navigation/CancelledSubscriptionBanner.tsx +5 -4
- package/src/components/navigation/SubscriptionBanner.tsx +13 -15
- package/src/modules/auth/utils/assert-management-subscription.ts +3 -5
- package/src/modules/auth/utils/assert-paid-subscription.ts +3 -5
- package/src/modules/subscriptions/hooks/use-cancelled-subscription-guard.ts +3 -5
- package/src/modules/subscriptions/utils/get-subscription-expiry-date.ts +33 -0
|
@@ -4,6 +4,7 @@ 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
6
|
import { toCalendarDate } from "../../infra/utils/date";
|
|
7
|
+
import { getSubscriptionExpiryDate } from "../../modules/subscriptions/utils/get-subscription-expiry-date";
|
|
7
8
|
function CancelledSubscriptionBanner({ onReactivate }) {
|
|
8
9
|
const { data: { data: [subscription] = [] } = {} } = useActiveSubscription();
|
|
9
10
|
const [visible, setVisible] = useState(true);
|
|
@@ -16,9 +17,9 @@ function CancelledSubscriptionBanner({ onReactivate }) {
|
|
|
16
17
|
if (!visible) return null;
|
|
17
18
|
if (subscription?.type === "free" || subscription?.type === "trial") return null;
|
|
18
19
|
const today = toCalendarDate(/* @__PURE__ */ new Date());
|
|
19
|
-
const
|
|
20
|
-
if (!today || !
|
|
21
|
-
if (today.getTime() <
|
|
20
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
21
|
+
if (!today || !expiryDate || !subscription?.date_cancellation) return null;
|
|
22
|
+
if (today.getTime() < expiryDate.getTime()) return null;
|
|
22
23
|
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(
|
|
23
24
|
"div",
|
|
24
25
|
{
|
|
@@ -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 { toCalendarDate } from '../../infra/utils/date';\n\ninterface CancelledSubscriptionBannerProps {\n onReactivate?: () => void;\n}\n\nexport function CancelledSubscriptionBanner({ onReactivate }: CancelledSubscriptionBannerProps) {\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 const today = toCalendarDate(new Date());\n const
|
|
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 { toCalendarDate } from '../../infra/utils/date';\nimport { getSubscriptionExpiryDate } from '../../modules/subscriptions/utils/get-subscription-expiry-date';\n\ninterface CancelledSubscriptionBannerProps {\n onReactivate?: () => void;\n}\n\nexport function CancelledSubscriptionBanner({ onReactivate }: CancelledSubscriptionBannerProps) {\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 const today = toCalendarDate(new Date());\n const expiryDate = getSubscriptionExpiryDate(subscription);\n\n // Só mostra se há cancelamento E o período (com grace, no caso de client) já terminou.\n if (!today || !expiryDate || !subscription?.date_cancellation) return null;\n if (today.getTime() < expiryDate.getTime()) 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 Assinatura cancelada! Suas páginas foram retiradas do ar.\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 Escolher um novo plano\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":";AAuCQ,SACE,KADF;AArCR,SAAS,UAAU,WAAW,mBAAmB;AACjD,SAAS,kBAAkB,aAAa;AACxC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAMnC,SAAS,4BAA4B,EAAE,aAAa,GAAqC;AAC9F,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;AAE5E,QAAM,QAAQ,eAAe,oBAAI,KAAK,CAAC;AACvC,QAAM,aAAa,0BAA0B,YAAY;AAGzD,MAAI,CAAC,SAAS,CAAC,cAAc,CAAC,cAAc,kBAAmB,QAAO;AACtE,MAAI,MAAM,QAAQ,IAAI,WAAW,QAAQ,EAAG,QAAO;AAEnD,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,wCAAuC,0EAEvD;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,kDAAiD,oCAEjE;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":[]}
|
|
@@ -3,6 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useActiveSubscription } from "../../modules/subscriptions/hooks/find-active-subscription.hook";
|
|
4
4
|
import { useCharges } from "../../modules/charges/hooks/charges.hook";
|
|
5
5
|
import { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "../../modules/subscriptions/constants/subscription.constants";
|
|
6
|
+
import { getSubscriptionExpiryDate } from "../../modules/subscriptions/utils/get-subscription-expiry-date";
|
|
6
7
|
import { toCalendarDate, daysBetween } from "../../infra/utils/date";
|
|
7
8
|
import { OverdueInvoiceBanner } from "./OverdueInvoiceBanner";
|
|
8
9
|
import { UpcomingInvoiceBanner } from "./UpcomingInvoiceBanner";
|
|
@@ -20,7 +21,6 @@ function SubscriptionBanner({
|
|
|
20
21
|
limit: 1,
|
|
21
22
|
status: [0]
|
|
22
23
|
});
|
|
23
|
-
if (isLoadingCharges) return null;
|
|
24
24
|
if (!subscription?.date_due) return null;
|
|
25
25
|
if (subscription.type === "free" || subscription.type === "trial")
|
|
26
26
|
return null;
|
|
@@ -31,17 +31,18 @@ function SubscriptionBanner({
|
|
|
31
31
|
const daysToDue = daysBetween(today, dueDate);
|
|
32
32
|
const hasPendingCharge = !!pendingChargesData?.data?.[0];
|
|
33
33
|
const isBoleto = subscription.payment_method === PAYMENT_METHOD_BOLETO;
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
34
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
35
|
+
const hasCancellation = !!subscription.date_cancellation;
|
|
36
|
+
const isEffectivelyCancelled = hasCancellation && !!expiryDate && today.getTime() >= expiryDate.getTime();
|
|
37
|
+
const isPendingCancellation = hasCancellation && !!expiryDate && today.getTime() < expiryDate.getTime();
|
|
38
|
+
if (isEffectivelyCancelled) {
|
|
38
39
|
return /* @__PURE__ */ jsx(CancelledSubscriptionBanner, { onReactivate });
|
|
39
40
|
}
|
|
40
|
-
if (
|
|
41
|
+
if (isPendingCancellation) {
|
|
41
42
|
return /* @__PURE__ */ jsx(
|
|
42
43
|
PendingCancellationBanner,
|
|
43
44
|
{
|
|
44
|
-
limitDate:
|
|
45
|
+
limitDate: expiryDate,
|
|
45
46
|
onReactivate
|
|
46
47
|
}
|
|
47
48
|
);
|
|
@@ -50,7 +51,7 @@ function SubscriptionBanner({
|
|
|
50
51
|
if (daysSinceDue > 0 && daysSinceDue <= SUBSCRIPTION_GRACE_PERIOD_DAYS) {
|
|
51
52
|
return /* @__PURE__ */ jsx(OverdueInvoiceBanner, { onDetails });
|
|
52
53
|
}
|
|
53
|
-
if (isBoleto && hasPendingCharge && daysToDue >= 0 && daysToDue <= UPCOMING_WINDOW_DAYS
|
|
54
|
+
if (isBoleto && !isLoadingCharges && hasPendingCharge && daysToDue >= 0 && daysToDue <= UPCOMING_WINDOW_DAYS) {
|
|
54
55
|
return /* @__PURE__ */ jsx(UpcomingInvoiceBanner, { dueDate, onDetails });
|
|
55
56
|
}
|
|
56
57
|
return null;
|
|
@@ -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 { 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 (
|
|
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 // Cutoff em que as páginas efetivamente saem do ar (whitelabel = date_cancellation,\n // client = date_due + grace). Mesma semântica de hasSubscriptionExpired e dos asserts.\n const expiryDate = getSubscriptionExpiryDate(subscription);\n const hasCancellation = !!subscription.date_cancellation;\n const isEffectivelyCancelled =\n hasCancellation && !!expiryDate && today.getTime() >= expiryDate.getTime();\n const isPendingCancellation =\n hasCancellation && !!expiryDate && today.getTime() < expiryDate.getTime();\n\n if (isEffectivelyCancelled) {\n return <CancelledSubscriptionBanner onReactivate={onReactivate} />;\n }\n\n if (isPendingCancellation) {\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":";AAsDW;AApDX,SAAS,6BAA6B;AACtC,SAAS,kBAAkB;AAC3B,SAAS,sCAAsC;AAC/C,SAAS,iCAAiC;AAC1C,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;AAIjD,QAAM,aAAa,0BAA0B,YAAY;AACzD,QAAM,kBAAkB,CAAC,CAAC,aAAa;AACvC,QAAM,yBACJ,mBAAmB,CAAC,CAAC,cAAc,MAAM,QAAQ,KAAK,WAAW,QAAQ;AAC3E,QAAM,wBACJ,mBAAmB,CAAC,CAAC,cAAc,MAAM,QAAQ,IAAI,WAAW,QAAQ;AAE1E,MAAI,wBAAwB;AAC1B,WAAO,oBAAC,+BAA4B,cAA4B;AAAA,EAClE;AAEA,MAAI,uBAAuB;AACzB,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":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import { ApiError } from "../../../infra/api/types";
|
|
3
3
|
import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
|
|
4
|
-
import {
|
|
4
|
+
import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
|
|
5
5
|
async function assertManagementSubscription() {
|
|
6
6
|
const result = await listSubscriptionsAction({ active: true });
|
|
7
7
|
if (!result.success) {
|
|
@@ -19,8 +19,8 @@ async function assertManagementSubscription() {
|
|
|
19
19
|
403
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
|
-
const
|
|
23
|
-
const isExpired =
|
|
22
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
23
|
+
const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
24
24
|
if (isExpired) {
|
|
25
25
|
throw new ApiError(
|
|
26
26
|
"Sua assinatura est\xE1 vencida. Assine um novo plano para continuar.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/auth/utils/assert-management-subscription.ts"],"sourcesContent":["import \"server-only\";\n\nimport { ApiError } from \"../../../infra/api/types\";\nimport { listSubscriptionsAction } from \"../../subscriptions/actions/list-subscriptions.action\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/auth/utils/assert-management-subscription.ts"],"sourcesContent":["import \"server-only\";\n\nimport { ApiError } from \"../../../infra/api/types\";\nimport { listSubscriptionsAction } from \"../../subscriptions/actions/list-subscriptions.action\";\nimport { getSubscriptionExpiryDate } from \"../../subscriptions/utils/get-subscription-expiry-date\";\n\nexport async function assertManagementSubscription() {\n const result = await listSubscriptionsAction({ active: true });\n\n if (!result.success) {\n throw new ApiError(\n result.error || \"Não foi possível validar a assinatura atual\",\n \"SUBSCRIPTION_CONTEXT_FAILED\",\n 403,\n );\n }\n\n const subscription = result.data?.data?.[0];\n\n if (!subscription) {\n throw new ApiError(\n \"Nenhuma assinatura ativa encontrada. Assine um plano para continuar.\",\n \"SUBSCRIPTION_NOT_FOUND\",\n 403,\n );\n }\n\n const expiryDate = getSubscriptionExpiryDate(subscription);\n const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();\n\n if (isExpired) {\n throw new ApiError(\n \"Sua assinatura está vencida. Assine um novo plano para continuar.\",\n \"SUBSCRIPTION_EXPIRED\",\n 403,\n );\n }\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,gBAAgB;AACzB,SAAS,+BAA+B;AACxC,SAAS,iCAAiC;AAE1C,eAAsB,+BAA+B;AACnD,QAAM,SAAS,MAAM,wBAAwB,EAAE,QAAQ,KAAK,CAAC;AAE7D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,OAAO,SAAS;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,MAAM,OAAO,CAAC;AAE1C,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,0BAA0B,YAAY;AACzD,QAAM,YAAY,CAAC,CAAC,cAAc,KAAK,IAAI,KAAK,WAAW,QAAQ;AAEnE,MAAI,WAAW;AACb,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import { ApiError } from "../../../infra/api/types";
|
|
3
3
|
import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
|
|
4
|
-
import {
|
|
4
|
+
import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
|
|
5
5
|
async function assertPaidSubscription() {
|
|
6
6
|
const result = await listSubscriptionsAction({ active: true });
|
|
7
7
|
if (!result.success) {
|
|
@@ -19,8 +19,8 @@ async function assertPaidSubscription() {
|
|
|
19
19
|
403
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
|
-
const
|
|
23
|
-
const isExpired =
|
|
22
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
23
|
+
const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
24
24
|
if (isExpired) {
|
|
25
25
|
throw new ApiError(
|
|
26
26
|
"Sua assinatura est\xE1 vencida. Assine um novo plano para continuar.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/auth/utils/assert-paid-subscription.ts"],"sourcesContent":["import \"server-only\";\n\nimport { ApiError } from \"../../../infra/api/types\";\nimport { listSubscriptionsAction } from \"../../subscriptions/actions/list-subscriptions.action\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/auth/utils/assert-paid-subscription.ts"],"sourcesContent":["import \"server-only\";\n\nimport { ApiError } from \"../../../infra/api/types\";\nimport { listSubscriptionsAction } from \"../../subscriptions/actions/list-subscriptions.action\";\nimport { getSubscriptionExpiryDate } from \"../../subscriptions/utils/get-subscription-expiry-date\";\n\nexport async function assertPaidSubscription() {\n const result = await listSubscriptionsAction({ active: true });\n\n if (!result.success) {\n throw new ApiError(\n result.error || \"Não foi possível validar a assinatura atual\",\n \"SUBSCRIPTION_CONTEXT_FAILED\",\n 403,\n );\n }\n\n const subscription = result.data?.data?.[0];\n\n if (!subscription) {\n throw new ApiError(\n \"Nenhuma assinatura ativa encontrada. Assine um plano para continuar.\",\n \"SUBSCRIPTION_NOT_FOUND\",\n 403,\n );\n }\n\n const expiryDate = getSubscriptionExpiryDate(subscription);\n const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();\n\n if (isExpired) {\n throw new ApiError(\n \"Sua assinatura está vencida. Assine um novo plano para continuar.\",\n \"SUBSCRIPTION_EXPIRED\",\n 403,\n );\n }\n\n \n if (subscription.type === 'free' || subscription.id_plan === 5) {\n throw new ApiError(\n \"Este recurso está disponível apenas para planos pagos. Faça upgrade do seu plano para continuar.\",\n \"PAID_SUBSCRIPTION_REQUIRED\",\n 403,\n );\n }\n}\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,gBAAgB;AACzB,SAAS,+BAA+B;AACxC,SAAS,iCAAiC;AAE1C,eAAsB,yBAAyB;AAC7C,QAAM,SAAS,MAAM,wBAAwB,EAAE,QAAQ,KAAK,CAAC;AAE7D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,OAAO,SAAS;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,OAAO,MAAM,OAAO,CAAC;AAE1C,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,0BAA0B,YAAY;AACzD,QAAM,YAAY,CAAC,CAAC,cAAc,KAAK,IAAI,KAAK,WAAW,QAAQ;AAEnE,MAAI,WAAW;AACb,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,MAAI,aAAa,SAAS,UAAU,aAAa,YAAY,GAAG;AAC9D,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -4,7 +4,7 @@ import { UserProfile } from "../../users/schema";
|
|
|
4
4
|
import { useAuth } from "../../../providers/auth.provider";
|
|
5
5
|
import { useModalManager } from "../../../store/useModalManager";
|
|
6
6
|
import { useActiveSubscription } from "./find-active-subscription.hook";
|
|
7
|
-
import {
|
|
7
|
+
import { getSubscriptionExpiryDate } from "../utils/get-subscription-expiry-date";
|
|
8
8
|
function useCancelledSubscriptionGuard() {
|
|
9
9
|
const { user } = useAuth();
|
|
10
10
|
const { openModal } = useModalManager();
|
|
@@ -12,8 +12,8 @@ function useCancelledSubscriptionGuard() {
|
|
|
12
12
|
data: { data: [subscription] = [] } = {}
|
|
13
13
|
} = useActiveSubscription();
|
|
14
14
|
const isViewer = user?.profile === UserProfile.viewer;
|
|
15
|
-
const
|
|
16
|
-
const isCancelledSubscription =
|
|
15
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
16
|
+
const isCancelledSubscription = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
17
17
|
const shouldBlockManagementAction = !isViewer && isCancelledSubscription;
|
|
18
18
|
const guardAction = useCallback(
|
|
19
19
|
(action) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/subscriptions/hooks/use-cancelled-subscription-guard.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 {
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/hooks/use-cancelled-subscription-guard.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 { getSubscriptionExpiryDate } from '../utils/get-subscription-expiry-date';\n\nexport function useCancelledSubscriptionGuard() {\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 expiryDate = getSubscriptionExpiryDate(subscription);\n const isCancelledSubscription = !!expiryDate && Date.now() >= expiryDate.getTime();\n const shouldBlockManagementAction = !isViewer && isCancelledSubscription;\n\n const guardAction = useCallback(\n (action?: () => void) => {\n if (shouldBlockManagementAction) {\n openModal('subscriptionExpiredModal');\n return false;\n }\n\n action?.();\n return true;\n },\n [openModal, shouldBlockManagementAction]\n );\n\n return {\n isCancelledSubscription,\n shouldBlockManagementAction,\n guardAction,\n };\n}\n"],"mappings":";AAEA,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,iCAAiC;AAEnC,SAAS,gCAAgC;AAC9C,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,aAAa,0BAA0B,YAAY;AACzD,QAAM,0BAA0B,CAAC,CAAC,cAAc,KAAK,IAAI,KAAK,WAAW,QAAQ;AACjF,QAAM,8BAA8B,CAAC,YAAY;AAEjD,QAAM,cAAc;AAAA,IAClB,CAAC,WAAwB;AACvB,UAAI,6BAA6B;AAC/B,kBAAU,0BAA0B;AACpC,eAAO;AAAA,MACT;AAEA,eAAS;AACT,aAAO;AAAA,IACT;AAAA,IACA,CAAC,WAAW,2BAA2B;AAAA,EACzC;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "../constants/subscription.constants";
|
|
2
|
+
import { toCalendarDate } from "../../../infra/utils/date";
|
|
3
|
+
function getSubscriptionExpiryDate(subscription) {
|
|
4
|
+
if (!subscription) return null;
|
|
5
|
+
if (subscription.type === "whitelabel") {
|
|
6
|
+
return toCalendarDate(subscription.date_cancellation);
|
|
7
|
+
}
|
|
8
|
+
const dueDate = toCalendarDate(subscription.date_due);
|
|
9
|
+
if (!dueDate) return null;
|
|
10
|
+
if (subscription.date_cancellation) return dueDate;
|
|
11
|
+
const result = new Date(dueDate);
|
|
12
|
+
result.setDate(result.getDate() + SUBSCRIPTION_GRACE_PERIOD_DAYS);
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
getSubscriptionExpiryDate
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=get-subscription-expiry-date.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/utils/get-subscription-expiry-date.ts"],"sourcesContent":["import { SUBSCRIPTION_GRACE_PERIOD_DAYS } from \"../constants/subscription.constants\";\nimport { toCalendarDate } from \"../../../infra/utils/date\";\nimport type { Subscription } from \"../types/subscription.type\";\n\n/**\n * Data efetiva em que as páginas saem do ar / a assinatura é considerada expirada.\n *\n * - whitelabel: date_cancellation (sem grace).\n * - client com cancelamento explícito: date_due (sem grace — cliente pediu pra sair).\n * - client em atraso (sem cancelamento): date_due + SUBSCRIPTION_GRACE_PERIOD_DAYS.\n */\nexport function getSubscriptionExpiryDate(\n subscription:\n | Pick<Subscription, \"type\" | \"date_due\" | \"date_cancellation\">\n | undefined\n | null,\n): Date | null {\n if (!subscription) return null;\n\n if (subscription.type === \"whitelabel\") {\n return toCalendarDate(subscription.date_cancellation);\n }\n\n const dueDate = toCalendarDate(subscription.date_due);\n\n if (!dueDate) return null;\n\n if (subscription.date_cancellation) return dueDate;\n\n const result = new Date(dueDate);\n result.setDate(result.getDate() + SUBSCRIPTION_GRACE_PERIOD_DAYS);\n return result;\n}\n"],"mappings":"AAAA,SAAS,sCAAsC;AAC/C,SAAS,sBAAsB;AAUxB,SAAS,0BACd,cAIa;AACb,MAAI,CAAC,aAAc,QAAO;AAE1B,MAAI,aAAa,SAAS,cAAc;AACtC,WAAO,eAAe,aAAa,iBAAiB;AAAA,EACtD;AAEA,QAAM,UAAU,eAAe,aAAa,QAAQ;AAEpD,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,aAAa,kBAAmB,QAAO;AAE3C,QAAM,SAAS,IAAI,KAAK,OAAO;AAC/B,SAAO,QAAQ,OAAO,QAAQ,IAAI,8BAA8B;AAChE,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ 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
6
|
import { toCalendarDate } from '../../infra/utils/date';
|
|
7
|
+
import { getSubscriptionExpiryDate } from '../../modules/subscriptions/utils/get-subscription-expiry-date';
|
|
7
8
|
|
|
8
9
|
interface CancelledSubscriptionBannerProps {
|
|
9
10
|
onReactivate?: () => void;
|
|
@@ -25,11 +26,11 @@ export function CancelledSubscriptionBanner({ onReactivate }: CancelledSubscript
|
|
|
25
26
|
if (subscription?.type === 'free' || subscription?.type === 'trial') return null;
|
|
26
27
|
|
|
27
28
|
const today = toCalendarDate(new Date());
|
|
28
|
-
const
|
|
29
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
29
30
|
|
|
30
|
-
// Só mostra
|
|
31
|
-
if (!today || !
|
|
32
|
-
if (today.getTime() <
|
|
31
|
+
// Só mostra se há cancelamento E o período (com grace, no caso de client) já terminou.
|
|
32
|
+
if (!today || !expiryDate || !subscription?.date_cancellation) return null;
|
|
33
|
+
if (today.getTime() < expiryDate.getTime()) return null;
|
|
33
34
|
|
|
34
35
|
return (
|
|
35
36
|
<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">
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useActiveSubscription } from "../../modules/subscriptions/hooks/find-active-subscription.hook";
|
|
4
4
|
import { useCharges } from "../../modules/charges/hooks/charges.hook";
|
|
5
5
|
import { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "../../modules/subscriptions/constants/subscription.constants";
|
|
6
|
+
import { getSubscriptionExpiryDate } from "../../modules/subscriptions/utils/get-subscription-expiry-date";
|
|
6
7
|
import { toCalendarDate, daysBetween } from "../../infra/utils/date";
|
|
7
8
|
import { OverdueInvoiceBanner } from "./OverdueInvoiceBanner";
|
|
8
9
|
import { UpcomingInvoiceBanner } from "./UpcomingInvoiceBanner";
|
|
@@ -28,7 +29,6 @@ export function SubscriptionBanner({
|
|
|
28
29
|
status: [0],
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
if (isLoadingCharges) return null;
|
|
32
32
|
if (!subscription?.date_due) return null;
|
|
33
33
|
if (subscription.type === "free" || subscription.type === "trial")
|
|
34
34
|
return null;
|
|
@@ -42,25 +42,23 @@ export function SubscriptionBanner({
|
|
|
42
42
|
const hasPendingCharge = !!pendingChargesData?.data?.[0];
|
|
43
43
|
const isBoleto = subscription.payment_method === PAYMENT_METHOD_BOLETO;
|
|
44
44
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
(!subscription.date_due ||
|
|
54
|
-
today.getTime() >= toCalendarDate(subscription.date_due)!.getTime());
|
|
45
|
+
// Cutoff em que as páginas efetivamente saem do ar (whitelabel = date_cancellation,
|
|
46
|
+
// client = date_due + grace). Mesma semântica de hasSubscriptionExpired e dos asserts.
|
|
47
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
48
|
+
const hasCancellation = !!subscription.date_cancellation;
|
|
49
|
+
const isEffectivelyCancelled =
|
|
50
|
+
hasCancellation && !!expiryDate && today.getTime() >= expiryDate.getTime();
|
|
51
|
+
const isPendingCancellation =
|
|
52
|
+
hasCancellation && !!expiryDate && today.getTime() < expiryDate.getTime();
|
|
55
53
|
|
|
56
|
-
if (
|
|
54
|
+
if (isEffectivelyCancelled) {
|
|
57
55
|
return <CancelledSubscriptionBanner onReactivate={onReactivate} />;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
if (
|
|
58
|
+
if (isPendingCancellation) {
|
|
61
59
|
return (
|
|
62
60
|
<PendingCancellationBanner
|
|
63
|
-
limitDate={
|
|
61
|
+
limitDate={expiryDate}
|
|
64
62
|
onReactivate={onReactivate}
|
|
65
63
|
/>
|
|
66
64
|
);
|
|
@@ -74,10 +72,10 @@ export function SubscriptionBanner({
|
|
|
74
72
|
|
|
75
73
|
if (
|
|
76
74
|
isBoleto &&
|
|
75
|
+
!isLoadingCharges &&
|
|
77
76
|
hasPendingCharge &&
|
|
78
77
|
daysToDue >= 0 &&
|
|
79
78
|
daysToDue <= UPCOMING_WINDOW_DAYS
|
|
80
|
-
&& !hasFutureCancellation
|
|
81
79
|
) {
|
|
82
80
|
return <UpcomingInvoiceBanner dueDate={dueDate} onDetails={onDetails} />;
|
|
83
81
|
}
|
|
@@ -2,7 +2,7 @@ import "server-only";
|
|
|
2
2
|
|
|
3
3
|
import { ApiError } from "../../../infra/api/types";
|
|
4
4
|
import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
|
|
5
|
-
import {
|
|
5
|
+
import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
|
|
6
6
|
|
|
7
7
|
export async function assertManagementSubscription() {
|
|
8
8
|
const result = await listSubscriptionsAction({ active: true });
|
|
@@ -25,10 +25,8 @@ export async function assertManagementSubscription() {
|
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
const isExpired =
|
|
30
|
-
? !!subscription?.date_cancellation && new Date() >= new Date(subscription.date_cancellation)
|
|
31
|
-
: hasSubscriptionExpired(subscription?.date_due);
|
|
28
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
29
|
+
const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
32
30
|
|
|
33
31
|
if (isExpired) {
|
|
34
32
|
throw new ApiError(
|
|
@@ -2,7 +2,7 @@ import "server-only";
|
|
|
2
2
|
|
|
3
3
|
import { ApiError } from "../../../infra/api/types";
|
|
4
4
|
import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
|
|
5
|
-
import {
|
|
5
|
+
import { getSubscriptionExpiryDate } from "../../subscriptions/utils/get-subscription-expiry-date";
|
|
6
6
|
|
|
7
7
|
export async function assertPaidSubscription() {
|
|
8
8
|
const result = await listSubscriptionsAction({ active: true });
|
|
@@ -25,10 +25,8 @@ export async function assertPaidSubscription() {
|
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
const isExpired =
|
|
30
|
-
? !!subscription.date_cancellation && new Date() >= new Date(subscription.date_cancellation)
|
|
31
|
-
: hasSubscriptionExpired(subscription.date_due);
|
|
28
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
29
|
+
const isExpired = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
32
30
|
|
|
33
31
|
if (isExpired) {
|
|
34
32
|
throw new ApiError(
|
|
@@ -5,7 +5,7 @@ import { UserProfile } from '../../users/schema';
|
|
|
5
5
|
import { useAuth } from '../../../providers/auth.provider';
|
|
6
6
|
import { useModalManager } from '../../../store/useModalManager';
|
|
7
7
|
import { useActiveSubscription } from './find-active-subscription.hook';
|
|
8
|
-
import {
|
|
8
|
+
import { getSubscriptionExpiryDate } from '../utils/get-subscription-expiry-date';
|
|
9
9
|
|
|
10
10
|
export function useCancelledSubscriptionGuard() {
|
|
11
11
|
const { user } = useAuth();
|
|
@@ -15,10 +15,8 @@ export function useCancelledSubscriptionGuard() {
|
|
|
15
15
|
} = useActiveSubscription();
|
|
16
16
|
|
|
17
17
|
const isViewer = user?.profile === UserProfile.viewer;
|
|
18
|
-
const
|
|
19
|
-
const isCancelledSubscription =
|
|
20
|
-
? !!subscription?.date_cancellation && new Date() >= new Date(subscription.date_cancellation)
|
|
21
|
-
: hasSubscriptionExpired(subscription?.date_due);
|
|
18
|
+
const expiryDate = getSubscriptionExpiryDate(subscription);
|
|
19
|
+
const isCancelledSubscription = !!expiryDate && Date.now() >= expiryDate.getTime();
|
|
22
20
|
const shouldBlockManagementAction = !isViewer && isCancelledSubscription;
|
|
23
21
|
|
|
24
22
|
const guardAction = useCallback(
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "../constants/subscription.constants";
|
|
2
|
+
import { toCalendarDate } from "../../../infra/utils/date";
|
|
3
|
+
import type { Subscription } from "../types/subscription.type";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Data efetiva em que as páginas saem do ar / a assinatura é considerada expirada.
|
|
7
|
+
*
|
|
8
|
+
* - whitelabel: date_cancellation (sem grace).
|
|
9
|
+
* - client com cancelamento explícito: date_due (sem grace — cliente pediu pra sair).
|
|
10
|
+
* - client em atraso (sem cancelamento): date_due + SUBSCRIPTION_GRACE_PERIOD_DAYS.
|
|
11
|
+
*/
|
|
12
|
+
export function getSubscriptionExpiryDate(
|
|
13
|
+
subscription:
|
|
14
|
+
| Pick<Subscription, "type" | "date_due" | "date_cancellation">
|
|
15
|
+
| undefined
|
|
16
|
+
| null,
|
|
17
|
+
): Date | null {
|
|
18
|
+
if (!subscription) return null;
|
|
19
|
+
|
|
20
|
+
if (subscription.type === "whitelabel") {
|
|
21
|
+
return toCalendarDate(subscription.date_cancellation);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const dueDate = toCalendarDate(subscription.date_due);
|
|
25
|
+
|
|
26
|
+
if (!dueDate) return null;
|
|
27
|
+
|
|
28
|
+
if (subscription.date_cancellation) return dueDate;
|
|
29
|
+
|
|
30
|
+
const result = new Date(dueDate);
|
|
31
|
+
result.setDate(result.getDate() + SUBSCRIPTION_GRACE_PERIOD_DAYS);
|
|
32
|
+
return result;
|
|
33
|
+
}
|