@hook-sdk/template 0.19.0 → 0.21.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 +747 -230
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +250 -10
- package/dist/index.d.ts +250 -10
- package/dist/index.js +699 -191
- 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(),
|
|
@@ -269,7 +278,7 @@ var FALLBACK_PAYWALL = {
|
|
|
269
278
|
};
|
|
270
279
|
var isMethodAvailable = (availability, method) => availability[method] !== false;
|
|
271
280
|
function usePaywallState() {
|
|
272
|
-
const { subscription, plan } = useHook3();
|
|
281
|
+
const { subscription, plan, authStatus, track: track2 } = useHook3();
|
|
273
282
|
const configFromCtx = useContext3(AppConfigContext);
|
|
274
283
|
const paywall = configFromCtx?.paywall ?? FALLBACK_PAYWALL;
|
|
275
284
|
const isFree = paywall.mode === "free";
|
|
@@ -286,7 +295,8 @@ 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
302
|
const initialCycle = isFree ? "MONTHLY" : paywall.cycles[0] ?? "MONTHLY";
|
|
@@ -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) => ({
|
|
@@ -358,6 +418,21 @@ function usePaywallState() {
|
|
|
358
418
|
[useDefaultMessages]
|
|
359
419
|
);
|
|
360
420
|
const submit = useCallback(async () => {
|
|
421
|
+
if (authStatus === "loading") return void 0;
|
|
422
|
+
if (authStatus !== "authenticated") {
|
|
423
|
+
track2("unauthenticated_submit_attempted", {
|
|
424
|
+
method: selectedMethod,
|
|
425
|
+
cycle,
|
|
426
|
+
cpf_valid: cpfValid
|
|
427
|
+
});
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
track2("payment_attempted", {
|
|
431
|
+
method: selectedMethod,
|
|
432
|
+
cycle,
|
|
433
|
+
cpf_valid: cpfValid,
|
|
434
|
+
selected_amount_cents: cycle === "YEARLY" ? plan.data?.yearlyPriceCents ?? (isFree ? 0 : paywall.prices.yearlyCents) : plan.data?.priceCents ?? (isFree ? 0 : paywall.prices.monthlyCents)
|
|
435
|
+
});
|
|
361
436
|
setSubmitting(true);
|
|
362
437
|
setError(null);
|
|
363
438
|
const methodToUse = selectedMethod;
|
|
@@ -418,7 +493,7 @@ function usePaywallState() {
|
|
|
418
493
|
setSubmitting(false);
|
|
419
494
|
return void 0;
|
|
420
495
|
}
|
|
421
|
-
}, [selectedMethod, availability, subscription, cycle, cpf, card, buildError]);
|
|
496
|
+
}, [authStatus, track2, selectedMethod, availability, subscription, cycle, cpf, cpfValid, card, buildError, plan, paywall]);
|
|
422
497
|
const checkout = useCallback(
|
|
423
498
|
async (args) => {
|
|
424
499
|
setSubmitting(true);
|
|
@@ -525,6 +600,20 @@ function usePaywallState() {
|
|
|
525
600
|
methods,
|
|
526
601
|
selectedMethod,
|
|
527
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,
|
|
528
617
|
// Form state
|
|
529
618
|
cpfState,
|
|
530
619
|
cardState,
|
|
@@ -556,13 +645,13 @@ var BLOCKING = /* @__PURE__ */ new Set([
|
|
|
556
645
|
"canceled",
|
|
557
646
|
"none"
|
|
558
647
|
]);
|
|
559
|
-
function SubscriptionGate({ Paywall, children }) {
|
|
648
|
+
function SubscriptionGate({ Paywall: Paywall2, children }) {
|
|
560
649
|
const { mode } = useTemplateConfig();
|
|
561
650
|
const { status, hasAccess, initialLoadComplete } = usePaywallState();
|
|
562
651
|
if (mode === "free") return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
563
652
|
if (!initialLoadComplete && status === "none") return null;
|
|
564
653
|
if (hasAccess === true && status !== "none") return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
565
|
-
if (BLOCKING.has(status)) return /* @__PURE__ */ jsx5(
|
|
654
|
+
if (BLOCKING.has(status)) return /* @__PURE__ */ jsx5(Paywall2, {});
|
|
566
655
|
return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
567
656
|
}
|
|
568
657
|
|
|
@@ -2008,23 +2097,162 @@ function I18nProvider({
|
|
|
2008
2097
|
return /* @__PURE__ */ jsx19(I18nextProvider, { i18n, children });
|
|
2009
2098
|
}
|
|
2010
2099
|
|
|
2011
|
-
// src/
|
|
2012
|
-
|
|
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";
|
|
2013
2111
|
import { useHook as useHook5 } from "@hook-sdk/sdk";
|
|
2014
|
-
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";
|
|
2015
2237
|
var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
|
|
2016
2238
|
var MAX_CYCLES = 3;
|
|
2017
2239
|
var SUPPORT_MAILTO = "mailto:suporte@usehook.net?subject=Pagamento%20pendente";
|
|
2018
2240
|
function PaymentReturnHandler({ children }) {
|
|
2019
|
-
const { subscription } =
|
|
2020
|
-
const subRef =
|
|
2241
|
+
const { subscription, track: track2 } = useHook6();
|
|
2242
|
+
const subRef = useRef5(subscription);
|
|
2021
2243
|
subRef.current = subscription;
|
|
2022
|
-
const runIdRef =
|
|
2023
|
-
const cyclesRef =
|
|
2024
|
-
const
|
|
2025
|
-
const
|
|
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(() => {
|
|
2026
2249
|
const runId = ++runIdRef.current;
|
|
2250
|
+
const isFirstRun = cyclesRef.current === 0;
|
|
2027
2251
|
cyclesRef.current += 1;
|
|
2252
|
+
if (isFirstRun) {
|
|
2253
|
+
startMsRef.current = Date.now();
|
|
2254
|
+
track2("payment_confirmation_started", {});
|
|
2255
|
+
}
|
|
2028
2256
|
setState("confirming");
|
|
2029
2257
|
let attempts = 0;
|
|
2030
2258
|
const tick = async () => {
|
|
@@ -2037,6 +2265,11 @@ function PaymentReturnHandler({ children }) {
|
|
|
2037
2265
|
if (runIdRef.current !== runId) return;
|
|
2038
2266
|
const status = subRef.current.status();
|
|
2039
2267
|
if (status === "active" || status === "trialing") {
|
|
2268
|
+
track2("payment_confirmation_succeeded", {
|
|
2269
|
+
cycle_count: cyclesRef.current,
|
|
2270
|
+
attempt_count: attempts,
|
|
2271
|
+
duration_ms: Date.now() - startMsRef.current
|
|
2272
|
+
});
|
|
2040
2273
|
const cleanUrl = new URL(window.location.href);
|
|
2041
2274
|
cleanUrl.searchParams.delete("paymentReturn");
|
|
2042
2275
|
window.history.replaceState({}, "", cleanUrl.toString());
|
|
@@ -2047,6 +2280,9 @@ function PaymentReturnHandler({ children }) {
|
|
|
2047
2280
|
const delay = BACKOFF_MS[attempts - 1];
|
|
2048
2281
|
if (delay === void 0) {
|
|
2049
2282
|
if (cyclesRef.current >= MAX_CYCLES) {
|
|
2283
|
+
track2("payment_confirmation_timed_out", {
|
|
2284
|
+
total_duration_ms: Date.now() - startMsRef.current
|
|
2285
|
+
});
|
|
2050
2286
|
setState("timeout");
|
|
2051
2287
|
} else {
|
|
2052
2288
|
setState("waiting");
|
|
@@ -2056,7 +2292,7 @@ function PaymentReturnHandler({ children }) {
|
|
|
2056
2292
|
setTimeout(tick, delay);
|
|
2057
2293
|
};
|
|
2058
2294
|
void tick();
|
|
2059
|
-
}, []);
|
|
2295
|
+
}, [track2]);
|
|
2060
2296
|
useEffect8(() => {
|
|
2061
2297
|
if (typeof window === "undefined") return;
|
|
2062
2298
|
const url = new URL(window.location.href);
|
|
@@ -2067,26 +2303,26 @@ function PaymentReturnHandler({ children }) {
|
|
|
2067
2303
|
runIdRef.current++;
|
|
2068
2304
|
};
|
|
2069
2305
|
}, [runPoll]);
|
|
2070
|
-
const goHome =
|
|
2306
|
+
const goHome = useCallback4(() => {
|
|
2071
2307
|
const cleanUrl = new URL(window.location.href);
|
|
2072
2308
|
cleanUrl.searchParams.delete("paymentReturn");
|
|
2073
2309
|
cleanUrl.pathname = "/app/home";
|
|
2074
2310
|
window.location.href = cleanUrl.toString();
|
|
2075
2311
|
}, []);
|
|
2076
2312
|
if (state === "confirming") {
|
|
2077
|
-
return /* @__PURE__ */
|
|
2313
|
+
return /* @__PURE__ */ jsx21("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
|
|
2078
2314
|
}
|
|
2079
2315
|
if (state === "waiting") {
|
|
2080
|
-
return /* @__PURE__ */
|
|
2081
|
-
/* @__PURE__ */
|
|
2082
|
-
/* @__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" })
|
|
2083
2319
|
] }) });
|
|
2084
2320
|
}
|
|
2085
2321
|
if (state === "timeout") {
|
|
2086
|
-
return /* @__PURE__ */
|
|
2087
|
-
/* @__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." }),
|
|
2088
2324
|
/* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
|
|
2089
|
-
/* @__PURE__ */
|
|
2325
|
+
/* @__PURE__ */ jsx21(
|
|
2090
2326
|
"button",
|
|
2091
2327
|
{
|
|
2092
2328
|
type: "button",
|
|
@@ -2099,7 +2335,7 @@ function PaymentReturnHandler({ children }) {
|
|
|
2099
2335
|
children: "Tentar de novo"
|
|
2100
2336
|
}
|
|
2101
2337
|
),
|
|
2102
|
-
/* @__PURE__ */
|
|
2338
|
+
/* @__PURE__ */ jsx21(
|
|
2103
2339
|
"button",
|
|
2104
2340
|
{
|
|
2105
2341
|
type: "button",
|
|
@@ -2109,7 +2345,7 @@ function PaymentReturnHandler({ children }) {
|
|
|
2109
2345
|
children: "Voltar pro app"
|
|
2110
2346
|
}
|
|
2111
2347
|
),
|
|
2112
|
-
/* @__PURE__ */
|
|
2348
|
+
/* @__PURE__ */ jsx21(
|
|
2113
2349
|
"a",
|
|
2114
2350
|
{
|
|
2115
2351
|
href: SUPPORT_MAILTO,
|
|
@@ -2121,7 +2357,7 @@ function PaymentReturnHandler({ children }) {
|
|
|
2121
2357
|
] })
|
|
2122
2358
|
] }) });
|
|
2123
2359
|
}
|
|
2124
|
-
return /* @__PURE__ */
|
|
2360
|
+
return /* @__PURE__ */ jsx21(Fragment5, { children });
|
|
2125
2361
|
}
|
|
2126
2362
|
var overlayStyle2 = {
|
|
2127
2363
|
position: "fixed",
|
|
@@ -2160,7 +2396,7 @@ var linkStyle = {
|
|
|
2160
2396
|
};
|
|
2161
2397
|
|
|
2162
2398
|
// src/AppRoot.tsx
|
|
2163
|
-
import { Fragment as Fragment6, jsx as
|
|
2399
|
+
import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2164
2400
|
function buildLegacyConfigShim(config) {
|
|
2165
2401
|
const paywall = config.paywall;
|
|
2166
2402
|
const isFree = paywall.mode === "free";
|
|
@@ -2208,9 +2444,10 @@ function AppRoot(props) {
|
|
|
2208
2444
|
Forgot,
|
|
2209
2445
|
Reset,
|
|
2210
2446
|
EmailVerify,
|
|
2211
|
-
Paywall,
|
|
2447
|
+
Paywall: Paywall2,
|
|
2212
2448
|
Onboarding,
|
|
2213
|
-
PreAuthFlow
|
|
2449
|
+
PreAuthFlow,
|
|
2450
|
+
devSkipOnboarding
|
|
2214
2451
|
} = props;
|
|
2215
2452
|
if (!Login || !Signup || !Forgot || !Reset) {
|
|
2216
2453
|
throw new Error(
|
|
@@ -2218,7 +2455,7 @@ function AppRoot(props) {
|
|
|
2218
2455
|
);
|
|
2219
2456
|
}
|
|
2220
2457
|
const config = parseAppConfig(rawConfig);
|
|
2221
|
-
if (config.paywall.mode !== "free" && !
|
|
2458
|
+
if (config.paywall.mode !== "free" && !Paywall2) {
|
|
2222
2459
|
throw new Error(
|
|
2223
2460
|
"[hook-template] <AppRoot>: Paywall slot prop is required when config.paywall.mode != 'free'."
|
|
2224
2461
|
);
|
|
@@ -2238,14 +2475,14 @@ function AppRoot(props) {
|
|
|
2238
2475
|
const basename = `/app/${config.slug}`;
|
|
2239
2476
|
const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
|
|
2240
2477
|
const position = config.install_prompt?.position ?? "post-paywall";
|
|
2241
|
-
const subscriptionGated = /* @__PURE__ */
|
|
2478
|
+
const subscriptionGated = /* @__PURE__ */ jsx22(SubscriptionGate, { Paywall: Paywall2 ?? FallbackPaywall, children: position === "post-paywall" ? /* @__PURE__ */ jsxs14(InstallGate, { position: "post-paywall", children: [
|
|
2242
2479
|
children,
|
|
2243
|
-
/* @__PURE__ */
|
|
2480
|
+
/* @__PURE__ */ jsx22(PushPrompt, {})
|
|
2244
2481
|
] }) : /* @__PURE__ */ jsxs14(Fragment6, { children: [
|
|
2245
2482
|
children,
|
|
2246
|
-
/* @__PURE__ */
|
|
2483
|
+
/* @__PURE__ */ jsx22(PushPrompt, {})
|
|
2247
2484
|
] }) });
|
|
2248
|
-
const authGated = /* @__PURE__ */
|
|
2485
|
+
const authGated = /* @__PURE__ */ jsx22(
|
|
2249
2486
|
AuthGated,
|
|
2250
2487
|
{
|
|
2251
2488
|
config,
|
|
@@ -2254,18 +2491,19 @@ function AppRoot(props) {
|
|
|
2254
2491
|
Forgot,
|
|
2255
2492
|
Reset,
|
|
2256
2493
|
EmailVerify,
|
|
2257
|
-
Paywall,
|
|
2494
|
+
Paywall: Paywall2,
|
|
2258
2495
|
Onboarding,
|
|
2259
2496
|
PreAuthFlow,
|
|
2260
2497
|
children: subscriptionGated
|
|
2261
2498
|
}
|
|
2262
2499
|
);
|
|
2263
2500
|
const routedTree = /* @__PURE__ */ jsxs14(Router, { ...routerProps, children: [
|
|
2264
|
-
/* @__PURE__ */
|
|
2265
|
-
/* @__PURE__ */
|
|
2266
|
-
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
|
|
2267
2505
|
] });
|
|
2268
|
-
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(
|
|
2269
2507
|
I18nProvider,
|
|
2270
2508
|
{
|
|
2271
2509
|
defaultLocale: config.i18n.defaultLocale,
|
|
@@ -2285,37 +2523,37 @@ function AuthGated({
|
|
|
2285
2523
|
EmailVerify,
|
|
2286
2524
|
PreAuthFlow
|
|
2287
2525
|
}) {
|
|
2288
|
-
const { authStatus } =
|
|
2526
|
+
const { authStatus } = useHook7();
|
|
2289
2527
|
if (authStatus === "loading") return null;
|
|
2290
2528
|
if (authStatus !== "authenticated") {
|
|
2291
2529
|
if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
|
|
2292
2530
|
return /* @__PURE__ */ jsxs14(Routes, { children: [
|
|
2293
|
-
/* @__PURE__ */
|
|
2294
|
-
/* @__PURE__ */
|
|
2295
|
-
/* @__PURE__ */
|
|
2296
|
-
/* @__PURE__ */
|
|
2297
|
-
EmailVerify ? /* @__PURE__ */
|
|
2298
|
-
/* @__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, {}) })
|
|
2299
2537
|
] });
|
|
2300
2538
|
}
|
|
2301
2539
|
return /* @__PURE__ */ jsxs14(Routes, { children: [
|
|
2302
|
-
/* @__PURE__ */
|
|
2303
|
-
/* @__PURE__ */
|
|
2304
|
-
/* @__PURE__ */
|
|
2305
|
-
/* @__PURE__ */
|
|
2306
|
-
EmailVerify ? /* @__PURE__ */
|
|
2307
|
-
/* @__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 }) })
|
|
2308
2546
|
] });
|
|
2309
2547
|
}
|
|
2310
|
-
return /* @__PURE__ */
|
|
2548
|
+
return /* @__PURE__ */ jsx22(Fragment6, { children });
|
|
2311
2549
|
}
|
|
2312
2550
|
function FallbackPaywall() {
|
|
2313
2551
|
return null;
|
|
2314
2552
|
}
|
|
2315
2553
|
|
|
2316
2554
|
// src/hooks/usePush.ts
|
|
2317
|
-
import { useCallback as
|
|
2318
|
-
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";
|
|
2319
2557
|
var DISMISS_STORAGE_KEY = "push:dismissed-until";
|
|
2320
2558
|
var DISMISS_TTL_MS2 = 7 * 24 * 60 * 60 * 1e3;
|
|
2321
2559
|
function detectIosNeedsInstall() {
|
|
@@ -2359,12 +2597,12 @@ function deriveState(push) {
|
|
|
2359
2597
|
return { kind: "prompt" };
|
|
2360
2598
|
}
|
|
2361
2599
|
function usePush() {
|
|
2362
|
-
const { push } =
|
|
2363
|
-
const [state, setState] =
|
|
2600
|
+
const { push } = useHook8();
|
|
2601
|
+
const [state, setState] = useState7(() => deriveState(push));
|
|
2364
2602
|
useEffect9(() => {
|
|
2365
2603
|
setState(deriveState(push));
|
|
2366
2604
|
}, [push]);
|
|
2367
|
-
const subscribe =
|
|
2605
|
+
const subscribe = useCallback5(async () => {
|
|
2368
2606
|
try {
|
|
2369
2607
|
await push.subscribe();
|
|
2370
2608
|
setState({ kind: "subscribed" });
|
|
@@ -2376,7 +2614,7 @@ function usePush() {
|
|
|
2376
2614
|
throw e;
|
|
2377
2615
|
}
|
|
2378
2616
|
}, [push]);
|
|
2379
|
-
const unsubscribe =
|
|
2617
|
+
const unsubscribe = useCallback5(async () => {
|
|
2380
2618
|
try {
|
|
2381
2619
|
await push.unsubscribe();
|
|
2382
2620
|
setState({ kind: "prompt" });
|
|
@@ -2385,7 +2623,7 @@ function usePush() {
|
|
|
2385
2623
|
throw e;
|
|
2386
2624
|
}
|
|
2387
2625
|
}, [push]);
|
|
2388
|
-
const dismiss =
|
|
2626
|
+
const dismiss = useCallback5(() => {
|
|
2389
2627
|
if (typeof localStorage !== "undefined") {
|
|
2390
2628
|
try {
|
|
2391
2629
|
localStorage.setItem(DISMISS_STORAGE_KEY, String(Date.now() + DISMISS_TTL_MS2));
|
|
@@ -2398,51 +2636,27 @@ function usePush() {
|
|
|
2398
2636
|
}
|
|
2399
2637
|
|
|
2400
2638
|
// src/components/PushPrompt.tsx
|
|
2401
|
-
import { jsx as
|
|
2402
|
-
function platformRecoveryCopy(texts) {
|
|
2403
|
-
if (typeof navigator === "undefined") return null;
|
|
2404
|
-
const ua = navigator.userAgent || "";
|
|
2405
|
-
const platform = detectPlatform(ua);
|
|
2406
|
-
switch (platform) {
|
|
2407
|
-
case "ios-safari":
|
|
2408
|
-
case "ios-other":
|
|
2409
|
-
return texts.deniedRecoveryIos ?? null;
|
|
2410
|
-
case "android":
|
|
2411
|
-
return texts.deniedRecoveryAndroid ?? null;
|
|
2412
|
-
case "desktop":
|
|
2413
|
-
return texts.deniedRecoveryDesktop ?? null;
|
|
2414
|
-
case "in-app":
|
|
2415
|
-
return texts.deniedRecoveryInApp ?? null;
|
|
2416
|
-
default:
|
|
2417
|
-
return null;
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2639
|
+
import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2420
2640
|
function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
|
|
2421
2641
|
const { state, subscribe } = usePush();
|
|
2422
|
-
if (state.kind === "
|
|
2642
|
+
if (state.kind === "denied" || state.kind === "dismissed" || state.kind === "subscribed") {
|
|
2643
|
+
return null;
|
|
2644
|
+
}
|
|
2423
2645
|
if (state.kind === "ios_needs_install") {
|
|
2424
2646
|
return /* @__PURE__ */ jsxs15("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
|
|
2425
|
-
/* @__PURE__ */
|
|
2426
|
-
/* @__PURE__ */
|
|
2427
|
-
onInstallRequested && texts.iosInstallCta && /* @__PURE__ */
|
|
2428
|
-
] });
|
|
2429
|
-
}
|
|
2430
|
-
if (state.kind === "denied") {
|
|
2431
|
-
const recovery = platformRecoveryCopy(texts);
|
|
2432
|
-
return /* @__PURE__ */ jsxs15("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
|
|
2433
|
-
/* @__PURE__ */ jsx22("h3", { children: texts.deniedTitle }),
|
|
2434
|
-
/* @__PURE__ */ jsx22("p", { children: texts.deniedBody }),
|
|
2435
|
-
recovery && /* @__PURE__ */ jsx22("p", { "data-testid": "denied-recovery", children: recovery })
|
|
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 })
|
|
2436
2650
|
] });
|
|
2437
2651
|
}
|
|
2438
2652
|
if (state.kind === "unsupported") {
|
|
2439
|
-
return /* @__PURE__ */
|
|
2653
|
+
return /* @__PURE__ */ jsx23("div", { className, role: "region", children: /* @__PURE__ */ jsx23("p", { children: texts.unsupportedBody }) });
|
|
2440
2654
|
}
|
|
2441
2655
|
if (state.kind === "error") {
|
|
2442
|
-
return /* @__PURE__ */
|
|
2656
|
+
return /* @__PURE__ */ jsx23("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx23("p", { children: state.message }) });
|
|
2443
2657
|
}
|
|
2444
2658
|
return /* @__PURE__ */ jsxs15("div", { className, role: "region", children: [
|
|
2445
|
-
/* @__PURE__ */
|
|
2659
|
+
/* @__PURE__ */ jsx23(
|
|
2446
2660
|
"button",
|
|
2447
2661
|
{
|
|
2448
2662
|
type: "button",
|
|
@@ -2456,13 +2670,13 @@ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, clas
|
|
|
2456
2670
|
children: texts.cta
|
|
2457
2671
|
}
|
|
2458
2672
|
),
|
|
2459
|
-
onDeclined && /* @__PURE__ */
|
|
2673
|
+
onDeclined && /* @__PURE__ */ jsx23("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
|
|
2460
2674
|
] });
|
|
2461
2675
|
}
|
|
2462
2676
|
|
|
2463
2677
|
// src/components/LanguageSwitcher.tsx
|
|
2464
2678
|
import { usePersistedState as usePersistedState2 } from "@hook-sdk/sdk";
|
|
2465
|
-
import { jsx as
|
|
2679
|
+
import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2466
2680
|
function LanguageSwitcher({ id, className, label = "Language" }) {
|
|
2467
2681
|
const config = useAppConfig();
|
|
2468
2682
|
const i18nConfig = config.i18n;
|
|
@@ -2472,39 +2686,39 @@ function LanguageSwitcher({ id, className, label = "Language" }) {
|
|
|
2472
2686
|
);
|
|
2473
2687
|
if (!i18nConfig) return null;
|
|
2474
2688
|
return /* @__PURE__ */ jsxs16("label", { className, children: [
|
|
2475
|
-
label ? /* @__PURE__ */
|
|
2476
|
-
/* @__PURE__ */
|
|
2689
|
+
label ? /* @__PURE__ */ jsx24("span", { children: label }) : null,
|
|
2690
|
+
/* @__PURE__ */ jsx24(
|
|
2477
2691
|
"select",
|
|
2478
2692
|
{
|
|
2479
2693
|
id,
|
|
2480
2694
|
value: userLocale,
|
|
2481
2695
|
onChange: (e) => setUserLocale(e.target.value),
|
|
2482
2696
|
"data-testid": "language-switcher",
|
|
2483
|
-
children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */
|
|
2697
|
+
children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */ jsx24("option", { value: loc, children: loc }, loc))
|
|
2484
2698
|
}
|
|
2485
2699
|
)
|
|
2486
2700
|
] });
|
|
2487
2701
|
}
|
|
2488
2702
|
|
|
2489
2703
|
// src/defaults/LoadingState.tsx
|
|
2490
|
-
import { jsx as
|
|
2704
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
2491
2705
|
function LoadingState({ message }) {
|
|
2492
|
-
return /* @__PURE__ */
|
|
2706
|
+
return /* @__PURE__ */ jsx25("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx25("span", { children: message ?? "Carregando..." }) });
|
|
2493
2707
|
}
|
|
2494
2708
|
|
|
2495
2709
|
// src/defaults/EmptyState.tsx
|
|
2496
|
-
import { jsx as
|
|
2710
|
+
import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2497
2711
|
function EmptyState({ title, description, action }) {
|
|
2498
2712
|
return /* @__PURE__ */ jsxs17("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
|
|
2499
|
-
/* @__PURE__ */
|
|
2500
|
-
description && /* @__PURE__ */
|
|
2501
|
-
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 })
|
|
2502
2716
|
] });
|
|
2503
2717
|
}
|
|
2504
2718
|
|
|
2505
2719
|
// src/hooks/useLoginForm.ts
|
|
2506
|
-
import { useCallback as
|
|
2507
|
-
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";
|
|
2508
2722
|
|
|
2509
2723
|
// src/errors.ts
|
|
2510
2724
|
import { SdkError, SdkAuthError, SdkRateLimitError } from "@hook-sdk/sdk";
|
|
@@ -2539,14 +2753,14 @@ function mapSdkError(err) {
|
|
|
2539
2753
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2540
2754
|
var MIN_PASSWORD = 8;
|
|
2541
2755
|
function useLoginForm() {
|
|
2542
|
-
const { auth } =
|
|
2543
|
-
const [email, setEmail] =
|
|
2544
|
-
const [password, setPassword] =
|
|
2545
|
-
const [submitting, setSubmitting] =
|
|
2546
|
-
const [error, setError] =
|
|
2547
|
-
const [touchedEmail, setTouchedEmail] =
|
|
2548
|
-
const [touchedPassword, setTouchedPassword] =
|
|
2549
|
-
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);
|
|
2550
2764
|
const validateEmail = useMemo4(() => {
|
|
2551
2765
|
if (email.length === 0) return null;
|
|
2552
2766
|
if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
@@ -2560,7 +2774,7 @@ function useLoginForm() {
|
|
|
2560
2774
|
const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
|
|
2561
2775
|
const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
|
|
2562
2776
|
const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
|
|
2563
|
-
const submit =
|
|
2777
|
+
const submit = useCallback6(async () => {
|
|
2564
2778
|
setFormSubmitAttempted(true);
|
|
2565
2779
|
if (!canSubmit) return false;
|
|
2566
2780
|
setSubmitting(true);
|
|
@@ -2594,21 +2808,21 @@ function useLoginForm() {
|
|
|
2594
2808
|
}
|
|
2595
2809
|
|
|
2596
2810
|
// src/hooks/useSignupForm.ts
|
|
2597
|
-
import { useCallback as
|
|
2598
|
-
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";
|
|
2599
2813
|
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2600
2814
|
var MIN_PASSWORD2 = 8;
|
|
2601
2815
|
function useSignupForm() {
|
|
2602
|
-
const { auth } =
|
|
2603
|
-
const [name, setName] =
|
|
2604
|
-
const [email, setEmail] =
|
|
2605
|
-
const [password, setPassword] =
|
|
2606
|
-
const [submitting, setSubmitting] =
|
|
2607
|
-
const [error, setError] =
|
|
2608
|
-
const [touchedName, setTouchedName] =
|
|
2609
|
-
const [touchedEmail, setTouchedEmail] =
|
|
2610
|
-
const [touchedPassword, setTouchedPassword] =
|
|
2611
|
-
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);
|
|
2612
2826
|
const validateName = useMemo5(() => {
|
|
2613
2827
|
if (name.length === 0) return null;
|
|
2614
2828
|
if (name.trim().length < 2) return "Nome muito curto.";
|
|
@@ -2628,7 +2842,7 @@ function useSignupForm() {
|
|
|
2628
2842
|
const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
|
|
2629
2843
|
const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
|
|
2630
2844
|
const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
|
|
2631
|
-
const submit =
|
|
2845
|
+
const submit = useCallback7(async () => {
|
|
2632
2846
|
setFormSubmitAttempted(true);
|
|
2633
2847
|
if (!canSubmit) return false;
|
|
2634
2848
|
setSubmitting(true);
|
|
@@ -2666,17 +2880,17 @@ function useSignupForm() {
|
|
|
2666
2880
|
}
|
|
2667
2881
|
|
|
2668
2882
|
// src/hooks/useForgotForm.ts
|
|
2669
|
-
import { useCallback as
|
|
2670
|
-
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";
|
|
2671
2885
|
var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2672
2886
|
function useForgotForm() {
|
|
2673
|
-
const { auth } =
|
|
2674
|
-
const [email, setEmail] =
|
|
2675
|
-
const [submitting, setSubmitting] =
|
|
2676
|
-
const [sent, setSent] =
|
|
2677
|
-
const [error, setError] =
|
|
2678
|
-
const [touchedEmail, setTouchedEmail] =
|
|
2679
|
-
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);
|
|
2680
2894
|
const validateEmail = useMemo6(() => {
|
|
2681
2895
|
if (email.length === 0) return null;
|
|
2682
2896
|
if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
|
|
@@ -2684,7 +2898,7 @@ function useForgotForm() {
|
|
|
2684
2898
|
}, [email]);
|
|
2685
2899
|
const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
|
|
2686
2900
|
const canSubmit = email.length > 0 && validateEmail === null && !submitting;
|
|
2687
|
-
const submit =
|
|
2901
|
+
const submit = useCallback8(async () => {
|
|
2688
2902
|
setFormSubmitAttempted(true);
|
|
2689
2903
|
if (!canSubmit) return false;
|
|
2690
2904
|
setSubmitting(true);
|
|
@@ -2715,20 +2929,20 @@ function useForgotForm() {
|
|
|
2715
2929
|
}
|
|
2716
2930
|
|
|
2717
2931
|
// src/hooks/useResetForm.ts
|
|
2718
|
-
import { useCallback as
|
|
2719
|
-
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";
|
|
2720
2934
|
var MIN_PASSWORD3 = 12;
|
|
2721
2935
|
function useResetForm() {
|
|
2722
|
-
const { auth } =
|
|
2723
|
-
const [token, setToken] =
|
|
2724
|
-
const [password, setPassword] =
|
|
2725
|
-
const [confirm, setConfirm] =
|
|
2726
|
-
const [submitting, setSubmitting] =
|
|
2727
|
-
const [done, setDone] =
|
|
2728
|
-
const [error, setError] =
|
|
2729
|
-
const [touchedPassword, setTouchedPassword] =
|
|
2730
|
-
const [touchedConfirm, setTouchedConfirm] =
|
|
2731
|
-
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);
|
|
2732
2946
|
useEffect10(() => {
|
|
2733
2947
|
if (typeof window === "undefined") return;
|
|
2734
2948
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -2748,7 +2962,7 @@ function useResetForm() {
|
|
|
2748
2962
|
const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
|
|
2749
2963
|
const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
|
|
2750
2964
|
const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
|
|
2751
|
-
const submit =
|
|
2965
|
+
const submit = useCallback9(async () => {
|
|
2752
2966
|
setFormSubmitAttempted(true);
|
|
2753
2967
|
if (!canSubmit || token === null) return;
|
|
2754
2968
|
setSubmitting(true);
|
|
@@ -2788,9 +3002,9 @@ function useResetForm() {
|
|
|
2788
3002
|
}
|
|
2789
3003
|
|
|
2790
3004
|
// src/hooks/usePlan.ts
|
|
2791
|
-
import { useHook as
|
|
3005
|
+
import { useHook as useHook13 } from "@hook-sdk/sdk";
|
|
2792
3006
|
function usePlan() {
|
|
2793
|
-
const { plan } =
|
|
3007
|
+
const { plan } = useHook13();
|
|
2794
3008
|
return plan;
|
|
2795
3009
|
}
|
|
2796
3010
|
|
|
@@ -2824,10 +3038,10 @@ function discountPercent(anchorCents, realCents) {
|
|
|
2824
3038
|
|
|
2825
3039
|
// src/hooks/useAuthPrimitives.ts
|
|
2826
3040
|
import { useEffect as useEffect11 } from "react";
|
|
2827
|
-
import { useHook as
|
|
3041
|
+
import { useHook as useHook14 } from "@hook-sdk/sdk";
|
|
2828
3042
|
var warned = false;
|
|
2829
3043
|
function useAuthPrimitives() {
|
|
2830
|
-
const { auth } =
|
|
3044
|
+
const { auth } = useHook14();
|
|
2831
3045
|
useEffect11(() => {
|
|
2832
3046
|
if (!warned && process.env.NODE_ENV !== "production") {
|
|
2833
3047
|
warned = true;
|
|
@@ -2850,9 +3064,9 @@ function useAuthPrimitives() {
|
|
|
2850
3064
|
}
|
|
2851
3065
|
|
|
2852
3066
|
// src/hooks/useAuth.ts
|
|
2853
|
-
import { useHook as
|
|
3067
|
+
import { useHook as useHook15 } from "@hook-sdk/sdk";
|
|
2854
3068
|
function useAuth() {
|
|
2855
|
-
const { user, authStatus, auth } =
|
|
3069
|
+
const { user, authStatus, auth } = useHook15();
|
|
2856
3070
|
return {
|
|
2857
3071
|
user,
|
|
2858
3072
|
authStatus,
|
|
@@ -2864,23 +3078,23 @@ function useAuth() {
|
|
|
2864
3078
|
import { useTrackOnboardingStep } from "@hook-sdk/sdk";
|
|
2865
3079
|
|
|
2866
3080
|
// src/hooks/useSubscription.ts
|
|
2867
|
-
import { useHook as
|
|
3081
|
+
import { useHook as useHook16 } from "@hook-sdk/sdk";
|
|
2868
3082
|
function useSubscription() {
|
|
2869
|
-
const { subscription } =
|
|
3083
|
+
const { subscription } = useHook16();
|
|
2870
3084
|
return {
|
|
2871
3085
|
status: subscription.status()
|
|
2872
3086
|
};
|
|
2873
3087
|
}
|
|
2874
3088
|
|
|
2875
3089
|
// src/hooks/useReminders.ts
|
|
2876
|
-
import { useCallback as
|
|
2877
|
-
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";
|
|
2878
3092
|
function useReminders() {
|
|
2879
|
-
const { push } =
|
|
3093
|
+
const { push } = useHook17();
|
|
2880
3094
|
const r = push.reminders;
|
|
2881
|
-
const [reminders, setReminders] =
|
|
2882
|
-
const [loading, setLoading] =
|
|
2883
|
-
const reload =
|
|
3095
|
+
const [reminders, setReminders] = useState12([]);
|
|
3096
|
+
const [loading, setLoading] = useState12(true);
|
|
3097
|
+
const reload = useCallback10(async () => {
|
|
2884
3098
|
setLoading(true);
|
|
2885
3099
|
try {
|
|
2886
3100
|
const next = await r.list();
|
|
@@ -2892,35 +3106,35 @@ function useReminders() {
|
|
|
2892
3106
|
useEffect12(() => {
|
|
2893
3107
|
void reload();
|
|
2894
3108
|
}, [reload]);
|
|
2895
|
-
const setReminder =
|
|
3109
|
+
const setReminder = useCallback10(async (input) => {
|
|
2896
3110
|
await r.set(input);
|
|
2897
3111
|
await reload();
|
|
2898
3112
|
}, [r, reload]);
|
|
2899
|
-
const deleteReminder =
|
|
3113
|
+
const deleteReminder = useCallback10(async (slot) => {
|
|
2900
3114
|
await r.delete(slot);
|
|
2901
3115
|
await reload();
|
|
2902
3116
|
}, [r, reload]);
|
|
2903
|
-
const schedule =
|
|
3117
|
+
const schedule = useCallback10(async (items) => {
|
|
2904
3118
|
return r.schedule(items);
|
|
2905
3119
|
}, [r]);
|
|
2906
|
-
const setFallbacks =
|
|
3120
|
+
const setFallbacks = useCallback10(async (items) => {
|
|
2907
3121
|
return r.setFallbacks(items);
|
|
2908
3122
|
}, [r]);
|
|
2909
3123
|
return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
|
|
2910
3124
|
}
|
|
2911
3125
|
|
|
2912
3126
|
// src/hooks/useToast.ts
|
|
2913
|
-
import { useCallback as
|
|
3127
|
+
import { useCallback as useCallback11, useState as useState13 } from "react";
|
|
2914
3128
|
function useToast() {
|
|
2915
|
-
const [items, setItems] =
|
|
2916
|
-
const show =
|
|
3129
|
+
const [items, setItems] = useState13([]);
|
|
3130
|
+
const show = useCallback11((message, kind = "info") => {
|
|
2917
3131
|
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2918
3132
|
setItems((prev) => [...prev, { id, message, kind }]);
|
|
2919
3133
|
setTimeout(() => {
|
|
2920
3134
|
setItems((prev) => prev.filter((t) => t.id !== id));
|
|
2921
3135
|
}, 4e3);
|
|
2922
3136
|
}, []);
|
|
2923
|
-
const dismiss =
|
|
3137
|
+
const dismiss = useCallback11((id) => {
|
|
2924
3138
|
setItems((prev) => prev.filter((t) => t.id !== id));
|
|
2925
3139
|
}, []);
|
|
2926
3140
|
return { items, show, dismiss };
|
|
@@ -2928,20 +3142,20 @@ function useToast() {
|
|
|
2928
3142
|
|
|
2929
3143
|
// src/RouteBoundary.tsx
|
|
2930
3144
|
import { Routes as Routes2, Route as Route2 } from "react-router-dom";
|
|
2931
|
-
import { jsx as
|
|
3145
|
+
import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2932
3146
|
function RouteBoundary({ children }) {
|
|
2933
3147
|
return /* @__PURE__ */ jsxs18(Routes2, { children: [
|
|
2934
3148
|
children,
|
|
2935
|
-
/* @__PURE__ */
|
|
3149
|
+
/* @__PURE__ */ jsx27(Route2, { path: "*", element: /* @__PURE__ */ jsx27(DefaultNotFound, {}) })
|
|
2936
3150
|
] });
|
|
2937
3151
|
}
|
|
2938
3152
|
function DefaultNotFound() {
|
|
2939
|
-
return /* @__PURE__ */
|
|
3153
|
+
return /* @__PURE__ */ jsx27("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
|
|
2940
3154
|
}
|
|
2941
3155
|
|
|
2942
3156
|
// src/PreAuthShell.tsx
|
|
2943
3157
|
import { BrowserRouter as BrowserRouter2, MemoryRouter as MemoryRouter2, Routes as Routes3 } from "react-router-dom";
|
|
2944
|
-
import { jsx as
|
|
3158
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
2945
3159
|
function PreAuthShell({
|
|
2946
3160
|
basename,
|
|
2947
3161
|
testRouter,
|
|
@@ -2949,14 +3163,14 @@ function PreAuthShell({
|
|
|
2949
3163
|
children
|
|
2950
3164
|
}) {
|
|
2951
3165
|
if (testRouter === "memory") {
|
|
2952
|
-
return /* @__PURE__ */
|
|
3166
|
+
return /* @__PURE__ */ jsx28(MemoryRouter2, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ jsx28(Routes3, { children }) });
|
|
2953
3167
|
}
|
|
2954
|
-
return /* @__PURE__ */
|
|
3168
|
+
return /* @__PURE__ */ jsx28(BrowserRouter2, { basename, children: /* @__PURE__ */ jsx28(Routes3, { children }) });
|
|
2955
3169
|
}
|
|
2956
3170
|
|
|
2957
3171
|
// src/OnboardingFlow.tsx
|
|
2958
|
-
import { useCallback as
|
|
2959
|
-
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";
|
|
2960
3174
|
|
|
2961
3175
|
// src/hooks/useOnboardingStep.ts
|
|
2962
3176
|
import { createContext as createContext3, useContext as useContext4 } from "react";
|
|
@@ -2972,7 +3186,7 @@ function useOnboardingStep() {
|
|
|
2972
3186
|
}
|
|
2973
3187
|
|
|
2974
3188
|
// src/OnboardingFlow.tsx
|
|
2975
|
-
import { jsx as
|
|
3189
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
2976
3190
|
var isFilled = (v) => v != null && v !== "";
|
|
2977
3191
|
var CURRENT_STEP_FIELD = "currentStep";
|
|
2978
3192
|
function readPersistedStepIdx(draft) {
|
|
@@ -2986,11 +3200,11 @@ function OnboardingFlow({
|
|
|
2986
3200
|
persistKey
|
|
2987
3201
|
}) {
|
|
2988
3202
|
const [draft, setDraft, status] = usePersistedState3(persistKey, {});
|
|
2989
|
-
const draftRef =
|
|
3203
|
+
const draftRef = useRef6(draft);
|
|
2990
3204
|
draftRef.current = draft;
|
|
2991
3205
|
const idx = readPersistedStepIdx(draft);
|
|
2992
3206
|
const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
|
|
2993
|
-
const setIdx =
|
|
3207
|
+
const setIdx = useCallback12(
|
|
2994
3208
|
(n) => {
|
|
2995
3209
|
setDraft((prev) => {
|
|
2996
3210
|
const prevIdx = readPersistedStepIdx(prev);
|
|
@@ -3000,7 +3214,7 @@ function OnboardingFlow({
|
|
|
3000
3214
|
},
|
|
3001
3215
|
[setDraft]
|
|
3002
3216
|
);
|
|
3003
|
-
const setValue =
|
|
3217
|
+
const setValue = useCallback12(
|
|
3004
3218
|
(patch) => {
|
|
3005
3219
|
draftRef.current = { ...draftRef.current, ...patch };
|
|
3006
3220
|
setDraft((prev) => ({ ...prev, ...patch }));
|
|
@@ -3008,7 +3222,7 @@ function OnboardingFlow({
|
|
|
3008
3222
|
[setDraft]
|
|
3009
3223
|
);
|
|
3010
3224
|
const step = steps[clampedIdx];
|
|
3011
|
-
const hookCtx =
|
|
3225
|
+
const hookCtx = useHook18();
|
|
3012
3226
|
const track2 = typeof hookCtx.track === "function" ? hookCtx.track : void 0;
|
|
3013
3227
|
useEffect13(() => {
|
|
3014
3228
|
if (status.loading) return;
|
|
@@ -3024,7 +3238,7 @@ function OnboardingFlow({
|
|
|
3024
3238
|
() => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
|
|
3025
3239
|
[draft, step]
|
|
3026
3240
|
);
|
|
3027
|
-
const next =
|
|
3241
|
+
const next = useCallback12(() => {
|
|
3028
3242
|
if (!step) return;
|
|
3029
3243
|
const current = draftRef.current;
|
|
3030
3244
|
const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
|
|
@@ -3035,7 +3249,7 @@ function OnboardingFlow({
|
|
|
3035
3249
|
setIdx(clampedIdx + 1);
|
|
3036
3250
|
}
|
|
3037
3251
|
}, [clampedIdx, onComplete, step, steps.length, setIdx]);
|
|
3038
|
-
const prevStep =
|
|
3252
|
+
const prevStep = useCallback12(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
|
|
3039
3253
|
const ctx = useMemo8(
|
|
3040
3254
|
() => ({
|
|
3041
3255
|
stepIndex: clampedIdx,
|
|
@@ -3062,7 +3276,7 @@ function OnboardingFlow({
|
|
|
3062
3276
|
`[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
|
|
3063
3277
|
);
|
|
3064
3278
|
}
|
|
3065
|
-
return /* @__PURE__ */
|
|
3279
|
+
return /* @__PURE__ */ jsx29(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx29(Screen, {}) });
|
|
3066
3280
|
}
|
|
3067
3281
|
|
|
3068
3282
|
// src/hooks/useFeature.ts
|
|
@@ -3070,11 +3284,298 @@ function useFeature(name) {
|
|
|
3070
3284
|
const config = useAppConfig();
|
|
3071
3285
|
return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
|
|
3072
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: [cardClassName, active ? cardSelectedClassName : ""].filter(Boolean).join(" "),
|
|
3368
|
+
children: [
|
|
3369
|
+
/* @__PURE__ */ jsx32("strong", { children: formatBRL(mainCents) }),
|
|
3370
|
+
/* @__PURE__ */ jsx32("span", { children: suffix }),
|
|
3371
|
+
/* @__PURE__ */ jsx32("span", { children: label }),
|
|
3372
|
+
anchorCents != null && anchorCents > mainCents ? /* @__PURE__ */ jsx32("span", { className: anchorClassName, 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.initialLoadComplete,
|
|
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
|
+
}
|
|
3073
3573
|
export {
|
|
3074
3574
|
AppConfigProvider,
|
|
3075
3575
|
AppConfigSchema,
|
|
3076
3576
|
AppRoot,
|
|
3077
3577
|
DeepLinkHandler,
|
|
3578
|
+
DevSkipOnboardingFab,
|
|
3078
3579
|
EmptyState,
|
|
3079
3580
|
ErrorBoundary,
|
|
3080
3581
|
I18nProvider,
|
|
@@ -3084,6 +3585,11 @@ export {
|
|
|
3084
3585
|
LoadingState,
|
|
3085
3586
|
OnboardingFlow,
|
|
3086
3587
|
PaymentReturnHandler,
|
|
3588
|
+
Paywall,
|
|
3589
|
+
PaywallCta,
|
|
3590
|
+
PaywallCyclePicker,
|
|
3591
|
+
PaywallMethodContent,
|
|
3592
|
+
PaywallMethodTabs,
|
|
3087
3593
|
PersistenceRegistry,
|
|
3088
3594
|
PreAuthShell,
|
|
3089
3595
|
PushPrompt2 as PushPrompt,
|
|
@@ -3098,10 +3604,12 @@ export {
|
|
|
3098
3604
|
detectStandalone,
|
|
3099
3605
|
discountPercent,
|
|
3100
3606
|
formatBRL,
|
|
3607
|
+
isDevToolsEnabled,
|
|
3101
3608
|
monthlyFromYearly,
|
|
3102
3609
|
parseAppConfig,
|
|
3103
3610
|
shouldBlockInstall,
|
|
3104
3611
|
shouldShowPermanentOption,
|
|
3612
|
+
skipOnboarding,
|
|
3105
3613
|
useAppConfig,
|
|
3106
3614
|
useAuth,
|
|
3107
3615
|
useAuthPrimitives,
|