@greatapps/common 1.1.714 → 1.1.715
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/index.mjs +32 -38
- package/dist/index.mjs.map +1 -1
- package/dist/providers/auth.provider.mjs +0 -2
- package/dist/providers/auth.provider.mjs.map +1 -1
- package/dist/testing/constants.mjs.map +1 -1
- package/dist/testing/factories/create-mock.mjs.map +1 -1
- package/dist/testing/factories/subscription.factory.mjs.map +1 -1
- package/dist/testing/index.mjs.map +1 -1
- package/dist/testing/msw/envelope.mjs.map +1 -1
- package/dist/testing/msw/session.handlers.mjs.map +1 -1
- package/dist/testing/react/test-query-client.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +0 -3
- package/src/providers/auth.provider.tsx +0 -3
- package/src/testing/constants.ts +3 -12
- package/src/testing/factories/create-mock.ts +3 -8
- package/src/testing/factories/subscription.factory.ts +1 -2
- package/src/testing/index.ts +4 -8
- package/src/testing/msw/envelope.ts +1 -5
- package/src/testing/msw/session.handlers.ts +3 -7
- package/src/testing/react/test-query-client.ts +1 -4
- package/dist/components/modals/HexaPromoModal.mjs +0 -169
- package/dist/components/modals/HexaPromoModal.mjs.map +0 -1
- package/dist/components/modals/HexaPromoModalGate.mjs +0 -61
- package/dist/components/modals/HexaPromoModalGate.mjs.map +0 -1
- package/dist/store/useHexaPromoModal.mjs +0 -14
- package/dist/store/useHexaPromoModal.mjs.map +0 -1
- package/dist/utils/cookies/hexa-promo.mjs +0 -88
- package/dist/utils/cookies/hexa-promo.mjs.map +0 -1
- package/src/components/modals/HexaPromoModal.tsx +0 -218
- package/src/components/modals/HexaPromoModalGate.tsx +0 -93
- package/src/store/useHexaPromoModal.ts +0 -13
- package/src/utils/cookies/hexa-promo.ts +0 -114
package/src/testing/constants.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// builds matches the one the handlers expect.
|
|
1
|
+
// Single source of truth shared by the JWT, factories and request handlers. Apps must mirror it
|
|
2
|
+
// in their test env (GAPPS_R3_API_URL etc.) so the URL the ApiClient builds matches the handlers.
|
|
3
|
+
// `.test` domains never resolve, so a leaked unhandled request fails loudly.
|
|
5
4
|
export const TEST_IDS = {
|
|
6
5
|
wl: 1,
|
|
7
6
|
account: 123,
|
|
@@ -9,13 +8,6 @@ export const TEST_IDS = {
|
|
|
9
8
|
page: 789,
|
|
10
9
|
} as const;
|
|
11
10
|
|
|
12
|
-
// Bases the apps MUST export as test env:
|
|
13
|
-
// GAPPS_R3_API_URL = TEST_API.gapps
|
|
14
|
-
// GPAGES_R3_API_URL = TEST_API.gpages
|
|
15
|
-
// WHITELABEL_TOKEN_MASTER = TEST_API.masterToken
|
|
16
|
-
// WHITELABEL_DOMAIN = TEST_API.domain
|
|
17
|
-
// `.test` domains never resolve on the real network, so a leaked unhandled request fails loudly
|
|
18
|
-
// instead of hitting a real backend.
|
|
19
11
|
export const TEST_API = {
|
|
20
12
|
gapps: 'https://gapps.test',
|
|
21
13
|
gpages: 'https://gpages.test',
|
|
@@ -23,6 +15,5 @@ export const TEST_API = {
|
|
|
23
15
|
domain: 'app.test',
|
|
24
16
|
} as const;
|
|
25
17
|
|
|
26
|
-
// Fixed locale and version the ApiClient uses to build the URL (`/v1/pt-br/{wl}`).
|
|
27
18
|
export const TEST_API_VERSION = 'v1';
|
|
28
19
|
export const TEST_API_LOCALE = 'pt-br';
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
type Factory<T, O> = (overrides?: O) => T;
|
|
2
2
|
|
|
3
|
-
// Reset registry for the cached defaults. Runner-agnostic on purpose: these factories also run
|
|
4
|
-
// under `next dev` (E2E request handlers) where vitest's `beforeEach` does not exist, so the app
|
|
5
|
-
// calls `resetMocks()` from its own `beforeEach`.
|
|
6
3
|
const resetters = new Set<() => void>();
|
|
7
4
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// Between tests, `resetMocks()` refreshes the defaults.
|
|
5
|
+
// Without overrides, returns the same cached default within a test (stable for asserts); with
|
|
6
|
+
// overrides, a fresh instance. `resetMocks()` refreshes the defaults between tests — runner-agnostic
|
|
7
|
+
// (these factories also run under `next dev` for E2E, where vitest's `beforeEach` is absent).
|
|
12
8
|
export function createMock<T, O = Partial<T>>(factory: Factory<T, O>): Factory<T, O> {
|
|
13
9
|
let instance = factory();
|
|
14
10
|
resetters.add(() => {
|
|
@@ -17,7 +13,6 @@ export function createMock<T, O = Partial<T>>(factory: Factory<T, O>): Factory<T
|
|
|
17
13
|
return (overrides?: O): T => (overrides ? factory(overrides) : instance);
|
|
18
14
|
}
|
|
19
15
|
|
|
20
|
-
// Refreshes every default cached by createMock. Call it from the setup's beforeEach.
|
|
21
16
|
export function resetMocks(): void {
|
|
22
17
|
for (const reset of resetters) reset();
|
|
23
18
|
}
|
|
@@ -3,7 +3,6 @@ import { Subscription, SubscriptionItem } from '../../modules/subscriptions/type
|
|
|
3
3
|
import { TEST_IDS } from '../constants';
|
|
4
4
|
import { createMock } from './create-mock';
|
|
5
5
|
|
|
6
|
-
// Dates are Date objects (the inferred output of the schema).
|
|
7
6
|
export const createSubscriptionItemMock = createMock<SubscriptionItem, Partial<SubscriptionItem>>(
|
|
8
7
|
(overrides) => ({
|
|
9
8
|
id: overrides?.id ?? faker.number.int({ min: 1, max: 99999 }),
|
|
@@ -21,7 +20,7 @@ export const createSubscriptionItemMock = createMock<SubscriptionItem, Partial<S
|
|
|
21
20
|
}),
|
|
22
21
|
);
|
|
23
22
|
|
|
24
|
-
// Default:
|
|
23
|
+
// Default: active with a future due date (a valid trial).
|
|
25
24
|
export const createSubscriptionMock = createMock<Subscription, Partial<Subscription>>(
|
|
26
25
|
(overrides) => ({
|
|
27
26
|
id: overrides?.id ?? faker.number.int({ min: 1, max: 99999 }),
|
package/src/testing/index.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// subpath — `@greatapps/common/testing/react` — so the barrel and its load-time side effects
|
|
6
|
-
// (e.g. zustand stores reading localStorage) don't leak into reducer/route-handler tests.
|
|
7
|
-
//
|
|
8
|
-
// `msw` and `@faker-js/faker` are OPTIONAL peer dependencies: only the files here import them.
|
|
1
|
+
// `@greatapps/common/testing` — light, node/E2E-safe (factories + session handlers only). The
|
|
2
|
+
// React providers live in `@greatapps/common/testing/react` so the client barrel and its
|
|
3
|
+
// load-time side effects (e.g. zustand stores reading localStorage) don't leak into
|
|
4
|
+
// reducer/route-handler tests. `msw` and `@faker-js/faker` are optional peer dependencies.
|
|
9
5
|
export { TEST_IDS, TEST_API, TEST_API_VERSION, TEST_API_LOCALE } from './constants';
|
|
10
6
|
export * from './factories';
|
|
11
7
|
export * from './msw';
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import { HttpResponse } from 'msw';
|
|
2
2
|
|
|
3
3
|
// The envelope GreatApps backends return. Services read `data` as an array and take `data[0]` on
|
|
4
|
-
// finds
|
|
5
|
-
// `apiOne` when the consumer expects `data` to be a single object.
|
|
4
|
+
// finds, so the success default is a list; use `apiOne` for the few endpoints returning an object.
|
|
6
5
|
export function apiList<T>(items: T[], total = items.length) {
|
|
7
6
|
return HttpResponse.json({ status: 1, data: items, total });
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
// Success with a single item wrapped in a list (`{ status:1, data:[item], total:1 }`).
|
|
11
9
|
export function apiFind<T>(item: T) {
|
|
12
10
|
return apiList([item], 1);
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
// Success with `data` as a single object (few endpoints; most use a list).
|
|
16
13
|
export function apiOne<T>(data: T) {
|
|
17
14
|
return HttpResponse.json({ status: 1, data });
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
// Backend-shaped error (`{ status:0, message }`) with an HTTP status.
|
|
21
17
|
export function apiError(message = 'test error', status = 400) {
|
|
22
18
|
return HttpResponse.json({ status: 0, message }, { status });
|
|
23
19
|
}
|
|
@@ -9,19 +9,15 @@ import { createWhitelabelTokenMock } from '../factories/whitelabel.factory';
|
|
|
9
9
|
import { apiFind } from './envelope';
|
|
10
10
|
|
|
11
11
|
export interface SessionHandlersOptions {
|
|
12
|
-
// gapps base (= GAPPS_R3_API_URL). Defaults to TEST_API.gapps.
|
|
13
12
|
gappsBase?: string;
|
|
14
13
|
user?: User;
|
|
15
14
|
account?: Account;
|
|
16
15
|
whitelabel?: WhitelabelTokenData;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// them with their own handlers:
|
|
23
|
-
// setupServer(...makeSessionHandlers(), ...myPageHandlers)
|
|
24
|
-
// Paths follow the ApiClient template: `{base}/v1/{locale}/{wl}{endpoint}`.
|
|
18
|
+
// Server-side session resolution any app triggers when booting an authenticated route with
|
|
19
|
+
// DUMMY_AUTH_TOKEN (whitelabel by domain, token by wl id, user, account). Compose with app
|
|
20
|
+
// handlers: setupServer(...makeSessionHandlers(), ...myPageHandlers).
|
|
25
21
|
export function makeSessionHandlers(opts: SessionHandlersOptions = {}) {
|
|
26
22
|
const base = opts.gappsBase ?? TEST_API.gapps;
|
|
27
23
|
const user = opts.user ?? createUserMock();
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { QueryClient } from '@tanstack/react-query';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* QueryClient para testes: sem retry (erros aparecem na hora), sem cache entre testes
|
|
5
|
-
* (`gcTime: 0`) e sem refetch por foco. Crie um por teste pra isolamento total.
|
|
6
|
-
*/
|
|
3
|
+
// retry off (errors surface immediately) and gcTime 0 (no cache across tests). Create one per test.
|
|
7
4
|
export function makeTestQueryClient(): QueryClient {
|
|
8
5
|
return new QueryClient({
|
|
9
6
|
defaultOptions: {
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { toast } from "sonner";
|
|
4
|
-
import {
|
|
5
|
-
IconCheck,
|
|
6
|
-
IconCopy,
|
|
7
|
-
IconInfoCircle,
|
|
8
|
-
IconStar,
|
|
9
|
-
IconTicket,
|
|
10
|
-
IconX
|
|
11
|
-
} from "@tabler/icons-react";
|
|
12
|
-
import { Dialog, DialogClose, DialogContent, DialogTitle } from "../ui/overlay/Dialog";
|
|
13
|
-
import { Toast } from "../ui/feedback/Toast";
|
|
14
|
-
import { Button } from "../ui/buttons/Button";
|
|
15
|
-
import useCopyToClipboard from "../../hooks/copy-to-clipboard.hook";
|
|
16
|
-
import { useModalManager } from "../../store/useModalManager";
|
|
17
|
-
import { useWhitelabelUrls } from "../../providers/whitelabel.provider";
|
|
18
|
-
import { cn } from "../../infra/utils/clsx";
|
|
19
|
-
const COPY = {
|
|
20
|
-
title: "Rumo ao Hexa!",
|
|
21
|
-
subtitlePrefix: "Ofertas exclusivas at\xE9 ",
|
|
22
|
-
subtitleDate: "24/06",
|
|
23
|
-
mostChosen: "Mais escolhido",
|
|
24
|
-
couponLabel: "Cupom",
|
|
25
|
-
choosePlan: "Escolher plano",
|
|
26
|
-
couponCopied: "Cupom copiado!",
|
|
27
|
-
close: "Fechar",
|
|
28
|
-
footerPrefix: "V\xE1lido para a ",
|
|
29
|
-
footerBold: "primeira contrata\xE7\xE3o",
|
|
30
|
-
footerSuffix: " dos planos Essencial, Neg\xF3cio ou Ag\xEAncia"
|
|
31
|
-
};
|
|
32
|
-
const COMBOS = [
|
|
33
|
-
{
|
|
34
|
-
coupon: "MENSAL10",
|
|
35
|
-
name: "Combo convoca\xE7\xE3o",
|
|
36
|
-
highlighted: false,
|
|
37
|
-
period: "monthly",
|
|
38
|
-
features: ["10% OFF no plano mensal", "Webinars exclusivos"]
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
coupon: "SEMESTRAL10",
|
|
42
|
-
name: "Combo Hexa",
|
|
43
|
-
highlighted: true,
|
|
44
|
-
period: "semiannual",
|
|
45
|
-
features: ["10% OFF no plano semestral", "3 templates exclusivos", "Webinars exclusivos"]
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
coupon: "ANUAL10",
|
|
49
|
-
name: "Combo escala\xE7\xE3o",
|
|
50
|
-
highlighted: false,
|
|
51
|
-
period: "annual",
|
|
52
|
-
features: ["10% OFF no plano anual", "3 templates exclusivos"]
|
|
53
|
-
}
|
|
54
|
-
];
|
|
55
|
-
const GRADIENT = "linear-gradient(180deg, #08c44f 0%, #7bd237 46.6%, #ffe31b 100%)";
|
|
56
|
-
function HexaPromoModal() {
|
|
57
|
-
const { activeModal, closeModal } = useModalManager();
|
|
58
|
-
const { accountsUrl } = useWhitelabelUrls();
|
|
59
|
-
const isOpen = activeModal === "hexaPromoModal";
|
|
60
|
-
const goToPlans = (coupon, period) => {
|
|
61
|
-
closeModal();
|
|
62
|
-
const url = `${accountsUrl}/subscriptions?tab=plans&coupon=${encodeURIComponent(coupon)}&period=${encodeURIComponent(period)}`;
|
|
63
|
-
window.location.assign(url);
|
|
64
|
-
};
|
|
65
|
-
return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: () => closeModal(), children: /* @__PURE__ */ jsxs(
|
|
66
|
-
DialogContent,
|
|
67
|
-
{
|
|
68
|
-
showCloseButton: false,
|
|
69
|
-
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
70
|
-
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",
|
|
71
|
-
children: [
|
|
72
|
-
/* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: COPY.title }),
|
|
73
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-[12px] p-1", style: { background: GRADIENT }, children: /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-[10px] bg-white p-8 shadow-[0px_2px_10px_0px_rgba(26,26,26,0.05)]", children: [
|
|
74
|
-
/* @__PURE__ */ jsx(
|
|
75
|
-
"img",
|
|
76
|
-
{
|
|
77
|
-
src: "/promo/hexa-confetti.png",
|
|
78
|
-
alt: "",
|
|
79
|
-
"aria-hidden": true,
|
|
80
|
-
className: "pointer-events-none absolute -top-10 right-0 w-[280px] rotate-[16deg] select-none"
|
|
81
|
-
}
|
|
82
|
-
),
|
|
83
|
-
/* @__PURE__ */ jsxs(DialogClose, { className: "absolute right-3 top-3 z-10 flex size-[34px] cursor-pointer items-center justify-center rounded-lg bg-white p-2 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-950 focus:outline-none", children: [
|
|
84
|
-
/* @__PURE__ */ jsx("span", { className: "sr-only", children: COPY.close }),
|
|
85
|
-
/* @__PURE__ */ jsx(IconX, { className: "size-[18px]" })
|
|
86
|
-
] }),
|
|
87
|
-
/* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex w-fit items-center gap-[9px] rounded-lg bg-zinc-100 px-[9px] py-2", children: [
|
|
88
|
-
/* @__PURE__ */ jsx("img", { src: "/promo/hexa-logo.svg", alt: "", "aria-hidden": true, className: "size-6 select-none" }),
|
|
89
|
-
/* @__PURE__ */ jsx("img", { src: "/promo/hexa-flag.png", alt: "", "aria-hidden": true, className: "size-6 rounded select-none" })
|
|
90
|
-
] }),
|
|
91
|
-
/* @__PURE__ */ jsxs("div", { className: "relative z-[1] mt-4 flex flex-col gap-1", children: [
|
|
92
|
-
/* @__PURE__ */ jsx("h2", { className: "font-['Outfit'] text-[32px] font-semibold leading-8 text-zinc-950", children: COPY.title }),
|
|
93
|
-
/* @__PURE__ */ jsxs("p", { className: "paragraph-medium-medium text-zinc-950", children: [
|
|
94
|
-
COPY.subtitlePrefix,
|
|
95
|
-
/* @__PURE__ */ jsx("span", { className: "font-bold", children: COPY.subtitleDate })
|
|
96
|
-
] })
|
|
97
|
-
] }),
|
|
98
|
-
/* @__PURE__ */ jsx("div", { className: "relative z-[1] mt-10 flex flex-col items-stretch gap-3 md:flex-row", children: COMBOS.map((combo) => /* @__PURE__ */ jsx(ComboCard, { combo, onChoose: () => goToPlans(combo.coupon, combo.period) }, combo.coupon)) }),
|
|
99
|
-
/* @__PURE__ */ jsxs("div", { className: "relative z-[1] mt-3 flex items-center justify-center gap-2.5 rounded-[10px] bg-zinc-100 p-3 text-center", children: [
|
|
100
|
-
/* @__PURE__ */ jsx(IconInfoCircle, { className: "size-4 shrink-0 text-zinc-950" }),
|
|
101
|
-
/* @__PURE__ */ jsxs("p", { className: "paragraph-small-medium text-zinc-950", children: [
|
|
102
|
-
COPY.footerPrefix,
|
|
103
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold", children: COPY.footerBold }),
|
|
104
|
-
COPY.footerSuffix
|
|
105
|
-
] })
|
|
106
|
-
] })
|
|
107
|
-
] }) })
|
|
108
|
-
]
|
|
109
|
-
}
|
|
110
|
-
) });
|
|
111
|
-
}
|
|
112
|
-
function ComboCard({ combo, onChoose }) {
|
|
113
|
-
const { isCopied, copy } = useCopyToClipboard({
|
|
114
|
-
onSuccess: () => {
|
|
115
|
-
toast.custom((t) => /* @__PURE__ */ jsx(Toast, { variant: "success", message: COPY.couponCopied, toastId: t }));
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
return /* @__PURE__ */ jsxs(
|
|
119
|
-
"div",
|
|
120
|
-
{
|
|
121
|
-
className: cn(
|
|
122
|
-
"relative flex flex-1 flex-col rounded-[10px] border bg-white pb-3 md:w-[276px]",
|
|
123
|
-
combo.highlighted ? "border-cyan-500" : "border-zinc-200"
|
|
124
|
-
),
|
|
125
|
-
children: [
|
|
126
|
-
combo.highlighted && /* @__PURE__ */ jsxs("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", children: [
|
|
127
|
-
/* @__PURE__ */ jsx(IconStar, { className: "size-3 text-cyan-600" }),
|
|
128
|
-
/* @__PURE__ */ jsx("span", { className: "paragraph-xsmall-semibold whitespace-nowrap text-cyan-600", children: COPY.mostChosen }),
|
|
129
|
-
/* @__PURE__ */ jsx(IconStar, { className: "size-3 text-cyan-600" })
|
|
130
|
-
] }),
|
|
131
|
-
/* @__PURE__ */ jsx("h3", { className: "border-b border-zinc-200 px-4 py-4 text-center font-['Outfit'] text-base font-semibold text-zinc-950", children: combo.name }),
|
|
132
|
-
/* @__PURE__ */ jsx("ul", { className: "flex flex-1 flex-col gap-5 px-5 pt-5", children: combo.features.map((feature) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2.5", children: [
|
|
133
|
-
/* @__PURE__ */ jsx(IconCheck, { className: "size-5 shrink-0 text-cyan-500" }),
|
|
134
|
-
/* @__PURE__ */ jsx("span", { className: "paragraph-small-medium text-zinc-950", children: feature })
|
|
135
|
-
] }, feature)) }),
|
|
136
|
-
/* @__PURE__ */ jsxs("div", { className: "mx-3 mt-5 flex items-center gap-3 rounded-lg bg-cyan-50 py-2 pl-3.5 pr-2.5", children: [
|
|
137
|
-
/* @__PURE__ */ jsx(IconTicket, { className: "size-5 shrink-0 text-cyan-600" }),
|
|
138
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
139
|
-
/* @__PURE__ */ jsx("span", { className: "paragraph-xsmall-medium text-zinc-400", children: COPY.couponLabel }),
|
|
140
|
-
/* @__PURE__ */ jsx("span", { className: "paragraph-small-semibold text-zinc-950", children: combo.coupon })
|
|
141
|
-
] }),
|
|
142
|
-
/* @__PURE__ */ jsx(
|
|
143
|
-
"button",
|
|
144
|
-
{
|
|
145
|
-
type: "button",
|
|
146
|
-
"aria-label": COPY.couponLabel,
|
|
147
|
-
onClick: () => copy(combo.coupon),
|
|
148
|
-
className: "flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-zinc-500 transition-colors hover:bg-cyan-100 hover:text-zinc-950",
|
|
149
|
-
children: isCopied ? /* @__PURE__ */ jsx(IconCheck, { className: "size-[18px]" }) : /* @__PURE__ */ jsx(IconCopy, { className: "size-[18px]" })
|
|
150
|
-
}
|
|
151
|
-
)
|
|
152
|
-
] }),
|
|
153
|
-
/* @__PURE__ */ jsx("div", { className: "mx-3 mt-3", children: /* @__PURE__ */ jsx(
|
|
154
|
-
Button,
|
|
155
|
-
{
|
|
156
|
-
variant: combo.highlighted ? "default" : "secondary",
|
|
157
|
-
className: cn("h-11! w-full", combo.highlighted && "bg-cyan-500 text-white hover:bg-cyan-600"),
|
|
158
|
-
onClick: onChoose,
|
|
159
|
-
children: COPY.choosePlan
|
|
160
|
-
}
|
|
161
|
-
) })
|
|
162
|
-
]
|
|
163
|
-
}
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
export {
|
|
167
|
-
HexaPromoModal as default
|
|
168
|
-
};
|
|
169
|
-
//# sourceMappingURL=HexaPromoModal.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
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\ntype ComboPeriod = 'monthly' | 'semiannual' | 'annual';\n\ninterface HexaCombo {\n coupon: string;\n name: string;\n features: string[];\n highlighted: boolean;\n period: ComboPeriod;\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 period: 'monthly',\n features: ['10% OFF no plano mensal', 'Webinars exclusivos'],\n },\n {\n coupon: 'SEMESTRAL10',\n name: 'Combo Hexa',\n highlighted: true,\n period: 'semiannual',\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 period: 'annual',\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, period: ComboPeriod) => {\n closeModal();\n const url = `${accountsUrl}/subscriptions?tab=plans&coupon=${encodeURIComponent(coupon)}&period=${encodeURIComponent(period)}`;\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-[34px] cursor-pointer items-center justify-center rounded-lg bg-white 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, combo.period)} />\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 cursor-pointer 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,61 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
3
|
-
import { useActiveSubscription } from "../../modules/subscriptions/hooks/find-active-subscription.hook";
|
|
4
|
-
import { useAuth } from "../../providers/auth.provider";
|
|
5
|
-
import { useWhitelabel } from "../../providers/whitelabel.provider";
|
|
6
|
-
import { useModalManager } from "../../store/useModalManager";
|
|
7
|
-
import {
|
|
8
|
-
getHexaPromoSeenDay,
|
|
9
|
-
getHexaPromoSince,
|
|
10
|
-
hexaPromoDayKey,
|
|
11
|
-
setHexaPromoSeenDay,
|
|
12
|
-
setHexaPromoSince
|
|
13
|
-
} from "../../utils/cookies/hexa-promo";
|
|
14
|
-
const ONLINE_DELAY_MS = 3 * 60 * 1e3;
|
|
15
|
-
const FORCE_OPEN_FOR_TEST = false;
|
|
16
|
-
function HexaPromoModalGate() {
|
|
17
|
-
const { isAuthenticated } = useAuth();
|
|
18
|
-
const { whitelabel } = useWhitelabel();
|
|
19
|
-
const { activeModal, openModal, closeModal } = useModalManager();
|
|
20
|
-
const { data } = useActiveSubscription();
|
|
21
|
-
const subscription = data?.data?.[0] ?? null;
|
|
22
|
-
const eligible = isAuthenticated && whitelabel?.id === 1 && subscription?.type === "trial";
|
|
23
|
-
const eligibleRef = useRef(eligible);
|
|
24
|
-
eligibleRef.current = eligible;
|
|
25
|
-
const forcedRef = useRef(false);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (FORCE_OPEN_FOR_TEST) return;
|
|
28
|
-
if (!eligible && activeModal === "hexaPromoModal") closeModal();
|
|
29
|
-
}, [eligible, activeModal, closeModal]);
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (typeof window === "undefined") return;
|
|
32
|
-
if (FORCE_OPEN_FOR_TEST) {
|
|
33
|
-
if (forcedRef.current || activeModal) return;
|
|
34
|
-
forcedRef.current = true;
|
|
35
|
-
openModal("hexaPromoModal");
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (!eligible) return;
|
|
39
|
-
if (activeModal) return;
|
|
40
|
-
const todayKey = hexaPromoDayKey();
|
|
41
|
-
if (getHexaPromoSeenDay() === todayKey) return;
|
|
42
|
-
let since = getHexaPromoSince();
|
|
43
|
-
if (!since || since.day !== todayKey) {
|
|
44
|
-
since = { day: todayKey, ms: Date.now() };
|
|
45
|
-
setHexaPromoSince(since.day, since.ms);
|
|
46
|
-
}
|
|
47
|
-
const remaining = Math.max(0, since.ms + ONLINE_DELAY_MS - Date.now());
|
|
48
|
-
const timer = setTimeout(() => {
|
|
49
|
-
if (!eligibleRef.current) return;
|
|
50
|
-
if (getHexaPromoSeenDay() === todayKey) return;
|
|
51
|
-
setHexaPromoSeenDay(todayKey);
|
|
52
|
-
openModal("hexaPromoModal");
|
|
53
|
-
}, remaining);
|
|
54
|
-
return () => clearTimeout(timer);
|
|
55
|
-
}, [eligible, activeModal, openModal]);
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
export {
|
|
59
|
-
HexaPromoModalGate as default
|
|
60
|
-
};
|
|
61
|
-
//# sourceMappingURL=HexaPromoModalGate.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { useModalManager } from "./useModalManager";
|
|
2
|
-
const HEXA_PROMO_MODAL_KEY = "hexaPromoModal";
|
|
3
|
-
const useHexaPromoModal = () => {
|
|
4
|
-
const { activeModal, openModal, closeModal } = useModalManager();
|
|
5
|
-
return {
|
|
6
|
-
open: activeModal === HEXA_PROMO_MODAL_KEY,
|
|
7
|
-
openModal: () => openModal(HEXA_PROMO_MODAL_KEY),
|
|
8
|
-
closeModal
|
|
9
|
-
};
|
|
10
|
-
};
|
|
11
|
-
export {
|
|
12
|
-
useHexaPromoModal
|
|
13
|
-
};
|
|
14
|
-
//# sourceMappingURL=useHexaPromoModal.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/store/useHexaPromoModal.ts"],"sourcesContent":["import { useModalManager } from './useModalManager';\n\nconst HEXA_PROMO_MODAL_KEY = 'hexaPromoModal';\n\nexport const useHexaPromoModal = () => {\n const { activeModal, openModal, closeModal } = useModalManager();\n\n return {\n open: activeModal === HEXA_PROMO_MODAL_KEY,\n openModal: () => openModal(HEXA_PROMO_MODAL_KEY),\n closeModal,\n };\n};\n"],"mappings":"AAAA,SAAS,uBAAuB;AAEhC,MAAM,uBAAuB;AAEtB,MAAM,oBAAoB,MAAM;AACrC,QAAM,EAAE,aAAa,WAAW,WAAW,IAAI,gBAAgB;AAE/D,SAAO;AAAA,IACL,MAAM,gBAAgB;AAAA,IACtB,WAAW,MAAM,UAAU,oBAAoB;AAAA,IAC/C;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
const SINCE_COOKIE = "gpromo-hexa-since";
|
|
2
|
-
const SEEN_COOKIE = "gpromo-hexa-seen";
|
|
3
|
-
const MAX_AGE_SECONDS = 60 * 60 * 24 * 2;
|
|
4
|
-
function parentCookieDomain() {
|
|
5
|
-
if (typeof window === "undefined") return void 0;
|
|
6
|
-
const host = window.location.hostname;
|
|
7
|
-
if (!host || host === "localhost") return void 0;
|
|
8
|
-
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) return void 0;
|
|
9
|
-
const parts = host.split(".");
|
|
10
|
-
if (parts.length < 2) return void 0;
|
|
11
|
-
return "." + parts.slice(1).join(".");
|
|
12
|
-
}
|
|
13
|
-
function readCookie(name) {
|
|
14
|
-
if (typeof document === "undefined") return null;
|
|
15
|
-
const m = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
|
|
16
|
-
if (!m) return null;
|
|
17
|
-
try {
|
|
18
|
-
return decodeURIComponent(m[1]);
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function writeCookie(name, value) {
|
|
24
|
-
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
25
|
-
const domain = parentCookieDomain();
|
|
26
|
-
const parts = [
|
|
27
|
-
`${name}=${encodeURIComponent(value)}`,
|
|
28
|
-
"Path=/",
|
|
29
|
-
`Max-Age=${MAX_AGE_SECONDS}`,
|
|
30
|
-
"SameSite=Lax",
|
|
31
|
-
domain ? `Domain=${domain}` : "",
|
|
32
|
-
window.location.protocol === "https:" ? "Secure" : ""
|
|
33
|
-
].filter(Boolean);
|
|
34
|
-
try {
|
|
35
|
-
document.cookie = parts.join("; ");
|
|
36
|
-
} catch {
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function expireCookie(name) {
|
|
40
|
-
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
41
|
-
const domain = parentCookieDomain();
|
|
42
|
-
const parts = [
|
|
43
|
-
`${name}=`,
|
|
44
|
-
"Path=/",
|
|
45
|
-
"Max-Age=0",
|
|
46
|
-
"SameSite=Lax",
|
|
47
|
-
domain ? `Domain=${domain}` : "",
|
|
48
|
-
window.location.protocol === "https:" ? "Secure" : ""
|
|
49
|
-
].filter(Boolean);
|
|
50
|
-
try {
|
|
51
|
-
document.cookie = parts.join("; ");
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function hexaPromoDayKey() {
|
|
56
|
-
const d = /* @__PURE__ */ new Date();
|
|
57
|
-
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
58
|
-
}
|
|
59
|
-
function getHexaPromoSeenDay() {
|
|
60
|
-
return readCookie(SEEN_COOKIE);
|
|
61
|
-
}
|
|
62
|
-
function setHexaPromoSeenDay(day) {
|
|
63
|
-
writeCookie(SEEN_COOKIE, day);
|
|
64
|
-
}
|
|
65
|
-
function getHexaPromoSince() {
|
|
66
|
-
const raw = readCookie(SINCE_COOKIE);
|
|
67
|
-
if (!raw) return null;
|
|
68
|
-
const [day, msRaw] = raw.split("|");
|
|
69
|
-
const ms = Number(msRaw);
|
|
70
|
-
if (!day || !Number.isFinite(ms)) return null;
|
|
71
|
-
return { day, ms };
|
|
72
|
-
}
|
|
73
|
-
function setHexaPromoSince(day, ms) {
|
|
74
|
-
writeCookie(SINCE_COOKIE, `${day}|${ms}`);
|
|
75
|
-
}
|
|
76
|
-
function clearHexaPromo() {
|
|
77
|
-
expireCookie(SINCE_COOKIE);
|
|
78
|
-
expireCookie(SEEN_COOKIE);
|
|
79
|
-
}
|
|
80
|
-
export {
|
|
81
|
-
clearHexaPromo,
|
|
82
|
-
getHexaPromoSeenDay,
|
|
83
|
-
getHexaPromoSince,
|
|
84
|
-
hexaPromoDayKey,
|
|
85
|
-
setHexaPromoSeenDay,
|
|
86
|
-
setHexaPromoSince
|
|
87
|
-
};
|
|
88
|
-
//# sourceMappingURL=hexa-promo.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cookies/hexa-promo.ts"],"sourcesContent":["/**\n * Estado do \"Rumo ao Hexa\" promo modal — persistido em **cookies no domínio registrável\n * pai** (ex.: `accounts.greatpages.dev.br` → `.greatpages.dev.br`) para serem compartilhados\n * entre os subdomínios dos apps (accounts/gapps ↔ pages/gpages). Assim a janela de \"3 minutos\n * online\" é cumulativa entre os apps e a modal abre **uma única vez** por sessão/dia,\n * independente de em qual app o usuário esteja.\n *\n * São dois cookies:\n * - `gpromo-hexa-since` = `<dia>|<timestampMs>` — início da janela de 3 min (gravado uma vez).\n * - `gpromo-hexa-seen` = `<dia>` — dia em que a modal já foi exibida.\n *\n * `<dia>` é a data local `YYYY-MM-DD`: vira automaticamente no dia seguinte, fazendo a modal\n * reaparecer. No logout, ambos os cookies são limpos (ver `auth.provider`), então um novo\n * login também faz a modal reaparecer.\n *\n * Em localhost (sem subdomínio) caem em cookies host-scoped — equivalente ao localStorage;\n * cross-port não compartilha (limitação aceita no dev local).\n */\n\nconst SINCE_COOKIE = 'gpromo-hexa-since';\nconst SEEN_COOKIE = 'gpromo-hexa-seen';\nconst MAX_AGE_SECONDS = 60 * 60 * 24 * 2; // 2 dias — folga sobre a janela de 1 dia.\n\nfunction parentCookieDomain(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n const host = window.location.hostname;\n if (!host || host === 'localhost') return undefined;\n // IP literal → sem atributo Domain (cookie host-scoped)\n if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(host)) return undefined;\n const parts = host.split('.');\n if (parts.length < 2) return undefined;\n // Tira o subdomínio mais à esquerda — `accounts.greatpages.dev.br` → `.greatpages.dev.br`\n return '.' + parts.slice(1).join('.');\n}\n\nfunction readCookie(name: string): string | null {\n if (typeof document === 'undefined') return null;\n const m = document.cookie.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]+)`));\n if (!m) return null;\n try {\n return decodeURIComponent(m[1]);\n } catch {\n return null;\n }\n}\n\nfunction writeCookie(name: string, value: string): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n const domain = parentCookieDomain();\n const parts = [\n `${name}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${MAX_AGE_SECONDS}`,\n 'SameSite=Lax',\n domain ? `Domain=${domain}` : '',\n window.location.protocol === 'https:' ? 'Secure' : '',\n ].filter(Boolean);\n try {\n document.cookie = parts.join('; ');\n } catch {\n // Se o navegador bloquear cookies, seguimos com a exibição única por sessão (timer no Gate).\n }\n}\n\nfunction expireCookie(name: string): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n const domain = parentCookieDomain();\n const parts = [\n `${name}=`,\n 'Path=/',\n 'Max-Age=0',\n 'SameSite=Lax',\n domain ? `Domain=${domain}` : '',\n window.location.protocol === 'https:' ? 'Secure' : '',\n ].filter(Boolean);\n try {\n document.cookie = parts.join('; ');\n } catch {\n // noop\n }\n}\n\n/** Chave do dia local (`YYYY-MM-DD`) usada para reabrir a modal a cada novo dia. */\nexport function hexaPromoDayKey(): string {\n const d = new Date();\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n}\n\nexport function getHexaPromoSeenDay(): string | null {\n return readCookie(SEEN_COOKIE);\n}\n\nexport function setHexaPromoSeenDay(day: string): void {\n writeCookie(SEEN_COOKIE, day);\n}\n\nexport function getHexaPromoSince(): { day: string; ms: number } | null {\n const raw = readCookie(SINCE_COOKIE);\n if (!raw) return null;\n const [day, msRaw] = raw.split('|');\n const ms = Number(msRaw);\n if (!day || !Number.isFinite(ms)) return null;\n return { day, ms };\n}\n\nexport function setHexaPromoSince(day: string, ms: number): void {\n writeCookie(SINCE_COOKIE, `${day}|${ms}`);\n}\n\n/** Limpa o estado da promo (chamado no logout para reabrir a modal no próximo login). */\nexport function clearHexaPromo(): void {\n expireCookie(SINCE_COOKIE);\n expireCookie(SEEN_COOKIE);\n}\n"],"mappings":"AAmBA,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,kBAAkB,KAAK,KAAK,KAAK;AAEvC,SAAS,qBAAyC;AAChD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,OAAO,OAAO,SAAS;AAC7B,MAAI,CAAC,QAAQ,SAAS,YAAa,QAAO;AAE1C,MAAI,uBAAuB,KAAK,IAAI,EAAG,QAAO;AAC9C,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,SAAO,MAAM,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG;AACtC;AAEA,SAAS,WAAW,MAA6B;AAC/C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,IAAI,SAAS,OAAO,MAAM,IAAI,OAAO,cAAc,IAAI,UAAU,CAAC;AACxE,MAAI,CAAC,EAAG,QAAO;AACf,MAAI;AACF,WAAO,mBAAmB,EAAE,CAAC,CAAC;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,MAAc,OAAqB;AACtD,MAAI,OAAO,aAAa,eAAe,OAAO,WAAW,YAAa;AACtE,QAAM,SAAS,mBAAmB;AAClC,QAAM,QAAQ;AAAA,IACZ,GAAG,IAAI,IAAI,mBAAmB,KAAK,CAAC;AAAA,IACpC;AAAA,IACA,WAAW,eAAe;AAAA,IAC1B;AAAA,IACA,SAAS,UAAU,MAAM,KAAK;AAAA,IAC9B,OAAO,SAAS,aAAa,WAAW,WAAW;AAAA,EACrD,EAAE,OAAO,OAAO;AAChB,MAAI;AACF,aAAS,SAAS,MAAM,KAAK,IAAI;AAAA,EACnC,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,aAAa,MAAoB;AACxC,MAAI,OAAO,aAAa,eAAe,OAAO,WAAW,YAAa;AACtE,QAAM,SAAS,mBAAmB;AAClC,QAAM,QAAQ;AAAA,IACZ,GAAG,IAAI;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,UAAU,MAAM,KAAK;AAAA,IAC9B,OAAO,SAAS,aAAa,WAAW,WAAW;AAAA,EACrD,EAAE,OAAO,OAAO;AAChB,MAAI;AACF,aAAS,SAAS,MAAM,KAAK,IAAI;AAAA,EACnC,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,kBAA0B;AACxC,QAAM,IAAI,oBAAI,KAAK;AACnB,SAAO,GAAG,EAAE,YAAY,CAAC,IAAI,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAChH;AAEO,SAAS,sBAAqC;AACnD,SAAO,WAAW,WAAW;AAC/B;AAEO,SAAS,oBAAoB,KAAmB;AACrD,cAAY,aAAa,GAAG;AAC9B;AAEO,SAAS,oBAAwD;AACtE,QAAM,MAAM,WAAW,YAAY;AACnC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,CAAC,KAAK,KAAK,IAAI,IAAI,MAAM,GAAG;AAClC,QAAM,KAAK,OAAO,KAAK;AACvB,MAAI,CAAC,OAAO,CAAC,OAAO,SAAS,EAAE,EAAG,QAAO;AACzC,SAAO,EAAE,KAAK,GAAG;AACnB;AAEO,SAAS,kBAAkB,KAAa,IAAkB;AAC/D,cAAY,cAAc,GAAG,GAAG,IAAI,EAAE,EAAE;AAC1C;AAGO,SAAS,iBAAuB;AACrC,eAAa,YAAY;AACzB,eAAa,WAAW;AAC1B;","names":[]}
|