@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.
@@ -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 { toCalendarDate } from "../../infra/utils/date";
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
- const today = toCalendarDate(/* @__PURE__ */ new Date());
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 { toCalendarDate } from '../../infra/utils/date';\nimport { getSubscriptionExpiryDate } from '../../modules/subscriptions/utils/get-subscription-expiry-date';\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 const today = toCalendarDate(new Date());\n const expiryDate = getSubscriptionExpiryDate(subscription);\n\n // mostra se 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 {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":";AAyCQ,SACE,KADF;AAvCR,SAAS,UAAU,WAAW,mBAAmB;AACjD,SAAS,kBAAkB,aAAa;AACxC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,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;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,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":[]}
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 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 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) {
36
+ const cancellationState = getSubscriptionCancellationState(subscription);
37
+ if (cancellationState === "cancelled") {
39
38
  return /* @__PURE__ */ jsx(CancelledSubscriptionBanner, { onReactivate });
40
39
  }
41
- if (isPendingCancellation) {
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 // 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
+ {"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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.681",
3
+ "version": "1.1.683",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -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 { toCalendarDate } from '../../infra/utils/date';
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
- const today = toCalendarDate(new Date());
31
- const expiryDate = getSubscriptionExpiryDate(subscription);
32
-
33
- // Só mostra se há cancelamento E o período (com grace, no caso de client) terminou.
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
- // 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.
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 hasCancellation = !!subscription.date_cancellation;
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 (isEffectivelyCancelled) {
53
+ if (cancellationState === "cancelled") {
55
54
  return <CancelledSubscriptionBanner onReactivate={onReactivate} />;
56
55
  }
57
56
 
58
- if (isPendingCancellation) {
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
+ }