@hook-sdk/template 0.20.0 → 0.22.0
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.cjs +714 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +242 -2
- package/dist/index.d.ts +242 -2
- package/dist/index.js +666 -163
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/AppRoot.tsx
|
|
2
2
|
import { useMemo as useMemo3 } from "react";
|
|
3
3
|
import { BrowserRouter, MemoryRouter, Navigate, Route, Routes } from "react-router-dom";
|
|
4
|
-
import { useHook as
|
|
4
|
+
import { useHook as useHook7 } from "@hook-sdk/sdk";
|
|
5
5
|
|
|
6
6
|
// src/config/AppConfigContext.tsx
|
|
7
7
|
import { createContext, useContext } from "react";
|
|
@@ -35,7 +35,16 @@ var AuthFlowSchema = z.object({
|
|
|
35
35
|
});
|
|
36
36
|
var PaywallNonFreeSchema = z.object({
|
|
37
37
|
mode: z.enum(["trial", "pay_first"]),
|
|
38
|
+
// Legacy flat trial — fallback when per-method fields aren't set.
|
|
38
39
|
trialDays: z.number().int().nonnegative().optional(),
|
|
40
|
+
// Per-method trial (ADR-022 Amendment 2026-05-12 + G154). PIX Auto can't
|
|
41
|
+
// offer trial on Asaas today (Jornada 2 unavailable), so apps that mix
|
|
42
|
+
// methods must split the value to avoid bait-and-switch on PIX shoppers.
|
|
43
|
+
trialDaysCard: z.number().int().nonnegative().optional(),
|
|
44
|
+
trialDaysPix: z.number().int().nonnegative().optional(),
|
|
45
|
+
// Which method the paywall preselects. null/undefined = no preselection
|
|
46
|
+
// (user picks). Per-app override reflects audience, not Hook bias.
|
|
47
|
+
defaultMethod: z.enum(["card", "pix-auto", "pix-once"]).nullable().optional(),
|
|
39
48
|
cycles: z.array(z.enum(["MONTHLY", "YEARLY"])).min(1),
|
|
40
49
|
prices: z.object({
|
|
41
50
|
monthlyCents: z.number().int().nonnegative(),
|
|
@@ -286,10 +295,11 @@ function usePaywallState() {
|
|
|
286
295
|
() => declaredMethods.filter((m) => isMethodAvailable(availability, m)),
|
|
287
296
|
[declaredMethods, availability]
|
|
288
297
|
);
|
|
289
|
-
const
|
|
298
|
+
const configDefault = !isFree && "defaultMethod" in paywall ? paywall.defaultMethod ?? null : null;
|
|
299
|
+
const defaultMethod = (configDefault && methods.includes(configDefault) ? configDefault : null) ?? methods[0] ?? declaredMethods[0] ?? "card";
|
|
290
300
|
const [selectedMethodRaw, setSelectedMethod] = useState(defaultMethod);
|
|
291
301
|
const selectedMethod = methods.includes(selectedMethodRaw) ? selectedMethodRaw : methods[0] ?? selectedMethodRaw;
|
|
292
|
-
const initialCycle = isFree ? "MONTHLY" : paywall.cycles[0] ?? "MONTHLY";
|
|
302
|
+
const initialCycle = isFree ? "MONTHLY" : paywall.cycles.includes("YEARLY") ? "YEARLY" : paywall.cycles[0] ?? "MONTHLY";
|
|
293
303
|
const [cycle, setCycle] = useState(initialCycle);
|
|
294
304
|
const cpfRequired = !isFree && paywall.requiresCpf;
|
|
295
305
|
const [cpf, setCpf] = useState("");
|
|
@@ -307,6 +317,22 @@ function usePaywallState() {
|
|
|
307
317
|
const [submitting, setSubmitting] = useState(false);
|
|
308
318
|
const status = subscription.status();
|
|
309
319
|
const daysLeftInTrial = subscription.daysLeftInTrial();
|
|
320
|
+
const trialDaysForMethod = useCallback(
|
|
321
|
+
(method) => {
|
|
322
|
+
if (isFree) return 0;
|
|
323
|
+
if (method === "card") {
|
|
324
|
+
if (typeof paywall.trialDaysCard === "number") return paywall.trialDaysCard;
|
|
325
|
+
if (typeof paywall.trialDays === "number") return paywall.trialDays;
|
|
326
|
+
return 7;
|
|
327
|
+
}
|
|
328
|
+
if (typeof paywall.trialDaysPix === "number") return paywall.trialDaysPix;
|
|
329
|
+
if (typeof paywall.trialDays === "number") return paywall.trialDays;
|
|
330
|
+
return 0;
|
|
331
|
+
},
|
|
332
|
+
[isFree, paywall]
|
|
333
|
+
);
|
|
334
|
+
const trialDaysCard = trialDaysForMethod("card");
|
|
335
|
+
const trialDaysPix = trialDaysForMethod("pix-auto");
|
|
310
336
|
const initialLoadComplete = subscription.initialLoadComplete;
|
|
311
337
|
const hasAccess = subscription.hasAccess;
|
|
312
338
|
const pixPending = useMemo2(() => {
|
|
@@ -346,6 +372,40 @@ function usePaywallState() {
|
|
|
346
372
|
discountPercent: discount
|
|
347
373
|
};
|
|
348
374
|
}, [paywall, cycle, isFree]);
|
|
375
|
+
const hasConsumedTrial = useMemo2(() => {
|
|
376
|
+
return ["active", "trialing", "past_due", "canceled", "expired"].includes(status);
|
|
377
|
+
}, [status]);
|
|
378
|
+
const currentPriceCents = useMemo2(() => {
|
|
379
|
+
if (isFree) return 0;
|
|
380
|
+
return cycle === "YEARLY" ? paywall.prices.yearlyCents : paywall.prices.monthlyCents;
|
|
381
|
+
}, [paywall, cycle, isFree]);
|
|
382
|
+
const currentMonthlyEquivCents = useMemo2(() => {
|
|
383
|
+
if (isFree) return 0;
|
|
384
|
+
if (cycle === "YEARLY") return Math.round(paywall.prices.yearlyCents / 12);
|
|
385
|
+
return paywall.prices.monthlyCents;
|
|
386
|
+
}, [paywall, cycle, isFree]);
|
|
387
|
+
const anchorPriceCents = useMemo2(() => {
|
|
388
|
+
if (isFree) return null;
|
|
389
|
+
const a = paywall.anchorPrices;
|
|
390
|
+
if (!a) return null;
|
|
391
|
+
return cycle === "YEARLY" ? a.yearlyCents : a.monthlyCents;
|
|
392
|
+
}, [paywall, cycle, isFree]);
|
|
393
|
+
const selectMethod = useCallback(
|
|
394
|
+
(next) => {
|
|
395
|
+
if (next === selectedMethod) return;
|
|
396
|
+
track2("paywall_method_tab_clicked", { method: next, from_method: selectedMethod });
|
|
397
|
+
setSelectedMethod(next);
|
|
398
|
+
},
|
|
399
|
+
[selectedMethod, track2]
|
|
400
|
+
);
|
|
401
|
+
const selectCycle = useCallback(
|
|
402
|
+
(next) => {
|
|
403
|
+
if (next === cycle) return;
|
|
404
|
+
track2("paywall_cycle_clicked", { cycle: next, from_cycle: cycle });
|
|
405
|
+
setCycle(next);
|
|
406
|
+
},
|
|
407
|
+
[cycle, track2]
|
|
408
|
+
);
|
|
349
409
|
const useDefaultMessages = paywall.mode !== "free" && paywall.errorMessages === "default";
|
|
350
410
|
const buildError = useCallback(
|
|
351
411
|
(code, fallbackMessage) => ({
|
|
@@ -540,6 +600,20 @@ function usePaywallState() {
|
|
|
540
600
|
methods,
|
|
541
601
|
selectedMethod,
|
|
542
602
|
setSelectedMethod,
|
|
603
|
+
// Conversion-max derivations (template 0.21)
|
|
604
|
+
hasConsumedTrial,
|
|
605
|
+
currentPriceCents,
|
|
606
|
+
currentMonthlyEquivCents,
|
|
607
|
+
anchorPriceCents,
|
|
608
|
+
selectMethod,
|
|
609
|
+
selectCycle,
|
|
610
|
+
isFree,
|
|
611
|
+
// Per-method trial (ADR-022 Amendment 2026-05-12). Use these to render
|
|
612
|
+
// asymmetric copy per method card on the paywall — never a global
|
|
613
|
+
// "free trial!" headline when both methods are visible.
|
|
614
|
+
trialDaysForMethod,
|
|
615
|
+
trialDaysCard,
|
|
616
|
+
trialDaysPix,
|
|
543
617
|
// Form state
|
|
544
618
|
cpfState,
|
|
545
619
|
cardState,
|
|
@@ -571,13 +645,13 @@ var BLOCKING = /* @__PURE__ */ new Set([
|
|
|
571
645
|
"canceled",
|
|
572
646
|
"none"
|
|
573
647
|
]);
|
|
574
|
-
function SubscriptionGate({ Paywall, children }) {
|
|
648
|
+
function SubscriptionGate({ Paywall: Paywall2, children }) {
|
|
575
649
|
const { mode } = useTemplateConfig();
|
|
576
650
|
const { status, hasAccess, initialLoadComplete } = usePaywallState();
|
|
577
651
|
if (mode === "free") return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
578
652
|
if (!initialLoadComplete && status === "none") return null;
|
|
579
653
|
if (hasAccess === true && status !== "none") return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
580
|
-
if (BLOCKING.has(status)) return /* @__PURE__ */ jsx5(
|
|
654
|
+
if (BLOCKING.has(status)) return /* @__PURE__ */ jsx5(Paywall2, {});
|
|
581
655
|
return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
582
656
|
}
|
|
583
657
|
|
|
@@ -2023,22 +2097,155 @@ function I18nProvider({
|
|
|
2023
2097
|
return /* @__PURE__ */ jsx19(I18nextProvider, { i18n, children });
|
|
2024
2098
|
}
|
|
2025
2099
|
|
|
2026
|
-
// src/
|
|
2027
|
-
|
|
2100
|
+
// src/dev/env.ts
|
|
2101
|
+
function isDevToolsEnabled() {
|
|
2102
|
+
const meta = import.meta;
|
|
2103
|
+
if (meta.env?.VITE_HOOK_DEV_TOOLS !== "1") return false;
|
|
2104
|
+
if (typeof window === "undefined") return false;
|
|
2105
|
+
const host = window.location.hostname;
|
|
2106
|
+
return host.includes(".staging.") || host === "localhost" || host === "127.0.0.1";
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
// src/dev/DevSkipOnboardingFab.tsx
|
|
2110
|
+
import { useCallback as useCallback3, useRef as useRef4, useState as useState5 } from "react";
|
|
2028
2111
|
import { useHook as useHook5 } from "@hook-sdk/sdk";
|
|
2029
|
-
import {
|
|
2112
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
2113
|
+
var STORAGE_KEY = "hook_dev_skip_email";
|
|
2114
|
+
var TEST_EMAIL_DOMAIN = "@hook.test";
|
|
2115
|
+
var TEST_PASSWORD = "SkipTest!2026";
|
|
2116
|
+
function makeEmail() {
|
|
2117
|
+
return `ryan+skip-${Date.now()}${TEST_EMAIL_DOMAIN}`;
|
|
2118
|
+
}
|
|
2119
|
+
async function ensureSignedIn(hook, maxAttempts = 3) {
|
|
2120
|
+
if (hook.authStatus === "authenticated") return null;
|
|
2121
|
+
let lastErr;
|
|
2122
|
+
for (let i = 0; i < maxAttempts; i += 1) {
|
|
2123
|
+
const email = makeEmail();
|
|
2124
|
+
try {
|
|
2125
|
+
await hook.auth.signup({ email, password: TEST_PASSWORD, name: "Ryan Test" });
|
|
2126
|
+
try {
|
|
2127
|
+
window.sessionStorage.setItem(STORAGE_KEY, email);
|
|
2128
|
+
} catch {
|
|
2129
|
+
}
|
|
2130
|
+
return email;
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
lastErr = err;
|
|
2133
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
throw lastErr ?? new Error("signup failed after retries");
|
|
2137
|
+
}
|
|
2138
|
+
async function skipOnboarding(hook, defaults, appSlug) {
|
|
2139
|
+
const { __seed, ...rest } = defaults;
|
|
2140
|
+
await ensureSignedIn(hook);
|
|
2141
|
+
await hook.appData.set("onboarding_data", {
|
|
2142
|
+
...rest,
|
|
2143
|
+
onboarding_completed: true
|
|
2144
|
+
});
|
|
2145
|
+
if (__seed) {
|
|
2146
|
+
await __seed(hook);
|
|
2147
|
+
}
|
|
2148
|
+
console.info("[hook-template] dev_skip_onboarding fired", {
|
|
2149
|
+
app_slug: appSlug,
|
|
2150
|
+
hostname: window.location.hostname
|
|
2151
|
+
});
|
|
2152
|
+
window.location.assign(`/app/${appSlug}/`);
|
|
2153
|
+
}
|
|
2154
|
+
var STYLES = {
|
|
2155
|
+
base: {
|
|
2156
|
+
position: "fixed",
|
|
2157
|
+
bottom: "16px",
|
|
2158
|
+
right: "16px",
|
|
2159
|
+
zIndex: 2147483647,
|
|
2160
|
+
padding: "10px 14px",
|
|
2161
|
+
borderRadius: "999px",
|
|
2162
|
+
border: "none",
|
|
2163
|
+
background: "#F59E0B",
|
|
2164
|
+
color: "#111827",
|
|
2165
|
+
fontWeight: 600,
|
|
2166
|
+
fontSize: "13px",
|
|
2167
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
2168
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.25)",
|
|
2169
|
+
cursor: "pointer",
|
|
2170
|
+
display: "flex",
|
|
2171
|
+
alignItems: "center",
|
|
2172
|
+
gap: "6px"
|
|
2173
|
+
},
|
|
2174
|
+
confirm: { background: "#DC2626", color: "#FFFFFF" },
|
|
2175
|
+
busy: { opacity: 0.6, cursor: "wait" }
|
|
2176
|
+
};
|
|
2177
|
+
var CONFIRM_TIMEOUT_MS = 3e3;
|
|
2178
|
+
function DevSkipOnboardingFab({ defaults }) {
|
|
2179
|
+
const hook = useHook5();
|
|
2180
|
+
const { slug } = useAppConfig();
|
|
2181
|
+
const [state, setState] = useState5("idle");
|
|
2182
|
+
const [errorMsg, setErrorMsg] = useState5(null);
|
|
2183
|
+
const timerRef = useRef4(null);
|
|
2184
|
+
const clearTimer = useCallback3(() => {
|
|
2185
|
+
if (timerRef.current) {
|
|
2186
|
+
clearTimeout(timerRef.current);
|
|
2187
|
+
timerRef.current = null;
|
|
2188
|
+
}
|
|
2189
|
+
}, []);
|
|
2190
|
+
const onClick = useCallback3(async () => {
|
|
2191
|
+
if (state === "busy") return;
|
|
2192
|
+
if (state === "idle" || state === "error") {
|
|
2193
|
+
setState("confirm");
|
|
2194
|
+
setErrorMsg(null);
|
|
2195
|
+
clearTimer();
|
|
2196
|
+
timerRef.current = setTimeout(() => setState("idle"), CONFIRM_TIMEOUT_MS);
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
clearTimer();
|
|
2200
|
+
setState("busy");
|
|
2201
|
+
try {
|
|
2202
|
+
await skipOnboarding(hook, defaults, slug);
|
|
2203
|
+
} catch (err) {
|
|
2204
|
+
setState("error");
|
|
2205
|
+
setErrorMsg(err instanceof Error ? err.message : String(err));
|
|
2206
|
+
}
|
|
2207
|
+
}, [state, hook, defaults, slug, clearTimer]);
|
|
2208
|
+
const label = (() => {
|
|
2209
|
+
if (state === "busy") return "skipping\u2026";
|
|
2210
|
+
if (state === "confirm") return "tap again to confirm";
|
|
2211
|
+
if (state === "error") return `failed \u2014 tap to retry`;
|
|
2212
|
+
return hook.authStatus === "authenticated" ? "\u26A1 skip onboarding" : "\u26A1 skip + signup";
|
|
2213
|
+
})();
|
|
2214
|
+
const style = {
|
|
2215
|
+
...STYLES.base,
|
|
2216
|
+
...state === "confirm" || state === "error" ? STYLES.confirm : {},
|
|
2217
|
+
...state === "busy" ? STYLES.busy : {}
|
|
2218
|
+
};
|
|
2219
|
+
return /* @__PURE__ */ jsx20(
|
|
2220
|
+
"button",
|
|
2221
|
+
{
|
|
2222
|
+
type: "button",
|
|
2223
|
+
"data-testid": "dev-skip-onboarding-fab",
|
|
2224
|
+
"aria-label": "Skip onboarding (staging dev only)",
|
|
2225
|
+
style,
|
|
2226
|
+
onClick,
|
|
2227
|
+
title: errorMsg ?? void 0,
|
|
2228
|
+
children: label
|
|
2229
|
+
}
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// src/internal/PaymentReturnHandler.tsx
|
|
2234
|
+
import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef5, useState as useState6 } from "react";
|
|
2235
|
+
import { useHook as useHook6 } from "@hook-sdk/sdk";
|
|
2236
|
+
import { Fragment as Fragment5, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2030
2237
|
var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
|
|
2031
2238
|
var MAX_CYCLES = 3;
|
|
2032
2239
|
var SUPPORT_MAILTO = "mailto:suporte@usehook.net?subject=Pagamento%20pendente";
|
|
2033
2240
|
function PaymentReturnHandler({ children }) {
|
|
2034
|
-
const { subscription, track: track2 } =
|
|
2035
|
-
const subRef =
|
|
2241
|
+
const { subscription, track: track2 } = useHook6();
|
|
2242
|
+
const subRef = useRef5(subscription);
|
|
2036
2243
|
subRef.current = subscription;
|
|
2037
|
-
const runIdRef =
|
|
2038
|
-
const cyclesRef =
|
|
2039
|
-
const startMsRef =
|
|
2040
|
-
const [state, setState] =
|
|
2041
|
-
const runPoll =
|
|
2244
|
+
const runIdRef = useRef5(0);
|
|
2245
|
+
const cyclesRef = useRef5(0);
|
|
2246
|
+
const startMsRef = useRef5(0);
|
|
2247
|
+
const [state, setState] = useState6("idle");
|
|
2248
|
+
const runPoll = useCallback4(() => {
|
|
2042
2249
|
const runId = ++runIdRef.current;
|
|
2043
2250
|
const isFirstRun = cyclesRef.current === 0;
|
|
2044
2251
|
cyclesRef.current += 1;
|
|
@@ -2096,26 +2303,26 @@ function PaymentReturnHandler({ children }) {
|
|
|
2096
2303
|
runIdRef.current++;
|
|
2097
2304
|
};
|
|
2098
2305
|
}, [runPoll]);
|
|
2099
|
-
const goHome =
|
|
2306
|
+
const goHome = useCallback4(() => {
|
|
2100
2307
|
const cleanUrl = new URL(window.location.href);
|
|
2101
2308
|
cleanUrl.searchParams.delete("paymentReturn");
|
|
2102
2309
|
cleanUrl.pathname = "/app/home";
|
|
2103
2310
|
window.location.href = cleanUrl.toString();
|
|
2104
2311
|
}, []);
|
|
2105
2312
|
if (state === "confirming") {
|
|
2106
|
-
return /* @__PURE__ */
|
|
2313
|
+
return /* @__PURE__ */ jsx21("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
|
|
2107
2314
|
}
|
|
2108
2315
|
if (state === "waiting") {
|
|
2109
|
-
return /* @__PURE__ */
|
|
2110
|
-
/* @__PURE__ */
|
|
2111
|
-
/* @__PURE__ */
|
|
2316
|
+
return /* @__PURE__ */ jsx21("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs13("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
|
|
2317
|
+
/* @__PURE__ */ jsx21("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
|
|
2318
|
+
/* @__PURE__ */ jsx21("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
|
|
2112
2319
|
] }) });
|
|
2113
2320
|
}
|
|
2114
2321
|
if (state === "timeout") {
|
|
2115
|
-
return /* @__PURE__ */
|
|
2116
|
-
/* @__PURE__ */
|
|
2322
|
+
return /* @__PURE__ */ jsx21("div", { role: "alert", "aria-live": "assertive", style: overlayStyle2, children: /* @__PURE__ */ jsxs13("div", { style: { maxWidth: 360, textAlign: "center", lineHeight: 1.5 }, children: [
|
|
2323
|
+
/* @__PURE__ */ jsx21("div", { style: { marginBottom: 16 }, children: "Ainda n\xE3o conseguimos confirmar seu pagamento com o banco. Voc\xEA pode tentar de novo, voltar pro app, ou falar com a gente." }),
|
|
2117
2324
|
/* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
|
|
2118
|
-
/* @__PURE__ */
|
|
2325
|
+
/* @__PURE__ */ jsx21(
|
|
2119
2326
|
"button",
|
|
2120
2327
|
{
|
|
2121
2328
|
type: "button",
|
|
@@ -2128,7 +2335,7 @@ function PaymentReturnHandler({ children }) {
|
|
|
2128
2335
|
children: "Tentar de novo"
|
|
2129
2336
|
}
|
|
2130
2337
|
),
|
|
2131
|
-
/* @__PURE__ */
|
|
2338
|
+
/* @__PURE__ */ jsx21(
|
|
2132
2339
|
"button",
|
|
2133
2340
|
{
|
|
2134
2341
|
type: "button",
|
|
@@ -2138,7 +2345,7 @@ function PaymentReturnHandler({ children }) {
|
|
|
2138
2345
|
children: "Voltar pro app"
|
|
2139
2346
|
}
|
|
2140
2347
|
),
|
|
2141
|
-
/* @__PURE__ */
|
|
2348
|
+
/* @__PURE__ */ jsx21(
|
|
2142
2349
|
"a",
|
|
2143
2350
|
{
|
|
2144
2351
|
href: SUPPORT_MAILTO,
|
|
@@ -2150,7 +2357,7 @@ function PaymentReturnHandler({ children }) {
|
|
|
2150
2357
|
] })
|
|
2151
2358
|
] }) });
|
|
2152
2359
|
}
|
|
2153
|
-
return /* @__PURE__ */
|
|
2360
|
+
return /* @__PURE__ */ jsx21(Fragment5, { children });
|
|
2154
2361
|
}
|
|
2155
2362
|
var overlayStyle2 = {
|
|
2156
2363
|
position: "fixed",
|
|
@@ -2189,7 +2396,7 @@ var linkStyle = {
|
|
|
2189
2396
|
};
|
|
2190
2397
|
|
|
2191
2398
|
// src/AppRoot.tsx
|
|
2192
|
-
import { Fragment as Fragment6, jsx as
|
|
2399
|
+
import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2193
2400
|
function buildLegacyConfigShim(config) {
|
|
2194
2401
|
const paywall = config.paywall;
|
|
2195
2402
|
const isFree = paywall.mode === "free";
|
|
@@ -2237,9 +2444,10 @@ function AppRoot(props) {
|
|
|
2237
2444
|
Forgot,
|
|
2238
2445
|
Reset,
|
|
2239
2446
|
EmailVerify,
|
|
2240
|
-
Paywall,
|
|
2447
|
+
Paywall: Paywall2,
|
|
2241
2448
|
Onboarding,
|
|
2242
|
-
PreAuthFlow
|
|
2449
|
+
PreAuthFlow,
|
|
2450
|
+
devSkipOnboarding
|
|
2243
2451
|
} = props;
|
|
2244
2452
|
if (!Login || !Signup || !Forgot || !Reset) {
|
|
2245
2453
|
throw new Error(
|
|
@@ -2247,7 +2455,7 @@ function AppRoot(props) {
|
|
|
2247
2455
|
);
|
|
2248
2456
|
}
|
|
2249
2457
|
const config = parseAppConfig(rawConfig);
|
|
2250
|
-
if (config.paywall.mode !== "free" && !
|
|
2458
|
+
if (config.paywall.mode !== "free" && !Paywall2) {
|
|
2251
2459
|
throw new Error(
|
|
2252
2460
|
"[hook-template] <AppRoot>: Paywall slot prop is required when config.paywall.mode != 'free'."
|
|
2253
2461
|
);
|
|
@@ -2267,14 +2475,14 @@ function AppRoot(props) {
|
|
|
2267
2475
|
const basename = `/app/${config.slug}`;
|
|
2268
2476
|
const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
|
|
2269
2477
|
const position = config.install_prompt?.position ?? "post-paywall";
|
|
2270
|
-
const subscriptionGated = /* @__PURE__ */
|
|
2478
|
+
const subscriptionGated = /* @__PURE__ */ jsx22(SubscriptionGate, { Paywall: Paywall2 ?? FallbackPaywall, children: position === "post-paywall" ? /* @__PURE__ */ jsxs14(InstallGate, { position: "post-paywall", children: [
|
|
2271
2479
|
children,
|
|
2272
|
-
/* @__PURE__ */
|
|
2480
|
+
/* @__PURE__ */ jsx22(PushPrompt, {})
|
|
2273
2481
|
] }) : /* @__PURE__ */ jsxs14(Fragment6, { children: [
|
|
2274
2482
|
children,
|
|
2275
|
-
/* @__PURE__ */
|
|
2483
|
+
/* @__PURE__ */ jsx22(PushPrompt, {})
|
|
2276
2484
|
] }) });
|
|
2277
|
-
const authGated = /* @__PURE__ */
|
|
2485
|
+
const authGated = /* @__PURE__ */ jsx22(
|
|
2278
2486
|
AuthGated,
|
|
2279
2487
|
{
|
|
2280
2488
|
config,
|
|
@@ -2283,18 +2491,19 @@ function AppRoot(props) {
|
|
|
2283
2491
|
Forgot,
|
|
2284
2492
|
Reset,
|
|
2285
2493
|
EmailVerify,
|
|
2286
|
-
Paywall,
|
|
2494
|
+
Paywall: Paywall2,
|
|
2287
2495
|
Onboarding,
|
|
2288
2496
|
PreAuthFlow,
|
|
2289
2497
|
children: subscriptionGated
|
|
2290
2498
|
}
|
|
2291
2499
|
);
|
|
2292
2500
|
const routedTree = /* @__PURE__ */ jsxs14(Router, { ...routerProps, children: [
|
|
2293
|
-
/* @__PURE__ */
|
|
2294
|
-
/* @__PURE__ */
|
|
2295
|
-
position === "pre-auth" ? /* @__PURE__ */
|
|
2501
|
+
/* @__PURE__ */ jsx22(DeepLinkHandler, { deepLinks: config.deepLinks }),
|
|
2502
|
+
/* @__PURE__ */ jsx22(SessionExpiredBanner, {}),
|
|
2503
|
+
position === "pre-auth" ? /* @__PURE__ */ jsx22(InstallGate, { position: "pre-auth", children: authGated }) : authGated,
|
|
2504
|
+
isDevToolsEnabled() && devSkipOnboarding ? /* @__PURE__ */ jsx22(DevSkipOnboardingFab, { defaults: devSkipOnboarding.defaults }) : null
|
|
2296
2505
|
] });
|
|
2297
|
-
return /* @__PURE__ */
|
|
2506
|
+
return /* @__PURE__ */ jsx22(ErrorBoundary, { children: /* @__PURE__ */ jsx22(AppConfigProvider, { config, children: /* @__PURE__ */ jsx22(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ jsx22(ThemeProvider, { children: /* @__PURE__ */ jsx22(PersistenceRegistry, { config: config.persistedKeys, children: config.i18n ? /* @__PURE__ */ jsx22(
|
|
2298
2507
|
I18nProvider,
|
|
2299
2508
|
{
|
|
2300
2509
|
defaultLocale: config.i18n.defaultLocale,
|
|
@@ -2314,37 +2523,37 @@ function AuthGated({
|
|
|
2314
2523
|
EmailVerify,
|
|
2315
2524
|
PreAuthFlow
|
|
2316
2525
|
}) {
|
|
2317
|
-
const { authStatus } =
|
|
2526
|
+
const { authStatus } = useHook7();
|
|
2318
2527
|
if (authStatus === "loading") return null;
|
|
2319
2528
|
if (authStatus !== "authenticated") {
|
|
2320
2529
|
if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
|
|
2321
2530
|
return /* @__PURE__ */ jsxs14(Routes, { children: [
|
|
2322
|
-
/* @__PURE__ */
|
|
2323
|
-
/* @__PURE__ */
|
|
2324
|
-
/* @__PURE__ */
|
|
2325
|
-
/* @__PURE__ */
|
|
2326
|
-
EmailVerify ? /* @__PURE__ */
|
|
2327
|
-
/* @__PURE__ */
|
|
2531
|
+
/* @__PURE__ */ jsx22(Route, { path: "/signin", element: /* @__PURE__ */ jsx22(Login, {}) }),
|
|
2532
|
+
/* @__PURE__ */ jsx22(Route, { path: "/signup", element: /* @__PURE__ */ jsx22(Signup, {}) }),
|
|
2533
|
+
/* @__PURE__ */ jsx22(Route, { path: "/forgot", element: /* @__PURE__ */ jsx22(Forgot, {}) }),
|
|
2534
|
+
/* @__PURE__ */ jsx22(Route, { path: "/reset", element: /* @__PURE__ */ jsx22(Reset, {}) }),
|
|
2535
|
+
EmailVerify ? /* @__PURE__ */ jsx22(Route, { path: "/verify", element: /* @__PURE__ */ jsx22(EmailVerify, {}) }) : null,
|
|
2536
|
+
/* @__PURE__ */ jsx22(Route, { path: "/*", element: /* @__PURE__ */ jsx22(PreAuthFlow, {}) })
|
|
2328
2537
|
] });
|
|
2329
2538
|
}
|
|
2330
2539
|
return /* @__PURE__ */ jsxs14(Routes, { children: [
|
|
2331
|
-
/* @__PURE__ */
|
|
2332
|
-
/* @__PURE__ */
|
|
2333
|
-
/* @__PURE__ */
|
|
2334
|
-
/* @__PURE__ */
|
|
2335
|
-
EmailVerify ? /* @__PURE__ */
|
|
2336
|
-
/* @__PURE__ */
|
|
2540
|
+
/* @__PURE__ */ jsx22(Route, { path: "/", element: /* @__PURE__ */ jsx22(Login, {}) }),
|
|
2541
|
+
/* @__PURE__ */ jsx22(Route, { path: "/signup", element: /* @__PURE__ */ jsx22(Signup, {}) }),
|
|
2542
|
+
/* @__PURE__ */ jsx22(Route, { path: "/forgot", element: /* @__PURE__ */ jsx22(Forgot, {}) }),
|
|
2543
|
+
/* @__PURE__ */ jsx22(Route, { path: "/reset", element: /* @__PURE__ */ jsx22(Reset, {}) }),
|
|
2544
|
+
EmailVerify ? /* @__PURE__ */ jsx22(Route, { path: "/verify", element: /* @__PURE__ */ jsx22(EmailVerify, {}) }) : null,
|
|
2545
|
+
/* @__PURE__ */ jsx22(Route, { path: "*", element: /* @__PURE__ */ jsx22(Navigate, { to: "/", replace: true }) })
|
|
2337
2546
|
] });
|
|
2338
2547
|
}
|
|
2339
|
-
return /* @__PURE__ */
|
|
2548
|
+
return /* @__PURE__ */ jsx22(Fragment6, { children });
|
|
2340
2549
|
}
|
|
2341
2550
|
function FallbackPaywall() {
|
|
2342
2551
|
return null;
|
|
2343
2552
|
}
|
|
2344
2553
|
|
|
2345
2554
|
// src/hooks/usePush.ts
|
|
2346
|
-
import { useCallback as
|
|
2347
|
-
import { useHook as
|
|
2555
|
+
import { useCallback as useCallback5, useEffect as useEffect9, useState as useState7 } from "react";
|
|
2556
|
+
import { useHook as useHook8 } from "@hook-sdk/sdk";
|
|
2348
2557
|
var DISMISS_STORAGE_KEY = "push:dismissed-until";
|
|
2349
2558
|
var DISMISS_TTL_MS2 = 7 * 24 * 60 * 60 * 1e3;
|
|
2350
2559
|
function detectIosNeedsInstall() {
|
|
@@ -2388,12 +2597,12 @@ function deriveState(push) {
|
|
|
2388
2597
|
return { kind: "prompt" };
|
|
2389
2598
|
}
|
|
2390
2599
|
function usePush() {
|
|
2391
|
-
const { push } =
|
|
2392
|
-
const [state, setState] =
|
|
2600
|
+
const { push } = useHook8();
|
|
2601
|
+
const [state, setState] = useState7(() => deriveState(push));
|
|
2393
2602
|
useEffect9(() => {
|
|
2394
2603
|
setState(deriveState(push));
|
|
2395
2604
|
}, [push]);
|
|
2396
|
-
const subscribe =
|
|
2605
|
+
const subscribe = useCallback5(async () => {
|
|
2397
2606
|
try {
|
|
2398
2607
|
await push.subscribe();
|
|
2399
2608
|
setState({ kind: "subscribed" });
|
|
@@ -2405,7 +2614,7 @@ function usePush() {
|
|
|
2405
2614
|
throw e;
|
|
2406
2615
|
}
|
|
2407
2616
|
}, [push]);
|
|
2408
|
-
const unsubscribe =
|
|
2617
|
+
const unsubscribe = useCallback5(async () => {
|
|
2409
2618
|
try {
|
|
2410
2619
|
await push.unsubscribe();
|
|
2411
2620
|
setState({ kind: "prompt" });
|
|
@@ -2414,7 +2623,7 @@ function usePush() {
|
|
|
2414
2623
|
throw e;
|
|
2415
2624
|
}
|
|
2416
2625
|
}, [push]);
|
|
2417
|
-
const dismiss =
|
|
2626
|
+
const dismiss = useCallback5(() => {
|
|
2418
2627
|
if (typeof localStorage !== "undefined") {
|
|
2419
2628
|
try {
|
|
2420
2629
|
localStorage.setItem(DISMISS_STORAGE_KEY, String(Date.now() + DISMISS_TTL_MS2));
|
|
@@ -2427,7 +2636,7 @@ function usePush() {
|
|
|
2427
2636
|
}
|
|
2428
2637
|
|
|
2429
2638
|
// src/components/PushPrompt.tsx
|
|
2430
|
-
import { jsx as
|
|
2639
|
+
import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2431
2640
|
function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
|
|
2432
2641
|
const { state, subscribe } = usePush();
|
|
2433
2642
|
if (state.kind === "denied" || state.kind === "dismissed" || state.kind === "subscribed") {
|
|
@@ -2435,19 +2644,19 @@ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, clas
|
|
|
2435
2644
|
}
|
|
2436
2645
|
if (state.kind === "ios_needs_install") {
|
|
2437
2646
|
return /* @__PURE__ */ jsxs15("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
|
|
2438
|
-
/* @__PURE__ */
|
|
2439
|
-
/* @__PURE__ */
|
|
2440
|
-
onInstallRequested && texts.iosInstallCta && /* @__PURE__ */
|
|
2647
|
+
/* @__PURE__ */ jsx23("h3", { children: texts.iosInstallTitle }),
|
|
2648
|
+
/* @__PURE__ */ jsx23("p", { children: texts.iosInstallBody }),
|
|
2649
|
+
onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx23("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
|
|
2441
2650
|
] });
|
|
2442
2651
|
}
|
|
2443
2652
|
if (state.kind === "unsupported") {
|
|
2444
|
-
return /* @__PURE__ */
|
|
2653
|
+
return /* @__PURE__ */ jsx23("div", { className, role: "region", children: /* @__PURE__ */ jsx23("p", { children: texts.unsupportedBody }) });
|
|
2445
2654
|
}
|
|
2446
2655
|
if (state.kind === "error") {
|
|
2447
|
-
return /* @__PURE__ */
|
|
2656
|
+
return /* @__PURE__ */ jsx23("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx23("p", { children: state.message }) });
|
|
2448
2657
|
}
|
|
2449
2658
|
return /* @__PURE__ */ jsxs15("div", { className, role: "region", children: [
|
|
2450
|
-
/* @__PURE__ */
|
|
2659
|
+
/* @__PURE__ */ jsx23(
|
|
2451
2660
|
"button",
|
|
2452
2661
|
{
|
|
2453
2662
|
type: "button",
|
|
@@ -2461,13 +2670,13 @@ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, clas
|
|
|
2461
2670
|
children: texts.cta
|
|
2462
2671
|
}
|
|
2463
2672
|
),
|
|
2464
|
-
onDeclined && /* @__PURE__ */
|
|
2673
|
+
onDeclined && /* @__PURE__ */ jsx23("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
|
|
2465
2674
|
] });
|
|
2466
2675
|
}
|
|
2467
2676
|
|
|
2468
2677
|
// src/components/LanguageSwitcher.tsx
|
|
2469
2678
|
import { usePersistedState as usePersistedState2 } from "@hook-sdk/sdk";
|
|
2470
|
-
import { jsx as
|
|
2679
|
+
import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2471
2680
|
function LanguageSwitcher({ id, className, label = "Language" }) {
|
|
2472
2681
|
const config = useAppConfig();
|
|
2473
2682
|
const i18nConfig = config.i18n;
|
|
@@ -2477,39 +2686,39 @@ function LanguageSwitcher({ id, className, label = "Language" }) {
|
|
|
2477
2686
|
);
|
|
2478
2687
|
if (!i18nConfig) return null;
|
|
2479
2688
|
return /* @__PURE__ */ jsxs16("label", { className, children: [
|
|
2480
|
-
label ? /* @__PURE__ */
|
|
2481
|
-
/* @__PURE__ */
|
|
2689
|
+
label ? /* @__PURE__ */ jsx24("span", { children: label }) : null,
|
|
2690
|
+
/* @__PURE__ */ jsx24(
|
|
2482
2691
|
"select",
|
|
2483
2692
|
{
|
|
2484
2693
|
id,
|
|
2485
2694
|
value: userLocale,
|
|
2486
2695
|
onChange: (e) => setUserLocale(e.target.value),
|
|
2487
2696
|
"data-testid": "language-switcher",
|
|
2488
|
-
children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */
|
|
2697
|
+
children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */ jsx24("option", { value: loc, children: loc }, loc))
|
|
2489
2698
|
}
|
|
2490
2699
|
)
|
|
2491
2700
|
] });
|
|
2492
2701
|
}
|
|
2493
2702
|
|
|
2494
2703
|
// src/defaults/LoadingState.tsx
|
|
2495
|
-
import { jsx as
|
|
2704
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
2496
2705
|
function LoadingState({ message }) {
|
|
2497
|
-
return /* @__PURE__ */
|
|
2706
|
+
return /* @__PURE__ */ jsx25("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx25("span", { children: message ?? "Carregando..." }) });
|
|
2498
2707
|
}
|
|
2499
2708
|
|
|
2500
2709
|
// src/defaults/EmptyState.tsx
|
|
2501
|
-
import { jsx as
|
|
2710
|
+
import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2502
2711
|
function EmptyState({ title, description, action }) {
|
|
2503
2712
|
return /* @__PURE__ */ jsxs17("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
|
|
2504
|
-
/* @__PURE__ */
|
|
2505
|
-
description && /* @__PURE__ */
|
|
2506
|
-
action && /* @__PURE__ */
|
|
2713
|
+
/* @__PURE__ */ jsx26("h2", { style: { marginBottom: 8 }, children: title }),
|
|
2714
|
+
description && /* @__PURE__ */ jsx26("p", { style: { opacity: 0.7 }, children: description }),
|
|
2715
|
+
action && /* @__PURE__ */ jsx26("div", { style: { marginTop: 16 }, children: action })
|
|
2507
2716
|
] });
|
|
2508
2717
|
}
|
|
2509
2718
|
|
|
2510
2719
|
// src/hooks/useLoginForm.ts
|
|
2511
|
-
import { useCallback as
|
|
2512
|
-
import { useHook as
|
|
2720
|
+
import { useCallback as useCallback6, useMemo as useMemo4, useState as useState8 } from "react";
|
|
2721
|
+
import { useHook as useHook9 } from "@hook-sdk/sdk";
|
|
2513
2722
|
|
|
2514
2723
|
// src/errors.ts
|
|
2515
2724
|
import { SdkError, SdkAuthError, SdkRateLimitError } from "@hook-sdk/sdk";
|
|
@@ -2544,14 +2753,14 @@ function mapSdkError(err) {
|
|
|
2544
2753
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2545
2754
|
var MIN_PASSWORD = 8;
|
|
2546
2755
|
function useLoginForm() {
|
|
2547
|
-
const { auth } =
|
|
2548
|
-
const [email, setEmail] =
|
|
2549
|
-
const [password, setPassword] =
|
|
2550
|
-
const [submitting, setSubmitting] =
|
|
2551
|
-
const [error, setError] =
|
|
2552
|
-
const [touchedEmail, setTouchedEmail] =
|
|
2553
|
-
const [touchedPassword, setTouchedPassword] =
|
|
2554
|
-
const [formSubmitAttempted, setFormSubmitAttempted] =
|
|
2756
|
+
const { auth } = useHook9();
|
|
2757
|
+
const [email, setEmail] = useState8("");
|
|
2758
|
+
const [password, setPassword] = useState8("");
|
|
2759
|
+
const [submitting, setSubmitting] = useState8(false);
|
|
2760
|
+
const [error, setError] = useState8(null);
|
|
2761
|
+
const [touchedEmail, setTouchedEmail] = useState8(false);
|
|
2762
|
+
const [touchedPassword, setTouchedPassword] = useState8(false);
|
|
2763
|
+
const [formSubmitAttempted, setFormSubmitAttempted] = useState8(false);
|
|
2555
2764
|
const validateEmail = useMemo4(() => {
|
|
2556
2765
|
if (email.length === 0) return null;
|
|
2557
2766
|
if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
@@ -2565,7 +2774,7 @@ function useLoginForm() {
|
|
|
2565
2774
|
const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
|
|
2566
2775
|
const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
|
|
2567
2776
|
const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
|
|
2568
|
-
const submit =
|
|
2777
|
+
const submit = useCallback6(async () => {
|
|
2569
2778
|
setFormSubmitAttempted(true);
|
|
2570
2779
|
if (!canSubmit) return false;
|
|
2571
2780
|
setSubmitting(true);
|
|
@@ -2599,21 +2808,21 @@ function useLoginForm() {
|
|
|
2599
2808
|
}
|
|
2600
2809
|
|
|
2601
2810
|
// src/hooks/useSignupForm.ts
|
|
2602
|
-
import { useCallback as
|
|
2603
|
-
import { useHook as
|
|
2811
|
+
import { useCallback as useCallback7, useMemo as useMemo5, useState as useState9 } from "react";
|
|
2812
|
+
import { useHook as useHook10 } from "@hook-sdk/sdk";
|
|
2604
2813
|
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2605
2814
|
var MIN_PASSWORD2 = 8;
|
|
2606
2815
|
function useSignupForm() {
|
|
2607
|
-
const { auth } =
|
|
2608
|
-
const [name, setName] =
|
|
2609
|
-
const [email, setEmail] =
|
|
2610
|
-
const [password, setPassword] =
|
|
2611
|
-
const [submitting, setSubmitting] =
|
|
2612
|
-
const [error, setError] =
|
|
2613
|
-
const [touchedName, setTouchedName] =
|
|
2614
|
-
const [touchedEmail, setTouchedEmail] =
|
|
2615
|
-
const [touchedPassword, setTouchedPassword] =
|
|
2616
|
-
const [formSubmitAttempted, setFormSubmitAttempted] =
|
|
2816
|
+
const { auth } = useHook10();
|
|
2817
|
+
const [name, setName] = useState9("");
|
|
2818
|
+
const [email, setEmail] = useState9("");
|
|
2819
|
+
const [password, setPassword] = useState9("");
|
|
2820
|
+
const [submitting, setSubmitting] = useState9(false);
|
|
2821
|
+
const [error, setError] = useState9(null);
|
|
2822
|
+
const [touchedName, setTouchedName] = useState9(false);
|
|
2823
|
+
const [touchedEmail, setTouchedEmail] = useState9(false);
|
|
2824
|
+
const [touchedPassword, setTouchedPassword] = useState9(false);
|
|
2825
|
+
const [formSubmitAttempted, setFormSubmitAttempted] = useState9(false);
|
|
2617
2826
|
const validateName = useMemo5(() => {
|
|
2618
2827
|
if (name.length === 0) return null;
|
|
2619
2828
|
if (name.trim().length < 2) return "Nome muito curto.";
|
|
@@ -2633,7 +2842,7 @@ function useSignupForm() {
|
|
|
2633
2842
|
const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
|
|
2634
2843
|
const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
|
|
2635
2844
|
const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
|
|
2636
|
-
const submit =
|
|
2845
|
+
const submit = useCallback7(async () => {
|
|
2637
2846
|
setFormSubmitAttempted(true);
|
|
2638
2847
|
if (!canSubmit) return false;
|
|
2639
2848
|
setSubmitting(true);
|
|
@@ -2671,17 +2880,17 @@ function useSignupForm() {
|
|
|
2671
2880
|
}
|
|
2672
2881
|
|
|
2673
2882
|
// src/hooks/useForgotForm.ts
|
|
2674
|
-
import { useCallback as
|
|
2675
|
-
import { useHook as
|
|
2883
|
+
import { useCallback as useCallback8, useMemo as useMemo6, useState as useState10 } from "react";
|
|
2884
|
+
import { useHook as useHook11 } from "@hook-sdk/sdk";
|
|
2676
2885
|
var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2677
2886
|
function useForgotForm() {
|
|
2678
|
-
const { auth } =
|
|
2679
|
-
const [email, setEmail] =
|
|
2680
|
-
const [submitting, setSubmitting] =
|
|
2681
|
-
const [sent, setSent] =
|
|
2682
|
-
const [error, setError] =
|
|
2683
|
-
const [touchedEmail, setTouchedEmail] =
|
|
2684
|
-
const [formSubmitAttempted, setFormSubmitAttempted] =
|
|
2887
|
+
const { auth } = useHook11();
|
|
2888
|
+
const [email, setEmail] = useState10("");
|
|
2889
|
+
const [submitting, setSubmitting] = useState10(false);
|
|
2890
|
+
const [sent, setSent] = useState10(false);
|
|
2891
|
+
const [error, setError] = useState10(null);
|
|
2892
|
+
const [touchedEmail, setTouchedEmail] = useState10(false);
|
|
2893
|
+
const [formSubmitAttempted, setFormSubmitAttempted] = useState10(false);
|
|
2685
2894
|
const validateEmail = useMemo6(() => {
|
|
2686
2895
|
if (email.length === 0) return null;
|
|
2687
2896
|
if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
@@ -2689,7 +2898,7 @@ function useForgotForm() {
|
|
|
2689
2898
|
}, [email]);
|
|
2690
2899
|
const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
|
|
2691
2900
|
const canSubmit = email.length > 0 && validateEmail === null && !submitting;
|
|
2692
|
-
const submit =
|
|
2901
|
+
const submit = useCallback8(async () => {
|
|
2693
2902
|
setFormSubmitAttempted(true);
|
|
2694
2903
|
if (!canSubmit) return false;
|
|
2695
2904
|
setSubmitting(true);
|
|
@@ -2720,20 +2929,20 @@ function useForgotForm() {
|
|
|
2720
2929
|
}
|
|
2721
2930
|
|
|
2722
2931
|
// src/hooks/useResetForm.ts
|
|
2723
|
-
import { useCallback as
|
|
2724
|
-
import { useHook as
|
|
2932
|
+
import { useCallback as useCallback9, useEffect as useEffect10, useMemo as useMemo7, useState as useState11 } from "react";
|
|
2933
|
+
import { useHook as useHook12 } from "@hook-sdk/sdk";
|
|
2725
2934
|
var MIN_PASSWORD3 = 12;
|
|
2726
2935
|
function useResetForm() {
|
|
2727
|
-
const { auth } =
|
|
2728
|
-
const [token, setToken] =
|
|
2729
|
-
const [password, setPassword] =
|
|
2730
|
-
const [confirm, setConfirm] =
|
|
2731
|
-
const [submitting, setSubmitting] =
|
|
2732
|
-
const [done, setDone] =
|
|
2733
|
-
const [error, setError] =
|
|
2734
|
-
const [touchedPassword, setTouchedPassword] =
|
|
2735
|
-
const [touchedConfirm, setTouchedConfirm] =
|
|
2736
|
-
const [formSubmitAttempted, setFormSubmitAttempted] =
|
|
2936
|
+
const { auth } = useHook12();
|
|
2937
|
+
const [token, setToken] = useState11(null);
|
|
2938
|
+
const [password, setPassword] = useState11("");
|
|
2939
|
+
const [confirm, setConfirm] = useState11("");
|
|
2940
|
+
const [submitting, setSubmitting] = useState11(false);
|
|
2941
|
+
const [done, setDone] = useState11(false);
|
|
2942
|
+
const [error, setError] = useState11(null);
|
|
2943
|
+
const [touchedPassword, setTouchedPassword] = useState11(false);
|
|
2944
|
+
const [touchedConfirm, setTouchedConfirm] = useState11(false);
|
|
2945
|
+
const [formSubmitAttempted, setFormSubmitAttempted] = useState11(false);
|
|
2737
2946
|
useEffect10(() => {
|
|
2738
2947
|
if (typeof window === "undefined") return;
|
|
2739
2948
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -2753,7 +2962,7 @@ function useResetForm() {
|
|
|
2753
2962
|
const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
|
|
2754
2963
|
const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
|
|
2755
2964
|
const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
|
|
2756
|
-
const submit =
|
|
2965
|
+
const submit = useCallback9(async () => {
|
|
2757
2966
|
setFormSubmitAttempted(true);
|
|
2758
2967
|
if (!canSubmit || token === null) return;
|
|
2759
2968
|
setSubmitting(true);
|
|
@@ -2793,9 +3002,9 @@ function useResetForm() {
|
|
|
2793
3002
|
}
|
|
2794
3003
|
|
|
2795
3004
|
// src/hooks/usePlan.ts
|
|
2796
|
-
import { useHook as
|
|
3005
|
+
import { useHook as useHook13 } from "@hook-sdk/sdk";
|
|
2797
3006
|
function usePlan() {
|
|
2798
|
-
const { plan } =
|
|
3007
|
+
const { plan } = useHook13();
|
|
2799
3008
|
return plan;
|
|
2800
3009
|
}
|
|
2801
3010
|
|
|
@@ -2829,10 +3038,10 @@ function discountPercent(anchorCents, realCents) {
|
|
|
2829
3038
|
|
|
2830
3039
|
// src/hooks/useAuthPrimitives.ts
|
|
2831
3040
|
import { useEffect as useEffect11 } from "react";
|
|
2832
|
-
import { useHook as
|
|
3041
|
+
import { useHook as useHook14 } from "@hook-sdk/sdk";
|
|
2833
3042
|
var warned = false;
|
|
2834
3043
|
function useAuthPrimitives() {
|
|
2835
|
-
const { auth } =
|
|
3044
|
+
const { auth } = useHook14();
|
|
2836
3045
|
useEffect11(() => {
|
|
2837
3046
|
if (!warned && process.env.NODE_ENV !== "production") {
|
|
2838
3047
|
warned = true;
|
|
@@ -2855,9 +3064,9 @@ function useAuthPrimitives() {
|
|
|
2855
3064
|
}
|
|
2856
3065
|
|
|
2857
3066
|
// src/hooks/useAuth.ts
|
|
2858
|
-
import { useHook as
|
|
3067
|
+
import { useHook as useHook15 } from "@hook-sdk/sdk";
|
|
2859
3068
|
function useAuth() {
|
|
2860
|
-
const { user, authStatus, auth } =
|
|
3069
|
+
const { user, authStatus, auth } = useHook15();
|
|
2861
3070
|
return {
|
|
2862
3071
|
user,
|
|
2863
3072
|
authStatus,
|
|
@@ -2869,23 +3078,23 @@ function useAuth() {
|
|
|
2869
3078
|
import { useTrackOnboardingStep } from "@hook-sdk/sdk";
|
|
2870
3079
|
|
|
2871
3080
|
// src/hooks/useSubscription.ts
|
|
2872
|
-
import { useHook as
|
|
3081
|
+
import { useHook as useHook16 } from "@hook-sdk/sdk";
|
|
2873
3082
|
function useSubscription() {
|
|
2874
|
-
const { subscription } =
|
|
3083
|
+
const { subscription } = useHook16();
|
|
2875
3084
|
return {
|
|
2876
3085
|
status: subscription.status()
|
|
2877
3086
|
};
|
|
2878
3087
|
}
|
|
2879
3088
|
|
|
2880
3089
|
// src/hooks/useReminders.ts
|
|
2881
|
-
import { useCallback as
|
|
2882
|
-
import { useHook as
|
|
3090
|
+
import { useCallback as useCallback10, useEffect as useEffect12, useState as useState12 } from "react";
|
|
3091
|
+
import { useHook as useHook17 } from "@hook-sdk/sdk";
|
|
2883
3092
|
function useReminders() {
|
|
2884
|
-
const { push } =
|
|
3093
|
+
const { push } = useHook17();
|
|
2885
3094
|
const r = push.reminders;
|
|
2886
|
-
const [reminders, setReminders] =
|
|
2887
|
-
const [loading, setLoading] =
|
|
2888
|
-
const reload =
|
|
3095
|
+
const [reminders, setReminders] = useState12([]);
|
|
3096
|
+
const [loading, setLoading] = useState12(true);
|
|
3097
|
+
const reload = useCallback10(async () => {
|
|
2889
3098
|
setLoading(true);
|
|
2890
3099
|
try {
|
|
2891
3100
|
const next = await r.list();
|
|
@@ -2897,35 +3106,35 @@ function useReminders() {
|
|
|
2897
3106
|
useEffect12(() => {
|
|
2898
3107
|
void reload();
|
|
2899
3108
|
}, [reload]);
|
|
2900
|
-
const setReminder =
|
|
3109
|
+
const setReminder = useCallback10(async (input) => {
|
|
2901
3110
|
await r.set(input);
|
|
2902
3111
|
await reload();
|
|
2903
3112
|
}, [r, reload]);
|
|
2904
|
-
const deleteReminder =
|
|
3113
|
+
const deleteReminder = useCallback10(async (slot) => {
|
|
2905
3114
|
await r.delete(slot);
|
|
2906
3115
|
await reload();
|
|
2907
3116
|
}, [r, reload]);
|
|
2908
|
-
const schedule =
|
|
3117
|
+
const schedule = useCallback10(async (items) => {
|
|
2909
3118
|
return r.schedule(items);
|
|
2910
3119
|
}, [r]);
|
|
2911
|
-
const setFallbacks =
|
|
3120
|
+
const setFallbacks = useCallback10(async (items) => {
|
|
2912
3121
|
return r.setFallbacks(items);
|
|
2913
3122
|
}, [r]);
|
|
2914
3123
|
return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
|
|
2915
3124
|
}
|
|
2916
3125
|
|
|
2917
3126
|
// src/hooks/useToast.ts
|
|
2918
|
-
import { useCallback as
|
|
3127
|
+
import { useCallback as useCallback11, useState as useState13 } from "react";
|
|
2919
3128
|
function useToast() {
|
|
2920
|
-
const [items, setItems] =
|
|
2921
|
-
const show =
|
|
3129
|
+
const [items, setItems] = useState13([]);
|
|
3130
|
+
const show = useCallback11((message, kind = "info") => {
|
|
2922
3131
|
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2923
3132
|
setItems((prev) => [...prev, { id, message, kind }]);
|
|
2924
3133
|
setTimeout(() => {
|
|
2925
3134
|
setItems((prev) => prev.filter((t) => t.id !== id));
|
|
2926
3135
|
}, 4e3);
|
|
2927
3136
|
}, []);
|
|
2928
|
-
const dismiss =
|
|
3137
|
+
const dismiss = useCallback11((id) => {
|
|
2929
3138
|
setItems((prev) => prev.filter((t) => t.id !== id));
|
|
2930
3139
|
}, []);
|
|
2931
3140
|
return { items, show, dismiss };
|
|
@@ -2933,20 +3142,20 @@ function useToast() {
|
|
|
2933
3142
|
|
|
2934
3143
|
// src/RouteBoundary.tsx
|
|
2935
3144
|
import { Routes as Routes2, Route as Route2 } from "react-router-dom";
|
|
2936
|
-
import { jsx as
|
|
3145
|
+
import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2937
3146
|
function RouteBoundary({ children }) {
|
|
2938
3147
|
return /* @__PURE__ */ jsxs18(Routes2, { children: [
|
|
2939
3148
|
children,
|
|
2940
|
-
/* @__PURE__ */
|
|
3149
|
+
/* @__PURE__ */ jsx27(Route2, { path: "*", element: /* @__PURE__ */ jsx27(DefaultNotFound, {}) })
|
|
2941
3150
|
] });
|
|
2942
3151
|
}
|
|
2943
3152
|
function DefaultNotFound() {
|
|
2944
|
-
return /* @__PURE__ */
|
|
3153
|
+
return /* @__PURE__ */ jsx27("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
|
|
2945
3154
|
}
|
|
2946
3155
|
|
|
2947
3156
|
// src/PreAuthShell.tsx
|
|
2948
3157
|
import { BrowserRouter as BrowserRouter2, MemoryRouter as MemoryRouter2, Routes as Routes3 } from "react-router-dom";
|
|
2949
|
-
import { jsx as
|
|
3158
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
2950
3159
|
function PreAuthShell({
|
|
2951
3160
|
basename,
|
|
2952
3161
|
testRouter,
|
|
@@ -2954,14 +3163,14 @@ function PreAuthShell({
|
|
|
2954
3163
|
children
|
|
2955
3164
|
}) {
|
|
2956
3165
|
if (testRouter === "memory") {
|
|
2957
|
-
return /* @__PURE__ */
|
|
3166
|
+
return /* @__PURE__ */ jsx28(MemoryRouter2, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ jsx28(Routes3, { children }) });
|
|
2958
3167
|
}
|
|
2959
|
-
return /* @__PURE__ */
|
|
3168
|
+
return /* @__PURE__ */ jsx28(BrowserRouter2, { basename, children: /* @__PURE__ */ jsx28(Routes3, { children }) });
|
|
2960
3169
|
}
|
|
2961
3170
|
|
|
2962
3171
|
// src/OnboardingFlow.tsx
|
|
2963
|
-
import { useCallback as
|
|
2964
|
-
import { usePersistedState as usePersistedState3, useHook as
|
|
3172
|
+
import { useCallback as useCallback12, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef6 } from "react";
|
|
3173
|
+
import { usePersistedState as usePersistedState3, useHook as useHook18 } from "@hook-sdk/sdk";
|
|
2965
3174
|
|
|
2966
3175
|
// src/hooks/useOnboardingStep.ts
|
|
2967
3176
|
import { createContext as createContext3, useContext as useContext4 } from "react";
|
|
@@ -2977,7 +3186,7 @@ function useOnboardingStep() {
|
|
|
2977
3186
|
}
|
|
2978
3187
|
|
|
2979
3188
|
// src/OnboardingFlow.tsx
|
|
2980
|
-
import { jsx as
|
|
3189
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
2981
3190
|
var isFilled = (v) => v != null && v !== "";
|
|
2982
3191
|
var CURRENT_STEP_FIELD = "currentStep";
|
|
2983
3192
|
function readPersistedStepIdx(draft) {
|
|
@@ -2991,11 +3200,11 @@ function OnboardingFlow({
|
|
|
2991
3200
|
persistKey
|
|
2992
3201
|
}) {
|
|
2993
3202
|
const [draft, setDraft, status] = usePersistedState3(persistKey, {});
|
|
2994
|
-
const draftRef =
|
|
3203
|
+
const draftRef = useRef6(draft);
|
|
2995
3204
|
draftRef.current = draft;
|
|
2996
3205
|
const idx = readPersistedStepIdx(draft);
|
|
2997
3206
|
const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
|
|
2998
|
-
const setIdx =
|
|
3207
|
+
const setIdx = useCallback12(
|
|
2999
3208
|
(n) => {
|
|
3000
3209
|
setDraft((prev) => {
|
|
3001
3210
|
const prevIdx = readPersistedStepIdx(prev);
|
|
@@ -3005,7 +3214,7 @@ function OnboardingFlow({
|
|
|
3005
3214
|
},
|
|
3006
3215
|
[setDraft]
|
|
3007
3216
|
);
|
|
3008
|
-
const setValue =
|
|
3217
|
+
const setValue = useCallback12(
|
|
3009
3218
|
(patch) => {
|
|
3010
3219
|
draftRef.current = { ...draftRef.current, ...patch };
|
|
3011
3220
|
setDraft((prev) => ({ ...prev, ...patch }));
|
|
@@ -3013,7 +3222,7 @@ function OnboardingFlow({
|
|
|
3013
3222
|
[setDraft]
|
|
3014
3223
|
);
|
|
3015
3224
|
const step = steps[clampedIdx];
|
|
3016
|
-
const hookCtx =
|
|
3225
|
+
const hookCtx = useHook18();
|
|
3017
3226
|
const track2 = typeof hookCtx.track === "function" ? hookCtx.track : void 0;
|
|
3018
3227
|
useEffect13(() => {
|
|
3019
3228
|
if (status.loading) return;
|
|
@@ -3029,7 +3238,7 @@ function OnboardingFlow({
|
|
|
3029
3238
|
() => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
|
|
3030
3239
|
[draft, step]
|
|
3031
3240
|
);
|
|
3032
|
-
const next =
|
|
3241
|
+
const next = useCallback12(() => {
|
|
3033
3242
|
if (!step) return;
|
|
3034
3243
|
const current = draftRef.current;
|
|
3035
3244
|
const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
|
|
@@ -3040,7 +3249,7 @@ function OnboardingFlow({
|
|
|
3040
3249
|
setIdx(clampedIdx + 1);
|
|
3041
3250
|
}
|
|
3042
3251
|
}, [clampedIdx, onComplete, step, steps.length, setIdx]);
|
|
3043
|
-
const prevStep =
|
|
3252
|
+
const prevStep = useCallback12(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
|
|
3044
3253
|
const ctx = useMemo8(
|
|
3045
3254
|
() => ({
|
|
3046
3255
|
stepIndex: clampedIdx,
|
|
@@ -3067,7 +3276,7 @@ function OnboardingFlow({
|
|
|
3067
3276
|
`[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
|
|
3068
3277
|
);
|
|
3069
3278
|
}
|
|
3070
|
-
return /* @__PURE__ */
|
|
3279
|
+
return /* @__PURE__ */ jsx29(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx29(Screen, {}) });
|
|
3071
3280
|
}
|
|
3072
3281
|
|
|
3073
3282
|
// src/hooks/useFeature.ts
|
|
@@ -3075,11 +3284,298 @@ function useFeature(name) {
|
|
|
3075
3284
|
const config = useAppConfig();
|
|
3076
3285
|
return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
|
|
3077
3286
|
}
|
|
3287
|
+
|
|
3288
|
+
// src/components/paywall/Paywall.tsx
|
|
3289
|
+
import { useEffect as useEffect14, useMemo as useMemo9 } from "react";
|
|
3290
|
+
import { useHook as useHook19 } from "@hook-sdk/sdk";
|
|
3291
|
+
|
|
3292
|
+
// src/components/paywall/PaywallMethodTabs.tsx
|
|
3293
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
3294
|
+
function PaywallMethodTabs({
|
|
3295
|
+
methods,
|
|
3296
|
+
selected,
|
|
3297
|
+
onSelect,
|
|
3298
|
+
labels,
|
|
3299
|
+
className,
|
|
3300
|
+
tabClassName,
|
|
3301
|
+
tabActiveClassName
|
|
3302
|
+
}) {
|
|
3303
|
+
if (methods.length < 2) return null;
|
|
3304
|
+
return /* @__PURE__ */ jsx30("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", className, children: methods.map((m) => {
|
|
3305
|
+
const active = m === selected;
|
|
3306
|
+
const label = labels[m] ?? m;
|
|
3307
|
+
return /* @__PURE__ */ jsx30(
|
|
3308
|
+
"button",
|
|
3309
|
+
{
|
|
3310
|
+
type: "button",
|
|
3311
|
+
role: "tab",
|
|
3312
|
+
"aria-selected": active,
|
|
3313
|
+
"aria-controls": `paywall-tab-${m}`,
|
|
3314
|
+
tabIndex: active ? 0 : -1,
|
|
3315
|
+
onClick: () => onSelect(m),
|
|
3316
|
+
className: [tabClassName, active ? tabActiveClassName : ""].filter(Boolean).join(" "),
|
|
3317
|
+
children: label
|
|
3318
|
+
},
|
|
3319
|
+
m
|
|
3320
|
+
);
|
|
3321
|
+
}) });
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
// src/components/paywall/PaywallMethodContent.tsx
|
|
3325
|
+
import { jsx as jsx31 } from "react/jsx-runtime";
|
|
3326
|
+
function PaywallMethodContent({
|
|
3327
|
+
method,
|
|
3328
|
+
copy,
|
|
3329
|
+
hasConsumedTrial = false,
|
|
3330
|
+
className,
|
|
3331
|
+
rowClassName
|
|
3332
|
+
}) {
|
|
3333
|
+
const useCardConsumed = method === "card" && hasConsumedTrial && copy.cardConsumedTrial;
|
|
3334
|
+
const rows = useCardConsumed ? copy.cardConsumedTrial.bodyRows : method === "pix-auto" || method === "pix-once" ? copy.pix.bodyRows : copy.card.bodyRows;
|
|
3335
|
+
return /* @__PURE__ */ jsx31("div", { role: "tabpanel", id: `paywall-tab-${method}`, className, children: rows.map((row, i) => /* @__PURE__ */ jsx31("div", { className: rowClassName, children: row }, i)) });
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
// src/components/paywall/PaywallCyclePicker.tsx
|
|
3339
|
+
import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3340
|
+
function PaywallCyclePicker({
|
|
3341
|
+
cycles,
|
|
3342
|
+
selected,
|
|
3343
|
+
onSelect,
|
|
3344
|
+
priceCentsByCycle,
|
|
3345
|
+
anchorCentsByCycle,
|
|
3346
|
+
monthlyEquivByCycle,
|
|
3347
|
+
labels,
|
|
3348
|
+
className,
|
|
3349
|
+
cardClassName,
|
|
3350
|
+
cardSelectedClassName,
|
|
3351
|
+
anchorClassName
|
|
3352
|
+
}) {
|
|
3353
|
+
if (cycles.length < 2) return null;
|
|
3354
|
+
return /* @__PURE__ */ jsx32("div", { role: "radiogroup", "aria-label": "Ciclo de cobran\xE7a", className, children: cycles.map((c) => {
|
|
3355
|
+
const active = c === selected;
|
|
3356
|
+
const label = c === "YEARLY" ? labels.annualLabel : labels.monthlyLabel;
|
|
3357
|
+
const suffix = c === "YEARLY" ? labels.annualSuffix : labels.monthlySuffix;
|
|
3358
|
+
const mainCents = c === "YEARLY" ? monthlyEquivByCycle[c] : priceCentsByCycle[c];
|
|
3359
|
+
const anchorCents = anchorCentsByCycle[c];
|
|
3360
|
+
return /* @__PURE__ */ jsxs19(
|
|
3361
|
+
"button",
|
|
3362
|
+
{
|
|
3363
|
+
type: "button",
|
|
3364
|
+
role: "radio",
|
|
3365
|
+
"aria-checked": active,
|
|
3366
|
+
onClick: () => onSelect(c),
|
|
3367
|
+
className: ["flex flex-col items-center gap-0.5", cardClassName, active ? cardSelectedClassName : ""].filter(Boolean).join(" "),
|
|
3368
|
+
children: [
|
|
3369
|
+
/* @__PURE__ */ jsx32("span", { className: "font-bold text-base leading-tight", children: formatBRL(mainCents) }),
|
|
3370
|
+
/* @__PURE__ */ jsx32("span", { className: "text-xs opacity-70 leading-tight", children: suffix }),
|
|
3371
|
+
/* @__PURE__ */ jsx32("span", { className: "text-xs opacity-60 leading-tight", children: label }),
|
|
3372
|
+
anchorCents != null && anchorCents > mainCents ? /* @__PURE__ */ jsx32("span", { className: anchorClassName ?? "text-xs opacity-50", children: /* @__PURE__ */ jsx32("s", { children: formatBRL(anchorCents) }) }) : null
|
|
3373
|
+
]
|
|
3374
|
+
},
|
|
3375
|
+
c
|
|
3376
|
+
);
|
|
3377
|
+
}) });
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// src/components/paywall/PaywallCta.tsx
|
|
3381
|
+
import { jsx as jsx33, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3382
|
+
function PaywallCta({
|
|
3383
|
+
ctaLabel,
|
|
3384
|
+
loadingLabel,
|
|
3385
|
+
switchHint,
|
|
3386
|
+
trustLine,
|
|
3387
|
+
onClick,
|
|
3388
|
+
disabled = false,
|
|
3389
|
+
loading = false,
|
|
3390
|
+
className,
|
|
3391
|
+
buttonClassName,
|
|
3392
|
+
switchHintClassName,
|
|
3393
|
+
trustClassName
|
|
3394
|
+
}) {
|
|
3395
|
+
const label = loading && loadingLabel ? loadingLabel : ctaLabel;
|
|
3396
|
+
return /* @__PURE__ */ jsxs20("div", { className, children: [
|
|
3397
|
+
/* @__PURE__ */ jsx33(
|
|
3398
|
+
"button",
|
|
3399
|
+
{
|
|
3400
|
+
type: "button",
|
|
3401
|
+
onClick,
|
|
3402
|
+
disabled: disabled || loading,
|
|
3403
|
+
className: buttonClassName,
|
|
3404
|
+
children: label
|
|
3405
|
+
}
|
|
3406
|
+
),
|
|
3407
|
+
switchHint ? /* @__PURE__ */ jsx33("p", { className: switchHintClassName, children: switchHint }) : null,
|
|
3408
|
+
/* @__PURE__ */ jsx33("p", { className: trustClassName, children: trustLine })
|
|
3409
|
+
] });
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// src/components/paywall/Paywall.tsx
|
|
3413
|
+
import { jsx as jsx34, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
3414
|
+
var NBSP = "\xA0";
|
|
3415
|
+
function Paywall({
|
|
3416
|
+
copy,
|
|
3417
|
+
themeClasses = {},
|
|
3418
|
+
slots = {},
|
|
3419
|
+
onBeforeCheckout
|
|
3420
|
+
}) {
|
|
3421
|
+
const { track: track2 } = useHook19();
|
|
3422
|
+
const s = usePaywallState();
|
|
3423
|
+
const priceLabel = formatBRL(s.currentPriceCents).replace(new RegExp(NBSP, "g"), " ");
|
|
3424
|
+
const monthlyEquivLabel = formatBRL(s.currentMonthlyEquivCents).replace(new RegExp(NBSP, "g"), " ");
|
|
3425
|
+
const trialDaysCardLabel = String(s.trialDaysCard);
|
|
3426
|
+
const ctaLabel = useMemo9(() => {
|
|
3427
|
+
if (s.isFree) return copy.freeCta ?? "Come\xE7ar agora";
|
|
3428
|
+
if (s.selectedMethod === "card") {
|
|
3429
|
+
if (s.hasConsumedTrial && copy.cardConsumedTrial) {
|
|
3430
|
+
return interp(copy.cardConsumedTrial.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
|
|
3431
|
+
}
|
|
3432
|
+
if (s.trialDaysCard > 0) {
|
|
3433
|
+
return interp(copy.card.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
|
|
3434
|
+
}
|
|
3435
|
+
return copy.cardConsumedTrial ? interp(copy.cardConsumedTrial.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel }) : `Assinar por ${priceLabel}`;
|
|
3436
|
+
}
|
|
3437
|
+
return interp(copy.pix.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
|
|
3438
|
+
}, [
|
|
3439
|
+
s.isFree,
|
|
3440
|
+
s.selectedMethod,
|
|
3441
|
+
s.hasConsumedTrial,
|
|
3442
|
+
s.trialDaysCard,
|
|
3443
|
+
copy,
|
|
3444
|
+
priceLabel,
|
|
3445
|
+
trialDaysCardLabel
|
|
3446
|
+
]);
|
|
3447
|
+
const switchHint = useMemo9(() => {
|
|
3448
|
+
if (s.methods.length < 2) return void 0;
|
|
3449
|
+
return s.selectedMethod === "card" ? copy.card.switchHint : copy.pix.switchHint;
|
|
3450
|
+
}, [s.methods.length, s.selectedMethod, copy]);
|
|
3451
|
+
useEffect14(() => {
|
|
3452
|
+
if (!s.initialLoadComplete) return;
|
|
3453
|
+
track2("paywall_view", {
|
|
3454
|
+
default_method: s.selectedMethod,
|
|
3455
|
+
default_cycle: s.cycle,
|
|
3456
|
+
available_methods: s.methods
|
|
3457
|
+
});
|
|
3458
|
+
}, [s.initialLoadComplete]);
|
|
3459
|
+
const handleCta = async () => {
|
|
3460
|
+
track2("paywall_cta_clicked", {
|
|
3461
|
+
method: s.selectedMethod,
|
|
3462
|
+
cycle: s.cycle,
|
|
3463
|
+
price_cents: s.currentPriceCents,
|
|
3464
|
+
had_consumed_trial: s.hasConsumedTrial
|
|
3465
|
+
});
|
|
3466
|
+
if (onBeforeCheckout) {
|
|
3467
|
+
await onBeforeCheckout(s.selectedMethod, s.cycle);
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
await s.submit();
|
|
3471
|
+
};
|
|
3472
|
+
const ctaTheme = s.selectedMethod === "card" ? themeClasses.ctaCard : themeClasses.ctaPix;
|
|
3473
|
+
return /* @__PURE__ */ jsxs21("div", { className: themeClasses.container, children: [
|
|
3474
|
+
slots.heroSlot,
|
|
3475
|
+
/* @__PURE__ */ jsx34("h1", { className: themeClasses.headline, children: copy.headline }),
|
|
3476
|
+
/* @__PURE__ */ jsx34("ul", { children: copy.features.map((f) => /* @__PURE__ */ jsxs21("li", { className: themeClasses.feature, children: [
|
|
3477
|
+
"\u2713 ",
|
|
3478
|
+
/* @__PURE__ */ jsx34("span", { children: f })
|
|
3479
|
+
] }, f)) }),
|
|
3480
|
+
copy.socialProof ? /* @__PURE__ */ jsx34("p", { className: themeClasses.socialProof, children: copy.socialProof }) : null,
|
|
3481
|
+
slots.cyclePickerSlot ?? /* @__PURE__ */ jsx34(
|
|
3482
|
+
PaywallCyclePicker,
|
|
3483
|
+
{
|
|
3484
|
+
cycles: ["MONTHLY", "YEARLY"],
|
|
3485
|
+
selected: s.cycle,
|
|
3486
|
+
onSelect: s.selectCycle,
|
|
3487
|
+
priceCentsByCycle: {
|
|
3488
|
+
MONTHLY: priceCentsForCycle(s, "MONTHLY"),
|
|
3489
|
+
YEARLY: priceCentsForCycle(s, "YEARLY")
|
|
3490
|
+
},
|
|
3491
|
+
anchorCentsByCycle: {
|
|
3492
|
+
MONTHLY: anchorForCycle(s, "MONTHLY"),
|
|
3493
|
+
YEARLY: anchorForCycle(s, "YEARLY")
|
|
3494
|
+
},
|
|
3495
|
+
monthlyEquivByCycle: {
|
|
3496
|
+
MONTHLY: priceCentsForCycle(s, "MONTHLY"),
|
|
3497
|
+
YEARLY: Math.round(priceCentsForCycle(s, "YEARLY") / 12)
|
|
3498
|
+
},
|
|
3499
|
+
labels: copy.cycle,
|
|
3500
|
+
cardClassName: themeClasses.cycleCard,
|
|
3501
|
+
cardSelectedClassName: themeClasses.cycleCardSelected,
|
|
3502
|
+
anchorClassName: themeClasses.anchorPrice
|
|
3503
|
+
}
|
|
3504
|
+
),
|
|
3505
|
+
/* @__PURE__ */ jsx34(
|
|
3506
|
+
PaywallMethodTabs,
|
|
3507
|
+
{
|
|
3508
|
+
methods: s.methods,
|
|
3509
|
+
selected: s.selectedMethod,
|
|
3510
|
+
onSelect: s.selectMethod,
|
|
3511
|
+
labels: { "pix-auto": copy.pix.tabLabel, card: copy.card.tabLabel },
|
|
3512
|
+
className: themeClasses.tabs,
|
|
3513
|
+
tabClassName: themeClasses.tab,
|
|
3514
|
+
tabActiveClassName: themeClasses.tabActive
|
|
3515
|
+
}
|
|
3516
|
+
),
|
|
3517
|
+
/* @__PURE__ */ jsx34(
|
|
3518
|
+
PaywallMethodContent,
|
|
3519
|
+
{
|
|
3520
|
+
method: s.selectedMethod,
|
|
3521
|
+
copy: {
|
|
3522
|
+
pix: interpolateCopy(copy.pix, priceLabel, trialDaysCardLabel),
|
|
3523
|
+
card: interpolateCopy(copy.card, priceLabel, trialDaysCardLabel),
|
|
3524
|
+
cardConsumedTrial: copy.cardConsumedTrial ? {
|
|
3525
|
+
bodyRows: copy.cardConsumedTrial.bodyRows.map(
|
|
3526
|
+
(r) => interp(r, { price: priceLabel, days: trialDaysCardLabel })
|
|
3527
|
+
),
|
|
3528
|
+
ctaTemplate: copy.cardConsumedTrial.ctaTemplate
|
|
3529
|
+
} : void 0
|
|
3530
|
+
},
|
|
3531
|
+
hasConsumedTrial: s.hasConsumedTrial,
|
|
3532
|
+
className: themeClasses.tabContent,
|
|
3533
|
+
rowClassName: themeClasses.tabContentRow
|
|
3534
|
+
}
|
|
3535
|
+
),
|
|
3536
|
+
slots.beforeCtaSlot,
|
|
3537
|
+
/* @__PURE__ */ jsx34(
|
|
3538
|
+
PaywallCta,
|
|
3539
|
+
{
|
|
3540
|
+
ctaLabel,
|
|
3541
|
+
loadingLabel: "Abrindo checkout\u2026",
|
|
3542
|
+
switchHint,
|
|
3543
|
+
trustLine: copy.trustLine,
|
|
3544
|
+
onClick: handleCta,
|
|
3545
|
+
disabled: s.submitting,
|
|
3546
|
+
loading: s.submitting,
|
|
3547
|
+
buttonClassName: ctaTheme,
|
|
3548
|
+
switchHintClassName: themeClasses.switchHint,
|
|
3549
|
+
trustClassName: themeClasses.trustLine
|
|
3550
|
+
}
|
|
3551
|
+
)
|
|
3552
|
+
] });
|
|
3553
|
+
}
|
|
3554
|
+
function interp(tpl, vars) {
|
|
3555
|
+
return tpl.replace(/\{(\w+)\}/g, (_m, k) => vars[k] ?? "");
|
|
3556
|
+
}
|
|
3557
|
+
function interpolateCopy(m, price, days) {
|
|
3558
|
+
return {
|
|
3559
|
+
tabLabel: m.tabLabel,
|
|
3560
|
+
bodyRows: m.bodyRows.map((r) => interp(r, { price, days })),
|
|
3561
|
+
ctaTemplate: m.ctaTemplate,
|
|
3562
|
+
switchHint: m.switchHint
|
|
3563
|
+
};
|
|
3564
|
+
}
|
|
3565
|
+
function priceCentsForCycle(s, c) {
|
|
3566
|
+
return s.plan ? c === "YEARLY" ? s.plan.yearlyCents ?? 0 : s.plan.monthlyCents : 0;
|
|
3567
|
+
}
|
|
3568
|
+
function anchorForCycle(s, c) {
|
|
3569
|
+
if (!s.plan) return null;
|
|
3570
|
+
if (c === "YEARLY") return s.plan.anchorYearlyCents ?? null;
|
|
3571
|
+
return s.plan.anchorMonthlyCents ?? null;
|
|
3572
|
+
}
|
|
3078
3573
|
export {
|
|
3079
3574
|
AppConfigProvider,
|
|
3080
3575
|
AppConfigSchema,
|
|
3081
3576
|
AppRoot,
|
|
3082
3577
|
DeepLinkHandler,
|
|
3578
|
+
DevSkipOnboardingFab,
|
|
3083
3579
|
EmptyState,
|
|
3084
3580
|
ErrorBoundary,
|
|
3085
3581
|
I18nProvider,
|
|
@@ -3089,6 +3585,11 @@ export {
|
|
|
3089
3585
|
LoadingState,
|
|
3090
3586
|
OnboardingFlow,
|
|
3091
3587
|
PaymentReturnHandler,
|
|
3588
|
+
Paywall,
|
|
3589
|
+
PaywallCta,
|
|
3590
|
+
PaywallCyclePicker,
|
|
3591
|
+
PaywallMethodContent,
|
|
3592
|
+
PaywallMethodTabs,
|
|
3092
3593
|
PersistenceRegistry,
|
|
3093
3594
|
PreAuthShell,
|
|
3094
3595
|
PushPrompt2 as PushPrompt,
|
|
@@ -3103,10 +3604,12 @@ export {
|
|
|
3103
3604
|
detectStandalone,
|
|
3104
3605
|
discountPercent,
|
|
3105
3606
|
formatBRL,
|
|
3607
|
+
isDevToolsEnabled,
|
|
3106
3608
|
monthlyFromYearly,
|
|
3107
3609
|
parseAppConfig,
|
|
3108
3610
|
shouldBlockInstall,
|
|
3109
3611
|
shouldShowPermanentOption,
|
|
3612
|
+
skipOnboarding,
|
|
3110
3613
|
useAppConfig,
|
|
3111
3614
|
useAuth,
|
|
3112
3615
|
useAuthPrimitives,
|