@hook-sdk/template 0.28.2 → 0.28.5

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.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 useHook7 } from "@hook-sdk/sdk";
4
+ import { useHook as useHook8 } from "@hook-sdk/sdk";
5
5
 
6
6
  // src/config/AppConfigContext.tsx
7
7
  import { createContext, useContext } from "react";
@@ -2033,9 +2033,53 @@ function SessionExpiredBanner() {
2033
2033
  ] });
2034
2034
  }
2035
2035
 
2036
+ // src/internal/EmailVerifyBanner.tsx
2037
+ import { useState as useState5 } from "react";
2038
+ import { useHook as useHook5 } from "@hook-sdk/sdk";
2039
+ import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2040
+ function EmailVerifyBanner() {
2041
+ const { user, auth } = useHook5();
2042
+ const [sending, setSending] = useState5(false);
2043
+ const [sent, setSent] = useState5(false);
2044
+ if (!user || user.emailVerified) return null;
2045
+ async function handleResend() {
2046
+ if (sending || sent) return;
2047
+ setSending(true);
2048
+ try {
2049
+ await auth.resendVerify();
2050
+ setSent(true);
2051
+ } catch {
2052
+ } finally {
2053
+ setSending(false);
2054
+ }
2055
+ }
2056
+ const label = sent ? "Enviado!" : sending ? "Enviando..." : "Reenviar link";
2057
+ return /* @__PURE__ */ jsxs12(
2058
+ "div",
2059
+ {
2060
+ role: "status",
2061
+ "data-testid": "email-verify-banner",
2062
+ className: "sticky top-0 inset-x-0 z-50 bg-yellow-100 text-yellow-900 border-b border-yellow-200 px-4 py-2 flex items-center justify-between gap-3 text-sm",
2063
+ children: [
2064
+ /* @__PURE__ */ jsx18("span", { children: "Confirma teu e-mail pra liberar tudo." }),
2065
+ /* @__PURE__ */ jsx18(
2066
+ "button",
2067
+ {
2068
+ type: "button",
2069
+ onClick: handleResend,
2070
+ disabled: sending || sent,
2071
+ className: "px-3 py-1 rounded text-xs font-medium bg-yellow-900 text-yellow-50 hover:bg-yellow-800 disabled:opacity-60 disabled:cursor-not-allowed",
2072
+ children: label
2073
+ }
2074
+ )
2075
+ ]
2076
+ }
2077
+ );
2078
+ }
2079
+
2036
2080
  // src/defaults/ErrorBoundary.tsx
2037
2081
  import { Component } from "react";
