@greatapps/common 1.1.646 → 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.
Files changed (25) hide show
  1. package/dist/components/embeds/CrispEmbed.mjs +2 -46
  2. package/dist/components/embeds/CrispEmbed.mjs.map +1 -1
  3. package/dist/components/navigation/CancelledSubscriptionBanner.mjs +4 -3
  4. package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
  5. package/dist/components/navigation/SubscriptionBanner.mjs +9 -8
  6. package/dist/components/navigation/SubscriptionBanner.mjs.map +1 -1
  7. package/dist/modules/auth/utils/assert-management-subscription.mjs +3 -3
  8. package/dist/modules/auth/utils/assert-management-subscription.mjs.map +1 -1
  9. package/dist/modules/auth/utils/assert-paid-subscription.mjs +3 -3
  10. package/dist/modules/auth/utils/assert-paid-subscription.mjs.map +1 -1
  11. package/dist/modules/subscriptions/hooks/use-cancelled-subscription-guard.mjs +3 -3
  12. package/dist/modules/subscriptions/hooks/use-cancelled-subscription-guard.mjs.map +1 -1
  13. package/dist/modules/subscriptions/utils/get-subscription-expiry-date.mjs +18 -0
  14. package/dist/modules/subscriptions/utils/get-subscription-expiry-date.mjs.map +1 -0
  15. package/dist/providers/auth.provider.mjs +3 -6
  16. package/dist/providers/auth.provider.mjs.map +1 -1
  17. package/package.json +1 -1
  18. package/src/components/embeds/CrispEmbed.tsx +2 -49
  19. package/src/components/navigation/CancelledSubscriptionBanner.tsx +5 -4
  20. package/src/components/navigation/SubscriptionBanner.tsx +13 -15
  21. package/src/modules/auth/utils/assert-management-subscription.ts +3 -5
  22. package/src/modules/auth/utils/assert-paid-subscription.ts +3 -5
  23. package/src/modules/subscriptions/hooks/use-cancelled-subscription-guard.ts +3 -5
  24. package/src/modules/subscriptions/utils/get-subscription-expiry-date.ts +33 -0
  25. package/src/providers/auth.provider.tsx +3 -6
@@ -15,9 +15,8 @@ const CRISP_SCRIPT_URL = "https://client.crisp.chat/l.js";
15
15
  const CRISP_LOAD_DELAY = 800;
16
16
  function safeCrispPush(args) {
17
17
  try {
18
- const win = window;
19
- if (!win.$crisp) win.$crisp = [];
20
- win.$crisp.push(args);
18
+ const $crisp = window?.$crisp;
19
+ if ($crisp) $crisp.push(args);
21
20
  } catch {
22
21
  }
23
22
  }
@@ -39,28 +38,6 @@ function hideCrisp() {
39
38
  function showCrisp() {
40
39
  crispDo("chat:show");
41
40
  }
42
- const FREE_PLAN_WELCOME_PENDING_KEY = "crisp:free-plan-welcome:pending";
43
- const FREE_PLAN_WELCOME_SHOWN_KEY = "crisp:free-plan-welcome:shown";
44
- const FREE_PLAN_WELCOME_MESSAGE = `Oi! Tenho uma novidade exclusiva para voc\xEA \u{1F389}
45
-
46
- Criamos o Plano Iniciante pensando exatamente em voc\xEA: R$ 14,90/m\xEAs para quem j\xE1 deu o primeiro passo e quer ir al\xE9m.
47
-
48
- Com ele voc\xEA conecta seu pr\xF3prio dom\xEDnio, publica at\xE9 3 p\xE1ginas e libera recursos como scripts, API Meta, exporta\xE7\xE3o de leads e possibilidade de contratar GreatAI.
49
-
50
- Acesse o menu "Assinaturas" e assine agora!`;
51
- function markFreePlanWelcomePending(userId) {
52
- try {
53
- if (typeof window === "undefined") return;
54
- window.localStorage.setItem(FREE_PLAN_WELCOME_PENDING_KEY, String(userId));
55
- } catch {
56
- }
57
- }
58
- function sendCrispMessage(text) {
59
- safeCrispPush(["do", "message:show", ["text", text]]);
60
- }
61
- function openCrispChat() {
62
- crispDo("chat:open");
63
- }
64
41
  function updateCrispUser(data) {
65
42
  try {
66
43
  if (data.email) safeCrispPush(["set", "user:email", data.email]);
@@ -138,33 +115,12 @@ function CrispEmbed() {
138
115
  } catch {
139
116
  }
140
117
  }, [user, account, isV4, shouldLoadCrisp, subscription?.plan_name]);
141
- useEffect(() => {
142
- if (!user || !shouldLoadCrisp) return;
143
- try {
144
- const userId = String(user.id);
145
- const pending = window.localStorage.getItem(FREE_PLAN_WELCOME_PENDING_KEY);
146
- if (pending !== userId) return;
147
- const shownKey = `${FREE_PLAN_WELCOME_SHOWN_KEY}:${userId}`;
148
- if (window.localStorage.getItem(shownKey)) {
149
- window.localStorage.removeItem(FREE_PLAN_WELCOME_PENDING_KEY);
150
- return;
151
- }
152
- sendCrispMessage(FREE_PLAN_WELCOME_MESSAGE);
153
- openCrispChat();
154
- window.localStorage.setItem(shownKey, "1");
155
- window.localStorage.removeItem(FREE_PLAN_WELCOME_PENDING_KEY);
156
- } catch {
157
- }
158
- }, [user, shouldLoadCrisp]);
159
118
  return null;
160
119
  }
