@greatapps/common 1.1.701 → 1.1.702
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/modals/HexaPromoModal.mjs +1 -1
- package/dist/components/modals/HexaPromoModal.mjs.map +1 -1
- package/dist/components/modals/HexaPromoModalGate.mjs +8 -1
- package/dist/components/modals/HexaPromoModalGate.mjs.map +1 -1
- package/dist/modules/subscriptions/hooks/update-subscription-plan.hook.mjs +2 -0
- package/dist/modules/subscriptions/hooks/update-subscription-plan.hook.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/modals/HexaPromoModal.tsx +1 -1
- package/src/components/modals/HexaPromoModalGate.tsx +14 -1
- package/src/modules/subscriptions/hooks/update-subscription-plan.hook.ts +3 -0
|
@@ -74,7 +74,7 @@ function HexaPromoModal() {
|
|
|
74
74
|
src: "/promo/hexa-confetti.png",
|
|
75
75
|
alt: "",
|
|
76
76
|
"aria-hidden": true,
|
|
77
|
-
className: "pointer-events-none absolute -top-10 right-0 w-[280px] -rotate-
|
|
77
|
+
className: "pointer-events-none absolute -top-10 right-0 w-[280px] -rotate-[16deg] select-none"
|
|
78
78
|
}
|
|
79
79
|
),
|
|
80
80
|
/* @__PURE__ */ jsxs(DialogClose, { className: "absolute right-3 top-3 z-10 flex size-8 items-center justify-center rounded-lg p-2 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-950 focus:outline-none", children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/modals/HexaPromoModal.tsx"],"sourcesContent":["'use client';\n\nimport { toast } from 'sonner';\nimport {\n IconCheck,\n IconCopy,\n IconInfoCircle,\n IconStar,\n IconTicket,\n IconX,\n} from '@tabler/icons-react';\nimport { Dialog, DialogClose, DialogContent, DialogTitle } from '../ui/overlay/Dialog';\nimport { Toast } from '../ui/feedback/Toast';\nimport { Button } from '../ui/buttons/Button';\nimport useCopyToClipboard from '../../hooks/copy-to-clipboard.hook';\nimport { useModalManager } from '../../store/useModalManager';\nimport { useWhitelabelUrls } from '../../providers/whitelabel.provider';\nimport { cn } from '../../infra/utils/clsx';\n\ninterface HexaCombo {\n coupon: string;\n name: string;\n features: string[];\n highlighted: boolean;\n}\n\n// Campanha \"Rumo ao Hexa\" — exclusiva da whitelabel 1 (público pt-br). O texto é fixo de\n// propósito: a modal precisa renderizar também no gapps, que não tem provider de next-intl.\nconst COPY = {\n title: 'Rumo ao Hexa!',\n subtitlePrefix: 'Ofertas exclusivas até ',\n subtitleDate: '24/06',\n mostChosen: 'Mais escolhido',\n couponLabel: 'Cupom',\n choosePlan: 'Escolher plano',\n couponCopied: 'Cupom copiado!',\n close: 'Fechar',\n footerPrefix: 'Válido para a ',\n footerBold: 'primeira contratação',\n footerSuffix: ' dos planos Essencial, Negócio ou Agência',\n};\n\nconst COMBOS: HexaCombo[] = [\n {\n coupon: 'MENSAL10',\n name: 'Combo convocação',\n highlighted: false,\n features: ['10% OFF no plano mensal', 'Webinars exclusivos'],\n },\n {\n coupon: 'SEMESTRAL10',\n name: 'Combo Hexa',\n highlighted: true,\n features: ['10% OFF no plano semestral', '3 templates exclusivos', 'Webinars exclusivos'],\n },\n {\n coupon: 'ANUAL10',\n name: 'Combo escalação',\n highlighted: false,\n features: ['10% OFF no plano anual', '3 templates exclusivos'],\n },\n];\n\n// Cores promocionais (verde/amarelo do Brasil) — específicas desta campanha, fora do design\n// system, por isso ficam hardcoded apenas aqui.\nconst GRADIENT = 'linear-gradient(180deg, #08c44f 0%, #7bd237 46.6%, #ffe31b 100%)';\n\nexport default function HexaPromoModal() {\n const { activeModal, closeModal } = useModalManager();\n const { accountsUrl } = useWhitelabelUrls();\n const isOpen = activeModal === 'hexaPromoModal';\n\n const goToPlans = (coupon: string) => {\n closeModal();\n const url = `${accountsUrl}/subscriptions?tab=plans&coupon=${encodeURIComponent(coupon)}`;\n window.location.assign(url);\n };\n\n return (\n <Dialog open={isOpen} onOpenChange={() => closeModal()}>\n <DialogContent\n showCloseButton={false}\n onOpenAutoFocus={(e) => e.preventDefault()}\n className=\"border-0 bg-transparent p-0 shadow-none w-[calc(100%-32px)] sm:max-w-[940px] md:w-[924px] lg:w-[924px] max-h-[calc(100dvh-32px)] overflow-y-auto\"\n >\n <DialogTitle className=\"sr-only\">{COPY.title}</DialogTitle>\n\n <div className=\"rounded-[12px] p-1\" style={{ background: GRADIENT }}>\n <div className=\"relative overflow-hidden rounded-[10px] bg-white p-8 shadow-[0px_2px_10px_0px_rgba(26,26,26,0.05)]\">\n {/* Arte decorativa (festão/bandeira) no canto superior direito */}\n <img\n src=\"/promo/hexa-confetti.png\"\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute -top-10 right-0 w-[280px] -rotate-16 select-none\"\n />\n\n <DialogClose className=\"absolute right-3 top-3 z-10 flex size-8 items-center justify-center rounded-lg p-2 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-950 focus:outline-none\">\n <span className=\"sr-only\">{COPY.close}</span>\n <IconX className=\"size-[18px]\" />\n </DialogClose>\n\n {/* Header: logo + bandeira */}\n <div className=\"relative z-[1] flex w-fit items-center gap-[9px] rounded-lg bg-zinc-100 px-[9px] py-2\">\n <img src=\"/promo/hexa-logo.svg\" alt=\"\" aria-hidden className=\"size-6 select-none\" />\n <img src=\"/promo/hexa-flag.png\" alt=\"\" aria-hidden className=\"size-6 rounded select-none\" />\n </div>\n\n {/* Título */}\n <div className=\"relative z-[1] mt-4 flex flex-col gap-1\">\n <h2 className=\"font-['Outfit'] text-[32px] font-semibold leading-8 text-zinc-950\">\n {COPY.title}\n </h2>\n <p className=\"paragraph-medium-medium text-zinc-950\">\n {COPY.subtitlePrefix}\n <span className=\"font-bold\">{COPY.subtitleDate}</span>\n </p>\n </div>\n\n {/* Cards */}\n <div className=\"relative z-[1] mt-10 flex flex-col items-stretch gap-3 md:flex-row\">\n {COMBOS.map((combo) => (\n <ComboCard key={combo.coupon} combo={combo} onChoose={() => goToPlans(combo.coupon)} />\n ))}\n </div>\n\n {/* Rodapé informativo */}\n <div className=\"relative z-[1] mt-3 flex items-center justify-center gap-2.5 rounded-[10px] bg-zinc-100 p-3 text-center\">\n <IconInfoCircle className=\"size-4 shrink-0 text-zinc-950\" />\n <p className=\"paragraph-small-medium text-zinc-950\">\n {COPY.footerPrefix}\n <span className=\"font-semibold\">{COPY.footerBold}</span>\n {COPY.footerSuffix}\n </p>\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n\ninterface ComboCardProps {\n combo: HexaCombo;\n onChoose: () => void;\n}\n\nfunction ComboCard({ combo, onChoose }: ComboCardProps) {\n const { isCopied, copy } = useCopyToClipboard({\n onSuccess: () => {\n toast.custom((t) => <Toast variant=\"success\" message={COPY.couponCopied} toastId={t} />);\n },\n });\n\n return (\n <div\n className={cn(\n 'relative flex flex-1 flex-col rounded-[10px] border bg-white pb-3 md:w-[276px]',\n combo.highlighted ? 'border-cyan-500' : 'border-zinc-200',\n )}\n >\n {combo.highlighted && (\n <div className=\"absolute left-1/2 top-0 z-[1] flex h-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-[5px] rounded-md border-4 border-white bg-cyan-100 px-2\">\n <IconStar className=\"size-3 text-cyan-600\" />\n <span className=\"paragraph-xsmall-semibold whitespace-nowrap text-cyan-600\">\n {COPY.mostChosen}\n </span>\n <IconStar className=\"size-3 text-cyan-600\" />\n </div>\n )}\n\n <h3 className=\"border-b border-zinc-200 px-4 py-4 text-center font-['Outfit'] text-base font-semibold text-zinc-950\">\n {combo.name}\n </h3>\n\n <ul className=\"flex flex-1 flex-col gap-5 px-5 pt-5\">\n {combo.features.map((feature) => (\n <li key={feature} className=\"flex items-center gap-2.5\">\n <IconCheck className=\"size-5 shrink-0 text-cyan-500\" />\n <span className=\"paragraph-small-medium text-zinc-950\">{feature}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"mx-3 mt-5 flex items-center gap-3 rounded-lg bg-cyan-50 py-2 pl-3.5 pr-2.5\">\n <IconTicket className=\"size-5 shrink-0 text-cyan-600\" />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"paragraph-xsmall-medium text-zinc-400\">{COPY.couponLabel}</span>\n <span className=\"paragraph-small-semibold text-zinc-950\">{combo.coupon}</span>\n </div>\n <button\n type=\"button\"\n aria-label={COPY.couponLabel}\n onClick={() => copy(combo.coupon)}\n className=\"flex size-8 shrink-0 items-center justify-center rounded-md text-zinc-500 transition-colors hover:bg-cyan-100 hover:text-zinc-950\"\n >\n {isCopied ? <IconCheck className=\"size-[18px]\" /> : <IconCopy className=\"size-[18px]\" />}\n </button>\n </div>\n\n <div className=\"mx-3 mt-3\">\n <Button\n variant={combo.highlighted ? 'default' : 'secondary'}\n className={cn('h-11! w-full', combo.highlighted && 'bg-cyan-500 text-white hover:bg-cyan-600')}\n onClick={onChoose}\n >\n {COPY.choosePlan}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/modals/HexaPromoModal.tsx"],"sourcesContent":["'use client';\n\nimport { toast } from 'sonner';\nimport {\n IconCheck,\n IconCopy,\n IconInfoCircle,\n IconStar,\n IconTicket,\n IconX,\n} from '@tabler/icons-react';\nimport { Dialog, DialogClose, DialogContent, DialogTitle } from '../ui/overlay/Dialog';\nimport { Toast } from '../ui/feedback/Toast';\nimport { Button } from '../ui/buttons/Button';\nimport useCopyToClipboard from '../../hooks/copy-to-clipboard.hook';\nimport { useModalManager } from '../../store/useModalManager';\nimport { useWhitelabelUrls } from '../../providers/whitelabel.provider';\nimport { cn } from '../../infra/utils/clsx';\n\ninterface HexaCombo {\n coupon: string;\n name: string;\n features: string[];\n highlighted: boolean;\n}\n\n// Campanha \"Rumo ao Hexa\" — exclusiva da whitelabel 1 (público pt-br). O texto é fixo de\n// propósito: a modal precisa renderizar também no gapps, que não tem provider de next-intl.\nconst COPY = {\n title: 'Rumo ao Hexa!',\n subtitlePrefix: 'Ofertas exclusivas até ',\n subtitleDate: '24/06',\n mostChosen: 'Mais escolhido',\n couponLabel: 'Cupom',\n choosePlan: 'Escolher plano',\n couponCopied: 'Cupom copiado!',\n close: 'Fechar',\n footerPrefix: 'Válido para a ',\n footerBold: 'primeira contratação',\n footerSuffix: ' dos planos Essencial, Negócio ou Agência',\n};\n\nconst COMBOS: HexaCombo[] = [\n {\n coupon: 'MENSAL10',\n name: 'Combo convocação',\n highlighted: false,\n features: ['10% OFF no plano mensal', 'Webinars exclusivos'],\n },\n {\n coupon: 'SEMESTRAL10',\n name: 'Combo Hexa',\n highlighted: true,\n features: ['10% OFF no plano semestral', '3 templates exclusivos', 'Webinars exclusivos'],\n },\n {\n coupon: 'ANUAL10',\n name: 'Combo escalação',\n highlighted: false,\n features: ['10% OFF no plano anual', '3 templates exclusivos'],\n },\n];\n\n// Cores promocionais (verde/amarelo do Brasil) — específicas desta campanha, fora do design\n// system, por isso ficam hardcoded apenas aqui.\nconst GRADIENT = 'linear-gradient(180deg, #08c44f 0%, #7bd237 46.6%, #ffe31b 100%)';\n\nexport default function HexaPromoModal() {\n const { activeModal, closeModal } = useModalManager();\n const { accountsUrl } = useWhitelabelUrls();\n const isOpen = activeModal === 'hexaPromoModal';\n\n const goToPlans = (coupon: string) => {\n closeModal();\n const url = `${accountsUrl}/subscriptions?tab=plans&coupon=${encodeURIComponent(coupon)}`;\n window.location.assign(url);\n };\n\n return (\n <Dialog open={isOpen} onOpenChange={() => closeModal()}>\n <DialogContent\n showCloseButton={false}\n onOpenAutoFocus={(e) => e.preventDefault()}\n className=\"border-0 bg-transparent p-0 shadow-none w-[calc(100%-32px)] sm:max-w-[940px] md:w-[924px] lg:w-[924px] max-h-[calc(100dvh-32px)] overflow-y-auto\"\n >\n <DialogTitle className=\"sr-only\">{COPY.title}</DialogTitle>\n\n <div className=\"rounded-[12px] p-1\" style={{ background: GRADIENT }}>\n <div className=\"relative overflow-hidden rounded-[10px] bg-white p-8 shadow-[0px_2px_10px_0px_rgba(26,26,26,0.05)]\">\n {/* Arte decorativa (festão/bandeira) no canto superior direito */}\n <img\n src=\"/promo/hexa-confetti.png\"\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute -top-10 right-0 w-[280px] -rotate-[16deg] select-none\"\n />\n\n <DialogClose className=\"absolute right-3 top-3 z-10 flex size-8 items-center justify-center rounded-lg p-2 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-950 focus:outline-none\">\n <span className=\"sr-only\">{COPY.close}</span>\n <IconX className=\"size-[18px]\" />\n </DialogClose>\n\n {/* Header: logo + bandeira */}\n <div className=\"relative z-[1] flex w-fit items-center gap-[9px] rounded-lg bg-zinc-100 px-[9px] py-2\">\n <img src=\"/promo/hexa-logo.svg\" alt=\"\" aria-hidden className=\"size-6 select-none\" />\n <img src=\"/promo/hexa-flag.png\" alt=\"\" aria-hidden className=\"size-6 rounded select-none\" />\n </div>\n\n {/* Título */}\n <div className=\"relative z-[1] mt-4 flex flex-col gap-1\">\n <h2 className=\"font-['Outfit'] text-[32px] font-semibold leading-8 text-zinc-950\">\n {COPY.title}\n </h2>\n <p className=\"paragraph-medium-medium text-zinc-950\">\n {COPY.subtitlePrefix}\n <span className=\"font-bold\">{COPY.subtitleDate}</span>\n </p>\n </div>\n\n {/* Cards */}\n <div className=\"relative z-[1] mt-10 flex flex-col items-stretch gap-3 md:flex-row\">\n {COMBOS.map((combo) => (\n <ComboCard key={combo.coupon} combo={combo} onChoose={() => goToPlans(combo.coupon)} />\n ))}\n </div>\n\n {/* Rodapé informativo */}\n <div className=\"relative z-[1] mt-3 flex items-center justify-center gap-2.5 rounded-[10px] bg-zinc-100 p-3 text-center\">\n <IconInfoCircle className=\"size-4 shrink-0 text-zinc-950\" />\n <p className=\"paragraph-small-medium text-zinc-950\">\n {COPY.footerPrefix}\n <span className=\"font-semibold\">{COPY.footerBold}</span>\n {COPY.footerSuffix}\n </p>\n </div>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n\ninterface ComboCardProps {\n combo: HexaCombo;\n onChoose: () => void;\n}\n\nfunction ComboCard({ combo, onChoose }: ComboCardProps) {\n const { isCopied, copy } = useCopyToClipboard({\n onSuccess: () => {\n toast.custom((t) => <Toast variant=\"success\" message={COPY.couponCopied} toastId={t} />);\n },\n });\n\n return (\n <div\n className={cn(\n 'relative flex flex-1 flex-col rounded-[10px] border bg-white pb-3 md:w-[276px]',\n combo.highlighted ? 'border-cyan-500' : 'border-zinc-200',\n )}\n >\n {combo.highlighted && (\n <div className=\"absolute left-1/2 top-0 z-[1] flex h-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-[5px] rounded-md border-4 border-white bg-cyan-100 px-2\">\n <IconStar className=\"size-3 text-cyan-600\" />\n <span className=\"paragraph-xsmall-semibold whitespace-nowrap text-cyan-600\">\n {COPY.mostChosen}\n </span>\n <IconStar className=\"size-3 text-cyan-600\" />\n </div>\n )}\n\n <h3 className=\"border-b border-zinc-200 px-4 py-4 text-center font-['Outfit'] text-base font-semibold text-zinc-950\">\n {combo.name}\n </h3>\n\n <ul className=\"flex flex-1 flex-col gap-5 px-5 pt-5\">\n {combo.features.map((feature) => (\n <li key={feature} className=\"flex items-center gap-2.5\">\n <IconCheck className=\"size-5 shrink-0 text-cyan-500\" />\n <span className=\"paragraph-small-medium text-zinc-950\">{feature}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"mx-3 mt-5 flex items-center gap-3 rounded-lg bg-cyan-50 py-2 pl-3.5 pr-2.5\">\n <IconTicket className=\"size-5 shrink-0 text-cyan-600\" />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"paragraph-xsmall-medium text-zinc-400\">{COPY.couponLabel}</span>\n <span className=\"paragraph-small-semibold text-zinc-950\">{combo.coupon}</span>\n </div>\n <button\n type=\"button\"\n aria-label={COPY.couponLabel}\n onClick={() => copy(combo.coupon)}\n className=\"flex size-8 shrink-0 items-center justify-center rounded-md text-zinc-500 transition-colors hover:bg-cyan-100 hover:text-zinc-950\"\n >\n {isCopied ? <IconCheck className=\"size-[18px]\" /> : <IconCopy className=\"size-[18px]\" />}\n </button>\n </div>\n\n <div className=\"mx-3 mt-3\">\n <Button\n variant={combo.highlighted ? 'default' : 'secondary'}\n className={cn('h-11! w-full', combo.highlighted && 'bg-cyan-500 text-white hover:bg-cyan-600')}\n onClick={onChoose}\n >\n {COPY.choosePlan}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -16,11 +16,17 @@ const FORCE_OPEN_FOR_TEST = false;
|
|
|
16
16
|
function HexaPromoModalGate() {
|
|
17
17
|
const { isAuthenticated } = useAuth();
|
|
18
18
|
const { whitelabel } = useWhitelabel();
|
|
19
|
-
const { activeModal, openModal } = useModalManager();
|
|
19
|
+
const { activeModal, openModal, closeModal } = useModalManager();
|
|
20
20
|
const { data } = useActiveSubscription();
|
|
21
21
|
const subscription = data?.data?.[0] ?? null;
|
|
22
22
|
const eligible = isAuthenticated && whitelabel?.id === 1 && subscription?.type === "trial";
|
|
23
|
+
const eligibleRef = useRef(eligible);
|
|
24
|
+
eligibleRef.current = eligible;
|
|
23
25
|
const forcedRef = useRef(false);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (FORCE_OPEN_FOR_TEST) return;
|
|
28
|
+
if (!eligible && activeModal === "hexaPromoModal") closeModal();
|
|
29
|
+
}, [eligible, activeModal, closeModal]);
|
|
24
30
|
useEffect(() => {
|
|
25
31
|
if (typeof window === "undefined") return;
|
|
26
32
|
if (FORCE_OPEN_FOR_TEST) {
|
|
@@ -40,6 +46,7 @@ function HexaPromoModalGate() {
|
|
|
40
46
|
}
|
|
41
47
|
const remaining = Math.max(0, since.ms + ONLINE_DELAY_MS - Date.now());
|
|
42
48
|
const timer = setTimeout(() => {
|
|
49
|
+
if (!eligibleRef.current) return;
|
|
43
50
|
if (getHexaPromoSeenDay() === todayKey) return;
|
|
44
51
|
setHexaPromoSeenDay(todayKey);
|
|
45
52
|
openModal("hexaPromoModal");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/modals/HexaPromoModalGate.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { useAuth } from '../../providers/auth.provider';\nimport { useWhitelabel } from '../../providers/whitelabel.provider';\nimport { useModalManager } from '../../store/useModalManager';\nimport {\n getHexaPromoSeenDay,\n getHexaPromoSince,\n hexaPromoDayKey,\n setHexaPromoSeenDay,\n setHexaPromoSince,\n} from '../../utils/cookies/hexa-promo';\n\n/** Janela de tempo online antes de abrir a modal (3 minutos). */\nconst ONLINE_DELAY_MS = 3 * 60 * 1000;\n\n/**\n * TESTE: quando `true`, a modal abre imediatamente ao carregar o app, ignorando\n * trial / whitelabel / timer de 3 min / cookies. Manter `false` em produção.\n */\nconst FORCE_OPEN_FOR_TEST = false;\n\n/**\n * Dispara a modal \"Rumo ao Hexa\" quando um usuário em **trial** da **whitelabel 1** fica\n * 3 minutos online. A janela é cumulativa entre gapps e gpages (timestamp compartilhado em\n * cookie no domínio pai) e a modal abre **uma única vez** — reabrindo só no dia seguinte ou\n * após logout/login. Montar este componente uma vez no layout de cada app consumidor.\n */\nexport default function HexaPromoModalGate() {\n const { isAuthenticated } = useAuth();\n const { whitelabel } = useWhitelabel();\n const { activeModal, openModal } = useModalManager();\n const { data } = useActiveSubscription();\n\n const subscription = data?.data?.[0] ?? null;\n const eligible =\n isAuthenticated && whitelabel?.id === 1 && subscription?.type === 'trial';\n\n const forcedRef = useRef(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n // TESTE: abre uma vez ao carregar, sem nenhuma condição.\n if (FORCE_OPEN_FOR_TEST) {\n if (forcedRef.current || activeModal) return;\n forcedRef.current = true;\n openModal('hexaPromoModal');\n return;\n }\n\n if (!eligible) return;\n // Outra modal aberta: não empilha em cima. Reavalia quando ela fechar (dep `activeModal`).\n if (activeModal) return;\n\n const todayKey = hexaPromoDayKey();\n if (getHexaPromoSeenDay() === todayKey) return; // já exibida hoje/nesta sessão\n\n // Início da janela compartilhado entre os apps; reinicia a cada novo dia.\n let since = getHexaPromoSince();\n if (!since || since.day !== todayKey) {\n since = { day: todayKey, ms: Date.now() };\n setHexaPromoSince(since.day, since.ms);\n }\n\n const remaining = Math.max(0, since.ms + ONLINE_DELAY_MS - Date.now());\n const timer = setTimeout(() => {\n // Recheca o cookie no disparo: se o outro app já abriu, não reabre aqui.\n if (getHexaPromoSeenDay() === todayKey) return;\n setHexaPromoSeenDay(todayKey);\n openModal('hexaPromoModal');\n }, remaining);\n\n return () => clearTimeout(timer);\n }, [eligible, activeModal, openModal]);\n\n return null;\n}\n"],"mappings":";AAEA,SAAS,WAAW,cAAc;AAClC,SAAS,6BAA6B;AACtC,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,MAAM,kBAAkB,IAAI,KAAK;AAMjC,MAAM,sBAAsB;AAQb,SAAR,qBAAsC;AAC3C,QAAM,EAAE,gBAAgB,IAAI,QAAQ;AACpC,QAAM,EAAE,WAAW,IAAI,cAAc;AACrC,QAAM,EAAE,aAAa,
|
|
1
|
+
{"version":3,"sources":["../../../src/components/modals/HexaPromoModalGate.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useActiveSubscription } from '../../modules/subscriptions/hooks/find-active-subscription.hook';\nimport { useAuth } from '../../providers/auth.provider';\nimport { useWhitelabel } from '../../providers/whitelabel.provider';\nimport { useModalManager } from '../../store/useModalManager';\nimport {\n getHexaPromoSeenDay,\n getHexaPromoSince,\n hexaPromoDayKey,\n setHexaPromoSeenDay,\n setHexaPromoSince,\n} from '../../utils/cookies/hexa-promo';\n\n/** Janela de tempo online antes de abrir a modal (3 minutos). */\nconst ONLINE_DELAY_MS = 3 * 60 * 1000;\n\n/**\n * TESTE: quando `true`, a modal abre imediatamente ao carregar o app, ignorando\n * trial / whitelabel / timer de 3 min / cookies. Manter `false` em produção.\n */\nconst FORCE_OPEN_FOR_TEST = false;\n\n/**\n * Dispara a modal \"Rumo ao Hexa\" quando um usuário em **trial** da **whitelabel 1** fica\n * 3 minutos online. A janela é cumulativa entre gapps e gpages (timestamp compartilhado em\n * cookie no domínio pai) e a modal abre **uma única vez** — reabrindo só no dia seguinte ou\n * após logout/login. Montar este componente uma vez no layout de cada app consumidor.\n */\nexport default function HexaPromoModalGate() {\n const { isAuthenticated } = useAuth();\n const { whitelabel } = useWhitelabel();\n const { activeModal, openModal, closeModal } = useModalManager();\n const { data } = useActiveSubscription();\n\n const subscription = data?.data?.[0] ?? null;\n const eligible =\n isAuthenticated && whitelabel?.id === 1 && subscription?.type === 'trial';\n\n // Elegibilidade atual visível dentro do setTimeout (que captura o valor do agendamento).\n const eligibleRef = useRef(eligible);\n eligibleRef.current = eligible;\n\n const forcedRef = useRef(false);\n\n // Nunca deixa a modal aberta para usuário deslogado/inelegível: se ele abriu logado e\n // depois deslogou (ou perdeu a elegibilidade), fecha.\n useEffect(() => {\n if (FORCE_OPEN_FOR_TEST) return;\n if (!eligible && activeModal === 'hexaPromoModal') closeModal();\n }, [eligible, activeModal, closeModal]);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n // TESTE: abre uma vez ao carregar, sem nenhuma condição.\n if (FORCE_OPEN_FOR_TEST) {\n if (forcedRef.current || activeModal) return;\n forcedRef.current = true;\n openModal('hexaPromoModal');\n return;\n }\n\n if (!eligible) return;\n // Outra modal aberta: não empilha em cima. Reavalia quando ela fechar (dep `activeModal`).\n if (activeModal) return;\n\n const todayKey = hexaPromoDayKey();\n if (getHexaPromoSeenDay() === todayKey) return; // já exibida hoje/nesta sessão\n\n // Início da janela compartilhado entre os apps; reinicia a cada novo dia.\n let since = getHexaPromoSince();\n if (!since || since.day !== todayKey) {\n since = { day: todayKey, ms: Date.now() };\n setHexaPromoSince(since.day, since.ms);\n }\n\n const remaining = Math.max(0, since.ms + ONLINE_DELAY_MS - Date.now());\n const timer = setTimeout(() => {\n // Revalida no disparo: o usuário pode ter deslogado durante a janela.\n if (!eligibleRef.current) return;\n // Recheca o cookie no disparo: se o outro app já abriu, não reabre aqui.\n if (getHexaPromoSeenDay() === todayKey) return;\n setHexaPromoSeenDay(todayKey);\n openModal('hexaPromoModal');\n }, remaining);\n\n return () => clearTimeout(timer);\n }, [eligible, activeModal, openModal]);\n\n return null;\n}\n"],"mappings":";AAEA,SAAS,WAAW,cAAc;AAClC,SAAS,6BAA6B;AACtC,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,MAAM,kBAAkB,IAAI,KAAK;AAMjC,MAAM,sBAAsB;AAQb,SAAR,qBAAsC;AAC3C,QAAM,EAAE,gBAAgB,IAAI,QAAQ;AACpC,QAAM,EAAE,WAAW,IAAI,cAAc;AACrC,QAAM,EAAE,aAAa,WAAW,WAAW,IAAI,gBAAgB;AAC/D,QAAM,EAAE,KAAK,IAAI,sBAAsB;AAEvC,QAAM,eAAe,MAAM,OAAO,CAAC,KAAK;AACxC,QAAM,WACJ,mBAAmB,YAAY,OAAO,KAAK,cAAc,SAAS;AAGpE,QAAM,cAAc,OAAO,QAAQ;AACnC,cAAY,UAAU;AAEtB,QAAM,YAAY,OAAO,KAAK;AAI9B,YAAU,MAAM;AACd,QAAI,oBAAqB;AACzB,QAAI,CAAC,YAAY,gBAAgB,iBAAkB,YAAW;AAAA,EAChE,GAAG,CAAC,UAAU,aAAa,UAAU,CAAC;AAEtC,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AAGnC,QAAI,qBAAqB;AACvB,UAAI,UAAU,WAAW,YAAa;AACtC,gBAAU,UAAU;AACpB,gBAAU,gBAAgB;AAC1B;AAAA,IACF;AAEA,QAAI,CAAC,SAAU;AAEf,QAAI,YAAa;AAEjB,UAAM,WAAW,gBAAgB;AACjC,QAAI,oBAAoB,MAAM,SAAU;AAGxC,QAAI,QAAQ,kBAAkB;AAC9B,QAAI,CAAC,SAAS,MAAM,QAAQ,UAAU;AACpC,cAAQ,EAAE,KAAK,UAAU,IAAI,KAAK,IAAI,EAAE;AACxC,wBAAkB,MAAM,KAAK,MAAM,EAAE;AAAA,IACvC;AAEA,UAAM,YAAY,KAAK,IAAI,GAAG,MAAM,KAAK,kBAAkB,KAAK,IAAI,CAAC;AACrE,UAAM,QAAQ,WAAW,MAAM;AAE7B,UAAI,CAAC,YAAY,QAAS;AAE1B,UAAI,oBAAoB,MAAM,SAAU;AACxC,0BAAoB,QAAQ;AAC5B,gBAAU,gBAAgB;AAAA,IAC5B,GAAG,SAAS;AAEZ,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,UAAU,aAAa,SAAS,CAAC;AAErC,SAAO;AACT;","names":[]}
|
|
@@ -8,6 +8,7 @@ function useUpdateSubscriptionPlan() {
|
|
|
8
8
|
const queryClient = useQueryClient();
|
|
9
9
|
const subscriptionsKey = useAuthQueryKey([...SUBSCRIPTIONS_QUERY_KEY]);
|
|
10
10
|
const chargesKey = useAuthQueryKey([...CHARGES_QUERY_KEY]);
|
|
11
|
+
const iaCreditsKey = useAuthQueryKey(["ia-credits"]);
|
|
11
12
|
return useMutation({
|
|
12
13
|
mutationFn: ({
|
|
13
14
|
subscriptionId,
|
|
@@ -16,6 +17,7 @@ function useUpdateSubscriptionPlan() {
|
|
|
16
17
|
onSuccess: () => {
|
|
17
18
|
queryClient.invalidateQueries({ queryKey: subscriptionsKey });
|
|
18
19
|
queryClient.invalidateQueries({ queryKey: chargesKey });
|
|
20
|
+
queryClient.invalidateQueries({ queryKey: iaCreditsKey });
|
|
19
21
|
}
|
|
20
22
|
});
|
|
21
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/subscriptions/hooks/update-subscription-plan.hook.ts"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { withAction } from '../../../utils/withAction';\nimport { updateSubscriptionPlanAction } from '../actions/update-subscription-plan.action';\nimport { SUBSCRIPTIONS_QUERY_KEY, CHARGES_QUERY_KEY } from '../constants/query-keys.constants';\nimport type { UpdateSubscriptionPlanRequest } from '../types/calculate-subscription.type';\nimport { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';\n\nexport function useUpdateSubscriptionPlan() {\n const queryClient = useQueryClient();\n const subscriptionsKey = useAuthQueryKey([...SUBSCRIPTIONS_QUERY_KEY]);\n const chargesKey = useAuthQueryKey([...CHARGES_QUERY_KEY]);\n\n return useMutation({\n mutationFn: ({\n subscriptionId,\n data,\n }: {\n subscriptionId: number | string;\n data: UpdateSubscriptionPlanRequest;\n }) => withAction(updateSubscriptionPlanAction)(subscriptionId, data),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: subscriptionsKey });\n queryClient.invalidateQueries({ queryKey: chargesKey });\n },\n });\n}\n"],"mappings":";AAEA,SAAS,aAAa,sBAAsB;AAC5C,SAAS,kBAAkB;AAC3B,SAAS,oCAAoC;AAC7C,SAAS,yBAAyB,yBAAyB;AAE3D,SAAS,uBAAuB;AAEzB,SAAS,4BAA4B;AAC1C,QAAM,cAAc,eAAe;AACnC,QAAM,mBAAmB,gBAAgB,CAAC,GAAG,uBAAuB,CAAC;AACrE,QAAM,aAAa,gBAAgB,CAAC,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/subscriptions/hooks/update-subscription-plan.hook.ts"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { withAction } from '../../../utils/withAction';\nimport { updateSubscriptionPlanAction } from '../actions/update-subscription-plan.action';\nimport { SUBSCRIPTIONS_QUERY_KEY, CHARGES_QUERY_KEY } from '../constants/query-keys.constants';\nimport type { UpdateSubscriptionPlanRequest } from '../types/calculate-subscription.type';\nimport { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';\n\nexport function useUpdateSubscriptionPlan() {\n const queryClient = useQueryClient();\n const subscriptionsKey = useAuthQueryKey([...SUBSCRIPTIONS_QUERY_KEY]);\n const chargesKey = useAuthQueryKey([...CHARGES_QUERY_KEY]);\n const iaCreditsKey = useAuthQueryKey(['ia-credits']);\n\n return useMutation({\n mutationFn: ({\n subscriptionId,\n data,\n }: {\n subscriptionId: number | string;\n data: UpdateSubscriptionPlanRequest;\n }) => withAction(updateSubscriptionPlanAction)(subscriptionId, data),\n onSuccess: () => {\n queryClient.invalidateQueries({ queryKey: subscriptionsKey });\n queryClient.invalidateQueries({ queryKey: chargesKey });\n // Alterar plan_extras de IA muda o saldo de créditos — invalida por prefixo.\n queryClient.invalidateQueries({ queryKey: iaCreditsKey });\n },\n });\n}\n"],"mappings":";AAEA,SAAS,aAAa,sBAAsB;AAC5C,SAAS,kBAAkB;AAC3B,SAAS,oCAAoC;AAC7C,SAAS,yBAAyB,yBAAyB;AAE3D,SAAS,uBAAuB;AAEzB,SAAS,4BAA4B;AAC1C,QAAM,cAAc,eAAe;AACnC,QAAM,mBAAmB,gBAAgB,CAAC,GAAG,uBAAuB,CAAC;AACrE,QAAM,aAAa,gBAAgB,CAAC,GAAG,iBAAiB,CAAC;AACzD,QAAM,eAAe,gBAAgB,CAAC,YAAY,CAAC;AAEnD,SAAO,YAAY;AAAA,IACjB,YAAY,CAAC;AAAA,MACX;AAAA,MACA;AAAA,IACF,MAGM,WAAW,4BAA4B,EAAE,gBAAgB,IAAI;AAAA,IACnE,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,iBAAiB,CAAC;AAC5D,kBAAY,kBAAkB,EAAE,UAAU,WAAW,CAAC;AAEtD,kBAAY,kBAAkB,EAAE,UAAU,aAAa,CAAC;AAAA,IAC1D;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/package.json
CHANGED
|
@@ -92,7 +92,7 @@ export default function HexaPromoModal() {
|
|
|
92
92
|
src="/promo/hexa-confetti.png"
|
|
93
93
|
alt=""
|
|
94
94
|
aria-hidden
|
|
95
|
-
className="pointer-events-none absolute -top-10 right-0 w-[280px] -rotate-
|
|
95
|
+
className="pointer-events-none absolute -top-10 right-0 w-[280px] -rotate-[16deg] select-none"
|
|
96
96
|
/>
|
|
97
97
|
|
|
98
98
|
<DialogClose className="absolute right-3 top-3 z-10 flex size-8 items-center justify-center rounded-lg p-2 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-950 focus:outline-none">
|
|
@@ -31,15 +31,26 @@ const FORCE_OPEN_FOR_TEST = false;
|
|
|
31
31
|
export default function HexaPromoModalGate() {
|
|
32
32
|
const { isAuthenticated } = useAuth();
|
|
33
33
|
const { whitelabel } = useWhitelabel();
|
|
34
|
-
const { activeModal, openModal } = useModalManager();
|
|
34
|
+
const { activeModal, openModal, closeModal } = useModalManager();
|
|
35
35
|
const { data } = useActiveSubscription();
|
|
36
36
|
|
|
37
37
|
const subscription = data?.data?.[0] ?? null;
|
|
38
38
|
const eligible =
|
|
39
39
|
isAuthenticated && whitelabel?.id === 1 && subscription?.type === 'trial';
|
|
40
40
|
|
|
41
|
+
// Elegibilidade atual visível dentro do setTimeout (que captura o valor do agendamento).
|
|
42
|
+
const eligibleRef = useRef(eligible);
|
|
43
|
+
eligibleRef.current = eligible;
|
|
44
|
+
|
|
41
45
|
const forcedRef = useRef(false);
|
|
42
46
|
|
|
47
|
+
// Nunca deixa a modal aberta para usuário deslogado/inelegível: se ele abriu logado e
|
|
48
|
+
// depois deslogou (ou perdeu a elegibilidade), fecha.
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (FORCE_OPEN_FOR_TEST) return;
|
|
51
|
+
if (!eligible && activeModal === 'hexaPromoModal') closeModal();
|
|
52
|
+
}, [eligible, activeModal, closeModal]);
|
|
53
|
+
|
|
43
54
|
useEffect(() => {
|
|
44
55
|
if (typeof window === 'undefined') return;
|
|
45
56
|
|
|
@@ -67,6 +78,8 @@ export default function HexaPromoModalGate() {
|
|
|
67
78
|
|
|
68
79
|
const remaining = Math.max(0, since.ms + ONLINE_DELAY_MS - Date.now());
|
|
69
80
|
const timer = setTimeout(() => {
|
|
81
|
+
// Revalida no disparo: o usuário pode ter deslogado durante a janela.
|
|
82
|
+
if (!eligibleRef.current) return;
|
|
70
83
|
// Recheca o cookie no disparo: se o outro app já abriu, não reabre aqui.
|
|
71
84
|
if (getHexaPromoSeenDay() === todayKey) return;
|
|
72
85
|
setHexaPromoSeenDay(todayKey);
|
|
@@ -11,6 +11,7 @@ export function useUpdateSubscriptionPlan() {
|
|
|
11
11
|
const queryClient = useQueryClient();
|
|
12
12
|
const subscriptionsKey = useAuthQueryKey([...SUBSCRIPTIONS_QUERY_KEY]);
|
|
13
13
|
const chargesKey = useAuthQueryKey([...CHARGES_QUERY_KEY]);
|
|
14
|
+
const iaCreditsKey = useAuthQueryKey(['ia-credits']);
|
|
14
15
|
|
|
15
16
|
return useMutation({
|
|
16
17
|
mutationFn: ({
|
|
@@ -23,6 +24,8 @@ export function useUpdateSubscriptionPlan() {
|
|
|
23
24
|
onSuccess: () => {
|
|
24
25
|
queryClient.invalidateQueries({ queryKey: subscriptionsKey });
|
|
25
26
|
queryClient.invalidateQueries({ queryKey: chargesKey });
|
|
27
|
+
// Alterar plan_extras de IA muda o saldo de créditos — invalida por prefixo.
|
|
28
|
+
queryClient.invalidateQueries({ queryKey: iaCreditsKey });
|
|
26
29
|
},
|
|
27
30
|
});
|
|
28
31
|
}
|