2038
- import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2082
+ import { Fragment as Fragment4, jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
2039
2083
  var ErrorBoundary = class extends Component {
2040
2084
  state = { error: null };
2041
2085
  static getDerivedStateFromError(error) {
@@ -2053,12 +2097,12 @@ var ErrorBoundary = class extends Component {
2053
2097
  }
2054
2098
  render() {
2055
2099
  if (this.state.error) {
2056
- return this.props.fallback ?? /* @__PURE__ */ jsxs12("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
2057
- /* @__PURE__ */ jsx18("h2", { children: "Algo deu errado" }),
2058
- /* @__PURE__ */ jsx18("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
2100
+ return this.props.fallback ?? /* @__PURE__ */ jsxs13("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
2101
+ /* @__PURE__ */ jsx19("h2", { children: "Algo deu errado" }),
2102
+ /* @__PURE__ */ jsx19("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
2059
2103
  ] });
2060
2104
  }
2061
- return /* @__PURE__ */ jsx18(Fragment4, { children: this.props.children });
2105
+ return /* @__PURE__ */ jsx19(Fragment4, { children: this.props.children });
2062
2106
  }
2063
2107
  };
2064
2108
 
@@ -2067,7 +2111,7 @@ import { useEffect as useEffect7 } from "react";
2067
2111
  import i18n from "i18next";
2068
2112
  import { I18nextProvider, initReactI18next } from "react-i18next";
2069
2113
  import { usePersistedState } from "@hook-sdk/sdk";
2070
- import { jsx as jsx19 } from "react/jsx-runtime";
2114
+ import { jsx as jsx20 } from "react/jsx-runtime";
2071
2115
  function ensureInitialized(defaultLocale, supportedLocales, resources, initialLocale) {
2072
2116
  if (i18n.isInitialized) return;
2073
2117
  i18n.use(initReactI18next).init({
@@ -2096,7 +2140,7 @@ function I18nProvider({
2096
2140
  i18n.changeLanguage(userLocale);
2097
2141
  }
2098
2142
  }, [userLocale]);
2099
- return /* @__PURE__ */ jsx19(I18nextProvider, { i18n, children });
2143
+ return /* @__PURE__ */ jsx20(I18nextProvider, { i18n, children });
2100
2144
  }
2101
2145
 
2102
2146
  // src/dev/env.ts
@@ -2107,9 +2151,9 @@ function isDevToolsEnabled() {
2107
2151
  }
2108
2152
 
2109
2153
  // src/dev/DevSkipOnboardingFab.tsx
2110
- import { useCallback as useCallback3, useRef as useRef4, useState as useState5 } from "react";
2111
- import { useHook as useHook5, usePersistedState as usePersistedState2 } from "@hook-sdk/sdk";
2112
- import { jsx as jsx20 } from "react/jsx-runtime";
2154
+ import { useCallback as useCallback3, useRef as useRef4, useState as useState6 } from "react";
2155
+ import { useHook as useHook6, usePersistedState as usePersistedState2 } from "@hook-sdk/sdk";
2156
+ import { jsx as jsx21 } from "react/jsx-runtime";
2113
2157
  var STORAGE_KEY = "hook_dev_skip_email";
2114
2158
  var TEST_EMAIL_DOMAIN = "@hook.test";
2115
2159
  var TEST_PASSWORD = "SkipTest!2026";
@@ -2176,10 +2220,10 @@ var STYLES = {
2176
2220
  };
2177
2221
  var CONFIRM_TIMEOUT_MS = 3e3;
2178
2222
  function DevSkipOnboardingFab({ defaults }) {
2179
- const hook = useHook5();
2223
+ const hook = useHook6();
2180
2224
  const { slug } = useAppConfig();
2181
- const [state, setState] = useState5("idle");
2182
- const [errorMsg, setErrorMsg] = useState5(null);
2225
+ const [state, setState] = useState6("idle");
2226
+ const [errorMsg, setErrorMsg] = useState6(null);
2183
2227
  const timerRef = useRef4(null);
2184
2228
  const isAuthed = hook.authStatus === "authenticated";
2185
2229
  const [onboarding] = usePersistedState2(
@@ -2224,7 +2268,7 @@ function DevSkipOnboardingFab({ defaults }) {
2224
2268
  ...state === "confirm" || state === "error" ? STYLES.confirm : {},
2225
2269
  ...state === "busy" ? STYLES.busy : {}
2226
2270
  };
2227
- return /* @__PURE__ */ jsx20(
2271
+ return /* @__PURE__ */ jsx21(
2228
2272
  "button",
2229
2273
  {
2230
2274
  type: "button",
@@ -2239,20 +2283,20 @@ function DevSkipOnboardingFab({ defaults }) {
2239
2283
  }
2240
2284
 
2241
2285
  // src/internal/PaymentReturnHandler.tsx
2242
- import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef5, useState as useState6 } from "react";
2243
- import { useHook as useHook6 } from "@hook-sdk/sdk";
2244
- import { Fragment as Fragment5, jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
2286
+ import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef5, useState as useState7 } from "react";
2287
+ import { useHook as useHook7 } from "@hook-sdk/sdk";
2288
+ import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
2245
2289
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2246
2290
  var MAX_CYCLES = 3;
2247
2291
  var SUPPORT_MAILTO = "mailto:suporte@usehook.net?subject=Pagamento%20pendente";
2248
2292
  function PaymentReturnHandler({ children }) {
2249
- const { subscription, track: track2 } = useHook6();
2293
+ const { subscription, track: track2 } = useHook7();
2250
2294
  const subRef = useRef5(subscription);
2251
2295
  subRef.current = subscription;
2252
2296
  const runIdRef = useRef5(0);
2253
2297
  const cyclesRef = useRef5(0);
2254
2298
  const startMsRef = useRef5(0);
2255
- const [state, setState] = useState6("idle");
2299
+ const [state, setState] = useState7("idle");
2256
2300
  const runPoll = useCallback4(() => {
2257
2301
  const runId = ++runIdRef.current;
2258
2302
  const isFirstRun = cyclesRef.current === 0;
@@ -2318,19 +2362,19 @@ function PaymentReturnHandler({ children }) {
2318
2362
  window.location.href = cleanUrl.toString();
2319
2363
  }, []);
2320
2364
  if (state === "confirming") {
2321
- return /* @__PURE__ */ jsx21("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
2365
+ return /* @__PURE__ */ jsx22("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
2322
2366
  }
2323
2367
  if (state === "waiting") {
2324
- return /* @__PURE__ */ jsx21("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs13("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
2325
- /* @__PURE__ */ jsx21("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
2326
- /* @__PURE__ */ jsx21("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
2368
+ return /* @__PURE__ */ jsx22("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs14("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
2369
+ /* @__PURE__ */ jsx22("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
2370
+ /* @__PURE__ */ jsx22("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
2327
2371
  ] }) });
2328
2372
  }
2329
2373
  if (state === "timeout") {
2330
- return /* @__PURE__ */ jsx21("div", { role: "alert", "aria-live": "assertive", style: overlayStyle2, children: /* @__PURE__ */ jsxs13("div", { style: { maxWidth: 360, textAlign: "center", lineHeight: 1.5 }, children: [
2331
- /* @__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." }),
2332
- /* @__PURE__ */ jsxs13("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2333
- /* @__PURE__ */ jsx21(
2374
+ return /* @__PURE__ */ jsx22("div", { role: "alert", "aria-live": "assertive", style: overlayStyle2, children: /* @__PURE__ */ jsxs14("div", { style: { maxWidth: 360, textAlign: "center", lineHeight: 1.5 }, children: [
2375
+ /* @__PURE__ */ jsx22("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." }),
2376
+ /* @__PURE__ */ jsxs14("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2377
+ /* @__PURE__ */ jsx22(
2334
2378
  "button",
2335
2379
  {
2336
2380
  type: "button",
@@ -2343,7 +2387,7 @@ function PaymentReturnHandler({ children }) {
2343
2387
  children: "Tentar de novo"
2344
2388
  }
2345
2389
  ),
2346
- /* @__PURE__ */ jsx21(
2390
+ /* @__PURE__ */ jsx22(
2347
2391
  "button",
2348
2392
  {
2349
2393
  type: "button",
@@ -2353,7 +2397,7 @@ function PaymentReturnHandler({ children }) {
2353
2397
  children: "Voltar pro app"
2354
2398
  }
2355
2399
  ),
2356
- /* @__PURE__ */ jsx21(
2400
+ /* @__PURE__ */ jsx22(
2357
2401
  "a",
2358
2402
  {
2359
2403
  href: SUPPORT_MAILTO,
@@ -2365,7 +2409,7 @@ function PaymentReturnHandler({ children }) {
2365
2409
  ] })
2366
2410
  ] }) });
2367
2411
  }
2368
- return /* @__PURE__ */ jsx21(Fragment5, { children });
2412
+ return /* @__PURE__ */ jsx22(Fragment5, { children });
2369
2413
  }
2370
2414
  var overlayStyle2 = {
2371
2415
  position: "fixed",
@@ -2404,7 +2448,7 @@ var linkStyle = {
2404
2448
  };
2405
2449
 
2406
2450
  // src/AppRoot.tsx
2407
- import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
2451
+ import { Fragment as Fragment6, jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
2408
2452
  function buildLegacyConfigShim(config) {
2409
2453
  const paywall = config.paywall;
2410
2454
  const isFree = paywall.mode === "free";
@@ -2483,14 +2527,17 @@ function AppRoot(props) {
2483
2527
  const basename = `/app/${config.slug}`;
2484
2528
  const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
2485
2529
  const position = config.install_prompt?.position ?? "post-paywall";
2486
- const subscriptionGated = /* @__PURE__ */ jsx22(SubscriptionGate, { Paywall: Paywall2 ?? FallbackPaywall, children: position === "post-paywall" ? /* @__PURE__ */ jsxs14(InstallGate, { position: "post-paywall", children: [
2487
- children,
2488
- /* @__PURE__ */ jsx22(PushPrompt, {})
2489
- ] }) : /* @__PURE__ */ jsxs14(Fragment6, { children: [
2490
- children,
2491
- /* @__PURE__ */ jsx22(PushPrompt, {})
2492
- ] }) });
2493
- const authGated = /* @__PURE__ */ jsx22(
2530
+ const subscriptionGated = /* @__PURE__ */ jsxs15(SubscriptionGate, { Paywall: Paywall2 ?? FallbackPaywall, children: [
2531
+ /* @__PURE__ */ jsx23(EmailVerifyBanner, {}),
2532
+ position === "post-paywall" ? /* @__PURE__ */ jsxs15(InstallGate, { position: "post-paywall", children: [
2533
+ children,
2534
+ /* @__PURE__ */ jsx23(PushPrompt, {})
2535
+ ] }) : /* @__PURE__ */ jsxs15(Fragment6, { children: [
2536
+ children,
2537
+ /* @__PURE__ */ jsx23(PushPrompt, {})
2538
+ ] })
2539
+ ] });
2540
+ const authGated = /* @__PURE__ */ jsx23(
2494
2541
  AuthGated,
2495
2542
  {
2496
2543
  config,
@@ -2505,13 +2552,13 @@ function AppRoot(props) {
2505
2552
  children: subscriptionGated
2506
2553
  }
2507
2554
  );
2508
- const routedTree = /* @__PURE__ */ jsxs14(Router, { ...routerProps, children: [
2509
- /* @__PURE__ */ jsx22(DeepLinkHandler, { deepLinks: config.deepLinks }),
2510
- /* @__PURE__ */ jsx22(SessionExpiredBanner, {}),
2511
- position === "pre-auth" ? /* @__PURE__ */ jsx22(InstallGate, { position: "pre-auth", children: authGated }) : authGated,
2512
- isDevToolsEnabled() && devSkipOnboarding ? /* @__PURE__ */ jsx22(DevSkipOnboardingFab, { defaults: devSkipOnboarding.defaults }) : null
2555
+ const routedTree = /* @__PURE__ */ jsxs15(Router, { ...routerProps, children: [
2556
+ /* @__PURE__ */ jsx23(DeepLinkHandler, { deepLinks: config.deepLinks }),
2557
+ /* @__PURE__ */ jsx23(SessionExpiredBanner, {}),
2558
+ position === "pre-auth" ? /* @__PURE__ */ jsx23(InstallGate, { position: "pre-auth", children: authGated }) : authGated,
2559
+ isDevToolsEnabled() && devSkipOnboarding ? /* @__PURE__ */ jsx23(DevSkipOnboardingFab, { defaults: devSkipOnboarding.defaults }) : null
2513
2560
  ] });
2514
- 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(
2561
+ return /* @__PURE__ */ jsx23(ErrorBoundary, { children: /* @__PURE__ */ jsx23(AppConfigProvider, { config, children: /* @__PURE__ */ jsx23(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ jsx23(ThemeProvider, { children: /* @__PURE__ */ jsx23(PersistenceRegistry, { config: config.persistedKeys, children: config.i18n ? /* @__PURE__ */ jsx23(
2515
2562
  I18nProvider,
2516
2563
  {
2517
2564
  defaultLocale: config.i18n.defaultLocale,
@@ -2531,46 +2578,46 @@ function AuthGated({
2531
2578
  EmailVerify,
2532
2579
  PreAuthFlow
2533
2580
  }) {
2534
- const { authStatus } = useHook7();
2581
+ const { authStatus } = useHook8();
2535
2582
  if (authStatus === "loading") return null;
2536
2583
  if (authStatus !== "authenticated") {
2537
2584
  if (config.authFlow.signupMode === "pay_first" && PreAuthFlow) {
2538
- return /* @__PURE__ */ jsxs14(Routes, { children: [
2539
- /* @__PURE__ */ jsx22(Route, { path: "/signin", element: /* @__PURE__ */ jsx22(Login, {}) }),
2540
- /* @__PURE__ */ jsx22(Route, { path: "/forgot", element: /* @__PURE__ */ jsx22(Forgot, {}) }),
2541
- /* @__PURE__ */ jsx22(Route, { path: "/reset", element: /* @__PURE__ */ jsx22(Reset, {}) }),
2542
- EmailVerify ? /* @__PURE__ */ jsx22(Route, { path: "/verify", element: /* @__PURE__ */ jsx22(EmailVerify, {}) }) : null,
2543
- /* @__PURE__ */ jsx22(Route, { path: "/*", element: /* @__PURE__ */ jsx22(PreAuthFlow, {}) })
2585
+ return /* @__PURE__ */ jsxs15(Routes, { children: [
2586
+ /* @__PURE__ */ jsx23(Route, { path: "/signin", element: /* @__PURE__ */ jsx23(Login, {}) }),
2587
+ /* @__PURE__ */ jsx23(Route, { path: "/forgot", element: /* @__PURE__ */ jsx23(Forgot, {}) }),
2588
+ /* @__PURE__ */ jsx23(Route, { path: "/reset", element: /* @__PURE__ */ jsx23(Reset, {}) }),
2589
+ EmailVerify ? /* @__PURE__ */ jsx23(Route, { path: "/verify", element: /* @__PURE__ */ jsx23(EmailVerify, {}) }) : null,
2590
+ /* @__PURE__ */ jsx23(Route, { path: "/*", element: /* @__PURE__ */ jsx23(PreAuthFlow, {}) })
2544
2591
  ] });
2545
2592
  }
2546
2593
  if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
2547
- return /* @__PURE__ */ jsxs14(Routes, { children: [
2548
- /* @__PURE__ */ jsx22(Route, { path: "/signin", element: /* @__PURE__ */ jsx22(Login, {}) }),
2549
- /* @__PURE__ */ jsx22(Route, { path: "/signup", element: /* @__PURE__ */ jsx22(Signup, {}) }),
2550
- /* @__PURE__ */ jsx22(Route, { path: "/forgot", element: /* @__PURE__ */ jsx22(Forgot, {}) }),
2551
- /* @__PURE__ */ jsx22(Route, { path: "/reset", element: /* @__PURE__ */ jsx22(Reset, {}) }),
2552
- EmailVerify ? /* @__PURE__ */ jsx22(Route, { path: "/verify", element: /* @__PURE__ */ jsx22(EmailVerify, {}) }) : null,
2553
- /* @__PURE__ */ jsx22(Route, { path: "/*", element: /* @__PURE__ */ jsx22(PreAuthFlow, {}) })
2594
+ return /* @__PURE__ */ jsxs15(Routes, { children: [
2595
+ /* @__PURE__ */ jsx23(Route, { path: "/signin", element: /* @__PURE__ */ jsx23(Login, {}) }),
2596
+ /* @__PURE__ */ jsx23(Route, { path: "/signup", element: /* @__PURE__ */ jsx23(Signup, {}) }),
2597
+ /* @__PURE__ */ jsx23(Route, { path: "/forgot", element: /* @__PURE__ */ jsx23(Forgot, {}) }),
2598
+ /* @__PURE__ */ jsx23(Route, { path: "/reset", element: /* @__PURE__ */ jsx23(Reset, {}) }),
2599
+ EmailVerify ? /* @__PURE__ */ jsx23(Route, { path: "/verify", element: /* @__PURE__ */ jsx23(EmailVerify, {}) }) : null,
2600
+ /* @__PURE__ */ jsx23(Route, { path: "/*", element: /* @__PURE__ */ jsx23(PreAuthFlow, {}) })
2554
2601
  ] });
2555
2602
  }
2556
- return /* @__PURE__ */ jsxs14(Routes, { children: [
2557
- /* @__PURE__ */ jsx22(Route, { path: "/", element: /* @__PURE__ */ jsx22(Login, {}) }),
2558
- /* @__PURE__ */ jsx22(Route, { path: "/signup", element: /* @__PURE__ */ jsx22(Signup, {}) }),
2559
- /* @__PURE__ */ jsx22(Route, { path: "/forgot", element: /* @__PURE__ */ jsx22(Forgot, {}) }),
2560
- /* @__PURE__ */ jsx22(Route, { path: "/reset", element: /* @__PURE__ */ jsx22(Reset, {}) }),
2561
- EmailVerify ? /* @__PURE__ */ jsx22(Route, { path: "/verify", element: /* @__PURE__ */ jsx22(EmailVerify, {}) }) : null,
2562
- /* @__PURE__ */ jsx22(Route, { path: "*", element: /* @__PURE__ */ jsx22(Navigate, { to: "/", replace: true }) })
2603
+ return /* @__PURE__ */ jsxs15(Routes, { children: [
2604
+ /* @__PURE__ */ jsx23(Route, { path: "/", element: /* @__PURE__ */ jsx23(Login, {}) }),
2605
+ /* @__PURE__ */ jsx23(Route, { path: "/signup", element: /* @__PURE__ */ jsx23(Signup, {}) }),
2606
+ /* @__PURE__ */ jsx23(Route, { path: "/forgot", element: /* @__PURE__ */ jsx23(Forgot, {}) }),
2607
+ /* @__PURE__ */ jsx23(Route, { path: "/reset", element: /* @__PURE__ */ jsx23(Reset, {}) }),
2608
+ EmailVerify ? /* @__PURE__ */ jsx23(Route, { path: "/verify", element: /* @__PURE__ */ jsx23(EmailVerify, {}) }) : null,
2609
+ /* @__PURE__ */ jsx23(Route, { path: "*", element: /* @__PURE__ */ jsx23(Navigate, { to: "/", replace: true }) })
2563
2610
  ] });
2564
2611
  }
2565
- return /* @__PURE__ */ jsx22(Fragment6, { children });
2612
+ return /* @__PURE__ */ jsx23(Fragment6, { children });
2566
2613
  }
2567
2614
  function FallbackPaywall() {
2568
2615
  return null;
2569
2616
  }
2570
2617
 
2571
2618
  // src/hooks/usePush.ts
2572
- import { useCallback as useCallback5, useEffect as useEffect9, useState as useState7 } from "react";
2573
- import { useHook as useHook8 } from "@hook-sdk/sdk";
2619
+ import { useCallback as useCallback5, useEffect as useEffect9, useState as useState8 } from "react";
2620
+ import { useHook as useHook9 } from "@hook-sdk/sdk";
2574
2621
  var DISMISS_STORAGE_KEY = "push:dismissed-until";
2575
2622
  var DISMISS_TTL_MS2 = 7 * 24 * 60 * 60 * 1e3;
2576
2623
  function detectIosNeedsInstall() {
@@ -2614,8 +2661,8 @@ function deriveState(push) {
2614
2661
  return { kind: "prompt" };
2615
2662
  }
2616
2663
  function usePush() {
2617
- const { push } = useHook8();
2618
- const [state, setState] = useState7(() => deriveState(push));
2664
+ const { push } = useHook9();
2665
+ const [state, setState] = useState8(() => deriveState(push));
2619
2666
  useEffect9(() => {
2620
2667
  setState(deriveState(push));
2621
2668
  }, [push]);
@@ -2653,27 +2700,27 @@ function usePush() {
2653
2700
  }
2654
2701
 
2655
2702
  // src/components/PushPrompt.tsx
2656
- import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
2703
+ import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
2657
2704
  function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
2658
2705
  const { state, subscribe } = usePush();
2659
2706
  if (state.kind === "denied" || state.kind === "dismissed" || state.kind === "subscribed") {
2660
2707
  return null;
2661
2708
  }
2662
2709
  if (state.kind === "ios_needs_install") {
2663
- return /* @__PURE__ */ jsxs15("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2664
- /* @__PURE__ */ jsx23("h3", { children: texts.iosInstallTitle }),
2665
- /* @__PURE__ */ jsx23("p", { children: texts.iosInstallBody }),
2666
- onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx23("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2710
+ return /* @__PURE__ */ jsxs16("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2711
+ /* @__PURE__ */ jsx24("h3", { children: texts.iosInstallTitle }),
2712
+ /* @__PURE__ */ jsx24("p", { children: texts.iosInstallBody }),
2713
+ onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx24("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2667
2714
  ] });
2668
2715
  }
2669
2716
  if (state.kind === "unsupported") {
2670
- return /* @__PURE__ */ jsx23("div", { className, role: "region", children: /* @__PURE__ */ jsx23("p", { children: texts.unsupportedBody }) });
2717
+ return /* @__PURE__ */ jsx24("div", { className, role: "region", children: /* @__PURE__ */ jsx24("p", { children: texts.unsupportedBody }) });
2671
2718
  }
2672
2719
  if (state.kind === "error") {
2673
- return /* @__PURE__ */ jsx23("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx23("p", { children: state.message }) });
2720
+ return /* @__PURE__ */ jsx24("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx24("p", { children: state.message }) });
2674
2721
  }
2675
- return /* @__PURE__ */ jsxs15("div", { className, role: "region", children: [
2676
- /* @__PURE__ */ jsx23(
2722
+ return /* @__PURE__ */ jsxs16("div", { className, role: "region", children: [
2723
+ /* @__PURE__ */ jsx24(
2677
2724
  "button",
2678
2725
  {
2679
2726
  type: "button",
@@ -2687,13 +2734,13 @@ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, clas
2687
2734
  children: texts.cta
2688
2735
  }
2689
2736
  ),
2690
- onDeclined && /* @__PURE__ */ jsx23("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2737
+ onDeclined && /* @__PURE__ */ jsx24("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2691
2738
  ] });
2692
2739
  }
2693
2740
 
2694
2741
  // src/components/LanguageSwitcher.tsx
2695
2742
  import { usePersistedState as usePersistedState3 } from "@hook-sdk/sdk";
2696
- import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
2743
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
2697
2744
  function LanguageSwitcher({ id, className, label = "Language" }) {
2698
2745
  const config = useAppConfig();
2699
2746
  const i18nConfig = config.i18n;
@@ -2702,50 +2749,50 @@ function LanguageSwitcher({ id, className, label = "Language" }) {
2702
2749
  i18nConfig?.defaultLocale ?? "en-US"
2703
2750
  );
2704
2751
  if (!i18nConfig) return null;
2705
- return /* @__PURE__ */ jsxs16("label", { className, children: [
2706
- label ? /* @__PURE__ */ jsx24("span", { children: label }) : null,
2707
- /* @__PURE__ */ jsx24(
2752
+ return /* @__PURE__ */ jsxs17("label", { className, children: [
2753
+ label ? /* @__PURE__ */ jsx25("span", { children: label }) : null,
2754
+ /* @__PURE__ */ jsx25(
2708
2755
  "select",
2709
2756
  {
2710
2757
  id,
2711
2758
  value: userLocale,
2712
2759
  onChange: (e) => setUserLocale(e.target.value),
2713
2760
  "data-testid": "language-switcher",
2714
- children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */ jsx24("option", { value: loc, children: loc }, loc))
2761
+ children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */ jsx25("option", { value: loc, children: loc }, loc))
2715
2762
  }
2716
2763
  )
2717
2764
  ] });
2718
2765
  }
2719
2766
 
2720
2767
  // src/defaults/LoadingState.tsx
2721
- import { jsx as jsx25 } from "react/jsx-runtime";
2768
+ import { jsx as jsx26 } from "react/jsx-runtime";
2722
2769
  function LoadingState({ message }) {
2723
- return /* @__PURE__ */ jsx25("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx25("span", { children: message ?? "Carregando..." }) });
2770
+ return /* @__PURE__ */ jsx26("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx26("span", { children: message ?? "Carregando..." }) });
2724
2771
  }
2725
2772
 
2726
2773
  // src/defaults/EmptyState.tsx
2727
- import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
2774
+ import { jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
2728
2775
  function EmptyState({ title, description, action }) {
2729
- return /* @__PURE__ */ jsxs17("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2730
- /* @__PURE__ */ jsx26("h2", { style: { marginBottom: 8 }, children: title }),
2731
- description && /* @__PURE__ */ jsx26("p", { style: { opacity: 0.7 }, children: description }),
2732
- action && /* @__PURE__ */ jsx26("div", { style: { marginTop: 16 }, children: action })
2776
+ return /* @__PURE__ */ jsxs18("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2777
+ /* @__PURE__ */ jsx27("h2", { style: { marginBottom: 8 }, children: title }),
2778
+ description && /* @__PURE__ */ jsx27("p", { style: { opacity: 0.7 }, children: description }),
2779
+ action && /* @__PURE__ */ jsx27("div", { style: { marginTop: 16 }, children: action })
2733
2780
  ] });
2734
2781
  }
2735
2782
 
2736
2783
  // src/defaults/CheckoutPageDefault.tsx
2737
- import { useEffect as useEffect11, useMemo as useMemo5, useState as useState9 } from "react";
2784
+ import { useEffect as useEffect13, useMemo as useMemo6, useState as useState11 } from "react";
2738
2785
  import { useNavigate as useNavigate2 } from "react-router-dom";
2739
2786
 
2740
2787
  // src/hooks/useCheckoutForm.ts
2741
- import { useCallback as useCallback6, useEffect as useEffect10, useMemo as useMemo4, useRef as useRef6, useState as useState8 } from "react";
2788
+ import { useCallback as useCallback6, useEffect as useEffect10, useMemo as useMemo4, useRef as useRef6, useState as useState9 } from "react";
2742
2789
  import {
2743
- useHook as useHook9,
2790
+ useHook as useHook10,
2744
2791
  EmailTakenError
2745
2792
  } from "@hook-sdk/sdk";
2746
2793
 
2747
2794
  // src/errors.ts
2748
- import { SdkError, SdkAuthError, SdkRateLimitError } from "@hook-sdk/sdk";
2795
+ import { SdkError, SdkAuthError, SdkRateLimitError, SdkValidationError } from "@hook-sdk/sdk";
2749
2796
  function mapSdkError(err) {
2750
2797
  if (err instanceof SdkRateLimitError) {
2751
2798
  return {
@@ -2764,6 +2811,9 @@ function mapSdkError(err) {
2764
2811
  }
2765
2812
  return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
2766
2813
  }
2814
+ if (err instanceof SdkValidationError && err.code === "auth.email_taken") {
2815
+ return { code: "email_taken", message: "Esse e-mail j\xE1 tem conta." };
2816
+ }
2767
2817
  if (err instanceof SdkError && err.httpStatus === 0) {
2768
2818
  return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2769
2819
  }
@@ -2778,15 +2828,15 @@ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2778
2828
  var PHONE_RE = /^[0-9()+\-\s]{8,20}$/;
2779
2829
  var CHECK_DEBOUNCE_MS = 400;
2780
2830
  function useCheckoutForm(args) {
2781
- const { auth } = useHook9();
2782
- const [name, setName] = useState8("");
2783
- const [email, setEmail] = useState8("");
2784
- const [emailConfirm, setEmailConfirm] = useState8("");
2785
- const [phone, setPhone] = useState8("");
2786
- const [cpf, setCpf] = useState8("");
2787
- const [method, setMethod] = useState8(args.defaultMethod);
2788
- const [cycle, setCycle] = useState8(args.defaultCycle);
2789
- const [card, setCardState] = useState8({
2831
+ const { auth } = useHook10();
2832
+ const [name, setName] = useState9("");
2833
+ const [email, setEmail] = useState9("");
2834
+ const [emailConfirm, setEmailConfirm] = useState9("");
2835
+ const [phone, setPhone] = useState9("");
2836
+ const [cpf, setCpf] = useState9("");
2837
+ const [method, setMethod] = useState9(args.defaultMethod);
2838
+ const [cycle, setCycle] = useState9(args.defaultCycle);
2839
+ const [card, setCardState] = useState9({
2790
2840
  number: "",
2791
2841
  expiryMonth: "",
2792
2842
  expiryYear: "",
@@ -2796,17 +2846,17 @@ function useCheckoutForm(args) {
2796
2846
  const setCard = useCallback6((patch) => {
2797
2847
  setCardState((prev) => ({ ...prev, ...patch }));
2798
2848
  }, []);
2799
- const [touchedName, setTouchedName] = useState8(false);
2800
- const [touchedEmail, setTouchedEmail] = useState8(false);
2801
- const [touchedEmailConfirm, setTouchedEmailConfirm] = useState8(false);
2802
- const [touchedPhone, setTouchedPhone] = useState8(false);
2803
- const [touchedCpf, setTouchedCpf] = useState8(false);
2804
- const [formSubmitAttempted, setFormSubmitAttempted] = useState8(false);
2805
- const [submitting, setSubmitting] = useState8(false);
2806
- const [error, setError] = useState8(null);
2807
- const [emailTaken, setEmailTaken] = useState8(false);
2808
- const [loginUrl, setLoginUrl] = useState8(null);
2809
- const [emailStatus, setEmailStatus] = useState8("idle");
2849
+ const [touchedName, setTouchedName] = useState9(false);
2850
+ const [touchedEmail, setTouchedEmail] = useState9(false);
2851
+ const [touchedEmailConfirm, setTouchedEmailConfirm] = useState9(false);
2852
+ const [touchedPhone, setTouchedPhone] = useState9(false);
2853
+ const [touchedCpf, setTouchedCpf] = useState9(false);
2854
+ const [formSubmitAttempted, setFormSubmitAttempted] = useState9(false);
2855
+ const [submitting, setSubmitting] = useState9(false);
2856
+ const [error, setError] = useState9(null);
2857
+ const [emailTaken, setEmailTaken] = useState9(false);
2858
+ const [loginUrl, setLoginUrl] = useState9(null);
2859
+ const [emailStatus, setEmailStatus] = useState9("idle");
2810
2860
  const lastCheckedEmail = useRef6("");
2811
2861
  useEffect10(() => {
2812
2862
  if (!email || !EMAIL_RE.test(email)) {
@@ -2890,10 +2940,16 @@ function useCheckoutForm(args) {
2890
2940
  name: card.holderName || name.trim(),
2891
2941
  email,
2892
2942
  cpfCnpj: cpf.replace(/\D/g, ""),
2893
- // Empty postal/address: backend defaults if Asaas requires.
2894
- // Apps that need full address should override via custom form.
2895
- postalCode: "00000000",
2896
- addressNumber: "0",
2943
+ // Plan-V 0.28.4 Asaas's `creditCardHolderInfo.postalCode`
2944
+ // rejects all-zeros with `tokenize_failed:invalid_holderInfo`
2945
+ // ("O CEP informado é inválido."). The default CheckoutPage
2946
+ // doesn't collect CEP from the user (reference design skipped
2947
+ // it), so ship a known-valid placeholder. Apps that need real
2948
+ // customer addresses must override CheckoutPageDefault and
2949
+ // collect CEP — see personalburn's PaywallStepPagamento for
2950
+ // the pattern.
2951
+ postalCode: "01001000",
2952
+ addressNumber: "100",
2897
2953
  phone
2898
2954
  }
2899
2955
  };
@@ -2968,1867 +3024,1868 @@ function mod11(digits, len) {
2968
3024
  }
2969
3025
 
2970
3026
  // src/hooks/usePlan.ts
2971
- import { useHook as useHook10 } from "@hook-sdk/sdk";
3027
+ import { useHook as useHook11 } from "@hook-sdk/sdk";
2972
3028
  function usePlan() {
2973
- const { plan } = useHook10();
3029
+ const { plan } = useHook11();
2974
3030
  return plan;
2975
3031
  }
2976
3032
 
2977
- // src/defaults/CheckoutPageDefault.tsx
2978
- import { Fragment as Fragment7, jsx as jsx27, jsxs as jsxs18 } from "react/jsx-runtime";
2979
- var INTENT_KEY = "hook:paywall:intent";
2980
- var PIX_PAYLOAD_KEY = "hook:paywall:pix-pending";
2981
- function readIntent() {
2982
- if (typeof window === "undefined") return {};
2983
- try {
2984
- const raw = sessionStorage.getItem(INTENT_KEY);
2985
- if (!raw) return {};
2986
- return JSON.parse(raw);
2987
- } catch {
2988
- return {};
2989
- }
3033
+ // src/components/paywall/Paywall.tsx
3034
+ import { useEffect as useEffect11, useMemo as useMemo5 } from "react";
3035
+ import { useHook as useHook12 } from "@hook-sdk/sdk";
3036
+
3037
+ // src/utils/price.ts
3038
+ function formatBRL(cents) {
3039
+ if (cents === null || cents === void 0) return "";
3040
+ const reais = cents / 100;
3041
+ return new Intl.NumberFormat("pt-BR", {
3042
+ style: "currency",
3043
+ currency: "BRL"
3044
+ }).format(reais);
2990
3045
  }
2991
- function formatBrl(cents) {
2992
- return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(cents / 100);
3046
+ function monthlyFromYearly(yearlyCents) {
3047
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
3048
+ return Math.round(yearlyCents / 12);
2993
3049
  }
2994
- function formatCardNumber(v) {
2995
- const digits = v.replace(/\D/g, "").slice(0, 16);
2996
- return digits.replace(/(.{4})/g, "$1 ").trim();
3050
+ function dailyFromYearly(yearlyCents) {
3051
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
3052
+ return Math.round(yearlyCents / 365);
2997
3053
  }
2998
- function formatExpiryMmAa(v) {
2999
- const d = v.replace(/\D/g, "").slice(0, 4);
3000
- if (d.length < 3) return d;
3001
- return d.slice(0, 2) + "/" + d.slice(2);
3054
+ function computeAnchorCents(baseCents, multiplier) {
3055
+ if (multiplier === null || multiplier === void 0) return null;
3056
+ if (!Number.isFinite(multiplier)) return null;
3057
+ if (multiplier <= 1) return null;
3058
+ return Math.round(baseCents * multiplier);
3002
3059
  }
3003
- function parseExpiryMmAa(v) {
3004
- const d = v.replace(/\D/g, "");
3005
- return { month: d.slice(0, 2), year: d.slice(2, 4) };
3060
+ function discountPercent(anchorCents, realCents) {
3061
+ if (anchorCents <= realCents) return 0;
3062
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
3006
3063
  }
3007
- function formatCpf(v) {
3008
- const d = v.replace(/\D/g, "").slice(0, 11);
3009
- if (d.length <= 3) return d;
3010
- if (d.length <= 6) return d.slice(0, 3) + "." + d.slice(3);
3011
- if (d.length <= 9) return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6);
3012
- return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6, 9) + "-" + d.slice(9);
3064
+
3065
+ // src/components/paywall/PaywallProvider.tsx
3066
+ import { createContext as createContext3 } from "react";
3067
+ import { jsx as jsx28 } from "react/jsx-runtime";
3068
+ var PaywallContext = createContext3(null);
3069
+ function PaywallProvider({ children }) {
3070
+ const state = usePaywallState();
3071
+ return /* @__PURE__ */ jsx28(PaywallContext.Provider, { value: state, children });
3013
3072
  }
3014
- function detectCardBrand(num) {
3015
- const n = num.replace(/\s/g, "");
3016
- if (/^4/.test(n)) return "VISA";
3017
- if (/^(5[1-5]|2[2-7])/.test(n)) return "MASTER";
3018
- if (/^3[47]/.test(n)) return "AMEX";
3019
- if (/^(4011|4312|4389|4514|6011|6362|6363)/.test(n)) return "ELO";
3020
- if (/^(606282|3841)/.test(n)) return "HIPER";
3021
- return "";
3073
+
3074
+ // src/components/paywall/usePaywallContext.ts
3075
+ import { useContext as useContext4 } from "react";
3076
+ function usePaywallContext() {
3077
+ const ctx = useContext4(PaywallContext);
3078
+ if (!ctx) {
3079
+ throw new Error("usePaywallContext must be used within <PaywallProvider>");
3080
+ }
3081
+ return ctx;
3022
3082
  }
3023
- function CheckoutPageDefault() {
3024
- const navigate = useNavigate2();
3025
- const plan = usePlan();
3026
- const intent = useMemo5(readIntent, []);
3027
- const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3028
- const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3029
- const form = useCheckoutForm({ defaultMethod, defaultCycle });
3030
- const [expiryMmAa, setExpiryMmAa] = useState9("");
3031
- useEffect11(() => {
3032
- const { month, year } = parseExpiryMmAa(expiryMmAa);
3033
- if (month !== form.card.expiryMonth || year !== form.card.expiryYear) {
3034
- form.setCard({ expiryMonth: month, expiryYear: year });
3083
+
3084
+ // src/components/paywall/PaywallMethodTabs.tsx
3085
+ import { jsx as jsx29 } from "react/jsx-runtime";
3086
+ function PaywallMethodTabs({
3087
+ labels,
3088
+ className,
3089
+ tabClassName,
3090
+ tabActiveClassName
3091
+ }) {
3092
+ const { methods, selectedMethod, selectMethod } = usePaywallContext();
3093
+ if (methods.length < 2) return null;
3094
+ return /* @__PURE__ */ jsx29("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", className, children: methods.map((m) => {
3095
+ const active = m === selectedMethod;
3096
+ const label = labels[m] ?? m;
3097
+ return /* @__PURE__ */ jsx29(
3098
+ "button",
3099
+ {
3100
+ type: "button",
3101
+ role: "tab",
3102
+ "aria-selected": active,
3103
+ "aria-controls": `paywall-tab-${m}`,
3104
+ tabIndex: active ? 0 : -1,
3105
+ onClick: () => selectMethod(m),
3106
+ className: [tabClassName, active ? tabActiveClassName : ""].filter(Boolean).join(" "),
3107
+ children: label
3108
+ },
3109
+ m
3110
+ );
3111
+ }) });
3112
+ }
3113
+
3114
+ // src/components/paywall/PaywallMethodContent.tsx
3115
+ import { jsx as jsx30 } from "react/jsx-runtime";
3116
+ function PaywallMethodContent({ copy, className, rowClassName }) {
3117
+ const { selectedMethod, hasConsumedTrial } = usePaywallContext();
3118
+ const useCardConsumed = selectedMethod === "card" && hasConsumedTrial && copy.cardConsumedTrial;
3119
+ const rows = useCardConsumed ? copy.cardConsumedTrial.bodyRows : selectedMethod === "pix-auto" || selectedMethod === "pix-once" ? copy.pix.bodyRows : copy.card.bodyRows;
3120
+ return /* @__PURE__ */ jsx30("div", { role: "tabpanel", id: `paywall-tab-${selectedMethod}`, className, children: rows.map((row, i) => /* @__PURE__ */ jsx30("div", { className: rowClassName, children: row }, i)) });
3121
+ }
3122
+
3123
+ // src/components/paywall/PaywallCyclePicker.tsx
3124
+ import { jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
3125
+ var VARIANT_CLASSES = {
3126
+ default: { card: "", cardSelected: "" },
3127
+ "premium-gold": {
3128
+ card: "",
3129
+ cardSelected: "border-2 border-yellow-400/80 ring-2 ring-yellow-400/20"
3130
+ },
3131
+ "pink-pill": {
3132
+ card: "rounded-2xl",
3133
+ cardSelected: "border-2 border-pink-500"
3134
+ }
3135
+ };
3136
+ function PaywallCyclePicker({
3137
+ labels,
3138
+ className,
3139
+ cardClassName,
3140
+ cardSelectedClassName,
3141
+ anchorClassName,
3142
+ variant = "default",
3143
+ render
3144
+ }) {
3145
+ const ctx = usePaywallContext();
3146
+ const { cycle: selected, setCycle, plan, anchorPriceCents } = ctx;
3147
+ const cycles = ["MONTHLY", "YEARLY"];
3148
+ if (render) {
3149
+ return /* @__PURE__ */ jsx31("div", { className, children: render({ cycles, selected, setCycle, plan, anchorPriceCents }) });
3150
+ }
3151
+ if (cycles.length < 2) return null;
3152
+ const v = VARIANT_CLASSES[variant];
3153
+ const composedCardClassName = [v.card, cardClassName].filter(Boolean).join(" ");
3154
+ const composedCardSelectedClassName = [v.cardSelected, cardSelectedClassName].filter(Boolean).join(" ");
3155
+ const monthlyCents = plan?.monthlyCents ?? 0;
3156
+ const yearlyCents = plan?.yearlyCents ?? 0;
3157
+ const anchorMonthly = plan?.anchorMonthlyCents ?? null;
3158
+ const anchorYearly = plan?.anchorYearlyCents ?? null;
3159
+ return /* @__PURE__ */ jsx31(
3160
+ "div",
3161
+ {
3162
+ role: "radiogroup",
3163
+ "aria-label": "Ciclo de cobran\xE7a",
3164
+ className: ["flex flex-row gap-2", className].filter(Boolean).join(" "),
3165
+ children: cycles.map((c) => {
3166
+ const active = c === selected;
3167
+ const label = c === "YEARLY" ? labels.annualLabel : labels.monthlyLabel;
3168
+ const suffix = c === "YEARLY" ? labels.annualSuffix : labels.monthlySuffix;
3169
+ const mainCents = c === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents;
3170
+ const anchorCents = c === "YEARLY" ? anchorYearly : anchorMonthly;
3171
+ return /* @__PURE__ */ jsxs19(
3172
+ "button",
3173
+ {
3174
+ type: "button",
3175
+ role: "radio",
3176
+ "aria-checked": active,
3177
+ onClick: () => setCycle(c),
3178
+ className: [
3179
+ "flex flex-col items-center gap-0.5",
3180
+ composedCardClassName,
3181
+ active ? composedCardSelectedClassName : ""
3182
+ ].filter(Boolean).join(" "),
3183
+ children: [
3184
+ /* @__PURE__ */ jsx31("span", { className: "font-bold text-base leading-tight", children: formatBRL(mainCents) }),
3185
+ /* @__PURE__ */ jsx31("span", { className: "text-xs opacity-70 leading-tight", children: suffix }),
3186
+ /* @__PURE__ */ jsx31("span", { className: "text-xs opacity-60 leading-tight", children: label }),
3187
+ anchorCents != null && anchorCents > mainCents ? /* @__PURE__ */ jsx31("span", { className: anchorClassName ?? "text-xs opacity-50", children: /* @__PURE__ */ jsx31("s", { children: formatBRL(anchorCents) }) }) : null
3188
+ ]
3189
+ },
3190
+ c
3191
+ );
3192
+ })
3035
3193
  }
3036
- }, [expiryMmAa]);
3037
- useEffect11(() => {
3038
- if (form.emailTaken && form.loginUrl) {
3039
- const t = setTimeout(() => navigate(form.loginUrl), 1200);
3040
- return () => clearTimeout(t);
3194
+ );
3195
+ }
3196
+
3197
+ // src/components/paywall/Paywall.tsx
3198
+ import { jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
3199
+ var NBSP = "\xA0";
3200
+ function Paywall({
3201
+ copy,
3202
+ themeClasses = {},
3203
+ slots = {},
3204
+ onBeforeCheckout
3205
+ }) {
3206
+ return /* @__PURE__ */ jsx32(PaywallProvider, { children: /* @__PURE__ */ jsx32(
3207
+ PaywallInner,
3208
+ {
3209
+ copy,
3210
+ themeClasses,
3211
+ slots,
3212
+ onBeforeCheckout
3041
3213
  }
3042
- }, [form.emailTaken, form.loginUrl, navigate]);
3043
- const planInfo = plan.data ? {
3044
- priceCents: plan.data.priceCents,
3045
- yearlyPriceCents: plan.data.yearlyPriceCents,
3046
- trialDays: plan.data.trialDays
3047
- } : null;
3048
- const annual = form.cycle === "YEARLY";
3049
- const cyclePrice = useMemo5(() => {
3050
- if (!planInfo) return null;
3051
- return annual ? planInfo.yearlyPriceCents ?? planInfo.priceCents * 12 : planInfo.priceCents;
3052
- }, [planInfo, annual]);
3053
- const monthlyText = useMemo5(() => {
3054
- if (!planInfo) return "";
3055
- const monthly = annual && planInfo.yearlyPriceCents ? Math.round(planInfo.yearlyPriceCents / 12) : planInfo.priceCents;
3056
- return formatBrl(monthly);
3057
- }, [planInfo, annual]);
3058
- const todayCents = useMemo5(() => {
3059
- if (form.method === "pix-auto") return cyclePrice ?? 0;
3060
- const trialDays2 = planInfo?.trialDays ?? 0;
3061
- if (trialDays2 > 0) return 0;
3062
- return cyclePrice ?? 0;
3063
- }, [form.method, cyclePrice, planInfo]);
3064
- const todayAmount = formatBrl(todayCents);
3065
- const cyclePriceText = cyclePrice !== null ? formatBrl(cyclePrice) : "";
3066
- const annualSavingsCents = useMemo5(() => {
3067
- if (!planInfo || !planInfo.yearlyPriceCents) return 0;
3068
- return planInfo.priceCents * 12 - planInfo.yearlyPriceCents;
3069
- }, [planInfo]);
3070
- const trialDays = planInfo?.trialDays ?? 7;
3071
- const cardBrand = detectCardBrand(form.card.number);
3072
- async function onSubmit(e) {
3073
- e.preventDefault();
3074
- const result = await form.submit();
3075
- if (!result) return;
3076
- if (form.method === "pix-auto" && result.pix_qr_payload) {
3077
- try {
3078
- sessionStorage.setItem(
3079
- PIX_PAYLOAD_KEY,
3080
- JSON.stringify({
3081
- payload: result.pix_qr_payload,
3082
- base64: result.pix_qr_base64 ?? null,
3083
- subscriptionId: result.subscription_id,
3084
- pixAuthorizationId: result.pix_authorization_id ?? null
3085
- })
3086
- );
3087
- } catch {
3214
+ ) });
3215
+ }
3216
+ function PaywallInner({
3217
+ copy,
3218
+ themeClasses = {},
3219
+ slots = {},
3220
+ onBeforeCheckout
3221
+ }) {
3222
+ const { track: track2 } = useHook12();
3223
+ const s = usePaywallContext();
3224
+ const priceLabel = formatBRL(s.currentPriceCents).replace(new RegExp(NBSP, "g"), " ");
3225
+ const trialDaysCardLabel = String(s.trialDaysCard);
3226
+ const ctaLabel = useMemo5(() => {
3227
+ if (s.isFree) return copy.freeCta ?? "Come\xE7ar agora";
3228
+ if (s.selectedMethod === "card") {
3229
+ if (s.hasConsumedTrial && copy.cardConsumedTrial) {
3230
+ return interp(copy.cardConsumedTrial.ctaTemplate, {
3231
+ price: priceLabel,
3232
+ days: trialDaysCardLabel
3233
+ });
3088
3234
  }
3089
- navigate(result.redirect.replace(/^.*\/app\/[^/]+/, ""));
3235
+ if (s.trialDaysCard > 0) {
3236
+ return interp(copy.card.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
3237
+ }
3238
+ return copy.cardConsumedTrial ? interp(copy.cardConsumedTrial.ctaTemplate, {
3239
+ price: priceLabel,
3240
+ days: trialDaysCardLabel
3241
+ }) : `Assinar por ${priceLabel}`;
3242
+ }
3243
+ return interp(copy.pix.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
3244
+ }, [
3245
+ s.isFree,
3246
+ s.selectedMethod,
3247
+ s.hasConsumedTrial,
3248
+ s.trialDaysCard,
3249
+ copy,
3250
+ priceLabel,
3251
+ trialDaysCardLabel
3252
+ ]);
3253
+ const switchHint = useMemo5(() => {
3254
+ if (s.methods.length < 2) return void 0;
3255
+ return s.selectedMethod === "card" ? copy.card.switchHint : copy.pix.switchHint;
3256
+ }, [s.methods.length, s.selectedMethod, copy]);
3257
+ useEffect11(() => {
3258
+ if (!s.initialLoadComplete) return;
3259
+ track2("paywall_view", {
3260
+ default_method: s.selectedMethod,
3261
+ default_cycle: s.cycle,
3262
+ available_methods: s.methods
3263
+ });
3264
+ }, [s.initialLoadComplete]);
3265
+ const handleCta = async () => {
3266
+ track2("paywall_cta_clicked", {
3267
+ method: s.selectedMethod,
3268
+ cycle: s.cycle,
3269
+ price_cents: s.currentPriceCents,
3270
+ had_consumed_trial: s.hasConsumedTrial
3271
+ });
3272
+ if (onBeforeCheckout) {
3273
+ await onBeforeCheckout(s.selectedMethod, s.cycle);
3090
3274
  return;
3091
3275
  }
3092
- navigate(result.redirect.replace(/^.*\/app\/[^/]+/, "") || "/");
3093
- }
3094
- return /* @__PURE__ */ jsx27("div", { className: "flex-1 flex flex-col bg-background min-h-0", children: /* @__PURE__ */ jsxs18("form", { onSubmit, className: "flex-1 overflow-y-auto", children: [
3095
- form.emailTaken ? /* @__PURE__ */ jsx27("div", { className: "px-5 pt-4", children: /* @__PURE__ */ jsxs18("div", { role: "alert", className: "rounded-2xl bg-destructive/10 p-4 text-sm text-destructive border border-destructive/20", children: [
3096
- "Esse e-mail j\xE1 tem conta nesse app.",
3097
- " ",
3098
- /* @__PURE__ */ jsx27("a", { href: form.loginUrl ?? "/signin", className: "underline font-semibold", children: "Entrar agora" })
3099
- ] }) }) : null,
3100
- form.error ? /* @__PURE__ */ jsx27("div", { className: "px-5 pt-4", children: /* @__PURE__ */ jsx27("div", { role: "alert", className: "rounded-2xl bg-destructive/10 p-4 text-sm text-destructive border border-destructive/20", children: form.error.message || "N\xE3o foi poss\xEDvel concluir o pagamento. Tente novamente." }) }) : null,
3101
- /* @__PURE__ */ jsx27("div", { className: "px-5 pt-4", children: form.method === "card" ? /* @__PURE__ */ jsxs18("div", { className: "rounded-2xl bg-card border-[1.5px] border-foreground p-3.5", children: [
3102
- /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2 mb-2", children: [
3103
- /* @__PURE__ */ jsx27(ShieldIcon, { className: "w-4 h-4" }),
3104
- /* @__PURE__ */ jsx27("div", { className: "text-sm font-bold", children: "Voc\xEA N\xC3O ser\xE1 cobrada hoje" })
3105
- ] }),
3106
- /* @__PURE__ */ jsxs18("div", { className: "flex justify-between items-baseline text-sm text-muted-foreground", children: [
3107
- /* @__PURE__ */ jsx27("span", { children: "R$ 0,00 agora" }),
3108
- /* @__PURE__ */ jsx27("span", { className: "opacity-50", children: "\xB7" }),
3109
- /* @__PURE__ */ jsxs18("span", { children: [
3110
- monthlyText,
3111
- "/m\xEAs ap\xF3s ",
3112
- trialDays,
3113
- " dias"
3114
- ] })
3115
- ] }),
3116
- /* @__PURE__ */ jsxs18("div", { className: "mt-2.5 text-[11px] text-muted-foreground flex items-center gap-1.5", children: [
3117
- /* @__PURE__ */ jsx27(BellIcon, { className: "w-2.5 h-2.5" }),
3118
- "Avisamos por email 2 dias antes da primeira cobran\xE7a"
3119
- ] })
3120
- ] }) : /* @__PURE__ */ jsxs18("div", { className: "rounded-2xl p-3.5 bg-emerald-50 border-[1.5px] border-emerald-600/60", children: [
3121
- /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2 mb-2 text-emerald-900", children: [
3122
- /* @__PURE__ */ jsx27(ShieldIcon, { className: "w-4 h-4" }),
3123
- /* @__PURE__ */ jsxs18("div", { className: "text-sm font-bold", children: [
3124
- "Garantia incondicional de ",
3125
- trialDays,
3126
- " dias"
3127
- ] })
3128
- ] }),
3129
- /* @__PURE__ */ jsxs18("div", { className: "text-sm text-emerald-900 leading-snug", children: [
3130
- "Voc\xEA paga ",
3131
- /* @__PURE__ */ jsx27("b", { children: todayAmount }),
3132
- " agora via Pix.",
3133
- /* @__PURE__ */ jsx27("br", {}),
3134
- "N\xE3o gostou em ",
3135
- trialDays,
3136
- " dias? Devolvemos ",
3137
- /* @__PURE__ */ jsx27("b", { children: "100%" }),
3138
- " sem perguntas \u2014 direto pelo app."
3139
- ] })
3140
- ] }) }),
3141
- /* @__PURE__ */ jsxs18("section", { className: "px-5 pt-5", children: [
3142
- /* @__PURE__ */ jsx27("h2", { className: "font-display text-2xl mb-3.5 leading-tight text-foreground", children: "Quase l\xE1." }),
3143
- /* @__PURE__ */ jsx27(FieldLabel, { children: "Email" }),
3144
- /* @__PURE__ */ jsx27(
3145
- FieldInput,
3146
- {
3147
- type: "email",
3148
- inputMode: "email",
3149
- autoComplete: "email",
3150
- autoCapitalize: "none",
3151
- autoCorrect: "off",
3152
- spellCheck: false,
3153
- placeholder: "seu@email.com",
3154
- value: form.email,
3155
- onChange: form.setEmail,
3156
- onBlur: form.markEmailTouched,
3157
- error: form.emailError,
3158
- valid: form.emailStatus === "available"
3159
- }
3160
- ),
3161
- !form.emailError && /* @__PURE__ */ jsx27(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" }),
3162
- /* @__PURE__ */ jsx27("div", { className: "h-3" }),
3163
- /* @__PURE__ */ jsx27(FieldLabel, { children: "Nome completo" }),
3164
- /* @__PURE__ */ jsx27(
3165
- FieldInput,
3166
- {
3167
- type: "text",
3168
- autoComplete: "name",
3169
- placeholder: "como est\xE1 no documento",
3170
- value: form.name,
3171
- onChange: form.setName,
3172
- onBlur: form.markNameTouched,
3173
- error: form.nameError,
3174
- valid: !!form.name && !form.nameError
3175
- }
3176
- ),
3177
- /* @__PURE__ */ jsx27("div", { className: "h-3" }),
3178
- /* @__PURE__ */ jsx27(FieldLabel, { children: "CPF" }),
3179
- /* @__PURE__ */ jsx27(
3180
- FieldInput,
3181
- {
3182
- type: "text",
3183
- inputMode: "numeric",
3184
- placeholder: "000.000.000-00",
3185
- value: form.cpf,
3186
- onChange: (v) => form.setCpf(formatCpf(v)),
3187
- onBlur: form.markCpfTouched,
3188
- error: form.cpfError,
3189
- valid: !!form.cpf && !form.cpfError
3190
- }
3191
- ),
3192
- form.method === "card" ? /* @__PURE__ */ jsxs18(Fragment7, { children: [
3193
- /* @__PURE__ */ jsx27("div", { className: "h-3" }),
3194
- /* @__PURE__ */ jsx27(FieldLabel, { children: "Telefone" }),
3195
- /* @__PURE__ */ jsx27(
3196
- FieldInput,
3197
- {
3198
- type: "tel",
3199
- inputMode: "tel",
3200
- autoComplete: "tel",
3201
- placeholder: "(11) 99999-9999",
3202
- value: form.phone,
3203
- onChange: form.setPhone,
3204
- onBlur: form.markPhoneTouched,
3205
- error: form.phoneError,
3206
- valid: !!form.phone && !form.phoneError
3207
- }
3208
- ),
3209
- !form.phoneError && /* @__PURE__ */ jsx27(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
3210
- ] }) : null
3211
- ] }),
3212
- /* @__PURE__ */ jsxs18("section", { className: "px-5 pt-5", children: [
3213
- /* @__PURE__ */ jsx27(FieldLabel, { children: "Forma de pagamento" }),
3214
- /* @__PURE__ */ jsxs18("div", { role: "tablist", className: "flex gap-1.5 bg-muted p-1 rounded-xl", children: [
3215
- /* @__PURE__ */ jsx27(
3216
- TabButton,
3217
- {
3218
- active: form.method === "card",
3219
- onClick: () => form.setMethod("card"),
3220
- icon: /* @__PURE__ */ jsx27(CardIcon, { className: "w-3.5 h-3.5" }),
3221
- label: "Cart\xE3o",
3222
- subtitle: trialDays > 0 ? `${trialDays} dias gr\xE1tis` : "pague hoje",
3223
- subtitleActiveClass: "text-emerald-700"
3224
- }
3225
- ),
3226
- /* @__PURE__ */ jsx27(
3227
- TabButton,
3228
- {
3229
- active: form.method === "pix-auto",
3230
- onClick: () => form.setMethod("pix-auto"),
3231
- icon: /* @__PURE__ */ jsx27(PixIcon, { className: "w-3.5 h-3.5" }),
3232
- label: "Pix",
3233
- subtitle: `pague hoje \xB7 garantia ${trialDays}d`,
3234
- subtitleActiveClass: "text-foreground/70"
3235
- }
3236
- )
3237
- ] })
3238
- ] }),
3239
- form.method === "card" ? /* @__PURE__ */ jsxs18("section", { className: "px-5 pt-3.5", children: [
3240
- /* @__PURE__ */ jsx27(FieldLabel, { children: "N\xFAmero do cart\xE3o" }),
3241
- /* @__PURE__ */ jsxs18("div", { className: "relative", children: [
3242
- /* @__PURE__ */ jsx27(
3243
- FieldInput,
3244
- {
3245
- type: "text",
3246
- inputMode: "numeric",
3247
- autoComplete: "cc-number",
3248
- placeholder: "0000 0000 0000 0000",
3249
- value: form.card.number,
3250
- onChange: (v) => form.setCard({ number: formatCardNumber(v) }),
3251
- style: cardBrand ? { paddingRight: "4.5rem" } : void 0
3252
- }
3253
- ),
3254
- cardBrand && /* @__PURE__ */ jsx27("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-[10px] font-bold tracking-wide px-1.5 py-0.5 rounded bg-muted text-muted-foreground", children: cardBrand })
3255
- ] }),
3256
- /* @__PURE__ */ jsx27("div", { className: "h-3" }),
3257
- /* @__PURE__ */ jsxs18("div", { className: "flex gap-2.5", children: [
3258
- /* @__PURE__ */ jsxs18("div", { className: "flex-1", children: [
3259
- /* @__PURE__ */ jsx27(FieldLabel, { children: "Validade" }),
3260
- /* @__PURE__ */ jsx27(
3261
- FieldInput,
3262
- {
3263
- type: "text",
3264
- inputMode: "numeric",
3265
- autoComplete: "cc-exp",
3266
- placeholder: "MM/AA",
3267
- value: expiryMmAa,
3268
- onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v))
3269
- }
3270
- )
3271
- ] }),
3272
- /* @__PURE__ */ jsxs18("div", { className: "flex-1", children: [
3273
- /* @__PURE__ */ jsx27(FieldLabel, { children: "CVV" }),
3274
- /* @__PURE__ */ jsx27(
3275
- FieldInput,
3276
- {
3277
- type: "text",
3278
- inputMode: "numeric",
3279
- autoComplete: "cc-csc",
3280
- placeholder: "3 d\xEDgitos",
3281
- value: form.card.ccv,
3282
- onChange: (v) => form.setCard({ ccv: v.replace(/\D/g, "").slice(0, 4) })
3283
- }
3284
- )
3285
- ] })
3286
- ] }),
3287
- /* @__PURE__ */ jsx27("div", { className: "h-3" }),
3288
- /* @__PURE__ */ jsx27(FieldLabel, { children: "Nome no cart\xE3o" }),
3289
- /* @__PURE__ */ jsx27(
3290
- FieldInput,
3291
- {
3292
- type: "text",
3293
- autoComplete: "cc-name",
3294
- placeholder: "como est\xE1 no cart\xE3o",
3295
- value: form.card.holderName,
3296
- onChange: (v) => form.setCard({ holderName: v })
3297
- }
3298
- )
3299
- ] }) : /* @__PURE__ */ jsx27("section", { className: "px-5 pt-3.5", children: /* @__PURE__ */ jsxs18("div", { className: "rounded-2xl bg-card border border-border p-3.5 flex gap-3.5 items-center", children: [
3300
- /* @__PURE__ */ jsx27("div", { className: "w-[72px] h-[72px] rounded-xl shrink-0 border-2 border-foreground relative overflow-hidden bg-muted", children: /* @__PURE__ */ jsx27("div", { className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[22px] h-[22px] bg-background flex items-center justify-center", children: /* @__PURE__ */ jsx27(PixIcon, { className: "w-3.5 h-3.5 text-foreground" }) }) }),
3301
- /* @__PURE__ */ jsxs18("div", { className: "flex-1", children: [
3302
- /* @__PURE__ */ jsx27("div", { className: "text-xs font-bold uppercase tracking-wider text-muted-foreground", children: "pagamento em segundos" }),
3303
- /* @__PURE__ */ jsxs18("div", { className: "text-sm text-foreground mt-1 leading-snug", children: [
3304
- "Geramos seu ",
3305
- /* @__PURE__ */ jsx27("b", { children: "QR Pix" }),
3306
- " no pr\xF3ximo passo. Pague pelo app do banco e seu acesso libera ",
3307
- /* @__PURE__ */ jsx27("b", { children: "imediatamente" }),
3308
- "."
3309
- ] })
3310
- ] })
3311
- ] }) }),
3312
- /* @__PURE__ */ jsx27("section", { className: "px-5 pt-5", children: /* @__PURE__ */ jsxs18("div", { className: "bg-muted rounded-2xl p-4", children: [
3313
- /* @__PURE__ */ jsxs18("div", { className: "flex justify-between mb-2.5", children: [
3314
- /* @__PURE__ */ jsxs18("div", { children: [
3315
- /* @__PURE__ */ jsx27("div", { className: "text-sm font-semibold text-foreground", children: annual ? "Plano Anual" : "Plano Mensal" }),
3316
- /* @__PURE__ */ jsx27("div", { className: "text-[11px] text-muted-foreground", children: "Coach" })
3317
- ] }),
3318
- /* @__PURE__ */ jsxs18("div", { className: "text-right", children: [
3319
- /* @__PURE__ */ jsxs18("div", { className: "text-sm font-bold text-foreground", children: [
3320
- cyclePriceText,
3321
- "/",
3322
- annual ? "ano" : "m\xEAs"
3323
- ] }),
3324
- annual && annualSavingsCents > 0 && /* @__PURE__ */ jsxs18("div", { className: "text-[11px] text-emerald-700 font-semibold", children: [
3325
- "economia ",
3326
- formatBrl(annualSavingsCents)
3327
- ] })
3328
- ] })
3329
- ] }),
3330
- /* @__PURE__ */ jsx27("div", { className: "h-px bg-border my-3" }),
3331
- /* @__PURE__ */ jsxs18("div", { className: "flex justify-between items-baseline", children: [
3332
- /* @__PURE__ */ jsxs18("div", { children: [
3333
- /* @__PURE__ */ jsx27("div", { className: "text-sm font-bold text-foreground", children: "Voc\xEA paga hoje" }),
3334
- form.method === "card" && trialDays > 0 && /* @__PURE__ */ jsxs18("div", { className: "text-[11px] text-muted-foreground mt-0.5", children: [
3335
- "cobran\xE7a inicia no dia ",
3336
- trialDays
3337
- ] }),
3338
- form.method === "pix-auto" && /* @__PURE__ */ jsxs18("div", { className: "text-[11px] text-emerald-700 mt-0.5 font-semibold", children: [
3339
- "reembolso garantido at\xE9 o dia ",
3340
- trialDays
3341
- ] })
3342
- ] }),
3343
- /* @__PURE__ */ jsx27("div", { className: "text-2xl font-bold font-display tracking-tight text-foreground", children: todayAmount })
3344
- ] })
3345
- ] }) }),
3346
- /* @__PURE__ */ jsx27("div", { className: "h-4" }),
3347
- /* @__PURE__ */ jsxs18("div", { className: "sticky bottom-0 px-5 pt-3.5 pb-6 bg-gradient-to-b from-transparent to-background", children: [
3348
- /* @__PURE__ */ jsx27(
3276
+ await s.submit();
3277
+ };
3278
+ const ctaTheme = s.selectedMethod === "card" ? themeClasses.ctaCard : themeClasses.ctaPix;
3279
+ return /* @__PURE__ */ jsxs20("div", { className: themeClasses.container, children: [
3280
+ slots.heroSlot,
3281
+ /* @__PURE__ */ jsx32("h1", { className: themeClasses.headline, children: copy.headline }),
3282
+ /* @__PURE__ */ jsx32("ul", { children: copy.features.map((f) => /* @__PURE__ */ jsxs20("li", { className: themeClasses.feature, children: [
3283
+ "\u2713 ",
3284
+ /* @__PURE__ */ jsx32("span", { children: f })
3285
+ ] }, f)) }),
3286
+ copy.socialProof ? /* @__PURE__ */ jsx32("p", { className: themeClasses.socialProof, children: copy.socialProof }) : null,
3287
+ slots.cyclePickerSlot ?? /* @__PURE__ */ jsx32(
3288
+ PaywallCyclePicker,
3289
+ {
3290
+ labels: copy.cycle,
3291
+ cardClassName: themeClasses.cycleCard,
3292
+ cardSelectedClassName: themeClasses.cycleCardSelected,
3293
+ anchorClassName: themeClasses.anchorPrice
3294
+ }
3295
+ ),
3296
+ /* @__PURE__ */ jsx32(
3297
+ PaywallMethodTabs,
3298
+ {
3299
+ labels: { "pix-auto": copy.pix.tabLabel, card: copy.card.tabLabel },
3300
+ className: themeClasses.tabs,
3301
+ tabClassName: themeClasses.tab,
3302
+ tabActiveClassName: themeClasses.tabActive
3303
+ }
3304
+ ),
3305
+ /* @__PURE__ */ jsx32(
3306
+ PaywallMethodContent,
3307
+ {
3308
+ copy: {
3309
+ pix: interpolateCopy(copy.pix, priceLabel, trialDaysCardLabel),
3310
+ card: interpolateCopy(copy.card, priceLabel, trialDaysCardLabel),
3311
+ cardConsumedTrial: copy.cardConsumedTrial ? {
3312
+ bodyRows: copy.cardConsumedTrial.bodyRows.map(
3313
+ (r) => interp(r, { price: priceLabel, days: trialDaysCardLabel })
3314
+ ),
3315
+ ctaTemplate: copy.cardConsumedTrial.ctaTemplate
3316
+ } : void 0
3317
+ },
3318
+ className: themeClasses.tabContent,
3319
+ rowClassName: themeClasses.tabContentRow
3320
+ }
3321
+ ),
3322
+ slots.beforeCtaSlot,
3323
+ /* @__PURE__ */ jsxs20("div", { children: [
3324
+ /* @__PURE__ */ jsx32(
3349
3325
  "button",
3350
3326
  {
3351
- type: "submit",
3352
- disabled: !form.canSubmit,
3353
- className: "w-full rounded-full bg-primary text-primary-foreground min-h-14 px-5 text-base font-bold inline-flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg",
3354
- children: form.submitting ? /* @__PURE__ */ jsxs18(Fragment7, { children: [
3355
- /* @__PURE__ */ jsx27(Spinner2, {}),
3356
- " ",
3357
- form.method === "pix-auto" ? "Gerando QR\u2026" : "Confirmando\u2026"
3358
- ] }) : form.method === "card" ? /* @__PURE__ */ jsxs18(Fragment7, { children: [
3359
- /* @__PURE__ */ jsx27(LockIcon, { className: "w-3.5 h-3.5" }),
3360
- " Confirmar e come\xE7ar gr\xE1tis"
3361
- ] }) : /* @__PURE__ */ jsxs18(Fragment7, { children: [
3362
- /* @__PURE__ */ jsx27(PixIcon, { className: "w-3.5 h-3.5" }),
3363
- " Gerar QR \xB7 pagar ",
3364
- todayAmount
3365
- ] })
3327
+ type: "button",
3328
+ onClick: () => {
3329
+ void handleCta();
3330
+ },
3331
+ disabled: s.submitting,
3332
+ className: ctaTheme,
3333
+ children: s.submitting ? "Abrindo checkout\u2026" : ctaLabel
3366
3334
  }
3367
3335
  ),
3368
- /* @__PURE__ */ jsxs18("div", { className: "text-center mt-2.5 text-xs text-muted-foreground", children: [
3369
- "Ao continuar, voc\xEA concorda com nossos ",
3370
- /* @__PURE__ */ jsx27("u", { children: "Termos" }),
3371
- ". Pagamento seguro Asaas."
3372
- ] }),
3373
- /* @__PURE__ */ jsxs18("div", { className: "mt-3 flex items-center justify-center gap-3.5 text-[11px] text-muted-foreground", children: [
3374
- /* @__PURE__ */ jsxs18("span", { className: "inline-flex items-center gap-1", children: [
3375
- /* @__PURE__ */ jsx27(LockIcon, { className: "w-2.5 h-2.5 opacity-60" }),
3376
- " SSL 256-bit"
3377
- ] }),
3378
- /* @__PURE__ */ jsx27("span", { className: "w-px h-2.5 bg-border" }),
3379
- /* @__PURE__ */ jsx27("span", { children: "Pagamento via Asaas" }),
3380
- /* @__PURE__ */ jsx27("span", { className: "w-px h-2.5 bg-border" }),
3381
- /* @__PURE__ */ jsxs18("span", { children: [
3382
- "Garantia ",
3383
- trialDays,
3384
- " dias"
3385
- ] })
3386
- ] })
3336
+ switchHint ? /* @__PURE__ */ jsx32("p", { className: themeClasses.switchHint, children: switchHint }) : null,
3337
+ /* @__PURE__ */ jsx32("p", { className: themeClasses.trustLine, children: copy.trustLine })
3387
3338
  ] })
3388
- ] }) });
3389
- }
3390
- function FieldLabel({ children }) {
3391
- return /* @__PURE__ */ jsx27("div", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-1.5", children });
3392
- }
3393
- function FieldInput(props) {
3394
- const baseClass = "w-full px-4 rounded-xl bg-card text-base text-foreground outline-none border-[1.5px] transition-colors";
3395
- const stateClass = props.error ? "border-destructive focus:border-destructive" : props.valid ? "border-emerald-600 focus:border-emerald-700" : "border-border focus:border-foreground";
3396
- return /* @__PURE__ */ jsxs18(Fragment7, { children: [
3397
- /* @__PURE__ */ jsx27(
3398
- "input",
3399
- {
3400
- type: props.type ?? "text",
3401
- inputMode: props.inputMode,
3402
- autoComplete: props.autoComplete,
3403
- autoCapitalize: props.autoCapitalize,
3404
- autoCorrect: props.autoCorrect,
3405
- spellCheck: props.spellCheck,
3406
- placeholder: props.placeholder,
3407
- value: props.value,
3408
- onChange: (e) => props.onChange(e.target.value),
3409
- onBlur: props.onBlur,
3410
- style: { height: "52px", ...props.style },
3411
- className: `${baseClass} ${stateClass}`
3412
- }
3413
- ),
3414
- props.error ? /* @__PURE__ */ jsx27("div", { className: "mt-1.5 text-xs text-destructive font-medium", children: props.error }) : null
3415
3339
  ] });
3416
3340
  }
3417
- function FieldHint({ children }) {
3418
- return /* @__PURE__ */ jsx27("div", { className: "mt-1.5 text-xs text-muted-foreground", children });
3341
+ function interp(tpl, vars) {
3342
+ return tpl.replace(/\{(\w+)\}/g, (_m, k) => vars[k] ?? "");
3419
3343
  }
3420
- function TabButton({ active, onClick, icon, label, subtitle, subtitleActiveClass }) {
3421
- return /* @__PURE__ */ jsxs18(
3422
- "button",
3423
- {
3424
- type: "button",
3425
- role: "tab",
3426
- "aria-selected": active,
3427
- onClick,
3428
- className: `flex-1 flex flex-col items-center justify-center gap-0.5 py-2.5 px-1.5 rounded-lg text-sm font-semibold transition-colors ${active ? "bg-card text-foreground shadow-sm" : "bg-transparent text-muted-foreground"}`,
3429
- style: { minHeight: 56 },
3430
- children: [
3431
- /* @__PURE__ */ jsxs18("span", { className: "inline-flex items-center gap-1.5", children: [
3432
- icon,
3433
- " ",
3434
- label
3435
- ] }),
3436
- /* @__PURE__ */ jsx27("span", { className: `text-[10px] font-medium ${active ? subtitleActiveClass : "text-muted-foreground/70"}`, children: subtitle })
3437
- ]
3438
- }
3439
- );
3440
- }
3441
- function CardIcon({ className }) {
3442
- return /* @__PURE__ */ jsxs18("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinejoin: "round", "aria-hidden": "true", children: [
3443
- /* @__PURE__ */ jsx27("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "2.5" }),
3444
- /* @__PURE__ */ jsx27("path", { d: "M2.5 10h19" })
3445
- ] });
3446
- }
3447
- function PixIcon({ className }) {
3448
- return /* @__PURE__ */ jsx27("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx27("path", { d: "M12 2L2 12l10 10 10-10L12 2zm0 4.83L17.17 12 12 17.17 6.83 12 12 6.83z" }) });
3449
- }
3450
- function LockIcon({ className }) {
3451
- return /* @__PURE__ */ jsxs18("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3452
- /* @__PURE__ */ jsx27("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2.5" }),
3453
- /* @__PURE__ */ jsx27("path", { d: "M7.5 10V7a4.5 4.5 0 119 0v3" })
3454
- ] });
3344
+ function interpolateCopy(m, price, days) {
3345
+ return {
3346
+ tabLabel: m.tabLabel,
3347
+ bodyRows: m.bodyRows.map((r) => interp(r, { price, days })),
3348
+ ctaTemplate: m.ctaTemplate,
3349
+ switchHint: m.switchHint
3350
+ };
3455
3351
  }
3456
- function ShieldIcon({ className }) {
3457
- return /* @__PURE__ */ jsxs18("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3458
- /* @__PURE__ */ jsx27("path", { d: "M12 2.5l8 3v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10v-6l8-3z" }),
3459
- /* @__PURE__ */ jsx27("path", { d: "M9 12l2 2 4-4" })
3352
+
3353
+ // src/components/paywall/PaywallCta.tsx
3354
+ import { jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
3355
+ function PaywallCta({
3356
+ ctaLabel,
3357
+ loadingLabel,
3358
+ switchHint,
3359
+ trustLine,
3360
+ className,
3361
+ buttonClassName,
3362
+ switchHintClassName,
3363
+ trustClassName
3364
+ }) {
3365
+ const { submit, submitting } = usePaywallContext();
3366
+ const label = submitting && loadingLabel ? loadingLabel : ctaLabel;
3367
+ return /* @__PURE__ */ jsxs21("div", { className, children: [
3368
+ /* @__PURE__ */ jsx33(
3369
+ "button",
3370
+ {
3371
+ type: "button",
3372
+ onClick: () => {
3373
+ void submit();
3374
+ },
3375
+ disabled: submitting,
3376
+ className: buttonClassName,
3377
+ children: label
3378
+ }
3379
+ ),
3380
+ switchHint ? /* @__PURE__ */ jsx33("p", { className: switchHintClassName, children: switchHint }) : null,
3381
+ /* @__PURE__ */ jsx33("p", { className: trustClassName, children: trustLine })
3460
3382
  ] });
3461
3383
  }
3462
- function BellIcon({ className }) {
3463
- return /* @__PURE__ */ jsxs18("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3464
- /* @__PURE__ */ jsx27("path", { d: "M6 17V11a6 6 0 1112 0v6l1.5 2H4.5L6 17z" }),
3465
- /* @__PURE__ */ jsx27("path", { d: "M10 21a2 2 0 004 0" })
3466
- ] });
3384
+
3385
+ // src/components/paywall/blocks/PaywallEyebrow.tsx
3386
+ import { jsx as jsx34 } from "react/jsx-runtime";
3387
+ var DEFAULT_EYEBROW_CLASSES = "text-xs uppercase tracking-widest font-semibold opacity-70";
3388
+ function PaywallEyebrow({ text, className }) {
3389
+ return /* @__PURE__ */ jsx34("div", { className: [DEFAULT_EYEBROW_CLASSES, className].filter(Boolean).join(" "), children: text });
3467
3390
  }
3468
- function Spinner2() {
3469
- return /* @__PURE__ */ jsx27(
3470
- "span",
3391
+
3392
+ // src/components/paywall/blocks/PaywallHero.tsx
3393
+ import { jsx as jsx35, jsxs as jsxs22 } from "react/jsx-runtime";
3394
+ var DEFAULT_GRADIENT = "absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent";
3395
+ function PaywallHero({
3396
+ src,
3397
+ alt = "",
3398
+ headline,
3399
+ aspectRatio = "16/9",
3400
+ gradientClassName,
3401
+ className,
3402
+ headlineClassName,
3403
+ imgClassName,
3404
+ render
3405
+ }) {
3406
+ if (render) {
3407
+ return /* @__PURE__ */ jsx35("div", { className, children: render({ src, headline }) });
3408
+ }
3409
+ return /* @__PURE__ */ jsxs22(
3410
+ "div",
3471
3411
  {
3472
- className: "w-4 h-4 rounded-full border-2 border-white/40 border-t-white",
3473
- style: { animation: "spin 0.7s linear infinite" }
3412
+ className: ["relative overflow-hidden", className].filter(Boolean).join(" "),
3413
+ style: { aspectRatio },
3414
+ children: [
3415
+ /* @__PURE__ */ jsx35(
3416
+ "img",
3417
+ {
3418
+ src,
3419
+ alt,
3420
+ className: ["absolute inset-0 w-full h-full object-cover", imgClassName].filter(Boolean).join(" ")
3421
+ }
3422
+ ),
3423
+ /* @__PURE__ */ jsx35("div", { className: gradientClassName ?? DEFAULT_GRADIENT, "aria-hidden": "true" }),
3424
+ headline ? /* @__PURE__ */ jsx35(
3425
+ "h1",
3426
+ {
3427
+ className: ["absolute bottom-0 left-0 right-0 p-4 text-white font-bold text-2xl", headlineClassName].filter(Boolean).join(" "),
3428
+ children: headline
3429
+ }
3430
+ ) : null
3431
+ ]
3474
3432
  }
3475
3433
  );
3476
3434
  }
3477
3435
 
3478
- // src/defaults/PixWaitingPageDefault.tsx
3479
- import { useEffect as useEffect12, useMemo as useMemo6, useState as useState10 } from "react";
3480
- import { useNavigate as useNavigate3 } from "react-router-dom";
3481
- import { useHook as useHook11 } from "@hook-sdk/sdk";
3482
- import { jsx as jsx28, jsxs as jsxs19 } from "react/jsx-runtime";
3483
- var PIX_PAYLOAD_KEY2 = "hook:paywall:pix-pending";
3484
- var TIMEOUT_MS = 30 * 60 * 1e3;
3485
- function readPixPayload() {
3486
- if (typeof window === "undefined") return null;
3487
- try {
3488
- const raw = sessionStorage.getItem(PIX_PAYLOAD_KEY2);
3489
- if (!raw) return null;
3490
- return JSON.parse(raw);
3491
- } catch {
3492
- return null;
3493
- }
3436
+ // src/components/paywall/blocks/PaywallHeadline.tsx
3437
+ import { jsx as jsx36 } from "react/jsx-runtime";
3438
+ var DEFAULT_HEADLINE_CLASSES = "text-2xl font-bold leading-tight";
3439
+ function PaywallHeadline({ text, className, as = "h1" }) {
3440
+ const Tag = as;
3441
+ return /* @__PURE__ */ jsx36(Tag, { className: [DEFAULT_HEADLINE_CLASSES, className].filter(Boolean).join(" "), children: text });
3494
3442
  }
3495
- function clearPixPayload() {
3496
- if (typeof window === "undefined") return;
3497
- try {
3498
- sessionStorage.removeItem(PIX_PAYLOAD_KEY2);
3499
- } catch {
3443
+
3444
+ // src/components/paywall/blocks/PaywallPriceHeadline.tsx
3445
+ import { jsx as jsx37 } from "react/jsx-runtime";
3446
+ var DEFAULT_CLASS = "text-2xl font-bold leading-tight";
3447
+ var CYCLE_LABEL = {
3448
+ MONTHLY: "mensal",
3449
+ YEARLY: "anual"
3450
+ };
3451
+ function PaywallPriceHeadline({
3452
+ template,
3453
+ className,
3454
+ as = "h1",
3455
+ render
3456
+ }) {
3457
+ const { cycle, currentMonthlyEquivCents, plan } = usePaywallContext();
3458
+ const yearlyCents = plan?.yearlyCents ?? null;
3459
+ const pricePerDay = formatBRL(dailyFromYearly(yearlyCents));
3460
+ const monthlyEquiv = currentMonthlyEquivCents ?? 0;
3461
+ const cycleLabel = CYCLE_LABEL[cycle] ?? cycle.toLowerCase();
3462
+ const rootClasses = [DEFAULT_CLASS, className].filter(Boolean).join(" ");
3463
+ if (render) {
3464
+ const RootTag2 = as;
3465
+ return /* @__PURE__ */ jsx37(RootTag2, { className: [className].filter(Boolean).join(" ") || void 0, children: render({ pricePerDay, currentMonthlyEquivCents: monthlyEquiv, cycle }) });
3500
3466
  }
3467
+ const text = template.replaceAll("{pricePerDay}", pricePerDay).replaceAll("{currentMonthlyEquiv}", formatBRL(monthlyEquiv)).replaceAll("{cycle}", cycleLabel);
3468
+ const RootTag = as;
3469
+ return /* @__PURE__ */ jsx37(RootTag, { className: rootClasses, children: text });
3501
3470
  }
3502
- function PixWaitingPageDefault() {
3503
- const navigate = useNavigate3();
3504
- const { subscription } = useHook11();
3505
- const payload = useMemo6(readPixPayload, []);
3506
- const [copied, setCopied] = useState10(false);
3507
- const [timedOut, setTimedOut] = useState10(false);
3508
- useEffect12(() => {
3509
- if (!payload) navigate("/paywall/checkout", { replace: true });
3510
- }, [payload, navigate]);
3511
- useEffect12(() => {
3512
- const t = setTimeout(() => setTimedOut(true), TIMEOUT_MS);
3513
- return () => clearTimeout(t);
3514
- }, []);
3515
- const hasAccess = subscription.hasAccess;
3516
- useEffect12(() => {
3517
- if (hasAccess) {
3518
- clearPixPayload();
3519
- navigate("/", { replace: true });
3520
- }
3521
- }, [hasAccess, navigate]);
3522
- async function copyPayload() {
3523
- if (!payload?.payload) return;
3524
- try {
3525
- await navigator.clipboard.writeText(payload.payload);
3526
- setCopied(true);
3527
- setTimeout(() => setCopied(false), 2e3);
3528
- } catch {
3529
- }
3471
+
3472
+ // src/components/paywall/blocks/PaywallCountdown.tsx
3473
+ import { useEffect as useEffect12, useRef as useRef7, useState as useState10 } from "react";
3474
+ import { jsx as jsx38 } from "react/jsx-runtime";
3475
+ var DEFAULT_COUNTDOWN_CLASSES = "font-mono tabular-nums";
3476
+ function resolveDeadlineMs(deadline) {
3477
+ if (deadline instanceof Date) return deadline.getTime();
3478
+ if (typeof deadline === "string") return new Date(deadline).getTime();
3479
+ const { sessionStorageKey, durationMs } = deadline;
3480
+ if (typeof window === "undefined" || typeof window.sessionStorage === "undefined") {
3481
+ return Date.now() + durationMs;
3530
3482
  }
3531
- if (!payload) return null;
3532
- if (timedOut) {
3533
- return /* @__PURE__ */ jsxs19("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-10 text-center bg-background space-y-4", children: [
3534
- /* @__PURE__ */ jsx28("h1", { className: "font-display text-2xl text-foreground", children: "PIX expirado" }),
3535
- /* @__PURE__ */ jsx28("p", { className: "text-sm text-muted-foreground", children: "O tempo pra pagar acabou. Gere um novo PIX." }),
3536
- /* @__PURE__ */ jsx28(
3537
- "button",
3538
- {
3539
- onClick: () => {
3540
- clearPixPayload();
3541
- navigate("/paywall/checkout", { replace: true });
3542
- },
3543
- className: "rounded-xl bg-primary px-6 py-3 text-base font-semibold text-primary-foreground",
3544
- children: "Tentar novamente"
3545
- }
3546
- )
3547
- ] });
3483
+ const stored = window.sessionStorage.getItem(sessionStorageKey);
3484
+ const parsed = stored ? Number.parseInt(stored, 10) : NaN;
3485
+ const now = Date.now();
3486
+ if (!Number.isFinite(parsed) || parsed < now) {
3487
+ const target = now + durationMs;
3488
+ window.sessionStorage.setItem(sessionStorageKey, String(target));
3489
+ return target;
3548
3490
  }
3549
- return /* @__PURE__ */ jsxs19("div", { className: "flex-1 flex flex-col items-center px-6 py-8 bg-background space-y-6", children: [
3550
- /* @__PURE__ */ jsxs19("header", { className: "text-center space-y-2", children: [
3551
- /* @__PURE__ */ jsx28("h1", { className: "font-display text-2xl text-foreground", children: "Pague o PIX" }),
3552
- /* @__PURE__ */ jsx28("p", { className: "text-sm text-muted-foreground", children: "Escaneie o QR Code no app do seu banco. O acesso libera assim que confirmarmos o pagamento." })
3553
- ] }),
3554
- payload.base64 ? /* @__PURE__ */ jsx28(
3555
- "img",
3556
- {
3557
- src: `data:image/png;base64,${payload.base64}`,
3558
- alt: "QR Code PIX",
3559
- className: "w-64 h-64 rounded-2xl border border-border bg-card p-2"
3491
+ return parsed;
3492
+ }
3493
+ function computeRemaining(deadlineMs) {
3494
+ const diff = Math.max(0, deadlineMs - Date.now());
3495
+ const totalSeconds = Math.floor(diff / 1e3);
3496
+ const h = Math.floor(totalSeconds / 3600);
3497
+ const m = Math.floor(totalSeconds % 3600 / 60);
3498
+ const s = totalSeconds % 60;
3499
+ return { h, m, s, expired: diff === 0 };
3500
+ }
3501
+ function pad(n) {
3502
+ return String(n).padStart(2, "0");
3503
+ }
3504
+ function PaywallCountdown({
3505
+ deadline,
3506
+ format = "h:m:s",
3507
+ onExpire,
3508
+ className,
3509
+ render
3510
+ }) {
3511
+ const deadlineMsRef = useRef7(null);
3512
+ if (deadlineMsRef.current === null) {
3513
+ deadlineMsRef.current = resolveDeadlineMs(deadline);
3514
+ }
3515
+ const [state, setState] = useState10(() => computeRemaining(deadlineMsRef.current));
3516
+ const expiredCalledRef = useRef7(false);
3517
+ useEffect12(() => {
3518
+ if (state.expired) {
3519
+ if (!expiredCalledRef.current) {
3520
+ expiredCalledRef.current = true;
3521
+ onExpire?.();
3560
3522
  }
3561
- ) : /* @__PURE__ */ jsx28("div", { className: "w-64 h-64 rounded-2xl border border-border bg-card flex items-center justify-center text-sm text-muted-foreground", children: "QR indispon\xEDvel" }),
3562
- payload.payload ? /* @__PURE__ */ jsx28(
3563
- "button",
3564
- {
3565
- onClick: copyPayload,
3566
- className: "w-full max-w-xs rounded-xl border border-border bg-card px-4 py-3 text-sm font-medium text-foreground",
3567
- children: copied ? "\u2713 Copiado!" : "Copiar c\xF3digo PIX"
3523
+ return;
3524
+ }
3525
+ const tick = () => {
3526
+ const next = computeRemaining(deadlineMsRef.current);
3527
+ setState(next);
3528
+ if (next.expired && !expiredCalledRef.current) {
3529
+ expiredCalledRef.current = true;
3530
+ onExpire?.();
3568
3531
  }
3569
- ) : null,
3570
- /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
3571
- /* @__PURE__ */ jsx28("span", { className: "inline-block w-2 h-2 rounded-full bg-primary animate-pulse" }),
3572
- "Aguardando pagamento\u2026"
3573
- ] }),
3574
- /* @__PURE__ */ jsx28("p", { className: "text-center text-xs text-muted-foreground", children: "Pode fechar essa janela \u2014 tamb\xE9m enviamos um link de acesso pro seu e-mail." })
3575
- ] });
3532
+ };
3533
+ const id = setInterval(tick, 1e3);
3534
+ return () => clearInterval(id);
3535
+ }, [state.expired]);
3536
+ if (render) {
3537
+ return /* @__PURE__ */ jsx38("div", { className, children: render(state) });
3538
+ }
3539
+ const formatted = format === "h:m:s" ? `${pad(state.h)}:${pad(state.m)}:${pad(state.s)}` : `${pad(state.h * 60 + state.m)}:${pad(state.s)}`;
3540
+ return /* @__PURE__ */ jsx38("div", { className: [DEFAULT_COUNTDOWN_CLASSES, className].filter(Boolean).join(" "), children: formatted });
3576
3541
  }
3577
3542
 
3578
- // src/hooks/useLoginForm.ts
3579
- import { useCallback as useCallback7, useMemo as useMemo7, useState as useState11 } from "react";
3580
- import { useHook as useHook12 } from "@hook-sdk/sdk";
3581
- var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3582
- var MIN_PASSWORD = 8;
3583
- function useLoginForm() {
3584
- const { auth } = useHook12();
3585
- const [email, setEmail] = useState11("");
3586
- const [password, setPassword] = useState11("");
3587
- const [submitting, setSubmitting] = useState11(false);
3588
- const [error, setError] = useState11(null);
3589
- const [touchedEmail, setTouchedEmail] = useState11(false);
3590
- const [touchedPassword, setTouchedPassword] = useState11(false);
3591
- const [formSubmitAttempted, setFormSubmitAttempted] = useState11(false);
3592
- const validateEmail = useMemo7(() => {
3593
- if (email.length === 0) return null;
3594
- if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
3595
- return null;
3596
- }, [email]);
3597
- const validatePassword = useMemo7(() => {
3598
- if (password.length === 0) return null;
3599
- if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
3600
- return null;
3601
- }, [password]);
3602
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3603
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3604
- const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
3605
- const submit = useCallback7(async () => {
3606
- setFormSubmitAttempted(true);
3607
- if (!canSubmit) return false;
3608
- setSubmitting(true);
3609
- setError(null);
3610
- try {
3611
- await auth.login({ email, password });
3612
- return true;
3613
- } catch (err) {
3614
- setError(mapSdkError(err));
3615
- return false;
3616
- } finally {
3617
- setSubmitting(false);
3618
- }
3619
- }, [auth, email, password, canSubmit]);
3620
- return {
3621
- email,
3622
- setEmail,
3623
- emailError,
3624
- markEmailTouched: () => setTouchedEmail(true),
3625
- password,
3626
- setPassword,
3627
- passwordError,
3628
- markPasswordTouched: () => setTouchedPassword(true),
3629
- formSubmitAttempted,
3630
- submit,
3631
- submitting,
3632
- canSubmit,
3633
- error,
3634
- loginWithGoogle: () => auth.loginWithGoogle()
3635
- };
3636
- }
3637
-
3638
- // src/hooks/useSignupForm.ts
3639
- import { useCallback as useCallback8, useMemo as useMemo8, useState as useState12 } from "react";
3640
- import { useHook as useHook13 } from "@hook-sdk/sdk";
3641
- var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3642
- var MIN_PASSWORD2 = 8;
3643
- function useSignupForm() {
3644
- const { auth } = useHook13();
3645
- const [name, setName] = useState12("");
3646
- const [email, setEmail] = useState12("");
3647
- const [password, setPassword] = useState12("");
3648
- const [submitting, setSubmitting] = useState12(false);
3649
- const [error, setError] = useState12(null);
3650
- const [touchedName, setTouchedName] = useState12(false);
3651
- const [touchedEmail, setTouchedEmail] = useState12(false);
3652
- const [touchedPassword, setTouchedPassword] = useState12(false);
3653
- const [formSubmitAttempted, setFormSubmitAttempted] = useState12(false);
3654
- const validateName = useMemo8(() => {
3655
- if (name.length === 0) return null;
3656
- if (name.trim().length < 2) return "Nome muito curto.";
3657
- return null;
3658
- }, [name]);
3659
- const validateEmail = useMemo8(() => {
3660
- if (email.length === 0) return null;
3661
- if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
3662
- return null;
3663
- }, [email]);
3664
- const validatePassword = useMemo8(() => {
3665
- if (password.length === 0) return null;
3666
- if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
3667
- return null;
3668
- }, [password]);
3669
- const nameError = touchedName || formSubmitAttempted ? validateName : null;
3670
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3671
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3672
- const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
3673
- const submit = useCallback8(async () => {
3674
- setFormSubmitAttempted(true);
3675
- if (!canSubmit) return false;
3676
- setSubmitting(true);
3677
- setError(null);
3678
- try {
3679
- await auth.signup({ name, email, password });
3680
- return true;
3681
- } catch (err) {
3682
- setError(mapSdkError(err));
3683
- return false;
3684
- } finally {
3685
- setSubmitting(false);
3686
- }
3687
- }, [auth, name, email, password, canSubmit]);
3688
- return {
3689
- name,
3690
- setName,
3691
- nameError,
3692
- markNameTouched: () => setTouchedName(true),
3693
- email,
3694
- setEmail,
3695
- emailError,
3696
- markEmailTouched: () => setTouchedEmail(true),
3697
- password,
3698
- setPassword,
3699
- passwordError,
3700
- markPasswordTouched: () => setTouchedPassword(true),
3701
- formSubmitAttempted,
3702
- submit,
3703
- submitting,
3704
- canSubmit,
3705
- error,
3706
- loginWithGoogle: () => auth.loginWithGoogle()
3707
- };
3708
- }
3709
-
3710
- // src/hooks/useForgotForm.ts
3711
- import { useCallback as useCallback9, useMemo as useMemo9, useState as useState13 } from "react";
3712
- import { useHook as useHook14 } from "@hook-sdk/sdk";
3713
- var EMAIL_RE4 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3714
- function useForgotForm() {
3715
- const { auth } = useHook14();
3716
- const [email, setEmail] = useState13("");
3717
- const [submitting, setSubmitting] = useState13(false);
3718
- const [sent, setSent] = useState13(false);
3719
- const [error, setError] = useState13(null);
3720
- const [touchedEmail, setTouchedEmail] = useState13(false);
3721
- const [formSubmitAttempted, setFormSubmitAttempted] = useState13(false);
3722
- const validateEmail = useMemo9(() => {
3723
- if (email.length === 0) return null;
3724
- if (!EMAIL_RE4.test(email)) return "Formato de e-mail inv\xE1lido.";
3725
- return null;
3726
- }, [email]);
3727
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3728
- const canSubmit = email.length > 0 && validateEmail === null && !submitting;
3729
- const submit = useCallback9(async () => {
3730
- setFormSubmitAttempted(true);
3731
- if (!canSubmit) return false;
3732
- setSubmitting(true);
3733
- setError(null);
3734
- try {
3735
- await auth.forgot({ email });
3736
- setSent(true);
3737
- return true;
3738
- } catch (err) {
3739
- setError(mapSdkError(err));
3740
- return false;
3741
- } finally {
3742
- setSubmitting(false);
3743
- }
3744
- }, [auth, email, canSubmit]);
3745
- return {
3746
- email,
3747
- setEmail,
3748
- emailError,
3749
- markEmailTouched: () => setTouchedEmail(true),
3750
- formSubmitAttempted,
3751
- submit,
3752
- submitting,
3753
- canSubmit,
3754
- sent,
3755
- error
3756
- };
3757
- }
3758
-
3759
- // src/hooks/useResetForm.ts
3760
- import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo10, useState as useState14 } from "react";
3761
- import { useHook as useHook15 } from "@hook-sdk/sdk";
3762
- var MIN_PASSWORD3 = 12;
3763
- function useResetForm() {
3764
- const { auth } = useHook15();
3765
- const [token, setToken] = useState14(null);
3766
- const [password, setPassword] = useState14("");
3767
- const [confirm, setConfirm] = useState14("");
3768
- const [submitting, setSubmitting] = useState14(false);
3769
- const [done, setDone] = useState14(false);
3770
- const [error, setError] = useState14(null);
3771
- const [touchedPassword, setTouchedPassword] = useState14(false);
3772
- const [touchedConfirm, setTouchedConfirm] = useState14(false);
3773
- const [formSubmitAttempted, setFormSubmitAttempted] = useState14(false);
3774
- useEffect13(() => {
3775
- if (typeof window === "undefined") return;
3776
- const params = new URLSearchParams(window.location.search);
3777
- const t = params.get("token");
3778
- setToken(t && t.length > 0 ? t : null);
3779
- }, []);
3780
- const validatePassword = useMemo10(() => {
3781
- if (password.length === 0) return null;
3782
- if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
3783
- return null;
3784
- }, [password]);
3785
- const validateConfirm = useMemo10(() => {
3786
- if (confirm.length === 0) return null;
3787
- if (confirm !== password) return "Senhas n\xE3o coincidem.";
3788
- return null;
3789
- }, [confirm, password]);
3790
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3791
- const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
3792
- const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
3793
- const submit = useCallback10(async () => {
3794
- setFormSubmitAttempted(true);
3795
- if (!canSubmit || token === null) return;
3796
- setSubmitting(true);
3797
- setError(null);
3798
- try {
3799
- await auth.reset({ token, newPassword: password });
3800
- setDone(true);
3801
- if (typeof window !== "undefined") {
3802
- const url = new URL(window.location.href);
3803
- url.searchParams.delete("token");
3804
- url.searchParams.delete("screen");
3805
- window.history.replaceState({}, "", url.toString());
3806
- }
3807
- } catch (err) {
3808
- setError(mapSdkError(err));
3809
- } finally {
3810
- setSubmitting(false);
3811
- }
3812
- }, [auth, token, password, canSubmit]);
3813
- return {
3814
- token,
3815
- password,
3816
- setPassword,
3817
- passwordError,
3818
- markPasswordTouched: () => setTouchedPassword(true),
3819
- confirm,
3820
- setConfirm,
3821
- confirmError,
3822
- markConfirmTouched: () => setTouchedConfirm(true),
3823
- formSubmitAttempted,
3824
- submit,
3825
- submitting,
3826
- canSubmit,
3827
- done,
3828
- error
3829
- };
3830
- }
3831
-
3832
- // src/utils/price.ts
3833
- function formatBRL(cents) {
3834
- if (cents === null || cents === void 0) return "";
3835
- const reais = cents / 100;
3836
- return new Intl.NumberFormat("pt-BR", {
3837
- style: "currency",
3838
- currency: "BRL"
3839
- }).format(reais);
3840
- }
3841
- function monthlyFromYearly(yearlyCents) {
3842
- if (yearlyCents === null || yearlyCents === void 0) return 0;
3843
- return Math.round(yearlyCents / 12);
3844
- }
3845
- function dailyFromYearly(yearlyCents) {
3846
- if (yearlyCents === null || yearlyCents === void 0) return 0;
3847
- return Math.round(yearlyCents / 365);
3848
- }
3849
- function computeAnchorCents(baseCents, multiplier) {
3850
- if (multiplier === null || multiplier === void 0) return null;
3851
- if (!Number.isFinite(multiplier)) return null;
3852
- if (multiplier <= 1) return null;
3853
- return Math.round(baseCents * multiplier);
3854
- }
3855
- function discountPercent(anchorCents, realCents) {
3856
- if (anchorCents <= realCents) return 0;
3857
- return Math.floor((anchorCents - realCents) / anchorCents * 100);
3858
- }
3859
-
3860
- // src/hooks/useAuthPrimitives.ts
3861
- import { useEffect as useEffect14 } from "react";
3862
- import { useHook as useHook16 } from "@hook-sdk/sdk";
3863
- var warned = false;
3864
- function useAuthPrimitives() {
3865
- const { auth } = useHook16();
3866
- useEffect14(() => {
3867
- if (!warned && process.env.NODE_ENV !== "production") {
3868
- warned = true;
3869
- console.warn(
3870
- "[@hook-sdk/template] useAuthPrimitives() \xE9 escape hatch. Pra login/signup/forgot, use useLoginForm/useSignupForm/useForgotForm. Docs: docs/19-golden-template.md#escape-hatch"
3871
- );
3872
- }
3873
- }, []);
3874
- return {
3875
- login: auth.login,
3876
- signup: auth.signup,
3877
- logout: auth.logout,
3878
- logoutAll: auth.logoutAll,
3879
- forgot: auth.forgot,
3880
- resendVerify: auth.resendVerify,
3881
- changePassword: auth.changePassword,
3882
- changeEmail: auth.changeEmail,
3883
- refresh: auth.refresh
3884
- };
3885
- }
3886
-
3887
- // src/hooks/useAuth.ts
3888
- import { useHook as useHook17 } from "@hook-sdk/sdk";
3889
- function useAuth() {
3890
- const { user, authStatus, auth } = useHook17();
3891
- return {
3892
- user,
3893
- authStatus,
3894
- refresh: auth.refresh
3895
- };
3896
- }
3897
-
3898
- // src/index.ts
3899
- import { useTrackOnboardingStep } from "@hook-sdk/sdk";
3900
-
3901
- // src/hooks/useSubscription.ts
3902
- import { useHook as useHook18 } from "@hook-sdk/sdk";
3903
- function useSubscription() {
3904
- const { subscription } = useHook18();
3905
- return {
3906
- status: subscription.status()
3907
- };
3908
- }
3909
-
3910
- // src/hooks/useReminders.ts
3911
- import { useCallback as useCallback11, useEffect as useEffect15, useState as useState15 } from "react";
3912
- import { useHook as useHook19 } from "@hook-sdk/sdk";
3913
- function useReminders() {
3914
- const { push } = useHook19();
3915
- const r = push.reminders;
3916
- const [reminders, setReminders] = useState15([]);
3917
- const [loading, setLoading] = useState15(true);
3918
- const reload = useCallback11(async () => {
3919
- setLoading(true);
3920
- try {
3921
- const next = await r.list();
3922
- setReminders(next);
3923
- } finally {
3924
- setLoading(false);
3925
- }
3926
- }, [r]);
3927
- useEffect15(() => {
3928
- void reload();
3929
- }, [reload]);
3930
- const setReminder = useCallback11(async (input) => {
3931
- await r.set(input);
3932
- await reload();
3933
- }, [r, reload]);
3934
- const deleteReminder = useCallback11(async (slot) => {
3935
- await r.delete(slot);
3936
- await reload();
3937
- }, [r, reload]);
3938
- const schedule = useCallback11(async (items) => {
3939
- return r.schedule(items);
3940
- }, [r]);
3941
- const setFallbacks = useCallback11(async (items) => {
3942
- return r.setFallbacks(items);
3943
- }, [r]);
3944
- return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
3945
- }
3946
-
3947
- // src/hooks/useToast.ts
3948
- import { useCallback as useCallback12, useState as useState16 } from "react";
3949
- function useToast() {
3950
- const [items, setItems] = useState16([]);
3951
- const show = useCallback12((message, kind = "info") => {
3952
- const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3953
- setItems((prev) => [...prev, { id, message, kind }]);
3954
- setTimeout(() => {
3955
- setItems((prev) => prev.filter((t) => t.id !== id));
3956
- }, 4e3);
3957
- }, []);
3958
- const dismiss = useCallback12((id) => {
3959
- setItems((prev) => prev.filter((t) => t.id !== id));
3960
- }, []);
3961
- return { items, show, dismiss };
3962
- }
3963
-
3964
- // src/RouteBoundary.tsx
3965
- import { Routes as Routes2, Route as Route2 } from "react-router-dom";
3966
- import { jsx as jsx29, jsxs as jsxs20 } from "react/jsx-runtime";
3967
- function RouteBoundary({ children }) {
3968
- return /* @__PURE__ */ jsxs20(Routes2, { children: [
3969
- children,
3970
- /* @__PURE__ */ jsx29(Route2, { path: "*", element: /* @__PURE__ */ jsx29(DefaultNotFound, {}) })
3971
- ] });
3972
- }
3973
- function DefaultNotFound() {
3974
- return /* @__PURE__ */ jsx29("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
3975
- }
3976
-
3977
- // src/PreAuthShell.tsx
3978
- import { BrowserRouter as BrowserRouter2, MemoryRouter as MemoryRouter2, Routes as Routes3 } from "react-router-dom";
3979
- import { jsx as jsx30 } from "react/jsx-runtime";
3980
- function PreAuthShell({
3981
- basename,
3982
- testRouter,
3983
- testInitialEntries,
3984
- children
3543
+ // src/components/paywall/blocks/PaywallFeatures.tsx
3544
+ import { jsx as jsx39, jsxs as jsxs23 } from "react/jsx-runtime";
3545
+ function PaywallFeatures({
3546
+ items,
3547
+ IconComponent,
3548
+ className,
3549
+ itemClassName,
3550
+ iconClassName,
3551
+ render,
3552
+ renderItem
3985
3553
  }) {
3986
- if (testRouter === "memory") {
3987
- return /* @__PURE__ */ jsx30(MemoryRouter2, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ jsx30(Routes3, { children }) });
3554
+ if (render) {
3555
+ return /* @__PURE__ */ jsx39("div", { className, children: render({ items }) });
3556
+ }
3557
+ if (renderItem) {
3558
+ return /* @__PURE__ */ jsx39("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ jsx39("li", { children: renderItem(item, idx) }, idx)) });
3988
3559
  }
3989
- return /* @__PURE__ */ jsx30(BrowserRouter2, { basename, children: /* @__PURE__ */ jsx30(Routes3, { children }) });
3560
+ return /* @__PURE__ */ jsx39("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ jsxs23("li", { className: itemClassName, children: [
3561
+ IconComponent ? /* @__PURE__ */ jsx39(IconComponent, { className: iconClassName }) : /* @__PURE__ */ jsx39("span", { className: iconClassName, "aria-hidden": "true", children: "\u2713" }),
3562
+ /* @__PURE__ */ jsx39("span", { children: item })
3563
+ ] }, idx)) });
3990
3564
  }
3991
3565
 
3992
- // src/OnboardingFlow.tsx
3993
- import { useCallback as useCallback13, useEffect as useEffect16, useMemo as useMemo11, useRef as useRef7 } from "react";
3994
- import { usePersistedState as usePersistedState4, useHook as useHook20 } from "@hook-sdk/sdk";
3566
+ // src/components/paywall/blocks/PaywallFeaturesCard.tsx
3567
+ import { jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
3568
+ var DEFAULT_CARD_CLASSES = "rounded-xl border p-4";
3569
+ function PaywallFeaturesCard({
3570
+ title,
3571
+ items,
3572
+ className,
3573
+ cardClassName,
3574
+ titleClassName,
3575
+ itemClassName,
3576
+ renderItem
3577
+ }) {
3578
+ return /* @__PURE__ */ jsx40("div", { className, children: /* @__PURE__ */ jsxs24("div", { className: [DEFAULT_CARD_CLASSES, cardClassName].filter(Boolean).join(" "), children: [
3579
+ title ? /* @__PURE__ */ jsx40("div", { className: ["font-semibold mb-2", titleClassName].filter(Boolean).join(" "), children: title }) : null,
3580
+ /* @__PURE__ */ jsx40("ul", { children: items.map(
3581
+ (item, idx) => renderItem ? /* @__PURE__ */ jsx40("li", { children: renderItem(item, idx) }, idx) : /* @__PURE__ */ jsxs24("li", { className: itemClassName, children: [
3582
+ /* @__PURE__ */ jsx40("span", { "aria-hidden": "true", children: "\u2022" }),
3583
+ " ",
3584
+ /* @__PURE__ */ jsx40("span", { children: item })
3585
+ ] }, idx)
3586
+ ) })
3587
+ ] }) });
3588
+ }
3995
3589
 
3996
- // src/hooks/useOnboardingStep.ts
3997
- import { createContext as createContext3, useContext as useContext4 } from "react";
3998
- var OnboardingStepContext = createContext3(null);
3999
- function useOnboardingStep() {
4000
- const ctx = useContext4(OnboardingStepContext);
4001
- if (!ctx) {
4002
- throw new Error(
4003
- "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
4004
- );
3590
+ // src/components/paywall/blocks/PaywallTrophyBadge.tsx
3591
+ import { jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
3592
+ var DEFAULT_CHIP_CLASSES = "inline-flex items-center gap-1 px-3 py-1 rounded-full bg-yellow-100 text-yellow-900 text-sm font-medium";
3593
+ var FLOATING_CLASSES = "absolute top-2 right-2 z-10 shadow-md";
3594
+ function PaywallTrophyBadge({
3595
+ text,
3596
+ className,
3597
+ iconClassName,
3598
+ floating = false,
3599
+ render
3600
+ }) {
3601
+ if (render) {
3602
+ return /* @__PURE__ */ jsx41("div", { className, children: render({ text }) });
4005
3603
  }
4006
- return ctx;
3604
+ const rootClasses = [
3605
+ DEFAULT_CHIP_CLASSES,
3606
+ floating ? FLOATING_CLASSES : "",
3607
+ className
3608
+ ].filter(Boolean).join(" ");
3609
+ return /* @__PURE__ */ jsxs25("div", { className: rootClasses, children: [
3610
+ /* @__PURE__ */ jsx41("span", { className: iconClassName, "aria-hidden": "true", children: "\u{1F3C6}" }),
3611
+ /* @__PURE__ */ jsx41("span", { children: text })
3612
+ ] });
4007
3613
  }
4008
3614
 
4009
- // src/OnboardingFlow.tsx
4010
- import { jsx as jsx31 } from "react/jsx-runtime";
4011
- var isFilled = (v) => v != null && v !== "";
4012
- var CURRENT_STEP_FIELD = "currentStep";
4013
- function readPersistedStepIdx(draft) {
4014
- const raw = draft[CURRENT_STEP_FIELD];
4015
- return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
4016
- }
4017
- function OnboardingFlow({
4018
- steps,
4019
- screens,
4020
- onComplete,
4021
- persistKey
3615
+ // src/components/paywall/blocks/PaywallAnchorPrice.tsx
3616
+ import { jsx as jsx42 } from "react/jsx-runtime";
3617
+ var DEFAULT_CLASS2 = "text-sm opacity-60 line-through";
3618
+ function PaywallAnchorPrice({
3619
+ className,
3620
+ render
4022
3621
  }) {
4023
- const [draft, setDraft, status] = usePersistedState4(persistKey, {});
4024
- const draftRef = useRef7(draft);
4025
- draftRef.current = draft;
4026
- const idx = readPersistedStepIdx(draft);
4027
- const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
4028
- const setIdx = useCallback13(
4029
- (n) => {
4030
- setDraft((prev) => {
4031
- const prevIdx = readPersistedStepIdx(prev);
4032
- const nextIdx = typeof n === "function" ? n(prevIdx) : n;
4033
- return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
4034
- });
4035
- },
4036
- [setDraft]
4037
- );
4038
- const setValue = useCallback13(
4039
- (patch) => {
4040
- draftRef.current = { ...draftRef.current, ...patch };
4041
- setDraft((prev) => ({ ...prev, ...patch }));
4042
- },
4043
- [setDraft]
4044
- );
4045
- const step = steps[clampedIdx];
4046
- const hookCtx = useHook20();
4047
- const track2 = typeof hookCtx.track === "function" ? hookCtx.track : void 0;
4048
- useEffect16(() => {
4049
- if (status.loading) return;
4050
- if (!step) return;
4051
- if (!track2) return;
4052
- track2("onboarding_step_viewed", {
4053
- step: step.id,
4054
- step_index: clampedIdx,
4055
- total_steps: steps.length
4056
- });
4057
- }, [step?.id, clampedIdx, steps.length, status.loading, track2]);
4058
- const valid = useMemo11(
4059
- () => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
4060
- [draft, step]
4061
- );
4062
- const next = useCallback13(() => {
4063
- if (!step) return;
4064
- const current = draftRef.current;
4065
- const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
4066
- if (!validNow) return;
4067
- if (clampedIdx + 1 >= steps.length) {
4068
- onComplete(current);
4069
- } else {
4070
- setIdx(clampedIdx + 1);
4071
- }
4072
- }, [clampedIdx, onComplete, step, steps.length, setIdx]);
4073
- const prevStep = useCallback13(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
4074
- const ctx = useMemo11(
4075
- () => ({
4076
- stepIndex: clampedIdx,
4077
- totalSteps: steps.length,
4078
- value: draft,
4079
- setValue,
4080
- valid,
4081
- next,
4082
- prev: prevStep
4083
- }),
4084
- [clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
4085
- );
4086
- if (status.loading) {
3622
+ const { anchorPriceCents, cycle } = usePaywallContext();
3623
+ if (anchorPriceCents === null || anchorPriceCents === void 0 || anchorPriceCents <= 0) {
4087
3624
  return null;
4088
3625
  }
4089
- if (!step) {
4090
- throw new Error(
4091
- `[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
4092
- );
4093
- }
4094
- const Screen = screens[step.screen];
4095
- if (!Screen) {
4096
- throw new Error(
4097
- `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
4098
- );
3626
+ void cycle;
3627
+ const formatted = formatBRL(anchorPriceCents);
3628
+ const rootClasses = [DEFAULT_CLASS2, className].filter(Boolean).join(" ");
3629
+ if (render) {
3630
+ return /* @__PURE__ */ jsx42("span", { className: className || void 0, children: render({ anchorCents: anchorPriceCents, formatted }) });
4099
3631
  }
4100
- return /* @__PURE__ */ jsx31(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx31(Screen, {}) });
4101
- }
4102
-
4103
- // src/hooks/useFeature.ts
4104
- function useFeature(name) {
4105
- const config = useAppConfig();
4106
- return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
4107
- }
4108
-
4109
- // src/components/paywall/Paywall.tsx
4110
- import { useEffect as useEffect17, useMemo as useMemo12 } from "react";
4111
- import { useHook as useHook21 } from "@hook-sdk/sdk";
4112
-
4113
- // src/components/paywall/PaywallProvider.tsx
4114
- import { createContext as createContext4 } from "react";
4115
- import { jsx as jsx32 } from "react/jsx-runtime";
4116
- var PaywallContext = createContext4(null);
4117
- function PaywallProvider({ children }) {
4118
- const state = usePaywallState();
4119
- return /* @__PURE__ */ jsx32(PaywallContext.Provider, { value: state, children });
3632
+ return /* @__PURE__ */ jsx42("span", { className: rootClasses, children: formatted });
4120
3633
  }
4121
3634
 
4122
- // src/components/paywall/usePaywallContext.ts
4123
- import { useContext as useContext5 } from "react";
4124
- function usePaywallContext() {
4125
- const ctx = useContext5(PaywallContext);
4126
- if (!ctx) {
4127
- throw new Error("usePaywallContext must be used within <PaywallProvider>");
4128
- }
4129
- return ctx;
3635
+ // src/components/paywall/blocks/PaywallTestimonials.tsx
3636
+ import { jsx as jsx43, jsxs as jsxs26 } from "react/jsx-runtime";
3637
+ var DEFAULT_ROOT = "flex gap-3 overflow-x-auto snap-x snap-mandatory pb-2";
3638
+ var DEFAULT_CARD = "snap-start shrink-0 w-72 rounded-2xl border p-4 flex flex-col gap-2";
3639
+ var DEFAULT_AVATAR = "w-10 h-10 rounded-full object-cover";
3640
+ var DEFAULT_QUOTE = "text-sm leading-snug";
3641
+ var DEFAULT_NAME = "text-xs font-semibold opacity-80";
3642
+ var DEFAULT_STARS = "text-yellow-500 text-sm";
3643
+ function clampStars(n) {
3644
+ return Math.max(0, Math.min(5, Math.round(n)));
4130
3645
  }
4131
-
4132
- // src/components/paywall/PaywallMethodTabs.tsx
4133
- import { jsx as jsx33 } from "react/jsx-runtime";
4134
- function PaywallMethodTabs({
4135
- labels,
3646
+ function PaywallTestimonials({
3647
+ items,
4136
3648
  className,
4137
- tabClassName,
4138
- tabActiveClassName
3649
+ cardClassName,
3650
+ avatarClassName,
3651
+ quoteClassName,
3652
+ nameClassName,
3653
+ starsClassName,
3654
+ renderItem
4139
3655
  }) {
4140
- const { methods, selectedMethod, selectMethod } = usePaywallContext();
4141
- if (methods.length < 2) return null;
4142
- return /* @__PURE__ */ jsx33("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", className, children: methods.map((m) => {
4143
- const active = m === selectedMethod;
4144
- const label = labels[m] ?? m;
4145
- return /* @__PURE__ */ jsx33(
4146
- "button",
4147
- {
4148
- type: "button",
4149
- role: "tab",
4150
- "aria-selected": active,
4151
- "aria-controls": `paywall-tab-${m}`,
4152
- tabIndex: active ? 0 : -1,
4153
- onClick: () => selectMethod(m),
4154
- className: [tabClassName, active ? tabActiveClassName : ""].filter(Boolean).join(" "),
4155
- children: label
4156
- },
4157
- m
4158
- );
3656
+ const rootClasses = [DEFAULT_ROOT, className].filter(Boolean).join(" ");
3657
+ const cardClasses = [DEFAULT_CARD, cardClassName].filter(Boolean).join(" ");
3658
+ const avatarClasses = [DEFAULT_AVATAR, avatarClassName].filter(Boolean).join(" ");
3659
+ const quoteClasses = [DEFAULT_QUOTE, quoteClassName].filter(Boolean).join(" ");
3660
+ const nameClasses = [DEFAULT_NAME, nameClassName].filter(Boolean).join(" ");
3661
+ const starsClasses = [DEFAULT_STARS, starsClassName].filter(Boolean).join(" ");
3662
+ return /* @__PURE__ */ jsx43("div", { className: rootClasses, children: items.map((item, idx) => {
3663
+ if (renderItem) return renderItem(item, idx);
3664
+ const filled = clampStars(item.stars);
3665
+ const empty = 5 - filled;
3666
+ return /* @__PURE__ */ jsxs26("div", { className: cardClasses, children: [
3667
+ /* @__PURE__ */ jsxs26("div", { className: "flex items-center gap-2", children: [
3668
+ item.avatar ? /* @__PURE__ */ jsx43(
3669
+ "img",
3670
+ {
3671
+ src: item.avatar,
3672
+ alt: "",
3673
+ loading: "lazy",
3674
+ className: avatarClasses,
3675
+ "aria-hidden": "true"
3676
+ }
3677
+ ) : null,
3678
+ /* @__PURE__ */ jsx43("div", { className: nameClasses, children: item.name })
3679
+ ] }),
3680
+ /* @__PURE__ */ jsxs26("div", { className: starsClasses, "aria-label": `${filled} de 5 estrelas`, children: [
3681
+ "\u2605".repeat(filled),
3682
+ "\u2606".repeat(empty)
3683
+ ] }),
3684
+ /* @__PURE__ */ jsx43("p", { className: quoteClasses, children: item.quote })
3685
+ ] }, idx);
4159
3686
  }) });
4160
3687
  }
4161
3688
 
4162
- // src/components/paywall/PaywallMethodContent.tsx
4163
- import { jsx as jsx34 } from "react/jsx-runtime";
4164
- function PaywallMethodContent({ copy, className, rowClassName }) {
4165
- const { selectedMethod, hasConsumedTrial } = usePaywallContext();
4166
- const useCardConsumed = selectedMethod === "card" && hasConsumedTrial && copy.cardConsumedTrial;
4167
- const rows = useCardConsumed ? copy.cardConsumedTrial.bodyRows : selectedMethod === "pix-auto" || selectedMethod === "pix-once" ? copy.pix.bodyRows : copy.card.bodyRows;
4168
- return /* @__PURE__ */ jsx34("div", { role: "tabpanel", id: `paywall-tab-${selectedMethod}`, className, children: rows.map((row, i) => /* @__PURE__ */ jsx34("div", { className: rowClassName, children: row }, i)) });
4169
- }
4170
-
4171
- // src/components/paywall/PaywallCyclePicker.tsx
4172
- import { jsx as jsx35, jsxs as jsxs21 } from "react/jsx-runtime";
4173
- var VARIANT_CLASSES = {
4174
- default: { card: "", cardSelected: "" },
4175
- "premium-gold": {
4176
- card: "",
4177
- cardSelected: "border-2 border-yellow-400/80 ring-2 ring-yellow-400/20"
4178
- },
4179
- "pink-pill": {
4180
- card: "rounded-2xl",
4181
- cardSelected: "border-2 border-pink-500"
4182
- }
3689
+ // src/components/paywall/blocks/PaywallStatsRow.tsx
3690
+ import { jsx as jsx44, jsxs as jsxs27 } from "react/jsx-runtime";
3691
+ var DEFAULT_ROOT2 = "grid grid-cols-3 gap-4";
3692
+ var DEFAULT_CELL = "flex flex-col items-center text-center";
3693
+ var DEFAULT_VALUE = "text-2xl font-bold";
3694
+ var DEFAULT_LABEL = "text-xs opacity-70";
3695
+ function PaywallStatsRow({
3696
+ stats,
3697
+ className,
3698
+ cellClassName,
3699
+ valueClassName,
3700
+ labelClassName,
3701
+ renderCell
3702
+ }) {
3703
+ const rootClasses = [DEFAULT_ROOT2, className].filter(Boolean).join(" ");
3704
+ const cellClasses = [DEFAULT_CELL, cellClassName].filter(Boolean).join(" ");
3705
+ const valueClasses = [DEFAULT_VALUE, valueClassName].filter(Boolean).join(" ");
3706
+ const labelClasses = [DEFAULT_LABEL, labelClassName].filter(Boolean).join(" ");
3707
+ return /* @__PURE__ */ jsx44("div", { className: rootClasses, children: stats.map((stat, idx) => {
3708
+ if (renderCell) return renderCell(stat, idx);
3709
+ return /* @__PURE__ */ jsxs27("div", { className: cellClasses, children: [
3710
+ stat.icon ? /* @__PURE__ */ jsx44("div", { "aria-hidden": "true", children: stat.icon }) : null,
3711
+ /* @__PURE__ */ jsx44("div", { className: valueClasses, children: stat.value }),
3712
+ /* @__PURE__ */ jsx44("div", { className: labelClasses, children: stat.label })
3713
+ ] }, idx);
3714
+ }) });
3715
+ }
3716
+
3717
+ // src/components/paywall/blocks/PaywallFinePrint.tsx
3718
+ import { jsx as jsx45 } from "react/jsx-runtime";
3719
+ var DEFAULT_CLASS3 = "text-xs opacity-60 leading-snug";
3720
+ var CYCLE_LABEL2 = {
3721
+ MONTHLY: "mensal",
3722
+ YEARLY: "anual"
4183
3723
  };
4184
- function PaywallCyclePicker({
4185
- labels,
3724
+ function PaywallFinePrint({
3725
+ template,
4186
3726
  className,
4187
- cardClassName,
4188
- cardSelectedClassName,
4189
- anchorClassName,
4190
- variant = "default",
4191
3727
  render
4192
3728
  }) {
4193
- const ctx = usePaywallContext();
4194
- const { cycle: selected, setCycle, plan, anchorPriceCents } = ctx;
4195
- const cycles = ["MONTHLY", "YEARLY"];
3729
+ const {
3730
+ currentPriceCents,
3731
+ cycle,
3732
+ trialDaysCard,
3733
+ trialDaysPix,
3734
+ selectedMethod
3735
+ } = usePaywallContext();
3736
+ const trialDays = selectedMethod === "card" ? trialDaysCard : trialDaysPix;
3737
+ const cycleLabel = CYCLE_LABEL2[cycle] ?? cycle.toLowerCase();
3738
+ const priceFormatted = formatBRL(currentPriceCents ?? 0);
3739
+ const rootClasses = [DEFAULT_CLASS3, className].filter(Boolean).join(" ");
4196
3740
  if (render) {
4197
- return /* @__PURE__ */ jsx35("div", { className, children: render({ cycles, selected, setCycle, plan, anchorPriceCents }) });
3741
+ return /* @__PURE__ */ jsx45("p", { className: className || void 0, children: render({
3742
+ currentPriceCents: currentPriceCents ?? 0,
3743
+ cycle,
3744
+ trialDays: trialDays ?? 0,
3745
+ selectedMethod
3746
+ }) });
4198
3747
  }
4199
- if (cycles.length < 2) return null;
4200
- const v = VARIANT_CLASSES[variant];
4201
- const composedCardClassName = [v.card, cardClassName].filter(Boolean).join(" ");
4202
- const composedCardSelectedClassName = [v.cardSelected, cardSelectedClassName].filter(Boolean).join(" ");
4203
- const monthlyCents = plan?.monthlyCents ?? 0;
4204
- const yearlyCents = plan?.yearlyCents ?? 0;
4205
- const anchorMonthly = plan?.anchorMonthlyCents ?? null;
4206
- const anchorYearly = plan?.anchorYearlyCents ?? null;
4207
- return /* @__PURE__ */ jsx35(
4208
- "div",
4209
- {
4210
- role: "radiogroup",
4211
- "aria-label": "Ciclo de cobran\xE7a",
4212
- className: ["flex flex-row gap-2", className].filter(Boolean).join(" "),
4213
- children: cycles.map((c) => {
4214
- const active = c === selected;
4215
- const label = c === "YEARLY" ? labels.annualLabel : labels.monthlyLabel;
4216
- const suffix = c === "YEARLY" ? labels.annualSuffix : labels.monthlySuffix;
4217
- const mainCents = c === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents;
4218
- const anchorCents = c === "YEARLY" ? anchorYearly : anchorMonthly;
4219
- return /* @__PURE__ */ jsxs21(
4220
- "button",
4221
- {
4222
- type: "button",
4223
- role: "radio",
4224
- "aria-checked": active,
4225
- onClick: () => setCycle(c),
4226
- className: [
4227
- "flex flex-col items-center gap-0.5",
4228
- composedCardClassName,
4229
- active ? composedCardSelectedClassName : ""
4230
- ].filter(Boolean).join(" "),
4231
- children: [
4232
- /* @__PURE__ */ jsx35("span", { className: "font-bold text-base leading-tight", children: formatBRL(mainCents) }),
4233
- /* @__PURE__ */ jsx35("span", { className: "text-xs opacity-70 leading-tight", children: suffix }),
4234
- /* @__PURE__ */ jsx35("span", { className: "text-xs opacity-60 leading-tight", children: label }),
4235
- anchorCents != null && anchorCents > mainCents ? /* @__PURE__ */ jsx35("span", { className: anchorClassName ?? "text-xs opacity-50", children: /* @__PURE__ */ jsx35("s", { children: formatBRL(anchorCents) }) }) : null
4236
- ]
4237
- },
4238
- c
4239
- );
4240
- })
4241
- }
4242
- );
3748
+ const text = template.replaceAll("{price}", priceFormatted).replaceAll("{trialDays}", String(trialDays ?? 0)).replaceAll("{cycle}", cycleLabel);
3749
+ return /* @__PURE__ */ jsx45("p", { className: rootClasses, children: text });
4243
3750
  }
4244
3751
 
4245
- // src/components/paywall/Paywall.tsx
4246
- import { jsx as jsx36, jsxs as jsxs22 } from "react/jsx-runtime";
4247
- var NBSP = "\xA0";
4248
- function Paywall({
4249
- copy,
4250
- themeClasses = {},
4251
- slots = {},
4252
- onBeforeCheckout
3752
+ // src/components/paywall/blocks/PaywallTrustLine.tsx
3753
+ import { jsx as jsx46, jsxs as jsxs28 } from "react/jsx-runtime";
3754
+ var DEFAULT_ROOT3 = "flex items-center gap-3";
3755
+ var DEFAULT_ITEM = "flex items-center gap-1.5 text-xs";
3756
+ function PaywallTrustLine({
3757
+ items,
3758
+ className,
3759
+ itemClassName,
3760
+ renderItem
4253
3761
  }) {
4254
- return /* @__PURE__ */ jsx36(PaywallProvider, { children: /* @__PURE__ */ jsx36(
4255
- PaywallInner,
4256
- {
4257
- copy,
4258
- themeClasses,
4259
- slots,
4260
- onBeforeCheckout
4261
- }
4262
- ) });
3762
+ const rootClasses = [DEFAULT_ROOT3, className].filter(Boolean).join(" ");
3763
+ const itemClasses = [DEFAULT_ITEM, itemClassName].filter(Boolean).join(" ");
3764
+ return /* @__PURE__ */ jsx46("div", { className: rootClasses, children: items.map((item, idx) => {
3765
+ if (renderItem) return renderItem(item, idx);
3766
+ return /* @__PURE__ */ jsxs28("span", { className: itemClasses, children: [
3767
+ /* @__PURE__ */ jsx46("span", { "aria-hidden": "true", children: item.icon }),
3768
+ /* @__PURE__ */ jsx46("span", { children: item.text })
3769
+ ] }, idx);
3770
+ }) });
4263
3771
  }
4264
- function PaywallInner({
4265
- copy,
4266
- themeClasses = {},
4267
- slots = {},
4268
- onBeforeCheckout
3772
+
3773
+ // src/components/paywall/blocks/PaywallStickyFooter.tsx
3774
+ import { jsx as jsx47 } from "react/jsx-runtime";
3775
+ var DEFAULT_CLASSES = "sticky bottom-0 left-0 right-0 bg-background";
3776
+ var SAFE_AREA_CLASS = "pb-[env(safe-area-inset-bottom)]";
3777
+ function PaywallStickyFooter({
3778
+ children,
3779
+ className,
3780
+ safeAreaInsets = true
4269
3781
  }) {
4270
- const { track: track2 } = useHook21();
4271
- const s = usePaywallContext();
4272
- const priceLabel = formatBRL(s.currentPriceCents).replace(new RegExp(NBSP, "g"), " ");
4273
- const trialDaysCardLabel = String(s.trialDaysCard);
4274
- const ctaLabel = useMemo12(() => {
4275
- if (s.isFree) return copy.freeCta ?? "Come\xE7ar agora";
4276
- if (s.selectedMethod === "card") {
4277
- if (s.hasConsumedTrial && copy.cardConsumedTrial) {
4278
- return interp(copy.cardConsumedTrial.ctaTemplate, {
4279
- price: priceLabel,
4280
- days: trialDaysCardLabel
4281
- });
4282
- }
4283
- if (s.trialDaysCard > 0) {
4284
- return interp(copy.card.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
4285
- }
4286
- return copy.cardConsumedTrial ? interp(copy.cardConsumedTrial.ctaTemplate, {
4287
- price: priceLabel,
4288
- days: trialDaysCardLabel
4289
- }) : `Assinar por ${priceLabel}`;
3782
+ const classes = [DEFAULT_CLASSES, safeAreaInsets ? SAFE_AREA_CLASS : null, className].filter(Boolean).join(" ");
3783
+ return /* @__PURE__ */ jsx47("div", { className: classes, children });
3784
+ }
3785
+
3786
+ // src/defaults/CheckoutPageDefault.tsx
3787
+ import { Fragment as Fragment7, jsx as jsx48, jsxs as jsxs29 } from "react/jsx-runtime";
3788
+ var INTENT_KEY = "hook:paywall:intent";
3789
+ var PIX_PAYLOAD_KEY = "hook:paywall:pix-pending";
3790
+ function readIntent() {
3791
+ if (typeof window === "undefined") return {};
3792
+ try {
3793
+ const raw = sessionStorage.getItem(INTENT_KEY);
3794
+ if (!raw) return {};
3795
+ return JSON.parse(raw);
3796
+ } catch {
3797
+ return {};
3798
+ }
3799
+ }
3800
+ function formatBrl(cents) {
3801
+ return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(cents / 100);
3802
+ }
3803
+ function formatCardNumber(v) {
3804
+ const digits = v.replace(/\D/g, "").slice(0, 16);
3805
+ return digits.replace(/(.{4})/g, "$1 ").trim();
3806
+ }
3807
+ function formatExpiryMmAa(v) {
3808
+ const d = v.replace(/\D/g, "").slice(0, 4);
3809
+ if (d.length < 3) return d;
3810
+ return d.slice(0, 2) + "/" + d.slice(2);
3811
+ }
3812
+ function parseExpiryMmAa(v) {
3813
+ const d = v.replace(/\D/g, "");
3814
+ return { month: d.slice(0, 2), year: d.slice(2, 4) };
3815
+ }
3816
+ function formatCpf(v) {
3817
+ const d = v.replace(/\D/g, "").slice(0, 11);
3818
+ if (d.length <= 3) return d;
3819
+ if (d.length <= 6) return d.slice(0, 3) + "." + d.slice(3);
3820
+ if (d.length <= 9) return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6);
3821
+ return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6, 9) + "-" + d.slice(9);
3822
+ }
3823
+ function detectCardBrand(num) {
3824
+ const n = num.replace(/\s/g, "");
3825
+ if (/^4/.test(n)) return "VISA";
3826
+ if (/^(5[1-5]|2[2-7])/.test(n)) return "MASTER";
3827
+ if (/^3[47]/.test(n)) return "AMEX";
3828
+ if (/^(4011|4312|4389|4514|6011|6362|6363)/.test(n)) return "ELO";
3829
+ if (/^(606282|3841)/.test(n)) return "HIPER";
3830
+ return "";
3831
+ }
3832
+ function CheckoutPageDefault() {
3833
+ const navigate = useNavigate2();
3834
+ const plan = usePlan();
3835
+ const intent = useMemo6(readIntent, []);
3836
+ const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3837
+ const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3838
+ const form = useCheckoutForm({ defaultMethod, defaultCycle });
3839
+ const [expiryMmAa, setExpiryMmAa] = useState11("");
3840
+ useEffect13(() => {
3841
+ const { month, year } = parseExpiryMmAa(expiryMmAa);
3842
+ if (month !== form.card.expiryMonth || year !== form.card.expiryYear) {
3843
+ form.setCard({ expiryMonth: month, expiryYear: year });
4290
3844
  }
4291
- return interp(copy.pix.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
4292
- }, [
4293
- s.isFree,
4294
- s.selectedMethod,
4295
- s.hasConsumedTrial,
4296
- s.trialDaysCard,
4297
- copy,
4298
- priceLabel,
4299
- trialDaysCardLabel
4300
- ]);
4301
- const switchHint = useMemo12(() => {
4302
- if (s.methods.length < 2) return void 0;
4303
- return s.selectedMethod === "card" ? copy.card.switchHint : copy.pix.switchHint;
4304
- }, [s.methods.length, s.selectedMethod, copy]);
4305
- useEffect17(() => {
4306
- if (!s.initialLoadComplete) return;
4307
- track2("paywall_view", {
4308
- default_method: s.selectedMethod,
4309
- default_cycle: s.cycle,
4310
- available_methods: s.methods
4311
- });
4312
- }, [s.initialLoadComplete]);
4313
- const handleCta = async () => {
4314
- track2("paywall_cta_clicked", {
4315
- method: s.selectedMethod,
4316
- cycle: s.cycle,
4317
- price_cents: s.currentPriceCents,
4318
- had_consumed_trial: s.hasConsumedTrial
4319
- });
4320
- if (onBeforeCheckout) {
4321
- await onBeforeCheckout(s.selectedMethod, s.cycle);
3845
+ }, [expiryMmAa]);
3846
+ useEffect13(() => {
3847
+ if (form.emailTaken && form.loginUrl) {
3848
+ const t = setTimeout(() => navigate(form.loginUrl), 1200);
3849
+ return () => clearTimeout(t);
3850
+ }
3851
+ }, [form.emailTaken, form.loginUrl, navigate]);
3852
+ const planInfo = plan.data ? {
3853
+ priceCents: plan.data.priceCents,
3854
+ yearlyPriceCents: plan.data.yearlyPriceCents,
3855
+ trialDays: plan.data.trialDays
3856
+ } : null;
3857
+ const annual = form.cycle === "YEARLY";
3858
+ const cyclePrice = useMemo6(() => {
3859
+ if (!planInfo) return null;
3860
+ return annual ? planInfo.yearlyPriceCents ?? planInfo.priceCents * 12 : planInfo.priceCents;
3861
+ }, [planInfo, annual]);
3862
+ const monthlyText = useMemo6(() => {
3863
+ if (!planInfo) return "";
3864
+ const monthly = annual && planInfo.yearlyPriceCents ? Math.round(planInfo.yearlyPriceCents / 12) : planInfo.priceCents;
3865
+ return formatBrl(monthly);
3866
+ }, [planInfo, annual]);
3867
+ const todayCents = useMemo6(() => {
3868
+ if (form.method === "pix-auto") return cyclePrice ?? 0;
3869
+ const trialDays2 = planInfo?.trialDays ?? 0;
3870
+ if (trialDays2 > 0) return 0;
3871
+ return cyclePrice ?? 0;
3872
+ }, [form.method, cyclePrice, planInfo]);
3873
+ const todayAmount = formatBrl(todayCents);
3874
+ const cyclePriceText = cyclePrice !== null ? formatBrl(cyclePrice) : "";
3875
+ const annualSavingsCents = useMemo6(() => {
3876
+ if (!planInfo || !planInfo.yearlyPriceCents) return 0;
3877
+ return planInfo.priceCents * 12 - planInfo.yearlyPriceCents;
3878
+ }, [planInfo]);
3879
+ const trialDays = planInfo?.trialDays ?? 7;
3880
+ const cardBrand = detectCardBrand(form.card.number);
3881
+ async function onSubmit(e) {
3882
+ e.preventDefault();
3883
+ const result = await form.submit();
3884
+ if (!result) return;
3885
+ if (form.method === "pix-auto" && result.pix_qr_payload) {
3886
+ try {
3887
+ sessionStorage.setItem(
3888
+ PIX_PAYLOAD_KEY,
3889
+ JSON.stringify({
3890
+ payload: result.pix_qr_payload,
3891
+ base64: result.pix_qr_base64 ?? null,
3892
+ subscriptionId: result.subscription_id,
3893
+ pixAuthorizationId: result.pix_authorization_id ?? null
3894
+ })
3895
+ );
3896
+ } catch {
3897
+ }
3898
+ navigate(result.redirect.replace(/^.*\/app\/[^/]+/, ""));
4322
3899
  return;
4323
3900
  }
4324
- await s.submit();
4325
- };
4326
- const ctaTheme = s.selectedMethod === "card" ? themeClasses.ctaCard : themeClasses.ctaPix;
4327
- return /* @__PURE__ */ jsxs22("div", { className: themeClasses.container, children: [
4328
- slots.heroSlot,
4329
- /* @__PURE__ */ jsx36("h1", { className: themeClasses.headline, children: copy.headline }),
4330
- /* @__PURE__ */ jsx36("ul", { children: copy.features.map((f) => /* @__PURE__ */ jsxs22("li", { className: themeClasses.feature, children: [
4331
- "\u2713 ",
4332
- /* @__PURE__ */ jsx36("span", { children: f })
4333
- ] }, f)) }),
4334
- copy.socialProof ? /* @__PURE__ */ jsx36("p", { className: themeClasses.socialProof, children: copy.socialProof }) : null,
4335
- slots.cyclePickerSlot ?? /* @__PURE__ */ jsx36(
4336
- PaywallCyclePicker,
4337
- {
4338
- labels: copy.cycle,
4339
- cardClassName: themeClasses.cycleCard,
4340
- cardSelectedClassName: themeClasses.cycleCardSelected,
4341
- anchorClassName: themeClasses.anchorPrice
4342
- }
4343
- ),
4344
- /* @__PURE__ */ jsx36(
4345
- PaywallMethodTabs,
4346
- {
4347
- labels: { "pix-auto": copy.pix.tabLabel, card: copy.card.tabLabel },
4348
- className: themeClasses.tabs,
4349
- tabClassName: themeClasses.tab,
4350
- tabActiveClassName: themeClasses.tabActive
4351
- }
4352
- ),
4353
- /* @__PURE__ */ jsx36(
4354
- PaywallMethodContent,
4355
- {
4356
- copy: {
4357
- pix: interpolateCopy(copy.pix, priceLabel, trialDaysCardLabel),
4358
- card: interpolateCopy(copy.card, priceLabel, trialDaysCardLabel),
4359
- cardConsumedTrial: copy.cardConsumedTrial ? {
4360
- bodyRows: copy.cardConsumedTrial.bodyRows.map(
4361
- (r) => interp(r, { price: priceLabel, days: trialDaysCardLabel })
4362
- ),
4363
- ctaTemplate: copy.cardConsumedTrial.ctaTemplate
4364
- } : void 0
4365
- },
4366
- className: themeClasses.tabContent,
4367
- rowClassName: themeClasses.tabContentRow
4368
- }
4369
- ),
4370
- slots.beforeCtaSlot,
4371
- /* @__PURE__ */ jsxs22("div", { children: [
4372
- /* @__PURE__ */ jsx36(
3901
+ navigate(result.redirect.replace(/^.*\/app\/[^/]+/, "") || "/");
3902
+ }
3903
+ return /* @__PURE__ */ jsx48("div", { className: "flex-1 flex flex-col bg-background min-h-0", children: /* @__PURE__ */ jsxs29("form", { onSubmit, className: "flex-1 flex flex-col min-h-0", children: [
3904
+ /* @__PURE__ */ jsxs29("div", { className: "flex-1 overflow-y-auto pb-4", children: [
3905
+ form.emailTaken ? /* @__PURE__ */ jsx48("div", { className: "px-5 pt-4", children: /* @__PURE__ */ jsxs29("div", { role: "alert", className: "rounded-2xl bg-destructive/10 p-4 text-sm text-destructive border border-destructive/20", children: [
3906
+ "Esse e-mail j\xE1 tem conta nesse app.",
3907
+ " ",
3908
+ /* @__PURE__ */ jsx48("a", { href: form.loginUrl ?? "/signin", className: "underline font-semibold", children: "Entrar agora" })
3909
+ ] }) }) : null,
3910
+ form.error ? /* @__PURE__ */ jsx48("div", { className: "px-5 pt-4", children: /* @__PURE__ */ jsx48("div", { role: "alert", className: "rounded-2xl bg-destructive/10 p-4 text-sm text-destructive border border-destructive/20", children: form.error.message || "N\xE3o foi poss\xEDvel concluir o pagamento. Tente novamente." }) }) : null,
3911
+ /* @__PURE__ */ jsx48("div", { className: "px-5 pt-4", children: form.method === "card" ? /* @__PURE__ */ jsxs29("div", { className: "rounded-2xl bg-card border-[1.5px] border-foreground p-3.5", children: [
3912
+ /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2 mb-2", children: [
3913
+ /* @__PURE__ */ jsx48(ShieldIcon, { className: "w-4 h-4" }),
3914
+ /* @__PURE__ */ jsx48("div", { className: "text-sm font-bold", children: "Voc\xEA N\xC3O ser\xE1 cobrada hoje" })
3915
+ ] }),
3916
+ /* @__PURE__ */ jsxs29("div", { className: "flex justify-between items-baseline text-sm text-muted-foreground", children: [
3917
+ /* @__PURE__ */ jsx48("span", { children: "R$ 0,00 agora" }),
3918
+ /* @__PURE__ */ jsx48("span", { className: "opacity-50", children: "\xB7" }),
3919
+ /* @__PURE__ */ jsxs29("span", { children: [
3920
+ monthlyText,
3921
+ "/m\xEAs ap\xF3s ",
3922
+ trialDays,
3923
+ " dias"
3924
+ ] })
3925
+ ] }),
3926
+ /* @__PURE__ */ jsxs29("div", { className: "mt-2.5 text-[11px] text-muted-foreground flex items-center gap-1.5", children: [
3927
+ /* @__PURE__ */ jsx48(BellIcon, { className: "w-2.5 h-2.5" }),
3928
+ "Avisamos por email 2 dias antes da primeira cobran\xE7a"
3929
+ ] })
3930
+ ] }) : /* @__PURE__ */ jsxs29("div", { className: "rounded-2xl p-3.5 bg-emerald-50 border-[1.5px] border-emerald-600/60", children: [
3931
+ /* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-2 mb-2 text-emerald-900", children: [
3932
+ /* @__PURE__ */ jsx48(ShieldIcon, { className: "w-4 h-4" }),
3933
+ /* @__PURE__ */ jsxs29("div", { className: "text-sm font-bold", children: [
3934
+ "Garantia incondicional de ",
3935
+ trialDays,
3936
+ " dias"
3937
+ ] })
3938
+ ] }),
3939
+ /* @__PURE__ */ jsxs29("div", { className: "text-sm text-emerald-900 leading-snug", children: [
3940
+ "Voc\xEA paga hoje via Pix.",
3941
+ /* @__PURE__ */ jsx48("br", {}),
3942
+ "N\xE3o gostou em ",
3943
+ trialDays,
3944
+ " dias? Devolvemos ",
3945
+ /* @__PURE__ */ jsx48("b", { children: "100%" }),
3946
+ " sem perguntas \u2014 direto pelo app."
3947
+ ] })
3948
+ ] }) }),
3949
+ /* @__PURE__ */ jsxs29("section", { className: "px-5 pt-5", children: [
3950
+ /* @__PURE__ */ jsx48("h2", { className: "font-display text-2xl mb-3.5 leading-tight text-foreground", children: "Quase l\xE1." }),
3951
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "Email" }),
3952
+ /* @__PURE__ */ jsx48(
3953
+ FieldInput,
3954
+ {
3955
+ type: "email",
3956
+ inputMode: "email",
3957
+ autoComplete: "email",
3958
+ autoCapitalize: "none",
3959
+ autoCorrect: "off",
3960
+ spellCheck: false,
3961
+ placeholder: "seu@email.com",
3962
+ value: form.email,
3963
+ onChange: form.setEmail,
3964
+ onBlur: form.markEmailTouched,
3965
+ error: form.emailError,
3966
+ valid: form.emailStatus === "available"
3967
+ }
3968
+ ),
3969
+ !form.emailError && /* @__PURE__ */ jsx48(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" }),
3970
+ /* @__PURE__ */ jsx48("div", { className: "h-3" }),
3971
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "Nome completo" }),
3972
+ /* @__PURE__ */ jsx48(
3973
+ FieldInput,
3974
+ {
3975
+ type: "text",
3976
+ autoComplete: "name",
3977
+ placeholder: "como est\xE1 no documento",
3978
+ value: form.name,
3979
+ onChange: form.setName,
3980
+ onBlur: form.markNameTouched,
3981
+ error: form.nameError,
3982
+ valid: !!form.name && !form.nameError
3983
+ }
3984
+ ),
3985
+ /* @__PURE__ */ jsx48("div", { className: "h-3" }),
3986
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "CPF" }),
3987
+ /* @__PURE__ */ jsx48(
3988
+ FieldInput,
3989
+ {
3990
+ type: "text",
3991
+ inputMode: "numeric",
3992
+ placeholder: "000.000.000-00",
3993
+ value: form.cpf,
3994
+ onChange: (v) => form.setCpf(formatCpf(v)),
3995
+ onBlur: form.markCpfTouched,
3996
+ error: form.cpfError,
3997
+ valid: !!form.cpf && !form.cpfError
3998
+ }
3999
+ ),
4000
+ form.method === "card" ? /* @__PURE__ */ jsxs29(Fragment7, { children: [
4001
+ /* @__PURE__ */ jsx48("div", { className: "h-3" }),
4002
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "Telefone" }),
4003
+ /* @__PURE__ */ jsx48(
4004
+ FieldInput,
4005
+ {
4006
+ type: "tel",
4007
+ inputMode: "tel",
4008
+ autoComplete: "tel",
4009
+ placeholder: "(11) 99999-9999",
4010
+ value: form.phone,
4011
+ onChange: form.setPhone,
4012
+ onBlur: form.markPhoneTouched,
4013
+ error: form.phoneError,
4014
+ valid: !!form.phone && !form.phoneError
4015
+ }
4016
+ ),
4017
+ !form.phoneError && /* @__PURE__ */ jsx48(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
4018
+ ] }) : null
4019
+ ] }),
4020
+ /* @__PURE__ */ jsxs29("section", { className: "px-5 pt-5", children: [
4021
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "Forma de pagamento" }),
4022
+ /* @__PURE__ */ jsxs29("div", { role: "tablist", className: "flex gap-1.5 bg-muted p-1 rounded-xl", children: [
4023
+ /* @__PURE__ */ jsx48(
4024
+ TabButton,
4025
+ {
4026
+ active: form.method === "card",
4027
+ onClick: () => form.setMethod("card"),
4028
+ icon: /* @__PURE__ */ jsx48(CardIcon, { className: "w-3.5 h-3.5" }),
4029
+ label: "Cart\xE3o",
4030
+ subtitle: trialDays > 0 ? `${trialDays} dias gr\xE1tis` : "pague hoje",
4031
+ subtitleActiveClass: "text-emerald-700"
4032
+ }
4033
+ ),
4034
+ /* @__PURE__ */ jsx48(
4035
+ TabButton,
4036
+ {
4037
+ active: form.method === "pix-auto",
4038
+ onClick: () => form.setMethod("pix-auto"),
4039
+ icon: /* @__PURE__ */ jsx48(PixIcon, { className: "w-3.5 h-3.5" }),
4040
+ label: "Pix",
4041
+ subtitle: `pague hoje \xB7 garantia ${trialDays}d`,
4042
+ subtitleActiveClass: "text-foreground/70"
4043
+ }
4044
+ )
4045
+ ] })
4046
+ ] }),
4047
+ form.method === "card" ? /* @__PURE__ */ jsxs29("section", { className: "px-5 pt-3.5", children: [
4048
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "N\xFAmero do cart\xE3o" }),
4049
+ /* @__PURE__ */ jsxs29("div", { className: "relative", children: [
4050
+ /* @__PURE__ */ jsx48(
4051
+ FieldInput,
4052
+ {
4053
+ type: "text",
4054
+ inputMode: "numeric",
4055
+ autoComplete: "cc-number",
4056
+ placeholder: "0000 0000 0000 0000",
4057
+ value: form.card.number,
4058
+ onChange: (v) => form.setCard({ number: formatCardNumber(v) }),
4059
+ style: cardBrand ? { paddingRight: "4.5rem" } : void 0
4060
+ }
4061
+ ),
4062
+ cardBrand && /* @__PURE__ */ jsx48("span", { className: "absolute right-3 top-1/2 -translate-y-1/2 text-[10px] font-bold tracking-wide px-1.5 py-0.5 rounded bg-muted text-muted-foreground", children: cardBrand })
4063
+ ] }),
4064
+ /* @__PURE__ */ jsx48("div", { className: "h-3" }),
4065
+ /* @__PURE__ */ jsxs29("div", { className: "flex gap-2.5", children: [
4066
+ /* @__PURE__ */ jsxs29("div", { className: "flex-1", children: [
4067
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "Validade" }),
4068
+ /* @__PURE__ */ jsx48(
4069
+ FieldInput,
4070
+ {
4071
+ type: "text",
4072
+ inputMode: "numeric",
4073
+ autoComplete: "cc-exp",
4074
+ placeholder: "MM/AA",
4075
+ value: expiryMmAa,
4076
+ onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v))
4077
+ }
4078
+ )
4079
+ ] }),
4080
+ /* @__PURE__ */ jsxs29("div", { className: "flex-1", children: [
4081
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "CVV" }),
4082
+ /* @__PURE__ */ jsx48(
4083
+ FieldInput,
4084
+ {
4085
+ type: "text",
4086
+ inputMode: "numeric",
4087
+ autoComplete: "cc-csc",
4088
+ placeholder: "3 d\xEDgitos",
4089
+ value: form.card.ccv,
4090
+ onChange: (v) => form.setCard({ ccv: v.replace(/\D/g, "").slice(0, 4) })
4091
+ }
4092
+ )
4093
+ ] })
4094
+ ] }),
4095
+ /* @__PURE__ */ jsx48("div", { className: "h-3" }),
4096
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "Nome no cart\xE3o" }),
4097
+ /* @__PURE__ */ jsx48(
4098
+ FieldInput,
4099
+ {
4100
+ type: "text",
4101
+ autoComplete: "cc-name",
4102
+ placeholder: "como est\xE1 no cart\xE3o",
4103
+ value: form.card.holderName,
4104
+ onChange: (v) => form.setCard({ holderName: v })
4105
+ }
4106
+ )
4107
+ ] }) : /* @__PURE__ */ jsx48("section", { className: "px-5 pt-3.5", children: /* @__PURE__ */ jsxs29("div", { className: "rounded-2xl bg-card border border-border p-3.5 flex gap-3.5 items-center", children: [
4108
+ /* @__PURE__ */ jsx48("div", { className: "w-[72px] h-[72px] rounded-xl shrink-0 border-2 border-foreground relative overflow-hidden bg-muted", children: /* @__PURE__ */ jsx48("div", { className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[22px] h-[22px] bg-background flex items-center justify-center", children: /* @__PURE__ */ jsx48(PixIcon, { className: "w-3.5 h-3.5 text-foreground" }) }) }),
4109
+ /* @__PURE__ */ jsxs29("div", { className: "flex-1", children: [
4110
+ /* @__PURE__ */ jsx48("div", { className: "text-xs font-bold uppercase tracking-wider text-muted-foreground", children: "pagamento em segundos" }),
4111
+ /* @__PURE__ */ jsxs29("div", { className: "text-sm text-foreground mt-1 leading-snug", children: [
4112
+ "Geramos seu ",
4113
+ /* @__PURE__ */ jsx48("b", { children: "QR Pix" }),
4114
+ " no pr\xF3ximo passo. Pague pelo app do banco e seu acesso libera ",
4115
+ /* @__PURE__ */ jsx48("b", { children: "imediatamente" }),
4116
+ "."
4117
+ ] })
4118
+ ] })
4119
+ ] }) }),
4120
+ /* @__PURE__ */ jsx48("section", { className: "px-5 pt-5", children: /* @__PURE__ */ jsxs29("div", { className: "bg-muted rounded-2xl p-4", children: [
4121
+ /* @__PURE__ */ jsxs29("div", { className: "flex justify-between mb-2.5", children: [
4122
+ /* @__PURE__ */ jsxs29("div", { children: [
4123
+ /* @__PURE__ */ jsx48("div", { className: "text-sm font-semibold text-foreground", children: annual ? "Plano Anual" : "Plano Mensal" }),
4124
+ /* @__PURE__ */ jsx48("div", { className: "text-[11px] text-muted-foreground", children: "Coach" })
4125
+ ] }),
4126
+ /* @__PURE__ */ jsxs29("div", { className: "text-right", children: [
4127
+ /* @__PURE__ */ jsxs29("div", { className: "text-sm font-bold text-foreground", children: [
4128
+ cyclePriceText,
4129
+ "/",
4130
+ annual ? "ano" : "m\xEAs"
4131
+ ] }),
4132
+ annual && annualSavingsCents > 0 && /* @__PURE__ */ jsxs29("div", { className: "text-[11px] text-emerald-700 font-semibold", children: [
4133
+ "economia ",
4134
+ formatBrl(annualSavingsCents)
4135
+ ] })
4136
+ ] })
4137
+ ] }),
4138
+ /* @__PURE__ */ jsx48("div", { className: "h-px bg-border my-3" }),
4139
+ /* @__PURE__ */ jsxs29("div", { className: "flex justify-between items-baseline", children: [
4140
+ /* @__PURE__ */ jsxs29("div", { children: [
4141
+ /* @__PURE__ */ jsx48("div", { className: "text-sm font-bold text-foreground", children: "Voc\xEA paga hoje" }),
4142
+ form.method === "card" && trialDays > 0 && /* @__PURE__ */ jsxs29("div", { className: "text-[11px] text-muted-foreground mt-0.5", children: [
4143
+ "cobran\xE7a inicia no dia ",
4144
+ trialDays
4145
+ ] }),
4146
+ form.method === "pix-auto" && /* @__PURE__ */ jsxs29("div", { className: "text-[11px] text-emerald-700 mt-0.5 font-semibold", children: [
4147
+ "reembolso garantido at\xE9 o dia ",
4148
+ trialDays
4149
+ ] })
4150
+ ] }),
4151
+ /* @__PURE__ */ jsx48("div", { className: "text-2xl font-bold font-display tracking-tight text-foreground", children: todayAmount })
4152
+ ] })
4153
+ ] }) })
4154
+ ] }),
4155
+ /* @__PURE__ */ jsxs29(PaywallStickyFooter, { className: "px-5 pt-3.5 pb-5 border-t border-border", children: [
4156
+ /* @__PURE__ */ jsx48(
4373
4157
  "button",
4374
4158
  {
4375
- type: "button",
4376
- onClick: () => {
4377
- void handleCta();
4378
- },
4379
- disabled: s.submitting,
4380
- className: ctaTheme,
4381
- children: s.submitting ? "Abrindo checkout\u2026" : ctaLabel
4159
+ type: "submit",
4160
+ disabled: !form.canSubmit,
4161
+ className: "w-full rounded-full bg-primary text-primary-foreground min-h-14 px-5 text-base font-bold inline-flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg",
4162
+ children: form.submitting ? /* @__PURE__ */ jsxs29(Fragment7, { children: [
4163
+ /* @__PURE__ */ jsx48(Spinner2, {}),
4164
+ " ",
4165
+ form.method === "pix-auto" ? "Gerando QR\u2026" : "Confirmando\u2026"
4166
+ ] }) : form.method === "card" ? /* @__PURE__ */ jsxs29(Fragment7, { children: [
4167
+ /* @__PURE__ */ jsx48(LockIcon, { className: "w-3.5 h-3.5" }),
4168
+ " Confirmar e come\xE7ar gr\xE1tis"
4169
+ ] }) : /* @__PURE__ */ jsxs29(Fragment7, { children: [
4170
+ /* @__PURE__ */ jsx48(PixIcon, { className: "w-3.5 h-3.5" }),
4171
+ " Gerar QR Pix"
4172
+ ] })
4382
4173
  }
4383
4174
  ),
4384
- switchHint ? /* @__PURE__ */ jsx36("p", { className: themeClasses.switchHint, children: switchHint }) : null,
4385
- /* @__PURE__ */ jsx36("p", { className: themeClasses.trustLine, children: copy.trustLine })
4175
+ /* @__PURE__ */ jsxs29("div", { className: "text-center mt-2.5 text-xs text-muted-foreground", children: [
4176
+ "Ao continuar, voc\xEA concorda com nossos ",
4177
+ /* @__PURE__ */ jsx48("u", { children: "Termos" }),
4178
+ ". Pagamento seguro Asaas."
4179
+ ] }),
4180
+ /* @__PURE__ */ jsxs29("div", { className: "mt-3 flex items-center justify-center gap-3.5 text-[11px] text-muted-foreground", children: [
4181
+ /* @__PURE__ */ jsxs29("span", { className: "inline-flex items-center gap-1", children: [
4182
+ /* @__PURE__ */ jsx48(LockIcon, { className: "w-2.5 h-2.5 opacity-60" }),
4183
+ " SSL 256-bit"
4184
+ ] }),
4185
+ /* @__PURE__ */ jsx48("span", { className: "w-px h-2.5 bg-border" }),
4186
+ /* @__PURE__ */ jsx48("span", { children: "Pagamento via Asaas" }),
4187
+ /* @__PURE__ */ jsx48("span", { className: "w-px h-2.5 bg-border" }),
4188
+ /* @__PURE__ */ jsxs29("span", { children: [
4189
+ "Garantia ",
4190
+ trialDays,
4191
+ " dias"
4192
+ ] })
4193
+ ] })
4386
4194
  ] })
4387
- ] });
4388
- }
4389
- function interp(tpl, vars) {
4390
- return tpl.replace(/\{(\w+)\}/g, (_m, k) => vars[k] ?? "");
4195
+ ] }) });
4391
4196
  }
4392
- function interpolateCopy(m, price, days) {
4393
- return {
4394
- tabLabel: m.tabLabel,
4395
- bodyRows: m.bodyRows.map((r) => interp(r, { price, days })),
4396
- ctaTemplate: m.ctaTemplate,
4397
- switchHint: m.switchHint
4398
- };
4197
+ function FieldLabel({ children }) {
4198
+ return /* @__PURE__ */ jsx48("div", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-1.5", children });
4399
4199
  }
4400
-
4401
- // src/components/paywall/PaywallCta.tsx
4402
- import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
4403
- function PaywallCta({
4404
- ctaLabel,
4405
- loadingLabel,
4406
- switchHint,
4407
- trustLine,
4408
- className,
4409
- buttonClassName,
4410
- switchHintClassName,
4411
- trustClassName
4412
- }) {
4413
- const { submit, submitting } = usePaywallContext();
4414
- const label = submitting && loadingLabel ? loadingLabel : ctaLabel;
4415
- return /* @__PURE__ */ jsxs23("div", { className, children: [
4416
- /* @__PURE__ */ jsx37(
4417
- "button",
4200
+ function FieldInput(props) {
4201
+ const baseClass = "w-full px-4 rounded-xl bg-card text-base text-foreground outline-none border-[1.5px] transition-colors";
4202
+ const stateClass = props.error ? "border-destructive focus:border-destructive" : props.valid ? "border-emerald-600 focus:border-emerald-700" : "border-border focus:border-foreground";
4203
+ return /* @__PURE__ */ jsxs29(Fragment7, { children: [
4204
+ /* @__PURE__ */ jsx48(
4205
+ "input",
4418
4206
  {
4419
- type: "button",
4420
- onClick: () => {
4421
- void submit();
4422
- },
4423
- disabled: submitting,
4424
- className: buttonClassName,
4425
- children: label
4207
+ type: props.type ?? "text",
4208
+ inputMode: props.inputMode,
4209
+ autoComplete: props.autoComplete,
4210
+ autoCapitalize: props.autoCapitalize,
4211
+ autoCorrect: props.autoCorrect,
4212
+ spellCheck: props.spellCheck,
4213
+ placeholder: props.placeholder,
4214
+ value: props.value,
4215
+ onChange: (e) => props.onChange(e.target.value),
4216
+ onBlur: props.onBlur,
4217
+ style: { height: "52px", ...props.style },
4218
+ className: `${baseClass} ${stateClass}`
4426
4219
  }
4427
4220
  ),
4428
- switchHint ? /* @__PURE__ */ jsx37("p", { className: switchHintClassName, children: switchHint }) : null,
4429
- /* @__PURE__ */ jsx37("p", { className: trustClassName, children: trustLine })
4221
+ props.error ? /* @__PURE__ */ jsx48("div", { className: "mt-1.5 text-xs text-destructive font-medium", children: props.error }) : null
4430
4222
  ] });
4431
4223
  }
4432
-
4433
- // src/components/paywall/blocks/PaywallEyebrow.tsx
4434
- import { jsx as jsx38 } from "react/jsx-runtime";
4435
- var DEFAULT_EYEBROW_CLASSES = "text-xs uppercase tracking-widest font-semibold opacity-70";
4436
- function PaywallEyebrow({ text, className }) {
4437
- return /* @__PURE__ */ jsx38("div", { className: [DEFAULT_EYEBROW_CLASSES, className].filter(Boolean).join(" "), children: text });
4224
+ function FieldHint({ children }) {
4225
+ return /* @__PURE__ */ jsx48("div", { className: "mt-1.5 text-xs text-muted-foreground", children });
4438
4226
  }
4439
-
4440
- // src/components/paywall/blocks/PaywallHero.tsx
4441
- import { jsx as jsx39, jsxs as jsxs24 } from "react/jsx-runtime";
4442
- var DEFAULT_GRADIENT = "absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent";
4443
- function PaywallHero({
4444
- src,
4445
- alt = "",
4446
- headline,
4447
- aspectRatio = "16/9",
4448
- gradientClassName,
4449
- className,
4450
- headlineClassName,
4451
- imgClassName,
4452
- render
4453
- }) {
4454
- if (render) {
4455
- return /* @__PURE__ */ jsx39("div", { className, children: render({ src, headline }) });
4456
- }
4457
- return /* @__PURE__ */ jsxs24(
4458
- "div",
4227
+ function TabButton({ active, onClick, icon, label, subtitle, subtitleActiveClass }) {
4228
+ return /* @__PURE__ */ jsxs29(
4229
+ "button",
4459
4230
  {
4460
- className: ["relative overflow-hidden", className].filter(Boolean).join(" "),
4461
- style: { aspectRatio },
4231
+ type: "button",
4232
+ role: "tab",
4233
+ "aria-selected": active,
4234
+ onClick,
4235
+ className: `flex-1 flex flex-col items-center justify-center gap-0.5 py-2.5 px-1.5 rounded-lg text-sm font-semibold transition-colors ${active ? "bg-card text-foreground shadow-sm" : "bg-transparent text-muted-foreground"}`,
4236
+ style: { minHeight: 56 },
4462
4237
  children: [
4463
- /* @__PURE__ */ jsx39(
4464
- "img",
4465
- {
4466
- src,
4467
- alt,
4468
- className: ["absolute inset-0 w-full h-full object-cover", imgClassName].filter(Boolean).join(" ")
4469
- }
4470
- ),
4471
- /* @__PURE__ */ jsx39("div", { className: gradientClassName ?? DEFAULT_GRADIENT, "aria-hidden": "true" }),
4472
- headline ? /* @__PURE__ */ jsx39(
4473
- "h1",
4474
- {
4475
- className: ["absolute bottom-0 left-0 right-0 p-4 text-white font-bold text-2xl", headlineClassName].filter(Boolean).join(" "),
4476
- children: headline
4477
- }
4478
- ) : null
4238
+ /* @__PURE__ */ jsxs29("span", { className: "inline-flex items-center gap-1.5", children: [
4239
+ icon,
4240
+ " ",
4241
+ label
4242
+ ] }),
4243
+ /* @__PURE__ */ jsx48("span", { className: `text-[10px] font-medium ${active ? subtitleActiveClass : "text-muted-foreground/70"}`, children: subtitle })
4479
4244
  ]
4480
4245
  }
4481
4246
  );
4482
4247
  }
4248
+ function CardIcon({ className }) {
4249
+ return /* @__PURE__ */ jsxs29("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinejoin: "round", "aria-hidden": "true", children: [
4250
+ /* @__PURE__ */ jsx48("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "2.5" }),
4251
+ /* @__PURE__ */ jsx48("path", { d: "M2.5 10h19" })
4252
+ ] });
4253
+ }
4254
+ function PixIcon({ className }) {
4255
+ return /* @__PURE__ */ jsx48("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx48("path", { d: "M12 2L2 12l10 10 10-10L12 2zm0 4.83L17.17 12 12 17.17 6.83 12 12 6.83z" }) });
4256
+ }
4257
+ function LockIcon({ className }) {
4258
+ return /* @__PURE__ */ jsxs29("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
4259
+ /* @__PURE__ */ jsx48("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2.5" }),
4260
+ /* @__PURE__ */ jsx48("path", { d: "M7.5 10V7a4.5 4.5 0 119 0v3" })
4261
+ ] });
4262
+ }
4263
+ function ShieldIcon({ className }) {
4264
+ return /* @__PURE__ */ jsxs29("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
4265
+ /* @__PURE__ */ jsx48("path", { d: "M12 2.5l8 3v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10v-6l8-3z" }),
4266
+ /* @__PURE__ */ jsx48("path", { d: "M9 12l2 2 4-4" })
4267
+ ] });
4268
+ }
4269
+ function BellIcon({ className }) {
4270
+ return /* @__PURE__ */ jsxs29("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
4271
+ /* @__PURE__ */ jsx48("path", { d: "M6 17V11a6 6 0 1112 0v6l1.5 2H4.5L6 17z" }),
4272
+ /* @__PURE__ */ jsx48("path", { d: "M10 21a2 2 0 004 0" })
4273
+ ] });
4274
+ }
4275
+ function Spinner2() {
4276
+ return /* @__PURE__ */ jsx48(
4277
+ "span",
4278
+ {
4279
+ className: "w-4 h-4 rounded-full border-2 border-white/40 border-t-white",
4280
+ style: { animation: "spin 0.7s linear infinite" }
4281
+ }
4282
+ );
4283
+ }
4483
4284
 
4484
- // src/components/paywall/blocks/PaywallHeadline.tsx
4485
- import { jsx as jsx40 } from "react/jsx-runtime";
4486
- var DEFAULT_HEADLINE_CLASSES = "text-2xl font-bold leading-tight";
4487
- function PaywallHeadline({ text, className, as = "h1" }) {
4488
- const Tag = as;
4489
- return /* @__PURE__ */ jsx40(Tag, { className: [DEFAULT_HEADLINE_CLASSES, className].filter(Boolean).join(" "), children: text });
4285
+ // src/defaults/PixWaitingPageDefault.tsx
4286
+ import { useEffect as useEffect14, useMemo as useMemo7, useState as useState12 } from "react";
4287
+ import { useNavigate as useNavigate3 } from "react-router-dom";
4288
+ import { useHook as useHook13 } from "@hook-sdk/sdk";
4289
+ import { jsx as jsx49, jsxs as jsxs30 } from "react/jsx-runtime";
4290
+ var PIX_PAYLOAD_KEY2 = "hook:paywall:pix-pending";
4291
+ var TIMEOUT_MS = 30 * 60 * 1e3;
4292
+ function readPixPayload() {
4293
+ if (typeof window === "undefined") return null;
4294
+ try {
4295
+ const raw = sessionStorage.getItem(PIX_PAYLOAD_KEY2);
4296
+ if (!raw) return null;
4297
+ return JSON.parse(raw);
4298
+ } catch {
4299
+ return null;
4300
+ }
4490
4301
  }
4491
-
4492
- // src/components/paywall/blocks/PaywallPriceHeadline.tsx
4493
- import { jsx as jsx41 } from "react/jsx-runtime";
4494
- var DEFAULT_CLASS = "text-2xl font-bold leading-tight";
4495
- var CYCLE_LABEL = {
4496
- MONTHLY: "mensal",
4497
- YEARLY: "anual"
4498
- };
4499
- function PaywallPriceHeadline({
4500
- template,
4501
- className,
4502
- as = "h1",
4503
- render
4504
- }) {
4505
- const { cycle, currentMonthlyEquivCents, plan } = usePaywallContext();
4506
- const yearlyCents = plan?.yearlyCents ?? null;
4507
- const pricePerDay = formatBRL(dailyFromYearly(yearlyCents));
4508
- const monthlyEquiv = currentMonthlyEquivCents ?? 0;
4509
- const cycleLabel = CYCLE_LABEL[cycle] ?? cycle.toLowerCase();
4510
- const rootClasses = [DEFAULT_CLASS, className].filter(Boolean).join(" ");
4511
- if (render) {
4512
- const RootTag2 = as;
4513
- return /* @__PURE__ */ jsx41(RootTag2, { className: [className].filter(Boolean).join(" ") || void 0, children: render({ pricePerDay, currentMonthlyEquivCents: monthlyEquiv, cycle }) });
4302
+ function clearPixPayload() {
4303
+ if (typeof window === "undefined") return;
4304
+ try {
4305
+ sessionStorage.removeItem(PIX_PAYLOAD_KEY2);
4306
+ } catch {
4514
4307
  }
4515
- const text = template.replaceAll("{pricePerDay}", pricePerDay).replaceAll("{currentMonthlyEquiv}", formatBRL(monthlyEquiv)).replaceAll("{cycle}", cycleLabel);
4516
- const RootTag = as;
4517
- return /* @__PURE__ */ jsx41(RootTag, { className: rootClasses, children: text });
4518
4308
  }
4519
-
4520
- // src/components/paywall/blocks/PaywallCountdown.tsx
4521
- import { useEffect as useEffect18, useRef as useRef8, useState as useState17 } from "react";
4522
- import { jsx as jsx42 } from "react/jsx-runtime";
4523
- var DEFAULT_COUNTDOWN_CLASSES = "font-mono tabular-nums";
4524
- function resolveDeadlineMs(deadline) {
4525
- if (deadline instanceof Date) return deadline.getTime();
4526
- if (typeof deadline === "string") return new Date(deadline).getTime();
4527
- const { sessionStorageKey, durationMs } = deadline;
4528
- if (typeof window === "undefined" || typeof window.sessionStorage === "undefined") {
4529
- return Date.now() + durationMs;
4309
+ function PixWaitingPageDefault() {
4310
+ const navigate = useNavigate3();
4311
+ const { subscription } = useHook13();
4312
+ const payload = useMemo7(readPixPayload, []);
4313
+ const [copied, setCopied] = useState12(false);
4314
+ const [timedOut, setTimedOut] = useState12(false);
4315
+ useEffect14(() => {
4316
+ if (!payload) navigate("/paywall/checkout", { replace: true });
4317
+ }, [payload, navigate]);
4318
+ useEffect14(() => {
4319
+ window.scrollTo(0, 0);
4320
+ }, []);
4321
+ useEffect14(() => {
4322
+ const t = setTimeout(() => setTimedOut(true), TIMEOUT_MS);
4323
+ return () => clearTimeout(t);
4324
+ }, []);
4325
+ const hasAccess = subscription.hasAccess;
4326
+ useEffect14(() => {
4327
+ if (hasAccess) {
4328
+ clearPixPayload();
4329
+ navigate("/", { replace: true });
4330
+ }
4331
+ }, [hasAccess, navigate]);
4332
+ async function copyPayload() {
4333
+ if (!payload?.payload) return;
4334
+ try {
4335
+ await navigator.clipboard.writeText(payload.payload);
4336
+ setCopied(true);
4337
+ setTimeout(() => setCopied(false), 2e3);
4338
+ } catch {
4339
+ }
4530
4340
  }
4531
- const stored = window.sessionStorage.getItem(sessionStorageKey);
4532
- const parsed = stored ? Number.parseInt(stored, 10) : NaN;
4533
- const now = Date.now();
4534
- if (!Number.isFinite(parsed) || parsed < now) {
4535
- const target = now + durationMs;
4536
- window.sessionStorage.setItem(sessionStorageKey, String(target));
4537
- return target;
4341
+ if (!payload) return null;
4342
+ if (timedOut) {
4343
+ return /* @__PURE__ */ jsxs30("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-10 text-center bg-background space-y-4", children: [
4344
+ /* @__PURE__ */ jsx49("h1", { className: "font-display text-2xl text-foreground", children: "PIX expirado" }),
4345
+ /* @__PURE__ */ jsx49("p", { className: "text-sm text-muted-foreground", children: "O tempo pra pagar acabou. Gere um novo PIX." }),
4346
+ /* @__PURE__ */ jsx49(
4347
+ "button",
4348
+ {
4349
+ onClick: () => {
4350
+ clearPixPayload();
4351
+ navigate("/paywall/checkout", { replace: true });
4352
+ },
4353
+ className: "rounded-xl bg-primary px-6 py-3 text-base font-semibold text-primary-foreground",
4354
+ children: "Tentar novamente"
4355
+ }
4356
+ )
4357
+ ] });
4538
4358
  }
4539
- return parsed;
4359
+ return /* @__PURE__ */ jsxs30("div", { className: "flex-1 flex flex-col items-center px-6 py-8 bg-background space-y-6", children: [
4360
+ /* @__PURE__ */ jsxs30("header", { className: "text-center space-y-2", children: [
4361
+ /* @__PURE__ */ jsx49("h1", { className: "font-display text-2xl text-foreground", children: "Pague o PIX" }),
4362
+ /* @__PURE__ */ jsx49("p", { className: "text-sm text-muted-foreground", children: "Escaneie o QR Code no app do seu banco. O acesso libera assim que confirmarmos o pagamento." })
4363
+ ] }),
4364
+ payload.base64 ? /* @__PURE__ */ jsx49(
4365
+ "img",
4366
+ {
4367
+ src: `data:image/png;base64,${payload.base64}`,
4368
+ alt: "QR Code PIX",
4369
+ className: "w-64 h-64 rounded-2xl border border-border bg-card p-2"
4370
+ }
4371
+ ) : /* @__PURE__ */ jsx49("div", { className: "w-64 h-64 rounded-2xl border border-border bg-card flex items-center justify-center text-sm text-muted-foreground", children: "QR indispon\xEDvel" }),
4372
+ payload.payload ? /* @__PURE__ */ jsx49(
4373
+ "button",
4374
+ {
4375
+ onClick: copyPayload,
4376
+ className: "w-full max-w-xs rounded-xl border border-border bg-card px-4 py-3 text-sm font-medium text-foreground",
4377
+ children: copied ? "\u2713 Copiado!" : "Copiar c\xF3digo PIX"
4378
+ }
4379
+ ) : null,
4380
+ /* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
4381
+ /* @__PURE__ */ jsx49("span", { className: "inline-block w-2 h-2 rounded-full bg-primary animate-pulse" }),
4382
+ "Aguardando pagamento\u2026"
4383
+ ] }),
4384
+ /* @__PURE__ */ jsx49("p", { className: "text-center text-xs text-muted-foreground", children: "Pode fechar essa janela \u2014 tamb\xE9m enviamos um link de acesso pro seu e-mail." })
4385
+ ] });
4540
4386
  }
4541
- function computeRemaining(deadlineMs) {
4542
- const diff = Math.max(0, deadlineMs - Date.now());
4543
- const totalSeconds = Math.floor(diff / 1e3);
4544
- const h = Math.floor(totalSeconds / 3600);
4545
- const m = Math.floor(totalSeconds % 3600 / 60);
4546
- const s = totalSeconds % 60;
4547
- return { h, m, s, expired: diff === 0 };
4387
+
4388
+ // src/hooks/useLoginForm.ts
4389
+ import { useCallback as useCallback7, useMemo as useMemo8, useState as useState13 } from "react";
4390
+ import { useHook as useHook14 } from "@hook-sdk/sdk";
4391
+ var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4392
+ var MIN_PASSWORD = 8;
4393
+ function useLoginForm() {
4394
+ const { auth } = useHook14();
4395
+ const [email, setEmail] = useState13("");
4396
+ const [password, setPassword] = useState13("");
4397
+ const [submitting, setSubmitting] = useState13(false);
4398
+ const [error, setError] = useState13(null);
4399
+ const [touchedEmail, setTouchedEmail] = useState13(false);
4400
+ const [touchedPassword, setTouchedPassword] = useState13(false);
4401
+ const [formSubmitAttempted, setFormSubmitAttempted] = useState13(false);
4402
+ const validateEmail = useMemo8(() => {
4403
+ if (email.length === 0) return null;
4404
+ if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
4405
+ return null;
4406
+ }, [email]);
4407
+ const validatePassword = useMemo8(() => {
4408
+ if (password.length === 0) return null;
4409
+ if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
4410
+ return null;
4411
+ }, [password]);
4412
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
4413
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
4414
+ const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
4415
+ const submit = useCallback7(async () => {
4416
+ setFormSubmitAttempted(true);
4417
+ if (!canSubmit) return false;
4418
+ setSubmitting(true);
4419
+ setError(null);
4420
+ try {
4421
+ await auth.login({ email, password });
4422
+ return true;
4423
+ } catch (err) {
4424
+ setError(mapSdkError(err));
4425
+ return false;
4426
+ } finally {
4427
+ setSubmitting(false);
4428
+ }
4429
+ }, [auth, email, password, canSubmit]);
4430
+ return {
4431
+ email,
4432
+ setEmail,
4433
+ emailError,
4434
+ markEmailTouched: () => setTouchedEmail(true),
4435
+ password,
4436
+ setPassword,
4437
+ passwordError,
4438
+ markPasswordTouched: () => setTouchedPassword(true),
4439
+ formSubmitAttempted,
4440
+ submit,
4441
+ submitting,
4442
+ canSubmit,
4443
+ error,
4444
+ loginWithGoogle: () => auth.loginWithGoogle()
4445
+ };
4446
+ }
4447
+
4448
+ // src/hooks/useSignupForm.ts
4449
+ import { useCallback as useCallback8, useMemo as useMemo9, useState as useState14 } from "react";
4450
+ import { useHook as useHook15 } from "@hook-sdk/sdk";
4451
+ var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4452
+ var MIN_PASSWORD2 = 8;
4453
+ function useSignupForm() {
4454
+ const { auth } = useHook15();
4455
+ const [name, setName] = useState14("");
4456
+ const [email, setEmail] = useState14("");
4457
+ const [password, setPassword] = useState14("");
4458
+ const [submitting, setSubmitting] = useState14(false);
4459
+ const [error, setError] = useState14(null);
4460
+ const [touchedName, setTouchedName] = useState14(false);
4461
+ const [touchedEmail, setTouchedEmail] = useState14(false);
4462
+ const [touchedPassword, setTouchedPassword] = useState14(false);
4463
+ const [formSubmitAttempted, setFormSubmitAttempted] = useState14(false);
4464
+ const validateName = useMemo9(() => {
4465
+ if (name.length === 0) return null;
4466
+ if (name.trim().length < 2) return "Nome muito curto.";
4467
+ return null;
4468
+ }, [name]);
4469
+ const validateEmail = useMemo9(() => {
4470
+ if (email.length === 0) return null;
4471
+ if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
4472
+ return null;
4473
+ }, [email]);
4474
+ const validatePassword = useMemo9(() => {
4475
+ if (password.length === 0) return null;
4476
+ if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
4477
+ return null;
4478
+ }, [password]);
4479
+ const nameError = touchedName || formSubmitAttempted ? validateName : null;
4480
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
4481
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
4482
+ const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
4483
+ const submit = useCallback8(async () => {
4484
+ setFormSubmitAttempted(true);
4485
+ if (!canSubmit) return false;
4486
+ setSubmitting(true);
4487
+ setError(null);
4488
+ try {
4489
+ await auth.signup({ name, email, password });
4490
+ return true;
4491
+ } catch (err) {
4492
+ setError(mapSdkError(err));
4493
+ return false;
4494
+ } finally {
4495
+ setSubmitting(false);
4496
+ }
4497
+ }, [auth, name, email, password, canSubmit]);
4498
+ return {
4499
+ name,
4500
+ setName,
4501
+ nameError,
4502
+ markNameTouched: () => setTouchedName(true),
4503
+ email,
4504
+ setEmail,
4505
+ emailError,
4506
+ markEmailTouched: () => setTouchedEmail(true),
4507
+ password,
4508
+ setPassword,
4509
+ passwordError,
4510
+ markPasswordTouched: () => setTouchedPassword(true),
4511
+ formSubmitAttempted,
4512
+ submit,
4513
+ submitting,
4514
+ canSubmit,
4515
+ error,
4516
+ loginWithGoogle: () => auth.loginWithGoogle()
4517
+ };
4548
4518
  }
4549
- function pad(n) {
4550
- return String(n).padStart(2, "0");
4519
+
4520
+ // src/hooks/useForgotForm.ts
4521
+ import { useCallback as useCallback9, useMemo as useMemo10, useState as useState15 } from "react";
4522
+ import { useHook as useHook16 } from "@hook-sdk/sdk";
4523
+ var EMAIL_RE4 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4524
+ function useForgotForm() {
4525
+ const { auth } = useHook16();
4526
+ const [email, setEmail] = useState15("");
4527
+ const [submitting, setSubmitting] = useState15(false);
4528
+ const [sent, setSent] = useState15(false);
4529
+ const [error, setError] = useState15(null);
4530
+ const [touchedEmail, setTouchedEmail] = useState15(false);
4531
+ const [formSubmitAttempted, setFormSubmitAttempted] = useState15(false);
4532
+ const validateEmail = useMemo10(() => {
4533
+ if (email.length === 0) return null;
4534
+ if (!EMAIL_RE4.test(email)) return "Formato de e-mail inv\xE1lido.";
4535
+ return null;
4536
+ }, [email]);
4537
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
4538
+ const canSubmit = email.length > 0 && validateEmail === null && !submitting;
4539
+ const submit = useCallback9(async () => {
4540
+ setFormSubmitAttempted(true);
4541
+ if (!canSubmit) return false;
4542
+ setSubmitting(true);
4543
+ setError(null);
4544
+ try {
4545
+ await auth.forgot({ email });
4546
+ setSent(true);
4547
+ return true;
4548
+ } catch (err) {
4549
+ setError(mapSdkError(err));
4550
+ return false;
4551
+ } finally {
4552
+ setSubmitting(false);
4553
+ }
4554
+ }, [auth, email, canSubmit]);
4555
+ return {
4556
+ email,
4557
+ setEmail,
4558
+ emailError,
4559
+ markEmailTouched: () => setTouchedEmail(true),
4560
+ formSubmitAttempted,
4561
+ submit,
4562
+ submitting,
4563
+ canSubmit,
4564
+ sent,
4565
+ error
4566
+ };
4551
4567
  }
4552
- function PaywallCountdown({
4553
- deadline,
4554
- format = "h:m:s",
4555
- onExpire,
4556
- className,
4557
- render
4558
- }) {
4559
- const deadlineMsRef = useRef8(null);
4560
- if (deadlineMsRef.current === null) {
4561
- deadlineMsRef.current = resolveDeadlineMs(deadline);
4562
- }
4563
- const [state, setState] = useState17(() => computeRemaining(deadlineMsRef.current));
4564
- const expiredCalledRef = useRef8(false);
4565
- useEffect18(() => {
4566
- if (state.expired) {
4567
- if (!expiredCalledRef.current) {
4568
- expiredCalledRef.current = true;
4569
- onExpire?.();
4568
+
4569
+ // src/hooks/useResetForm.ts
4570
+ import { useCallback as useCallback10, useEffect as useEffect15, useMemo as useMemo11, useState as useState16 } from "react";
4571
+ import { useHook as useHook17 } from "@hook-sdk/sdk";
4572
+ var MIN_PASSWORD3 = 8;
4573
+ function useResetForm() {
4574
+ const { auth } = useHook17();
4575
+ const [token, setToken] = useState16(null);
4576
+ const [password, setPassword] = useState16("");
4577
+ const [confirm, setConfirm] = useState16("");
4578
+ const [submitting, setSubmitting] = useState16(false);
4579
+ const [done, setDone] = useState16(false);
4580
+ const [error, setError] = useState16(null);
4581
+ const [touchedPassword, setTouchedPassword] = useState16(false);
4582
+ const [touchedConfirm, setTouchedConfirm] = useState16(false);
4583
+ const [formSubmitAttempted, setFormSubmitAttempted] = useState16(false);
4584
+ useEffect15(() => {
4585
+ if (typeof window === "undefined") return;
4586
+ const params = new URLSearchParams(window.location.search);
4587
+ const t = params.get("token");
4588
+ setToken(t && t.length > 0 ? t : null);
4589
+ }, []);
4590
+ const validatePassword = useMemo11(() => {
4591
+ if (password.length === 0) return null;
4592
+ if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
4593
+ return null;
4594
+ }, [password]);
4595
+ const validateConfirm = useMemo11(() => {
4596
+ if (confirm.length === 0) return null;
4597
+ if (confirm !== password) return "Senhas n\xE3o coincidem.";
4598
+ return null;
4599
+ }, [confirm, password]);
4600
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
4601
+ const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
4602
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
4603
+ const submit = useCallback10(async () => {
4604
+ setFormSubmitAttempted(true);
4605
+ if (!canSubmit || token === null) return;
4606
+ setSubmitting(true);
4607
+ setError(null);
4608
+ try {
4609
+ await auth.reset({ token, newPassword: password });
4610
+ setDone(true);
4611
+ if (typeof window !== "undefined") {
4612
+ const url = new URL(window.location.href);
4613
+ url.searchParams.delete("token");
4614
+ url.searchParams.delete("screen");
4615
+ window.history.replaceState({}, "", url.toString());
4570
4616
  }
4571
- return;
4617
+ } catch (err) {
4618
+ setError(mapSdkError(err));
4619
+ } finally {
4620
+ setSubmitting(false);
4572
4621
  }
4573
- const tick = () => {
4574
- const next = computeRemaining(deadlineMsRef.current);
4575
- setState(next);
4576
- if (next.expired && !expiredCalledRef.current) {
4577
- expiredCalledRef.current = true;
4578
- onExpire?.();
4579
- }
4580
- };
4581
- const id = setInterval(tick, 1e3);
4582
- return () => clearInterval(id);
4583
- }, [state.expired]);
4584
- if (render) {
4585
- return /* @__PURE__ */ jsx42("div", { className, children: render(state) });
4586
- }
4587
- const formatted = format === "h:m:s" ? `${pad(state.h)}:${pad(state.m)}:${pad(state.s)}` : `${pad(state.h * 60 + state.m)}:${pad(state.s)}`;
4588
- return /* @__PURE__ */ jsx42("div", { className: [DEFAULT_COUNTDOWN_CLASSES, className].filter(Boolean).join(" "), children: formatted });
4622
+ }, [auth, token, password, canSubmit]);
4623
+ return {
4624
+ token,
4625
+ password,
4626
+ setPassword,
4627
+ passwordError,
4628
+ markPasswordTouched: () => setTouchedPassword(true),
4629
+ confirm,
4630
+ setConfirm,
4631
+ confirmError,
4632
+ markConfirmTouched: () => setTouchedConfirm(true),
4633
+ formSubmitAttempted,
4634
+ submit,
4635
+ submitting,
4636
+ canSubmit,
4637
+ done,
4638
+ error
4639
+ };
4589
4640
  }
4590
4641
 
4591
- // src/components/paywall/blocks/PaywallFeatures.tsx
4592
- import { jsx as jsx43, jsxs as jsxs25 } from "react/jsx-runtime";
4593
- function PaywallFeatures({
4594
- items,
4595
- IconComponent,
4596
- className,
4597
- itemClassName,
4598
- iconClassName,
4599
- render,
4600
- renderItem
4601
- }) {
4602
- if (render) {
4603
- return /* @__PURE__ */ jsx43("div", { className, children: render({ items }) });
4604
- }
4605
- if (renderItem) {
4606
- return /* @__PURE__ */ jsx43("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ jsx43("li", { children: renderItem(item, idx) }, idx)) });
4607
- }
4608
- return /* @__PURE__ */ jsx43("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ jsxs25("li", { className: itemClassName, children: [
4609
- IconComponent ? /* @__PURE__ */ jsx43(IconComponent, { className: iconClassName }) : /* @__PURE__ */ jsx43("span", { className: iconClassName, "aria-hidden": "true", children: "\u2713" }),
4610
- /* @__PURE__ */ jsx43("span", { children: item })
4611
- ] }, idx)) });
4642
+ // src/hooks/useAuthPrimitives.ts
4643
+ import { useEffect as useEffect16 } from "react";
4644
+ import { useHook as useHook18 } from "@hook-sdk/sdk";
4645
+ var warned = false;
4646
+ function useAuthPrimitives() {
4647
+ const { auth } = useHook18();
4648
+ useEffect16(() => {
4649
+ if (!warned && process.env.NODE_ENV !== "production") {
4650
+ warned = true;
4651
+ console.warn(
4652
+ "[@hook-sdk/template] useAuthPrimitives() \xE9 escape hatch. Pra login/signup/forgot, use useLoginForm/useSignupForm/useForgotForm. Docs: docs/19-golden-template.md#escape-hatch"
4653
+ );
4654
+ }
4655
+ }, []);
4656
+ return {
4657
+ login: auth.login,
4658
+ signup: auth.signup,
4659
+ logout: auth.logout,
4660
+ logoutAll: auth.logoutAll,
4661
+ forgot: auth.forgot,
4662
+ resendVerify: auth.resendVerify,
4663
+ changePassword: auth.changePassword,
4664
+ changeEmail: auth.changeEmail,
4665
+ refresh: auth.refresh
4666
+ };
4612
4667
  }
4613
4668
 
4614
- // src/components/paywall/blocks/PaywallFeaturesCard.tsx
4615
- import { jsx as jsx44, jsxs as jsxs26 } from "react/jsx-runtime";
4616
- var DEFAULT_CARD_CLASSES = "rounded-xl border p-4";
4617
- function PaywallFeaturesCard({
4618
- title,
4619
- items,
4620
- className,
4621
- cardClassName,
4622
- titleClassName,
4623
- itemClassName,
4624
- renderItem
4625
- }) {
4626
- return /* @__PURE__ */ jsx44("div", { className, children: /* @__PURE__ */ jsxs26("div", { className: [DEFAULT_CARD_CLASSES, cardClassName].filter(Boolean).join(" "), children: [
4627
- title ? /* @__PURE__ */ jsx44("div", { className: ["font-semibold mb-2", titleClassName].filter(Boolean).join(" "), children: title }) : null,
4628
- /* @__PURE__ */ jsx44("ul", { children: items.map(
4629
- (item, idx) => renderItem ? /* @__PURE__ */ jsx44("li", { children: renderItem(item, idx) }, idx) : /* @__PURE__ */ jsxs26("li", { className: itemClassName, children: [
4630
- /* @__PURE__ */ jsx44("span", { "aria-hidden": "true", children: "\u2022" }),
4631
- " ",
4632
- /* @__PURE__ */ jsx44("span", { children: item })
4633
- ] }, idx)
4634
- ) })
4635
- ] }) });
4669
+ // src/hooks/useAuth.ts
4670
+ import { useHook as useHook19 } from "@hook-sdk/sdk";
4671
+ function useAuth() {
4672
+ const { user, authStatus, auth } = useHook19();
4673
+ return {
4674
+ user,
4675
+ authStatus,
4676
+ refresh: auth.refresh
4677
+ };
4636
4678
  }
4637
4679
 
4638
- // src/components/paywall/blocks/PaywallTrophyBadge.tsx
4639
- import { jsx as jsx45, jsxs as jsxs27 } from "react/jsx-runtime";
4640
- var DEFAULT_CHIP_CLASSES = "inline-flex items-center gap-1 px-3 py-1 rounded-full bg-yellow-100 text-yellow-900 text-sm font-medium";
4641
- var FLOATING_CLASSES = "absolute top-2 right-2 z-10 shadow-md";
4642
- function PaywallTrophyBadge({
4643
- text,
4644
- className,
4645
- iconClassName,
4646
- floating = false,
4647
- render
4648
- }) {
4649
- if (render) {
4650
- return /* @__PURE__ */ jsx45("div", { className, children: render({ text }) });
4651
- }
4652
- const rootClasses = [
4653
- DEFAULT_CHIP_CLASSES,
4654
- floating ? FLOATING_CLASSES : "",
4655
- className
4656
- ].filter(Boolean).join(" ");
4657
- return /* @__PURE__ */ jsxs27("div", { className: rootClasses, children: [
4658
- /* @__PURE__ */ jsx45("span", { className: iconClassName, "aria-hidden": "true", children: "\u{1F3C6}" }),
4659
- /* @__PURE__ */ jsx45("span", { children: text })
4660
- ] });
4680
+ // src/index.ts
4681
+ import { useTrackOnboardingStep } from "@hook-sdk/sdk";
4682
+
4683
+ // src/hooks/useSubscription.ts
4684
+ import { useHook as useHook20 } from "@hook-sdk/sdk";
4685
+ function useSubscription() {
4686
+ const { subscription } = useHook20();
4687
+ return {
4688
+ status: subscription.status()
4689
+ };
4690
+ }
4691
+
4692
+ // src/hooks/useReminders.ts
4693
+ import { useCallback as useCallback11, useEffect as useEffect17, useState as useState17 } from "react";
4694
+ import { useHook as useHook21 } from "@hook-sdk/sdk";
4695
+ function useReminders() {
4696
+ const { push } = useHook21();
4697
+ const r = push.reminders;
4698
+ const [reminders, setReminders] = useState17([]);
4699
+ const [loading, setLoading] = useState17(true);
4700
+ const reload = useCallback11(async () => {
4701
+ setLoading(true);
4702
+ try {
4703
+ const next = await r.list();
4704
+ setReminders(next);
4705
+ } finally {
4706
+ setLoading(false);
4707
+ }
4708
+ }, [r]);
4709
+ useEffect17(() => {
4710
+ void reload();
4711
+ }, [reload]);
4712
+ const setReminder = useCallback11(async (input) => {
4713
+ await r.set(input);
4714
+ await reload();
4715
+ }, [r, reload]);
4716
+ const deleteReminder = useCallback11(async (slot) => {
4717
+ await r.delete(slot);
4718
+ await reload();
4719
+ }, [r, reload]);
4720
+ const schedule = useCallback11(async (items) => {
4721
+ return r.schedule(items);
4722
+ }, [r]);
4723
+ const setFallbacks = useCallback11(async (items) => {
4724
+ return r.setFallbacks(items);
4725
+ }, [r]);
4726
+ return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
4661
4727
  }
4662
4728
 
4663
- // src/components/paywall/blocks/PaywallAnchorPrice.tsx
4664
- import { jsx as jsx46 } from "react/jsx-runtime";
4665
- var DEFAULT_CLASS2 = "text-sm opacity-60 line-through";
4666
- function PaywallAnchorPrice({
4667
- className,
4668
- render
4669
- }) {
4670
- const { anchorPriceCents, cycle } = usePaywallContext();
4671
- if (anchorPriceCents === null || anchorPriceCents === void 0 || anchorPriceCents <= 0) {
4672
- return null;
4673
- }
4674
- void cycle;
4675
- const formatted = formatBRL(anchorPriceCents);
4676
- const rootClasses = [DEFAULT_CLASS2, className].filter(Boolean).join(" ");
4677
- if (render) {
4678
- return /* @__PURE__ */ jsx46("span", { className: className || void 0, children: render({ anchorCents: anchorPriceCents, formatted }) });
4679
- }
4680
- return /* @__PURE__ */ jsx46("span", { className: rootClasses, children: formatted });
4729
+ // src/hooks/useToast.ts
4730
+ import { useCallback as useCallback12, useState as useState18 } from "react";
4731
+ function useToast() {
4732
+ const [items, setItems] = useState18([]);
4733
+ const show = useCallback12((message, kind = "info") => {
4734
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4735
+ setItems((prev) => [...prev, { id, message, kind }]);
4736
+ setTimeout(() => {
4737
+ setItems((prev) => prev.filter((t) => t.id !== id));
4738
+ }, 4e3);
4739
+ }, []);
4740
+ const dismiss = useCallback12((id) => {
4741
+ setItems((prev) => prev.filter((t) => t.id !== id));
4742
+ }, []);
4743
+ return { items, show, dismiss };
4681
4744
  }
4682
4745
 
4683
- // src/components/paywall/blocks/PaywallTestimonials.tsx
4684
- import { jsx as jsx47, jsxs as jsxs28 } from "react/jsx-runtime";
4685
- var DEFAULT_ROOT = "flex gap-3 overflow-x-auto snap-x snap-mandatory pb-2";
4686
- var DEFAULT_CARD = "snap-start shrink-0 w-72 rounded-2xl border p-4 flex flex-col gap-2";
4687
- var DEFAULT_AVATAR = "w-10 h-10 rounded-full object-cover";
4688
- var DEFAULT_QUOTE = "text-sm leading-snug";
4689
- var DEFAULT_NAME = "text-xs font-semibold opacity-80";
4690
- var DEFAULT_STARS = "text-yellow-500 text-sm";
4691
- function clampStars(n) {
4692
- return Math.max(0, Math.min(5, Math.round(n)));
4746
+ // src/RouteBoundary.tsx
4747
+ import { Routes as Routes2, Route as Route2 } from "react-router-dom";
4748
+ import { jsx as jsx50, jsxs as jsxs31 } from "react/jsx-runtime";
4749
+ function RouteBoundary({ children }) {
4750
+ return /* @__PURE__ */ jsxs31(Routes2, { children: [
4751
+ children,
4752
+ /* @__PURE__ */ jsx50(Route2, { path: "*", element: /* @__PURE__ */ jsx50(DefaultNotFound, {}) })
4753
+ ] });
4693
4754
  }
4694
- function PaywallTestimonials({
4695
- items,
4696
- className,
4697
- cardClassName,
4698
- avatarClassName,
4699
- quoteClassName,
4700
- nameClassName,
4701
- starsClassName,
4702
- renderItem
4703
- }) {
4704
- const rootClasses = [DEFAULT_ROOT, className].filter(Boolean).join(" ");
4705
- const cardClasses = [DEFAULT_CARD, cardClassName].filter(Boolean).join(" ");
4706
- const avatarClasses = [DEFAULT_AVATAR, avatarClassName].filter(Boolean).join(" ");
4707
- const quoteClasses = [DEFAULT_QUOTE, quoteClassName].filter(Boolean).join(" ");
4708
- const nameClasses = [DEFAULT_NAME, nameClassName].filter(Boolean).join(" ");
4709
- const starsClasses = [DEFAULT_STARS, starsClassName].filter(Boolean).join(" ");
4710
- return /* @__PURE__ */ jsx47("div", { className: rootClasses, children: items.map((item, idx) => {
4711
- if (renderItem) return renderItem(item, idx);
4712
- const filled = clampStars(item.stars);
4713
- const empty = 5 - filled;
4714
- return /* @__PURE__ */ jsxs28("div", { className: cardClasses, children: [
4715
- /* @__PURE__ */ jsxs28("div", { className: "flex items-center gap-2", children: [
4716
- item.avatar ? /* @__PURE__ */ jsx47(
4717
- "img",
4718
- {
4719
- src: item.avatar,
4720
- alt: "",
4721
- loading: "lazy",
4722
- className: avatarClasses,
4723
- "aria-hidden": "true"
4724
- }
4725
- ) : null,
4726
- /* @__PURE__ */ jsx47("div", { className: nameClasses, children: item.name })
4727
- ] }),
4728
- /* @__PURE__ */ jsxs28("div", { className: starsClasses, "aria-label": `${filled} de 5 estrelas`, children: [
4729
- "\u2605".repeat(filled),
4730
- "\u2606".repeat(empty)
4731
- ] }),
4732
- /* @__PURE__ */ jsx47("p", { className: quoteClasses, children: item.quote })
4733
- ] }, idx);
4734
- }) });
4755
+ function DefaultNotFound() {
4756
+ return /* @__PURE__ */ jsx50("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
4735
4757
  }
4736
4758
 
4737
- // src/components/paywall/blocks/PaywallStatsRow.tsx
4738
- import { jsx as jsx48, jsxs as jsxs29 } from "react/jsx-runtime";
4739
- var DEFAULT_ROOT2 = "grid grid-cols-3 gap-4";
4740
- var DEFAULT_CELL = "flex flex-col items-center text-center";
4741
- var DEFAULT_VALUE = "text-2xl font-bold";
4742
- var DEFAULT_LABEL = "text-xs opacity-70";
4743
- function PaywallStatsRow({
4744
- stats,
4745
- className,
4746
- cellClassName,
4747
- valueClassName,
4748
- labelClassName,
4749
- renderCell
4759
+ // src/PreAuthShell.tsx
4760
+ import { BrowserRouter as BrowserRouter2, MemoryRouter as MemoryRouter2, Routes as Routes3 } from "react-router-dom";
4761
+ import { jsx as jsx51 } from "react/jsx-runtime";
4762
+ function PreAuthShell({
4763
+ basename,
4764
+ testRouter,
4765
+ testInitialEntries,
4766
+ children
4750
4767
  }) {
4751
- const rootClasses = [DEFAULT_ROOT2, className].filter(Boolean).join(" ");
4752
- const cellClasses = [DEFAULT_CELL, cellClassName].filter(Boolean).join(" ");
4753
- const valueClasses = [DEFAULT_VALUE, valueClassName].filter(Boolean).join(" ");
4754
- const labelClasses = [DEFAULT_LABEL, labelClassName].filter(Boolean).join(" ");
4755
- return /* @__PURE__ */ jsx48("div", { className: rootClasses, children: stats.map((stat, idx) => {
4756
- if (renderCell) return renderCell(stat, idx);
4757
- return /* @__PURE__ */ jsxs29("div", { className: cellClasses, children: [
4758
- stat.icon ? /* @__PURE__ */ jsx48("div", { "aria-hidden": "true", children: stat.icon }) : null,
4759
- /* @__PURE__ */ jsx48("div", { className: valueClasses, children: stat.value }),
4760
- /* @__PURE__ */ jsx48("div", { className: labelClasses, children: stat.label })
4761
- ] }, idx);
4762
- }) });
4768
+ if (testRouter === "memory") {
4769
+ return /* @__PURE__ */ jsx51(MemoryRouter2, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ jsx51(Routes3, { children }) });
4770
+ }
4771
+ return /* @__PURE__ */ jsx51(BrowserRouter2, { basename, children: /* @__PURE__ */ jsx51(Routes3, { children }) });
4763
4772
  }
4764
4773
 
4765
- // src/components/paywall/blocks/PaywallFinePrint.tsx
4766
- import { jsx as jsx49 } from "react/jsx-runtime";
4767
- var DEFAULT_CLASS3 = "text-xs opacity-60 leading-snug";
4768
- var CYCLE_LABEL2 = {
4769
- MONTHLY: "mensal",
4770
- YEARLY: "anual"
4771
- };
4772
- function PaywallFinePrint({
4773
- template,
4774
- className,
4775
- render
4776
- }) {
4777
- const {
4778
- currentPriceCents,
4779
- cycle,
4780
- trialDaysCard,
4781
- trialDaysPix,
4782
- selectedMethod
4783
- } = usePaywallContext();
4784
- const trialDays = selectedMethod === "card" ? trialDaysCard : trialDaysPix;
4785
- const cycleLabel = CYCLE_LABEL2[cycle] ?? cycle.toLowerCase();
4786
- const priceFormatted = formatBRL(currentPriceCents ?? 0);
4787
- const rootClasses = [DEFAULT_CLASS3, className].filter(Boolean).join(" ");
4788
- if (render) {
4789
- return /* @__PURE__ */ jsx49("p", { className: className || void 0, children: render({
4790
- currentPriceCents: currentPriceCents ?? 0,
4791
- cycle,
4792
- trialDays: trialDays ?? 0,
4793
- selectedMethod
4794
- }) });
4774
+ // src/OnboardingFlow.tsx
4775
+ import { useCallback as useCallback13, useEffect as useEffect18, useMemo as useMemo12, useRef as useRef8 } from "react";
4776
+ import { usePersistedState as usePersistedState4, useHook as useHook22 } from "@hook-sdk/sdk";
4777
+
4778
+ // src/hooks/useOnboardingStep.ts
4779
+ import { createContext as createContext4, useContext as useContext5 } from "react";
4780
+ var OnboardingStepContext = createContext4(null);
4781
+ function useOnboardingStep() {
4782
+ const ctx = useContext5(OnboardingStepContext);
4783
+ if (!ctx) {
4784
+ throw new Error(
4785
+ "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
4786
+ );
4795
4787
  }
4796
- const text = template.replaceAll("{price}", priceFormatted).replaceAll("{trialDays}", String(trialDays ?? 0)).replaceAll("{cycle}", cycleLabel);
4797
- return /* @__PURE__ */ jsx49("p", { className: rootClasses, children: text });
4788
+ return ctx;
4798
4789
  }
4799
4790
 
4800
- // src/components/paywall/blocks/PaywallTrustLine.tsx
4801
- import { jsx as jsx50, jsxs as jsxs30 } from "react/jsx-runtime";
4802
- var DEFAULT_ROOT3 = "flex items-center gap-3";
4803
- var DEFAULT_ITEM = "flex items-center gap-1.5 text-xs";
4804
- function PaywallTrustLine({
4805
- items,
4806
- className,
4807
- itemClassName,
4808
- renderItem
4791
+ // src/OnboardingFlow.tsx
4792
+ import { jsx as jsx52 } from "react/jsx-runtime";
4793
+ var isFilled = (v) => v != null && v !== "";
4794
+ var CURRENT_STEP_FIELD = "currentStep";
4795
+ function readPersistedStepIdx(draft) {
4796
+ const raw = draft[CURRENT_STEP_FIELD];
4797
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
4798
+ }
4799
+ function OnboardingFlow({
4800
+ steps,
4801
+ screens,
4802
+ onComplete,
4803
+ persistKey
4809
4804
  }) {
4810
- const rootClasses = [DEFAULT_ROOT3, className].filter(Boolean).join(" ");
4811
- const itemClasses = [DEFAULT_ITEM, itemClassName].filter(Boolean).join(" ");
4812
- return /* @__PURE__ */ jsx50("div", { className: rootClasses, children: items.map((item, idx) => {
4813
- if (renderItem) return renderItem(item, idx);
4814
- return /* @__PURE__ */ jsxs30("span", { className: itemClasses, children: [
4815
- /* @__PURE__ */ jsx50("span", { "aria-hidden": "true", children: item.icon }),
4816
- /* @__PURE__ */ jsx50("span", { children: item.text })
4817
- ] }, idx);
4818
- }) });
4805
+ const [draft, setDraft, status] = usePersistedState4(persistKey, {});
4806
+ const draftRef = useRef8(draft);
4807
+ draftRef.current = draft;
4808
+ const idx = readPersistedStepIdx(draft);
4809
+ const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
4810
+ const setIdx = useCallback13(
4811
+ (n) => {
4812
+ setDraft((prev) => {
4813
+ const prevIdx = readPersistedStepIdx(prev);
4814
+ const nextIdx = typeof n === "function" ? n(prevIdx) : n;
4815
+ return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
4816
+ });
4817
+ },
4818
+ [setDraft]
4819
+ );
4820
+ const setValue = useCallback13(
4821
+ (patch) => {
4822
+ draftRef.current = { ...draftRef.current, ...patch };
4823
+ setDraft((prev) => ({ ...prev, ...patch }));
4824
+ },
4825
+ [setDraft]
4826
+ );
4827
+ const step = steps[clampedIdx];
4828
+ const hookCtx = useHook22();
4829
+ const track2 = typeof hookCtx.track === "function" ? hookCtx.track : void 0;
4830
+ useEffect18(() => {
4831
+ if (status.loading) return;
4832
+ if (!step) return;
4833
+ if (!track2) return;
4834
+ track2("onboarding_step_viewed", {
4835
+ step: step.id,
4836
+ step_index: clampedIdx,
4837
+ total_steps: steps.length
4838
+ });
4839
+ }, [step?.id, clampedIdx, steps.length, status.loading, track2]);
4840
+ const valid = useMemo12(
4841
+ () => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
4842
+ [draft, step]
4843
+ );
4844
+ const next = useCallback13(() => {
4845
+ if (!step) return;
4846
+ const current = draftRef.current;
4847
+ const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
4848
+ if (!validNow) return;
4849
+ if (clampedIdx + 1 >= steps.length) {
4850
+ onComplete(current);
4851
+ } else {
4852
+ setIdx(clampedIdx + 1);
4853
+ }
4854
+ }, [clampedIdx, onComplete, step, steps.length, setIdx]);
4855
+ const prevStep = useCallback13(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
4856
+ const ctx = useMemo12(
4857
+ () => ({
4858
+ stepIndex: clampedIdx,
4859
+ totalSteps: steps.length,
4860
+ value: draft,
4861
+ setValue,
4862
+ valid,
4863
+ next,
4864
+ prev: prevStep
4865
+ }),
4866
+ [clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
4867
+ );
4868
+ if (status.loading) {
4869
+ return null;
4870
+ }
4871
+ if (!step) {
4872
+ throw new Error(
4873
+ `[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
4874
+ );
4875
+ }
4876
+ const Screen = screens[step.screen];
4877
+ if (!Screen) {
4878
+ throw new Error(
4879
+ `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
4880
+ );
4881
+ }
4882
+ return /* @__PURE__ */ jsx52(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx52(Screen, {}) });
4819
4883
  }
4820
4884
 
4821
- // src/components/paywall/blocks/PaywallStickyFooter.tsx
4822
- import { jsx as jsx51 } from "react/jsx-runtime";
4823
- var DEFAULT_CLASSES = "sticky bottom-0 left-0 right-0 bg-background";
4824
- var SAFE_AREA_CLASS = "pb-[env(safe-area-inset-bottom)]";
4825
- function PaywallStickyFooter({
4826
- children,
4827
- className,
4828
- safeAreaInsets = true
4829
- }) {
4830
- const classes = [DEFAULT_CLASSES, safeAreaInsets ? SAFE_AREA_CLASS : null, className].filter(Boolean).join(" ");
4831
- return /* @__PURE__ */ jsx51("div", { className: classes, children });
4885
+ // src/hooks/useFeature.ts
4886
+ function useFeature(name) {
4887
+ const config = useAppConfig();
4888
+ return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
4832
4889
  }
4833
4890
  export {
4834
4891
  AppConfigProvider,