161
120
  export {
162
121
  CrispEmbed,
163
122
  hideCrisp,
164
- markFreePlanWelcomePending,
165
- openCrispChat,
166
123
  openCrispHelpdesk,
167
- sendCrispMessage,
168
124
  showCrisp,
169
125
  updateCrispUser
170
126
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/embeds/CrispEmbed.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useAuth } from '../../providers/auth.provider';\nimport { useIsDefaultWhitelabel, useWhitelabel } from '../../providers/whitelabel.provider';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\n\nconst V4_WHITELABEL_ID = 367568;\n\nconst PROFILE_LABELS: Record<string, string> = {\n owner: 'Proprietário',\n admin: 'Administrador',\n editor: 'Colaborador',\n viewer: 'Visualizador',\n};\n\nconst CRISP_WEBSITE_ID = '3339a3ec-b4c6-4aac-8b86-45668fb7655c';\nconst CRISP_SCRIPT_URL = 'https://client.crisp.chat/l.js';\nconst CRISP_LOAD_DELAY = 800;\n\n// ── Crisp helpers ──────────────────────────────────────────────\n\nfunction safeCrispPush(args: unknown[]) {\n try {\n const win = window as any;\n if (!win.$crisp) win.$crisp = [];\n win.$crisp.push(args);\n } catch {}\n}\n\nfunction crispDo(action: string) {\n safeCrispPush(['do', action]);\n}\n\nfunction crispSegment(segment: string) {\n safeCrispPush(['set', 'session:segments', [[segment]]]);\n}\n\nfunction crispSessionData(key: string, value: string) {\n safeCrispPush(['set', 'session:data', [[[key, value]]]]);\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport function openCrispHelpdesk() {\n crispDo('helpdesk:search');\n}\n\nexport function hideCrisp() {\n crispDo('chat:hide');\n}\n\nexport function showCrisp() {\n crispDo('chat:show');\n}\n\nconst FREE_PLAN_WELCOME_PENDING_KEY = 'crisp:free-plan-welcome:pending';\nconst FREE_PLAN_WELCOME_SHOWN_KEY = 'crisp:free-plan-welcome:shown';\nconst FREE_PLAN_WELCOME_MESSAGE = `Oi! Tenho uma novidade exclusiva para você 🎉\n\nCriamos o Plano Iniciante pensando exatamente em você: R$ 14,90/mês para quem já deu o primeiro passo e quer ir além.\n\nCom ele você conecta seu próprio domínio, publica até 3 páginas e libera recursos como scripts, API Meta, exportação de leads e possibilidade de contratar GreatAI.\n\nAcesse o menu \"Assinaturas\" e assine agora!`;\n\nexport function markFreePlanWelcomePending(userId: number | string) {\n try {\n if (typeof window === 'undefined') return;\n window.localStorage.setItem(FREE_PLAN_WELCOME_PENDING_KEY, String(userId));\n } catch {}\n}\n\nexport function sendCrispMessage(text: string) {\n safeCrispPush(['do', 'message:show', ['text', text]]);\n}\n\nexport function openCrispChat() {\n crispDo('chat:open');\n}\n\nexport function updateCrispUser(data: {\n email?: string;\n name?: string;\n lastName?: string;\n company?: string;\n ddi?: string;\n phone?: string;\n photo?: string;\n}) {\n try {\n if (data.email) safeCrispPush(['set', 'user:email', data.email]);\n\n const fullName = `${data.name ?? ''} ${data.lastName ?? ''}`.trim();\n if (fullName) safeCrispPush(['set', 'user:nickname', fullName]);\n\n if (data.company) safeCrispPush(['set', 'user:company', data.company]);\n\n const phone = `${data.ddi ?? ''}${data.phone ?? ''}`.trim();\n if (phone) safeCrispPush(['set', 'user:phone', phone]);\n\n if (data.photo?.startsWith('http')) {\n safeCrispPush(['set', 'user:avatar', data.photo]);\n }\n } catch {}\n}\n\n// ── Component ──────────────────────────────────────────────────\n\nexport function CrispEmbed() {\n const { user, account } = useAuth();\n const { whitelabel } = useWhitelabel();\n const { data: subscriptionData } = useActiveSubscription();\n const scriptLoadedRef = useRef(false);\n\n const isGreat = useIsDefaultWhitelabel();\n const subscription = subscriptionData?.data?.[0] ?? null;\n const isV4 = whitelabel?.id === V4_WHITELABEL_ID;\n const shouldLoadCrisp = isV4 || isGreat;\n\n // Load Crisp script (once, with 800ms delay)\n useEffect(() => {\n if (!user || !shouldLoadCrisp || scriptLoadedRef.current) return;\n\n try {\n const win = window as any;\n\n if (!win.$crisp) {\n win.$crisp = [];\n win.CRISP_WEBSITE_ID = CRISP_WEBSITE_ID;\n win.$crisp.push(['config', 'container:index', [100]]);\n }\n\n if (document.querySelector(`script[src=\"${CRISP_SCRIPT_URL}\"]`)) {\n scriptLoadedRef.current = true;\n return;\n }\n\n const timeout = setTimeout(() => {\n try {\n if (document.querySelector(`script[src=\"${CRISP_SCRIPT_URL}\"]`)) return;\n const s = document.createElement('script');\n s.src = CRISP_SCRIPT_URL;\n s.async = true;\n document.head.appendChild(s);\n scriptLoadedRef.current = true;\n } catch {}\n }, CRISP_LOAD_DELAY);\n\n return () => clearTimeout(timeout);\n } catch {}\n }, [user, shouldLoadCrisp]);\n\n // Identify user\n useEffect(() => {\n try {\n if (!user || !shouldLoadCrisp || !(window as any)?.$crisp) return;\n\n updateCrispUser({\n email: user.email,\n name: user.name,\n lastName: user.last_name,\n company: account?.name,\n ddi: user.ddi,\n phone: user.phone,\n photo: user.photo ?? undefined,\n });\n\n if (isV4) {\n crispSegment('company_v4');\n crispSessionData('Company', 'V4');\n } else {\n const planName = subscription?.plan_name ?? '';\n const profileLabel = PROFILE_LABELS[user.profile ?? ''] ?? user.profile ?? '';\n if (planName) crispSegment(`plano_${planName}`);\n if (profileLabel) crispSegment(`usuario_${profileLabel}`);\n if (planName) crispSessionData('Plano', planName);\n if (profileLabel) crispSessionData('Usuario', profileLabel);\n }\n } catch {}\n }, [user, account, isV4, shouldLoadCrisp, subscription?.plan_name]);\n\n // Mostra mensagem de boas-vindas no Crisp para quem acabou de se cadastrar no plano free.\n useEffect(() => {\n if (!user || !shouldLoadCrisp) return;\n try {\n const userId = String(user.id);\n const pending = window.localStorage.getItem(FREE_PLAN_WELCOME_PENDING_KEY);\n if (pending !== userId) return;\n\n const shownKey = `${FREE_PLAN_WELCOME_SHOWN_KEY}:${userId}`;\n if (window.localStorage.getItem(shownKey)) {\n window.localStorage.removeItem(FREE_PLAN_WELCOME_PENDING_KEY);\n return;\n }\n\n sendCrispMessage(FREE_PLAN_WELCOME_MESSAGE);\n openCrispChat();\n window.localStorage.setItem(shownKey, '1');\n window.localStorage.removeItem(FREE_PLAN_WELCOME_PENDING_KEY);\n } catch {}\n }, [user, shouldLoadCrisp]);\n\n return null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/embeds/CrispEmbed.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useAuth } from '../../providers/auth.provider';\nimport { useIsDefaultWhitelabel, useWhitelabel } from '../../providers/whitelabel.provider';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\n\nconst V4_WHITELABEL_ID = 367568;\n\nconst PROFILE_LABELS: Record<string, string> = {\n owner: 'Proprietário',\n admin: 'Administrador',\n editor: 'Colaborador',\n viewer: 'Visualizador',\n};\n\nconst CRISP_WEBSITE_ID = '3339a3ec-b4c6-4aac-8b86-45668fb7655c';\nconst CRISP_SCRIPT_URL = 'https://client.crisp.chat/l.js';\nconst CRISP_LOAD_DELAY = 800;\n\n// ── Crisp helpers ──────────────────────────────────────────────\n\nfunction safeCrispPush(args: unknown[]) {\n try {\n const $crisp = (window as any)?.$crisp;\n if ($crisp) $crisp.push(args);\n } catch {}\n}\n\nfunction crispDo(action: string) {\n safeCrispPush(['do', action]);\n}\n\nfunction crispSegment(segment: string) {\n safeCrispPush(['set', 'session:segments', [[segment]]]);\n}\n\nfunction crispSessionData(key: string, value: string) {\n safeCrispPush(['set', 'session:data', [[[key, value]]]]);\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport function openCrispHelpdesk() {\n crispDo('helpdesk:search');\n}\n\nexport function hideCrisp() {\n crispDo('chat:hide');\n}\n\nexport function showCrisp() {\n crispDo('chat:show');\n}\n\nexport function updateCrispUser(data: {\n email?: string;\n name?: string;\n lastName?: string;\n company?: string;\n ddi?: string;\n phone?: string;\n photo?: string;\n}) {\n try {\n if (data.email) safeCrispPush(['set', 'user:email', data.email]);\n\n const fullName = `${data.name ?? ''} ${data.lastName ?? ''}`.trim();\n if (fullName) safeCrispPush(['set', 'user:nickname', fullName]);\n\n if (data.company) safeCrispPush(['set', 'user:company', data.company]);\n\n const phone = `${data.ddi ?? ''}${data.phone ?? ''}`.trim();\n if (phone) safeCrispPush(['set', 'user:phone', phone]);\n\n if (data.photo?.startsWith('http')) {\n safeCrispPush(['set', 'user:avatar', data.photo]);\n }\n } catch {}\n}\n\n// ── Component ──────────────────────────────────────────────────\n\nexport function CrispEmbed() {\n const { user, account } = useAuth();\n const { whitelabel } = useWhitelabel();\n const { data: subscriptionData } = useActiveSubscription();\n const scriptLoadedRef = useRef(false);\n\n const isGreat = useIsDefaultWhitelabel();\n const subscription = subscriptionData?.data?.[0] ?? null;\n const isV4 = whitelabel?.id === V4_WHITELABEL_ID;\n const shouldLoadCrisp = isV4 || isGreat;\n\n // Load Crisp script (once, with 800ms delay)\n useEffect(() => {\n if (!user || !shouldLoadCrisp || scriptLoadedRef.current) return;\n\n try {\n const win = window as any;\n\n if (!win.$crisp) {\n win.$crisp = [];\n win.CRISP_WEBSITE_ID = CRISP_WEBSITE_ID;\n win.$crisp.push(['config', 'container:index', [100]]);\n }\n\n if (document.querySelector(`script[src=\"${CRISP_SCRIPT_URL}\"]`)) {\n scriptLoadedRef.current = true;\n return;\n }\n\n const timeout = setTimeout(() => {\n try {\n if (document.querySelector(`script[src=\"${CRISP_SCRIPT_URL}\"]`)) return;\n const s = document.createElement('script');\n s.src = CRISP_SCRIPT_URL;\n s.async = true;\n document.head.appendChild(s);\n scriptLoadedRef.current = true;\n } catch {}\n }, CRISP_LOAD_DELAY);\n\n return () => clearTimeout(timeout);\n } catch {}\n }, [user, shouldLoadCrisp]);\n\n // Identify user\n useEffect(() => {\n try {\n if (!user || !shouldLoadCrisp || !(window as any)?.$crisp) return;\n\n updateCrispUser({\n email: user.email,\n name: user.name,\n lastName: user.last_name,\n company: account?.name,\n ddi: user.ddi,\n phone: user.phone,\n photo: user.photo ?? undefined,\n });\n\n if (isV4) {\n crispSegment('company_v4');\n crispSessionData('Company', 'V4');\n } else {\n const planName = subscription?.plan_name ?? '';\n const profileLabel = PROFILE_LABELS[user.profile ?? ''] ?? user.profile ?? '';\n if (planName) crispSegment(`plano_${planName}`);\n if (profileLabel) crispSegment(`usuario_${profileLabel}`);\n if (planName) crispSessionData('Plano', planName);\n if (profileLabel) crispSessionData('Usuario', profileLabel);\n }\n } catch {}\n }, [user, account, isV4, shouldLoadCrisp, subscription?.plan_name]);\n\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -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 cancellationDate = toCalendarDate(subscription?.date_cancellation);
20
- if (!today || !cancellationDate) return null;
21
- if (today.getTime() < cancellationDate.getTime()) return null;
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 cancellationDate = toCalendarDate(subscription?.date_cancellation);\n\n // Só mostra o banner se o cancelamento existe e efetivou (hoje >= data cancelamento).\n if (!today || !cancellationDate) return null;\n if (today.getTime() < cancellationDate.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":";AAsCQ,SACE,KADF;AApCR,SAAS,UAAU,WAAW,mBAAmB;AACjD,SAAS,kBAAkB,aAAa;AACxC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAMxB,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,mBAAmB,eAAe,cAAc,iBAAiB;AAGvE,MAAI,CAAC,SAAS,CAAC,iBAAkB,QAAO;AACxC,MAAI,MAAM,QAAQ,IAAI,iBAAiB,QAAQ,EAAG,QAAO;AAEzD,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":[]}
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 cancelamento E o período (com grace, no caso de client) 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 cancellationDate = toCalendarDate(subscription.date_cancellation);
35
- const hasFutureCancellation = !!subscription.date_cancellation && cancellationDate.getTime() > today.getTime();
36
- const isCancelled = hasFutureCancellation && (!subscription.date_due || today.getTime() >= toCalendarDate(subscription.date_due).getTime());
37
- if (isCancelled) {
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 (hasFutureCancellation) {
41
+ if (isPendingCancellation) {
41
42
  return /* @__PURE__ */ jsx(
42
43
  PendingCancellationBanner,
43
44
  {
44
- limitDate: cancellationDate,
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 && !hasFutureCancellation) {
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 (isLoadingCharges) return null;\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 // Cancelamento é efetivo quando date_cancellation existe E passou do dia de hoje\n // (comparação por calendar-day em horário local, via toCalendarDate).\n const cancellationDate = toCalendarDate(subscription.date_cancellation);\n const hasFutureCancellation =\n !!subscription.date_cancellation &&\n cancellationDate!.getTime() > today.getTime();\n const isCancelled =\n hasFutureCancellation &&\n (!subscription.date_due ||\n today.getTime() >= toCalendarDate(subscription.date_due)!.getTime());\n\n if (isCancelled) {\n return <CancelledSubscriptionBanner onReactivate={onReactivate} />;\n }\n\n if (hasFutureCancellation) {\n return (\n <PendingCancellationBanner\n limitDate={cancellationDate}\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 hasPendingCharge &&\n daysToDue >= 0 &&\n daysToDue <= UPCOMING_WINDOW_DAYS\n && !hasFutureCancellation\n ) {\n return <UpcomingInvoiceBanner dueDate={dueDate} onDetails={onDetails} />;\n }\n\n return null;\n}\n"],"mappings":";AAwDW;AAtDX,SAAS,6BAA6B;AACtC,SAAS,kBAAkB;AAC3B,SAAS,sCAAsC;AAC/C,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,iBAAkB,QAAO;AAC7B,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,mBAAmB,eAAe,aAAa,iBAAiB;AACtE,QAAM,wBACJ,CAAC,CAAC,aAAa,qBACf,iBAAkB,QAAQ,IAAI,MAAM,QAAQ;AAC9C,QAAM,cACJ,0BACC,CAAC,aAAa,YACb,MAAM,QAAQ,KAAK,eAAe,aAAa,QAAQ,EAAG,QAAQ;AAEtE,MAAI,aAAa;AACf,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,oBACA,aAAa,KACb,aAAa,wBACV,CAAC,uBACJ;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 { 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 { hasSubscriptionExpired } from "../../subscriptions/utils/has-subscription-expired";
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 isWhitelabelType = subscription?.type === "whitelabel";
23
- const isExpired = isWhitelabelType ? !!subscription?.date_cancellation && /* @__PURE__ */ new Date() >= new Date(subscription.date_cancellation) : hasSubscriptionExpired(subscription?.date_due);
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 { hasSubscriptionExpired } from \"../../subscriptions/utils/has-subscription-expired\";\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 isWhitelabelType = subscription?.type === 'whitelabel';\n const isExpired = isWhitelabelType\n ? !!subscription?.date_cancellation && new Date() >= new Date(subscription.date_cancellation)\n : hasSubscriptionExpired(subscription?.date_due);\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,8BAA8B;AAEvC,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,mBAAmB,cAAc,SAAS;AAChD,QAAM,YAAY,mBACd,CAAC,CAAC,cAAc,qBAAqB,oBAAI,KAAK,KAAK,IAAI,KAAK,aAAa,iBAAiB,IAC1F,uBAAuB,cAAc,QAAQ;AAEjD,MAAI,WAAW;AACb,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
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 { hasSubscriptionExpired } from "../../subscriptions/utils/has-subscription-expired";
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 isWhitelabelType = subscription.type === "whitelabel";
23
- const isExpired = isWhitelabelType ? !!subscription.date_cancellation && /* @__PURE__ */ new Date() >= new Date(subscription.date_cancellation) : hasSubscriptionExpired(subscription.date_due);
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 { hasSubscriptionExpired } from \"../../subscriptions/utils/has-subscription-expired\";\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 isWhitelabelType = subscription.type === 'whitelabel';\n const isExpired = isWhitelabelType\n ? !!subscription.date_cancellation && new Date() >= new Date(subscription.date_cancellation)\n : hasSubscriptionExpired(subscription.date_due);\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,8BAA8B;AAEvC,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,mBAAmB,aAAa,SAAS;AAC/C,QAAM,YAAY,mBACd,CAAC,CAAC,aAAa,qBAAqB,oBAAI,KAAK,KAAK,IAAI,KAAK,aAAa,iBAAiB,IACzF,uBAAuB,aAAa,QAAQ;AAEhD,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":[]}
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 { hasSubscriptionExpired } from "../utils/has-subscription-expired";
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 isWhitelabelType = subscription?.type === "whitelabel";
16
- const isCancelledSubscription = isWhitelabelType ? !!subscription?.date_cancellation && /* @__PURE__ */ new Date() >= new Date(subscription.date_cancellation) : hasSubscriptionExpired(subscription?.date_due);
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 { hasSubscriptionExpired } from '../utils/has-subscription-expired';\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 isWhitelabelType = subscription?.type === 'whitelabel';\n const isCancelledSubscription = isWhitelabelType\n ? !!subscription?.date_cancellation && new Date() >= new Date(subscription.date_cancellation)\n : hasSubscriptionExpired(subscription?.date_due);\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,8BAA8B;AAEhC,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,mBAAmB,cAAc,SAAS;AAChD,QAAM,0BAA0B,mBAC5B,CAAC,CAAC,cAAc,qBAAqB,oBAAI,KAAK,KAAK,IAAI,KAAK,aAAa,iBAAiB,IAC1F,uBAAuB,cAAc,QAAQ;AACjD,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":[]}
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":[]}
@@ -17,7 +17,6 @@ import { useRegister } from "../modules/auth/hooks/register.hook";
17
17
  import { useLogout } from "../modules/auth/hooks/logout.hook";
18
18
  import { useSocialGoogleLogin } from "../modules/auth/hooks/social-google-login.hook";
19
19
  import { useSocialGoogleOnboarding } from "../modules/auth/hooks/social-google-onboarding.hook";
20
- import { markFreePlanWelcomePending } from "../components/embeds/CrispEmbed";
21
20
  import {
22
21
  useCurrentAccount,
23
22
  ACCOUNT_QUERY_KEY
@@ -62,13 +61,11 @@ function AuthProvider({ children }) {
62
61
  );
63
62
  const register = useCallback(
64
63
  async (data) => {
65
- const isFreePlan = !data.idPlan;
66
64
  await registerMutate(data, {
67
- onSuccess: (response) => {
65
+ onSuccess: (data2) => {
68
66
  queryClient.clear();
69
- setUserData(response.user);
70
- queryClient.setQueryData(accountKey, response.account);
71
- if (isFreePlan) markFreePlanWelcomePending(response.user.id);
67
+ setUserData(data2.user);
68
+ queryClient.setQueryData(accountKey, data2.account);
72
69
  }
73
70
  });
74
71
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/auth.provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n ReactNode,\n useCallback,\n useContext,\n useMemo,\n} from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport {\n useUserQuery,\n useSetUserData,\n useUserValidateSession,\n} from \"../modules/auth/hooks/useUserQuery\";\nimport { useLogin } from \"../modules/auth/hooks/login.hook\";\nimport { useRegister } from \"../modules/auth/hooks/register.hook\";\nimport { useLogout } from \"../modules/auth/hooks/logout.hook\";\nimport { useSocialGoogleLogin } from \"../modules/auth/hooks/social-google-login.hook\";\nimport { useSocialGoogleOnboarding } from \"../modules/auth/hooks/social-google-onboarding.hook\";\nimport {\n AuthState,\n LoginRequest,\n RegisterRequest,\n SocialGooglePartial,\n SocialOnboardingRequest,\n} from \"../modules/auth/schema\";\nimport { markFreePlanWelcomePending } from \"../components/embeds/CrispEmbed\";\n\nexport type LoginResult =\n | { result: \"success\" }\n | { result: \"two_factor_required\"; cookie: string; twoFactorMode: \"verify\" | \"setup\" }\n | { result: \"error\"; message: string };\n\nexport type GoogleLoginResult =\n | { result: \"success\" }\n | { result: \"two_factor_required\"; cookie: string; twoFactorMode: \"verify\" | \"setup\" }\n | { result: \"needs_onboarding\"; onboardingToken: string; partial: SocialGooglePartial }\n | { result: \"link_requires_password\"; message: string }\n | { result: \"error\"; message: string };\nimport {\n useCurrentAccount,\n ACCOUNT_QUERY_KEY,\n} from \"../modules/accounts/hooks/current-account.hook\";\nimport { useWhitelabelUrls } from \"./whitelabel.provider\";\nimport { useWlQueryKey } from \"../hooks/useAuthQueryKey\";\n\ninterface AuthContextProps extends AuthState {\n login: (credentials: LoginRequest) => Promise<LoginResult>;\n register: (data: RegisterRequest) => Promise<void>;\n logout: () => Promise<void>;\n loginWithGoogle: (code: string) => Promise<GoogleLoginResult>;\n completeGoogleOnboarding: (data: SocialOnboardingRequest) => Promise<void>;\n}\n\nexport const AuthContext = createContext<AuthContextProps | undefined>(\n undefined,\n);\n\ninterface AuthProviderProps {\n children: ReactNode;\n}\n\nexport function AuthProvider({ children }: AuthProviderProps) {\n const { data: user, isLoading: isQueryLoading } = useUserQuery();\n const { data: account, isLoading: isAccountLoading } = useCurrentAccount();\n const { isLoading: isSessionLoading } = useUserValidateSession();\n\n const queryClient = useQueryClient();\n const setUserData = useSetUserData();\n const { accountsUrl, pagesUrl } = useWhitelabelUrls();\n const accountKey = useWlQueryKey([...ACCOUNT_QUERY_KEY]);\n\n const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();\n const { mutateAsync: registerMutate, isPending: isRegistering } =\n useRegister();\n const { mutateAsync: logoutMutate, isPending: isLoggingOut } = useLogout();\n const { mutateAsync: googleLoginMutate, isPending: isGoogleLogging } =\n useSocialGoogleLogin();\n const { mutateAsync: googleOnboardingMutate, isPending: isGoogleOnboarding } =\n useSocialGoogleOnboarding();\n\n const isAuthenticated = !!user;\n const isLoading =\n isLogging ||\n isRegistering ||\n isLoggingOut ||\n isGoogleLogging ||\n isGoogleOnboarding ||\n isQueryLoading ||\n isAccountLoading ||\n isSessionLoading;\n\n const login = useCallback(\n async (credentials: LoginRequest): Promise<LoginResult> => {\n try {\n const data = await loginMutate(credentials);\n\n if (data.result === \"two_factor_required\") {\n return { result: \"two_factor_required\", cookie: data.cookie, twoFactorMode: data.twoFactorMode };\n }\n\n queryClient.clear();\n setUserData(data.user);\n queryClient.setQueryData(accountKey, data.account);\n\n return { result: \"success\" };\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : \"Ocorreu um erro ao fazer login. Tente novamente.\";\n return { result: \"error\", message };\n }\n },\n [queryClient, setUserData, accountKey],\n );\n\n const register = useCallback(\n async (data: RegisterRequest): Promise<void> => {\n const isFreePlan = !data.idPlan;\n await registerMutate(data, {\n onSuccess: (response) => {\n queryClient.clear();\n setUserData(response.user);\n queryClient.setQueryData(accountKey, response.account);\n if (isFreePlan) markFreePlanWelcomePending(response.user.id);\n },\n });\n },\n [queryClient, setUserData, accountKey],\n );\n\n const loginWithGoogle = useCallback(\n async (code: string): Promise<GoogleLoginResult> => {\n try {\n const data = await googleLoginMutate(code);\n\n if (data.result === \"two_factor_required\") {\n return {\n result: \"two_factor_required\",\n cookie: data.cookie,\n twoFactorMode: data.twoFactorMode,\n };\n }\n\n if (data.result === \"needs_onboarding\") {\n return {\n result: \"needs_onboarding\",\n onboardingToken: data.onboardingToken,\n partial: data.partial,\n };\n }\n\n if (data.result === \"link_requires_password\") {\n return { result: \"link_requires_password\", message: data.message };\n }\n\n queryClient.clear();\n setUserData(data.user);\n queryClient.setQueryData(accountKey, data.account);\n\n return { result: \"success\" };\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : \"Ocorreu um erro ao fazer login com Google. Tente novamente.\";\n return { result: \"error\", message };\n }\n },\n [googleLoginMutate, queryClient, setUserData, accountKey],\n );\n\n const completeGoogleOnboarding = useCallback(\n async (data: SocialOnboardingRequest): Promise<void> => {\n await googleOnboardingMutate(data, {\n onSuccess: (data) => {\n queryClient.clear();\n setUserData(data.user);\n queryClient.setQueryData(accountKey, data.account);\n },\n });\n },\n [googleOnboardingMutate, queryClient, setUserData, accountKey],\n );\n\n const resolveLogoutRedirect = useCallback(() => {\n try {\n const currentOrigin = window.location.origin;\n const pagesOrigin = pagesUrl ? new URL(pagesUrl).origin : \"\";\n\n if (pagesOrigin && currentOrigin === pagesOrigin) {\n return `${accountsUrl}/login?redirect=${encodeURIComponent(pagesUrl)}`;\n }\n } catch {\n // fallback below\n }\n\n return `${accountsUrl}/login?redirect=${encodeURIComponent(pagesUrl || \"/\")}`;\n }, [accountsUrl, pagesUrl]);\n\n const logout = useCallback(async (): Promise<void> => {\n try {\n await logoutMutate(undefined);\n } catch {\n // cookie já removido no servidor mesmo se a API falhou\n } finally {\n queryClient.clear();\n window.location.assign(resolveLogoutRedirect());\n }\n }, [queryClient, logoutMutate, resolveLogoutRedirect]);\n\n const value: AuthContextProps = useMemo(\n () => ({\n user: user ?? null,\n account: account ?? null,\n isAuthenticated,\n isLoading,\n login,\n register,\n logout,\n loginWithGoogle,\n completeGoogleOnboarding,\n }),\n [\n user,\n account,\n isAuthenticated,\n isLoading,\n login,\n register,\n logout,\n loginWithGoogle,\n completeGoogleOnboarding,\n ],\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\nexport function useAuth(): AuthContextProps {\n const context = useContext(AuthContext);\n\n if (context === undefined) {\n throw new Error(\"useAuth deve ser usado dentro de um AuthProvider\");\n }\n\n return context;\n}\n"],"mappings":"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","names":["data"]}
1
+ {"version":3,"sources":["../../src/providers/auth.provider.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n ReactNode,\n useCallback,\n useContext,\n useMemo,\n} from \"react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport {\n useUserQuery,\n useSetUserData,\n useUserValidateSession,\n} from \"../modules/auth/hooks/useUserQuery\";\nimport { useLogin } from \"../modules/auth/hooks/login.hook\";\nimport { useRegister } from \"../modules/auth/hooks/register.hook\";\nimport { useLogout } from \"../modules/auth/hooks/logout.hook\";\nimport { useSocialGoogleLogin } from \"../modules/auth/hooks/social-google-login.hook\";\nimport { useSocialGoogleOnboarding } from \"../modules/auth/hooks/social-google-onboarding.hook\";\nimport {\n AuthState,\n LoginRequest,\n RegisterRequest,\n SocialGooglePartial,\n SocialOnboardingRequest,\n} from \"../modules/auth/schema\";\n\nexport type LoginResult =\n | { result: \"success\" }\n | { result: \"two_factor_required\"; cookie: string; twoFactorMode: \"verify\" | \"setup\" }\n | { result: \"error\"; message: string };\n\nexport type GoogleLoginResult =\n | { result: \"success\" }\n | { result: \"two_factor_required\"; cookie: string; twoFactorMode: \"verify\" | \"setup\" }\n | { result: \"needs_onboarding\"; onboardingToken: string; partial: SocialGooglePartial }\n | { result: \"link_requires_password\"; message: string }\n | { result: \"error\"; message: string };\nimport {\n useCurrentAccount,\n ACCOUNT_QUERY_KEY,\n} from \"../modules/accounts/hooks/current-account.hook\";\nimport { useWhitelabelUrls } from \"./whitelabel.provider\";\nimport { useWlQueryKey } from \"../hooks/useAuthQueryKey\";\n\ninterface AuthContextProps extends AuthState {\n login: (credentials: LoginRequest) => Promise<LoginResult>;\n register: (data: RegisterRequest) => Promise<void>;\n logout: () => Promise<void>;\n loginWithGoogle: (code: string) => Promise<GoogleLoginResult>;\n completeGoogleOnboarding: (data: SocialOnboardingRequest) => Promise<void>;\n}\n\nexport const AuthContext = createContext<AuthContextProps | undefined>(\n undefined,\n);\n\ninterface AuthProviderProps {\n children: ReactNode;\n}\n\nexport function AuthProvider({ children }: AuthProviderProps) {\n const { data: user, isLoading: isQueryLoading } = useUserQuery();\n const { data: account, isLoading: isAccountLoading } = useCurrentAccount();\n const { isLoading: isSessionLoading } = useUserValidateSession();\n\n const queryClient = useQueryClient();\n const setUserData = useSetUserData();\n const { accountsUrl, pagesUrl } = useWhitelabelUrls();\n const accountKey = useWlQueryKey([...ACCOUNT_QUERY_KEY]);\n\n const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();\n const { mutateAsync: registerMutate, isPending: isRegistering } =\n useRegister();\n const { mutateAsync: logoutMutate, isPending: isLoggingOut } = useLogout();\n const { mutateAsync: googleLoginMutate, isPending: isGoogleLogging } =\n useSocialGoogleLogin();\n const { mutateAsync: googleOnboardingMutate, isPending: isGoogleOnboarding } =\n useSocialGoogleOnboarding();\n\n const isAuthenticated = !!user;\n const isLoading =\n isLogging ||\n isRegistering ||\n isLoggingOut ||\n isGoogleLogging ||\n isGoogleOnboarding ||\n isQueryLoading ||\n isAccountLoading ||\n isSessionLoading;\n\n const login = useCallback(\n async (credentials: LoginRequest): Promise<LoginResult> => {\n try {\n const data = await loginMutate(credentials);\n\n if (data.result === \"two_factor_required\") {\n return { result: \"two_factor_required\", cookie: data.cookie, twoFactorMode: data.twoFactorMode };\n }\n\n queryClient.clear();\n setUserData(data.user);\n queryClient.setQueryData(accountKey, data.account);\n\n return { result: \"success\" };\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : \"Ocorreu um erro ao fazer login. Tente novamente.\";\n return { result: \"error\", message };\n }\n },\n [queryClient, setUserData, accountKey],\n );\n\n const register = useCallback(\n async (data: RegisterRequest): Promise<void> => {\n await registerMutate(data, {\n onSuccess: (data) => {\n queryClient.clear();\n setUserData(data.user);\n queryClient.setQueryData(accountKey, data.account);\n },\n });\n },\n [queryClient, setUserData, accountKey],\n );\n\n const loginWithGoogle = useCallback(\n async (code: string): Promise<GoogleLoginResult> => {\n try {\n const data = await googleLoginMutate(code);\n\n if (data.result === \"two_factor_required\") {\n return {\n result: \"two_factor_required\",\n cookie: data.cookie,\n twoFactorMode: data.twoFactorMode,\n };\n }\n\n if (data.result === \"needs_onboarding\") {\n return {\n result: \"needs_onboarding\",\n onboardingToken: data.onboardingToken,\n partial: data.partial,\n };\n }\n\n if (data.result === \"link_requires_password\") {\n return { result: \"link_requires_password\", message: data.message };\n }\n\n queryClient.clear();\n setUserData(data.user);\n queryClient.setQueryData(accountKey, data.account);\n\n return { result: \"success\" };\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : \"Ocorreu um erro ao fazer login com Google. Tente novamente.\";\n return { result: \"error\", message };\n }\n },\n [googleLoginMutate, queryClient, setUserData, accountKey],\n );\n\n const completeGoogleOnboarding = useCallback(\n async (data: SocialOnboardingRequest): Promise<void> => {\n await googleOnboardingMutate(data, {\n onSuccess: (data) => {\n queryClient.clear();\n setUserData(data.user);\n queryClient.setQueryData(accountKey, data.account);\n },\n });\n },\n [googleOnboardingMutate, queryClient, setUserData, accountKey],\n );\n\n const resolveLogoutRedirect = useCallback(() => {\n try {\n const currentOrigin = window.location.origin;\n const pagesOrigin = pagesUrl ? new URL(pagesUrl).origin : \"\";\n\n if (pagesOrigin && currentOrigin === pagesOrigin) {\n return `${accountsUrl}/login?redirect=${encodeURIComponent(pagesUrl)}`;\n }\n } catch {\n // fallback below\n }\n\n return `${accountsUrl}/login?redirect=${encodeURIComponent(pagesUrl || \"/\")}`;\n }, [accountsUrl, pagesUrl]);\n\n const logout = useCallback(async (): Promise<void> => {\n try {\n await logoutMutate(undefined);\n } catch {\n // cookie já removido no servidor mesmo se a API falhou\n } finally {\n queryClient.clear();\n window.location.assign(resolveLogoutRedirect());\n }\n }, [queryClient, logoutMutate, resolveLogoutRedirect]);\n\n const value: AuthContextProps = useMemo(\n () => ({\n user: user ?? null,\n account: account ?? null,\n isAuthenticated,\n isLoading,\n login,\n register,\n logout,\n loginWithGoogle,\n completeGoogleOnboarding,\n }),\n [\n user,\n account,\n isAuthenticated,\n isLoading,\n login,\n register,\n logout,\n loginWithGoogle,\n completeGoogleOnboarding,\n ],\n );\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\nexport function useAuth(): AuthContextProps {\n const context = useContext(AuthContext);\n\n if (context === undefined) {\n throw new Error(\"useAuth deve ser usado dentro de um AuthProvider\");\n }\n\n return context;\n}\n"],"mappings":"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","names":["data"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.646",
3
+ "version": "1.1.647",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -22,9 +22,8 @@ const CRISP_LOAD_DELAY = 800;
22
22
 
23
23
  function safeCrispPush(args: unknown[]) {
24
24
  try {
25
- const win = window as any;
26
- if (!win.$crisp) win.$crisp = [];
27
- win.$crisp.push(args);
25
+ const $crisp = (window as any)?.$crisp;
26
+ if ($crisp) $crisp.push(args);
28
27
  } catch {}
29
28
  }
30
29
 
@@ -54,31 +53,6 @@ export function showCrisp() {
54
53
  crispDo('chat:show');
55
54
  }
56
55
 
57
- const FREE_PLAN_WELCOME_PENDING_KEY = 'crisp:free-plan-welcome:pending';
58
- const FREE_PLAN_WELCOME_SHOWN_KEY = 'crisp:free-plan-welcome:shown';
59
- const FREE_PLAN_WELCOME_MESSAGE = `Oi! Tenho uma novidade exclusiva para você 🎉
60
-
61
- Criamos o Plano Iniciante pensando exatamente em você: R$ 14,90/mês para quem já deu o primeiro passo e quer ir além.
62
-
63
- Com ele você conecta seu próprio domínio, publica até 3 páginas e libera recursos como scripts, API Meta, exportação de leads e possibilidade de contratar GreatAI.
64
-
65
- Acesse o menu "Assinaturas" e assine agora!`;
66
-
67
- export function markFreePlanWelcomePending(userId: number | string) {
68
- try {
69
- if (typeof window === 'undefined') return;
70
- window.localStorage.setItem(FREE_PLAN_WELCOME_PENDING_KEY, String(userId));
71
- } catch {}
72
- }
73
-
74
- export function sendCrispMessage(text: string) {
75
- safeCrispPush(['do', 'message:show', ['text', text]]);
76
- }
77
-
78
- export function openCrispChat() {
79
- crispDo('chat:open');
80
- }
81
-
82
56
  export function updateCrispUser(data: {
83
57
  email?: string;
84
58
  name?: string;
@@ -180,26 +154,5 @@ export function CrispEmbed() {
180
154
  } catch {}
181
155
  }, [user, account, isV4, shouldLoadCrisp, subscription?.plan_name]);
182
156
 
183
- // Mostra mensagem de boas-vindas no Crisp para quem acabou de se cadastrar no plano free.
184
- useEffect(() => {
185
- if (!user || !shouldLoadCrisp) return;
186
- try {
187
- const userId = String(user.id);
188
- const pending = window.localStorage.getItem(FREE_PLAN_WELCOME_PENDING_KEY);
189
- if (pending !== userId) return;
190
-
191
- const shownKey = `${FREE_PLAN_WELCOME_SHOWN_KEY}:${userId}`;
192
- if (window.localStorage.getItem(shownKey)) {
193
- window.localStorage.removeItem(FREE_PLAN_WELCOME_PENDING_KEY);
194
- return;
195
- }
196
-
197
- sendCrispMessage(FREE_PLAN_WELCOME_MESSAGE);
198
- openCrispChat();
199
- window.localStorage.setItem(shownKey, '1');
200
- window.localStorage.removeItem(FREE_PLAN_WELCOME_PENDING_KEY);
201
- } catch {}
202
- }, [user, shouldLoadCrisp]);
203
-
204
157
  return null;
205
158
  }
@@ -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 cancellationDate = toCalendarDate(subscription?.date_cancellation);
29
+ const expiryDate = getSubscriptionExpiryDate(subscription);
29
30
 
30
- // Só mostra o banner se o cancelamento existe e efetivou (hoje >= data cancelamento).
31
- if (!today || !cancellationDate) return null;
32
- if (today.getTime() < cancellationDate.getTime()) return null;
31
+ // Só mostra se cancelamento E o período (com grace, no caso de client) 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
- // Cancelamento é efetivo quando date_cancellation existe E passou do dia de hoje
46
- // (comparação por calendar-day em horário local, via toCalendarDate).
47
- const cancellationDate = toCalendarDate(subscription.date_cancellation);
48
- const hasFutureCancellation =
49
- !!subscription.date_cancellation &&
50
- cancellationDate!.getTime() > today.getTime();
51
- const isCancelled =
52
- hasFutureCancellation &&
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 (isCancelled) {
54
+ if (isEffectivelyCancelled) {
57
55
  return <CancelledSubscriptionBanner onReactivate={onReactivate} />;
58
56
  }
59
57
 
60
- if (hasFutureCancellation) {
58
+ if (isPendingCancellation) {
61
59
  return (
62
60
  <PendingCancellationBanner
63
- limitDate={cancellationDate}
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 { hasSubscriptionExpired } from "../../subscriptions/utils/has-subscription-expired";
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 isWhitelabelType = subscription?.type === 'whitelabel';
29
- const isExpired = isWhitelabelType
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 { hasSubscriptionExpired } from "../../subscriptions/utils/has-subscription-expired";
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 isWhitelabelType = subscription.type === 'whitelabel';
29
- const isExpired = isWhitelabelType
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 { hasSubscriptionExpired } from '../utils/has-subscription-expired';
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 isWhitelabelType = subscription?.type === 'whitelabel';
19
- const isCancelledSubscription = isWhitelabelType
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
+ }
@@ -25,7 +25,6 @@ import {
25
25
  SocialGooglePartial,
26
26
  SocialOnboardingRequest,
27
27
  } from "../modules/auth/schema";
28
- import { markFreePlanWelcomePending } from "../components/embeds/CrispEmbed";
29
28
 
30
29
  export type LoginResult =
31
30
  | { result: "success" }
@@ -118,13 +117,11 @@ export function AuthProvider({ children }: AuthProviderProps) {
118
117
 
119
118
  const register = useCallback(
120
119
  async (data: RegisterRequest): Promise<void> => {
121
- const isFreePlan = !data.idPlan;
122
120
  await registerMutate(data, {
123
- onSuccess: (response) => {
121
+ onSuccess: (data) => {
124
122
  queryClient.clear();
125
- setUserData(response.user);
126
- queryClient.setQueryData(accountKey, response.account);
127
- if (isFreePlan) markFreePlanWelcomePending(response.user.id);
123
+ setUserData(data.user);
124
+ queryClient.setQueryData(accountKey, data.account);
128
125
  },
129
126
  });
130
127
  },