@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.cjs CHANGED
@@ -105,14 +105,14 @@ __export(index_exports, {
105
105
  useSignupForm: () => useSignupForm,
106
106
  useSubscription: () => useSubscription,
107
107
  useToast: () => useToast,
108
- useTrackOnboardingStep: () => import_sdk25.useTrackOnboardingStep
108
+ useTrackOnboardingStep: () => import_sdk26.useTrackOnboardingStep
109
109
  });
110
110
  module.exports = __toCommonJS(index_exports);
111
111
 
112
112
  // src/AppRoot.tsx
113
- var import_react15 = require("react");
113
+ var import_react16 = require("react");
114
114
  var import_react_router_dom2 = require("react-router-dom");
115
- var import_sdk8 = require("@hook-sdk/sdk");
115
+ var import_sdk9 = require("@hook-sdk/sdk");
116
116
 
117
117
  // src/config/AppConfigContext.tsx
118
118
  var import_react = require("react");
@@ -2144,10 +2144,54 @@ function SessionExpiredBanner() {
2144
2144
  ] });
2145
2145
  }
2146
2146
 
2147
- // src/defaults/ErrorBoundary.tsx
2147
+ // src/internal/EmailVerifyBanner.tsx
2148
2148
  var import_react11 = require("react");
2149
+ var import_sdk5 = require("@hook-sdk/sdk");
2149
2150
  var import_jsx_runtime18 = require("react/jsx-runtime");
2150
- var ErrorBoundary = class extends import_react11.Component {
2151
+ function EmailVerifyBanner() {
2152
+ const { user, auth } = (0, import_sdk5.useHook)();
2153
+ const [sending, setSending] = (0, import_react11.useState)(false);
2154
+ const [sent, setSent] = (0, import_react11.useState)(false);
2155
+ if (!user || user.emailVerified) return null;
2156
+ async function handleResend() {
2157
+ if (sending || sent) return;
2158
+ setSending(true);
2159
+ try {
2160
+ await auth.resendVerify();
2161
+ setSent(true);
2162
+ } catch {
2163
+ } finally {
2164
+ setSending(false);
2165
+ }
2166
+ }
2167
+ const label = sent ? "Enviado!" : sending ? "Enviando..." : "Reenviar link";
2168
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
2169
+ "div",
2170
+ {
2171
+ role: "status",
2172
+ "data-testid": "email-verify-banner",
2173
+ 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",
2174
+ children: [
2175
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "Confirma teu e-mail pra liberar tudo." }),
2176
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2177
+ "button",
2178
+ {
2179
+ type: "button",
2180
+ onClick: handleResend,
2181
+ disabled: sending || sent,
2182
+ 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",
2183
+ children: label
2184
+ }
2185
+ )
2186
+ ]
2187
+ }
2188
+ );
2189
+ }
2190
+
2191
+ // src/defaults/ErrorBoundary.tsx
2192
+ var import_react12 = require("react");
2193
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2194
+ var ErrorBoundary = class extends import_react12.Component {
2151
2195
  state = { error: null };
2152
2196
  static getDerivedStateFromError(error) {
2153
2197
  return { error };
@@ -2164,21 +2208,21 @@ var ErrorBoundary = class extends import_react11.Component {
2164
2208
  }
2165
2209
  render() {
2166
2210
  if (this.state.error) {
2167
- return this.props.fallback ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
2168
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { children: "Algo deu errado" }),
2169
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
2211
+ return this.props.fallback ?? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
2212
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h2", { children: "Algo deu errado" }),
2213
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
2170
2214
  ] });
2171
2215
  }
2172
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: this.props.children });
2216
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: this.props.children });
2173
2217
  }
2174
2218
  };
2175
2219
 
2176
2220
  // src/i18n/I18nProvider.tsx
2177
- var import_react12 = require("react");
2221
+ var import_react13 = require("react");
2178
2222
  var import_i18next = __toESM(require("i18next"), 1);
2179
2223
  var import_react_i18next = require("react-i18next");
2180
- var import_sdk5 = require("@hook-sdk/sdk");
2181
- var import_jsx_runtime19 = require("react/jsx-runtime");
2224
+ var import_sdk6 = require("@hook-sdk/sdk");
2225
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2182
2226
  function ensureInitialized(defaultLocale, supportedLocales, resources, initialLocale) {
2183
2227
  if (import_i18next.default.isInitialized) return;
2184
2228
  import_i18next.default.use(import_react_i18next.initReactI18next).init({
@@ -2200,14 +2244,14 @@ function I18nProvider({
2200
2244
  resources,
2201
2245
  children
2202
2246
  }) {
2203
- const [userLocale] = (0, import_sdk5.usePersistedState)("user-locale", defaultLocale);
2247
+ const [userLocale] = (0, import_sdk6.usePersistedState)("user-locale", defaultLocale);
2204
2248
  ensureInitialized(defaultLocale, supportedLocales, resources, userLocale);
2205
- (0, import_react12.useEffect)(() => {
2249
+ (0, import_react13.useEffect)(() => {
2206
2250
  if (import_i18next.default.isInitialized && import_i18next.default.language !== userLocale) {
2207
2251
  import_i18next.default.changeLanguage(userLocale);
2208
2252
  }
2209
2253
  }, [userLocale]);
2210
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_i18next.I18nextProvider, { i18n: import_i18next.default, children });
2254
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_i18next.I18nextProvider, { i18n: import_i18next.default, children });
2211
2255
  }
2212
2256
 
2213
2257
  // src/dev/env.ts
@@ -2218,9 +2262,9 @@ function isDevToolsEnabled() {
2218
2262
  }
2219
2263
 
2220
2264
  // src/dev/DevSkipOnboardingFab.tsx
2221
- var import_react13 = require("react");
2222
- var import_sdk6 = require("@hook-sdk/sdk");
2223
- var import_jsx_runtime20 = require("react/jsx-runtime");
2265
+ var import_react14 = require("react");
2266
+ var import_sdk7 = require("@hook-sdk/sdk");
2267
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2224
2268
  var STORAGE_KEY = "hook_dev_skip_email";
2225
2269
  var TEST_EMAIL_DOMAIN = "@hook.test";
2226
2270
  var TEST_PASSWORD = "SkipTest!2026";
@@ -2287,25 +2331,25 @@ var STYLES = {
2287
2331
  };
2288
2332
  var CONFIRM_TIMEOUT_MS = 3e3;
2289
2333
  function DevSkipOnboardingFab({ defaults }) {
2290
- const hook = (0, import_sdk6.useHook)();
2334
+ const hook = (0, import_sdk7.useHook)();
2291
2335
  const { slug } = useAppConfig();
2292
- const [state, setState] = (0, import_react13.useState)("idle");
2293
- const [errorMsg, setErrorMsg] = (0, import_react13.useState)(null);
2294
- const timerRef = (0, import_react13.useRef)(null);
2336
+ const [state, setState] = (0, import_react14.useState)("idle");
2337
+ const [errorMsg, setErrorMsg] = (0, import_react14.useState)(null);
2338
+ const timerRef = (0, import_react14.useRef)(null);
2295
2339
  const isAuthed = hook.authStatus === "authenticated";
2296
- const [onboarding] = (0, import_sdk6.usePersistedState)(
2340
+ const [onboarding] = (0, import_sdk7.usePersistedState)(
2297
2341
  "onboarding_data",
2298
2342
  null,
2299
2343
  { enabled: isAuthed }
2300
2344
  );
2301
2345
  const onboardingCompleted = isAuthed && onboarding?.onboarding_completed === true;
2302
- const clearTimer = (0, import_react13.useCallback)(() => {
2346
+ const clearTimer = (0, import_react14.useCallback)(() => {
2303
2347
  if (timerRef.current) {
2304
2348
  clearTimeout(timerRef.current);
2305
2349
  timerRef.current = null;
2306
2350
  }
2307
2351
  }, []);
2308
- const onClick = (0, import_react13.useCallback)(async () => {
2352
+ const onClick = (0, import_react14.useCallback)(async () => {
2309
2353
  if (state === "busy") return;
2310
2354
  if (state === "idle" || state === "error") {
2311
2355
  setState("confirm");
@@ -2335,7 +2379,7 @@ function DevSkipOnboardingFab({ defaults }) {
2335
2379
  ...state === "confirm" || state === "error" ? STYLES.confirm : {},
2336
2380
  ...state === "busy" ? STYLES.busy : {}
2337
2381
  };
2338
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2382
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2339
2383
  "button",
2340
2384
  {
2341
2385
  type: "button",
@@ -2350,21 +2394,21 @@ function DevSkipOnboardingFab({ defaults }) {
2350
2394
  }
2351
2395
 
2352
2396
  // src/internal/PaymentReturnHandler.tsx
2353
- var import_react14 = require("react");
2354
- var import_sdk7 = require("@hook-sdk/sdk");
2355
- var import_jsx_runtime21 = require("react/jsx-runtime");
2397
+ var import_react15 = require("react");
2398
+ var import_sdk8 = require("@hook-sdk/sdk");
2399
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2356
2400
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2357
2401
  var MAX_CYCLES = 3;
2358
2402
  var SUPPORT_MAILTO = "mailto:suporte@usehook.net?subject=Pagamento%20pendente";
2359
2403
  function PaymentReturnHandler({ children }) {
2360
- const { subscription, track: track2 } = (0, import_sdk7.useHook)();
2361
- const subRef = (0, import_react14.useRef)(subscription);
2404
+ const { subscription, track: track2 } = (0, import_sdk8.useHook)();
2405
+ const subRef = (0, import_react15.useRef)(subscription);
2362
2406
  subRef.current = subscription;
2363
- const runIdRef = (0, import_react14.useRef)(0);
2364
- const cyclesRef = (0, import_react14.useRef)(0);
2365
- const startMsRef = (0, import_react14.useRef)(0);
2366
- const [state, setState] = (0, import_react14.useState)("idle");
2367
- const runPoll = (0, import_react14.useCallback)(() => {
2407
+ const runIdRef = (0, import_react15.useRef)(0);
2408
+ const cyclesRef = (0, import_react15.useRef)(0);
2409
+ const startMsRef = (0, import_react15.useRef)(0);
2410
+ const [state, setState] = (0, import_react15.useState)("idle");
2411
+ const runPoll = (0, import_react15.useCallback)(() => {
2368
2412
  const runId = ++runIdRef.current;
2369
2413
  const isFirstRun = cyclesRef.current === 0;
2370
2414
  cyclesRef.current += 1;
@@ -2412,7 +2456,7 @@ function PaymentReturnHandler({ children }) {
2412
2456
  };
2413
2457
  void tick();
2414
2458
  }, [track2]);
2415
- (0, import_react14.useEffect)(() => {
2459
+ (0, import_react15.useEffect)(() => {
2416
2460
  if (typeof window === "undefined") return;
2417
2461
  const url = new URL(window.location.href);
2418
2462
  if (url.searchParams.get("paymentReturn") !== "1") return;
@@ -2422,26 +2466,26 @@ function PaymentReturnHandler({ children }) {
2422
2466
  runIdRef.current++;
2423
2467
  };
2424
2468
  }, [runPoll]);
2425
- const goHome = (0, import_react14.useCallback)(() => {
2469
+ const goHome = (0, import_react15.useCallback)(() => {
2426
2470
  const cleanUrl = new URL(window.location.href);
2427
2471
  cleanUrl.searchParams.delete("paymentReturn");
2428
2472
  cleanUrl.pathname = "/app/home";
2429
2473
  window.location.href = cleanUrl.toString();
2430
2474
  }, []);
2431
2475
  if (state === "confirming") {
2432
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
2476
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
2433
2477
  }
2434
2478
  if (state === "waiting") {
2435
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
2436
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
2437
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
2479
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
2480
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
2481
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
2438
2482
  ] }) });
2439
2483
  }
2440
2484
  if (state === "timeout") {
2441
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "alert", "aria-live": "assertive", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { maxWidth: 360, textAlign: "center", lineHeight: 1.5 }, children: [
2442
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("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." }),
2443
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2444
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2485
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { role: "alert", "aria-live": "assertive", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { maxWidth: 360, textAlign: "center", lineHeight: 1.5 }, children: [
2486
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("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." }),
2487
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2488
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2445
2489
  "button",
2446
2490
  {
2447
2491
  type: "button",
@@ -2454,7 +2498,7 @@ function PaymentReturnHandler({ children }) {
2454
2498
  children: "Tentar de novo"
2455
2499
  }
2456
2500
  ),
2457
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2501
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2458
2502
  "button",
2459
2503
  {
2460
2504
  type: "button",
@@ -2464,7 +2508,7 @@ function PaymentReturnHandler({ children }) {
2464
2508
  children: "Voltar pro app"
2465
2509
  }
2466
2510
  ),
2467
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2511
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2468
2512
  "a",
2469
2513
  {
2470
2514
  href: SUPPORT_MAILTO,
@@ -2476,7 +2520,7 @@ function PaymentReturnHandler({ children }) {
2476
2520
  ] })
2477
2521
  ] }) });
2478
2522
  }
2479
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_jsx_runtime21.Fragment, { children });
2523
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children });
2480
2524
  }
2481
2525
  var overlayStyle2 = {
2482
2526
  position: "fixed",
@@ -2515,7 +2559,7 @@ var linkStyle = {
2515
2559
  };
2516
2560
 
2517
2561
  // src/AppRoot.tsx
2518
- var import_jsx_runtime22 = require("react/jsx-runtime");
2562
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2519
2563
  function buildLegacyConfigShim(config) {
2520
2564
  const paywall = config.paywall;
2521
2565
  const isFree = paywall.mode === "free";
@@ -2589,19 +2633,22 @@ function AppRoot(props) {
2589
2633
  "[hook-template] <AppRoot>: PreAuthFlow slot prop is required when config.onboarding.trigger === 'pre_signup_custom'."
2590
2634
  );
2591
2635
  }
2592
- const legacyShim = (0, import_react15.useMemo)(() => buildLegacyConfigShim(config), [config]);
2636
+ const legacyShim = (0, import_react16.useMemo)(() => buildLegacyConfigShim(config), [config]);
2593
2637
  const Router = testRouter === "memory" ? import_react_router_dom2.MemoryRouter : import_react_router_dom2.BrowserRouter;
2594
2638
  const basename = `/app/${config.slug}`;
2595
2639
  const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
2596
2640
  const position = config.install_prompt?.position ?? "post-paywall";
2597
- const subscriptionGated = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SubscriptionGate, { Paywall: Paywall2 ?? FallbackPaywall, children: position === "post-paywall" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(InstallGate, { position: "post-paywall", children: [
2598
- children,
2599
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PushPrompt, {})
2600
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
2601
- children,
2602
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PushPrompt, {})
2603
- ] }) });
2604
- const authGated = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2641
+ const subscriptionGated = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(SubscriptionGate, { Paywall: Paywall2 ?? FallbackPaywall, children: [
2642
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(EmailVerifyBanner, {}),
2643
+ position === "post-paywall" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(InstallGate, { position: "post-paywall", children: [
2644
+ children,
2645
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PushPrompt, {})
2646
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
2647
+ children,
2648
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PushPrompt, {})
2649
+ ] })
2650
+ ] });
2651
+ const authGated = /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2605
2652
  AuthGated,
2606
2653
  {
2607
2654
  config,
@@ -2616,13 +2663,13 @@ function AppRoot(props) {
2616
2663
  children: subscriptionGated
2617
2664
  }
2618
2665
  );
2619
- const routedTree = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Router, { ...routerProps, children: [
2620
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DeepLinkHandler, { deepLinks: config.deepLinks }),
2621
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SessionExpiredBanner, {}),
2622
- position === "pre-auth" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(InstallGate, { position: "pre-auth", children: authGated }) : authGated,
2623
- isDevToolsEnabled() && devSkipOnboarding ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DevSkipOnboardingFab, { defaults: devSkipOnboarding.defaults }) : null
2666
+ const routedTree = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(Router, { ...routerProps, children: [
2667
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DeepLinkHandler, { deepLinks: config.deepLinks }),
2668
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(SessionExpiredBanner, {}),
2669
+ position === "pre-auth" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(InstallGate, { position: "pre-auth", children: authGated }) : authGated,
2670
+ isDevToolsEnabled() && devSkipOnboarding ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DevSkipOnboardingFab, { defaults: devSkipOnboarding.defaults }) : null
2624
2671
  ] });
2625
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AppConfigProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PersistenceRegistry, { config: config.persistedKeys, children: config.i18n ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2672
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(AppConfigProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PersistenceRegistry, { config: config.persistedKeys, children: config.i18n ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2626
2673
  I18nProvider,
2627
2674
  {
2628
2675
  defaultLocale: config.i18n.defaultLocale,
@@ -2642,46 +2689,46 @@ function AuthGated({
2642
2689
  EmailVerify,
2643
2690
  PreAuthFlow
2644
2691
  }) {
2645
- const { authStatus } = (0, import_sdk8.useHook)();
2692
+ const { authStatus } = (0, import_sdk9.useHook)();
2646
2693
  if (authStatus === "loading") return null;
2647
2694
  if (authStatus !== "authenticated") {
2648
2695
  if (config.authFlow.signupMode === "pay_first" && PreAuthFlow) {
2649
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_router_dom2.Routes, { children: [
2650
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/signin", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Login, {}) }),
2651
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Forgot, {}) }),
2652
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Reset, {}) }),
2653
- EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmailVerify, {}) }) : null,
2654
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/*", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PreAuthFlow, {}) })
2696
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_router_dom2.Routes, { children: [
2697
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/signin", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Login, {}) }),
2698
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Forgot, {}) }),
2699
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Reset, {}) }),
2700
+ EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(EmailVerify, {}) }) : null,
2701
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/*", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PreAuthFlow, {}) })
2655
2702
  ] });
2656
2703
  }
2657
2704
  if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
2658
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_router_dom2.Routes, { children: [
2659
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/signin", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Login, {}) }),
2660
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Signup, {}) }),
2661
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Forgot, {}) }),
2662
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Reset, {}) }),
2663
- EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmailVerify, {}) }) : null,
2664
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/*", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(PreAuthFlow, {}) })
2705
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_router_dom2.Routes, { children: [
2706
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/signin", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Login, {}) }),
2707
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Signup, {}) }),
2708
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Forgot, {}) }),
2709
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Reset, {}) }),
2710
+ EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(EmailVerify, {}) }) : null,
2711
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/*", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PreAuthFlow, {}) })
2665
2712
  ] });
2666
2713
  }
2667
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_router_dom2.Routes, { children: [
2668
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Login, {}) }),
2669
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Signup, {}) }),
2670
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Forgot, {}) }),
2671
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Reset, {}) }),
2672
- EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmailVerify, {}) }) : null,
2673
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom2.Navigate, { to: "/", replace: true }) })
2714
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_router_dom2.Routes, { children: [
2715
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Login, {}) }),
2716
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Signup, {}) }),
2717
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Forgot, {}) }),
2718
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Reset, {}) }),
2719
+ EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(EmailVerify, {}) }) : null,
2720
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom2.Navigate, { to: "/", replace: true }) })
2674
2721
  ] });
2675
2722
  }
2676
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children });
2723
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_jsx_runtime23.Fragment, { children });
2677
2724
  }
2678
2725
  function FallbackPaywall() {
2679
2726
  return null;
2680
2727
  }
2681
2728
 
2682
2729
  // src/hooks/usePush.ts
2683
- var import_react16 = require("react");
2684
- var import_sdk9 = require("@hook-sdk/sdk");
2730
+ var import_react17 = require("react");
2731
+ var import_sdk10 = require("@hook-sdk/sdk");
2685
2732
  var DISMISS_STORAGE_KEY = "push:dismissed-until";
2686
2733
  var DISMISS_TTL_MS2 = 7 * 24 * 60 * 60 * 1e3;
2687
2734
  function detectIosNeedsInstall() {
@@ -2725,12 +2772,12 @@ function deriveState(push) {
2725
2772
  return { kind: "prompt" };
2726
2773
  }
2727
2774
  function usePush() {
2728
- const { push } = (0, import_sdk9.useHook)();
2729
- const [state, setState] = (0, import_react16.useState)(() => deriveState(push));
2730
- (0, import_react16.useEffect)(() => {
2775
+ const { push } = (0, import_sdk10.useHook)();
2776
+ const [state, setState] = (0, import_react17.useState)(() => deriveState(push));
2777
+ (0, import_react17.useEffect)(() => {
2731
2778
  setState(deriveState(push));
2732
2779
  }, [push]);
2733
- const subscribe = (0, import_react16.useCallback)(async () => {
2780
+ const subscribe = (0, import_react17.useCallback)(async () => {
2734
2781
  try {
2735
2782
  await push.subscribe();
2736
2783
  setState({ kind: "subscribed" });
@@ -2742,7 +2789,7 @@ function usePush() {
2742
2789
  throw e;
2743
2790
  }
2744
2791
  }, [push]);
2745
- const unsubscribe = (0, import_react16.useCallback)(async () => {
2792
+ const unsubscribe = (0, import_react17.useCallback)(async () => {
2746
2793
  try {
2747
2794
  await push.unsubscribe();
2748
2795
  setState({ kind: "prompt" });
@@ -2751,7 +2798,7 @@ function usePush() {
2751
2798
  throw e;
2752
2799
  }
2753
2800
  }, [push]);
2754
- const dismiss = (0, import_react16.useCallback)(() => {
2801
+ const dismiss = (0, import_react17.useCallback)(() => {
2755
2802
  if (typeof localStorage !== "undefined") {
2756
2803
  try {
2757
2804
  localStorage.setItem(DISMISS_STORAGE_KEY, String(Date.now() + DISMISS_TTL_MS2));
@@ -2764,27 +2811,27 @@ function usePush() {
2764
2811
  }
2765
2812
 
2766
2813
  // src/components/PushPrompt.tsx
2767
- var import_jsx_runtime23 = require("react/jsx-runtime");
2814
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2768
2815
  function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
2769
2816
  const { state, subscribe } = usePush();
2770
2817
  if (state.kind === "denied" || state.kind === "dismissed" || state.kind === "subscribed") {
2771
2818
  return null;
2772
2819
  }
2773
2820
  if (state.kind === "ios_needs_install") {
2774
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2775
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { children: texts.iosInstallTitle }),
2776
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: texts.iosInstallBody }),
2777
- onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2821
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2822
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h3", { children: texts.iosInstallTitle }),
2823
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { children: texts.iosInstallBody }),
2824
+ onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2778
2825
  ] });
2779
2826
  }
2780
2827
  if (state.kind === "unsupported") {
2781
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className, role: "region", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: texts.unsupportedBody }) });
2828
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className, role: "region", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { children: texts.unsupportedBody }) });
2782
2829
  }
2783
2830
  if (state.kind === "error") {
2784
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: state.message }) });
2831
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { children: state.message }) });
2785
2832
  }
2786
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className, role: "region", children: [
2787
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2833
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className, role: "region", children: [
2834
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2788
2835
  "button",
2789
2836
  {
2790
2837
  type: "button",
@@ -2798,71 +2845,71 @@ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, clas
2798
2845
  children: texts.cta
2799
2846
  }
2800
2847
  ),
2801
- onDeclined && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2848
+ onDeclined && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2802
2849
  ] });
2803
2850
  }
2804
2851
 
2805
2852
  // src/components/LanguageSwitcher.tsx
2806
- var import_sdk10 = require("@hook-sdk/sdk");
2807
- var import_jsx_runtime24 = require("react/jsx-runtime");
2853
+ var import_sdk11 = require("@hook-sdk/sdk");
2854
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2808
2855
  function LanguageSwitcher({ id, className, label = "Language" }) {
2809
2856
  const config = useAppConfig();
2810
2857
  const i18nConfig = config.i18n;
2811
- const [userLocale, setUserLocale] = (0, import_sdk10.usePersistedState)(
2858
+ const [userLocale, setUserLocale] = (0, import_sdk11.usePersistedState)(
2812
2859
  "user-locale",
2813
2860
  i18nConfig?.defaultLocale ?? "en-US"
2814
2861
  );
2815
2862
  if (!i18nConfig) return null;
2816
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("label", { className, children: [
2817
- label ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { children: label }) : null,
2818
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2863
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("label", { className, children: [
2864
+ label ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: label }) : null,
2865
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2819
2866
  "select",
2820
2867
  {
2821
2868
  id,
2822
2869
  value: userLocale,
2823
2870
  onChange: (e) => setUserLocale(e.target.value),
2824
2871
  "data-testid": "language-switcher",
2825
- children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("option", { value: loc, children: loc }, loc))
2872
+ children: i18nConfig.supportedLocales.map((loc) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("option", { value: loc, children: loc }, loc))
2826
2873
  }
2827
2874
  )
2828
2875
  ] });
2829
2876
  }
2830
2877
 
2831
2878
  // src/defaults/LoadingState.tsx
2832
- var import_jsx_runtime25 = require("react/jsx-runtime");
2879
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2833
2880
  function LoadingState({ message }) {
2834
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { children: message ?? "Carregando..." }) });
2881
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: message ?? "Carregando..." }) });
2835
2882
  }
2836
2883
 
2837
2884
  // src/defaults/EmptyState.tsx
2838
- var import_jsx_runtime26 = require("react/jsx-runtime");
2885
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2839
2886
  function EmptyState({ title, description, action }) {
2840
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2841
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
2842
- description && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { style: { opacity: 0.7 }, children: description }),
2843
- action && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { style: { marginTop: 16 }, children: action })
2887
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2888
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
2889
+ description && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { style: { opacity: 0.7 }, children: description }),
2890
+ action && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { style: { marginTop: 16 }, children: action })
2844
2891
  ] });
2845
2892
  }
2846
2893
 
2847
2894
  // src/defaults/CheckoutPageDefault.tsx
2848
- var import_react18 = require("react");
2895
+ var import_react23 = require("react");
2849
2896
  var import_react_router_dom3 = require("react-router-dom");
2850
2897
 
2851
2898
  // src/hooks/useCheckoutForm.ts
2852
- var import_react17 = require("react");
2853
- var import_sdk12 = require("@hook-sdk/sdk");
2899
+ var import_react18 = require("react");
2900
+ var import_sdk13 = require("@hook-sdk/sdk");
2854
2901
 
2855
2902
  // src/errors.ts
2856
- var import_sdk11 = require("@hook-sdk/sdk");
2903
+ var import_sdk12 = require("@hook-sdk/sdk");
2857
2904
  function mapSdkError(err) {
2858
- if (err instanceof import_sdk11.SdkRateLimitError) {
2905
+ if (err instanceof import_sdk12.SdkRateLimitError) {
2859
2906
  return {
2860
2907
  code: "rate_limited",
2861
2908
  message: `Aguarde ${err.retryAfter}s e tente novamente.`,
2862
2909
  retryAfter: err.retryAfter
2863
2910
  };
2864
2911
  }
2865
- if (err instanceof import_sdk11.SdkAuthError) {
2912
+ if (err instanceof import_sdk12.SdkAuthError) {
2866
2913
  const detail = err.detail;
2867
2914
  if (detail === "email_unverified") {
2868
2915
  return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
@@ -2872,7 +2919,10 @@ function mapSdkError(err) {
2872
2919
  }
2873
2920
  return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
2874
2921
  }
2875
- if (err instanceof import_sdk11.SdkError && err.httpStatus === 0) {
2922
+ if (err instanceof import_sdk12.SdkValidationError && err.code === "auth.email_taken") {
2923
+ return { code: "email_taken", message: "Esse e-mail j\xE1 tem conta." };
2924
+ }
2925
+ if (err instanceof import_sdk12.SdkError && err.httpStatus === 0) {
2876
2926
  return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2877
2927
  }
2878
2928
  if (err instanceof TypeError) {
@@ -2886,37 +2936,37 @@ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2886
2936
  var PHONE_RE = /^[0-9()+\-\s]{8,20}$/;
2887
2937
  var CHECK_DEBOUNCE_MS = 400;
2888
2938
  function useCheckoutForm(args) {
2889
- const { auth } = (0, import_sdk12.useHook)();
2890
- const [name, setName] = (0, import_react17.useState)("");
2891
- const [email, setEmail] = (0, import_react17.useState)("");
2892
- const [emailConfirm, setEmailConfirm] = (0, import_react17.useState)("");
2893
- const [phone, setPhone] = (0, import_react17.useState)("");
2894
- const [cpf, setCpf] = (0, import_react17.useState)("");
2895
- const [method, setMethod] = (0, import_react17.useState)(args.defaultMethod);
2896
- const [cycle, setCycle] = (0, import_react17.useState)(args.defaultCycle);
2897
- const [card, setCardState] = (0, import_react17.useState)({
2939
+ const { auth } = (0, import_sdk13.useHook)();
2940
+ const [name, setName] = (0, import_react18.useState)("");
2941
+ const [email, setEmail] = (0, import_react18.useState)("");
2942
+ const [emailConfirm, setEmailConfirm] = (0, import_react18.useState)("");
2943
+ const [phone, setPhone] = (0, import_react18.useState)("");
2944
+ const [cpf, setCpf] = (0, import_react18.useState)("");
2945
+ const [method, setMethod] = (0, import_react18.useState)(args.defaultMethod);
2946
+ const [cycle, setCycle] = (0, import_react18.useState)(args.defaultCycle);
2947
+ const [card, setCardState] = (0, import_react18.useState)({
2898
2948
  number: "",
2899
2949
  expiryMonth: "",
2900
2950
  expiryYear: "",
2901
2951
  ccv: "",
2902
2952
  holderName: ""
2903
2953
  });
2904
- const setCard = (0, import_react17.useCallback)((patch) => {
2954
+ const setCard = (0, import_react18.useCallback)((patch) => {
2905
2955
  setCardState((prev) => ({ ...prev, ...patch }));
2906
2956
  }, []);
2907
- const [touchedName, setTouchedName] = (0, import_react17.useState)(false);
2908
- const [touchedEmail, setTouchedEmail] = (0, import_react17.useState)(false);
2909
- const [touchedEmailConfirm, setTouchedEmailConfirm] = (0, import_react17.useState)(false);
2910
- const [touchedPhone, setTouchedPhone] = (0, import_react17.useState)(false);
2911
- const [touchedCpf, setTouchedCpf] = (0, import_react17.useState)(false);
2912
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react17.useState)(false);
2913
- const [submitting, setSubmitting] = (0, import_react17.useState)(false);
2914
- const [error, setError] = (0, import_react17.useState)(null);
2915
- const [emailTaken, setEmailTaken] = (0, import_react17.useState)(false);
2916
- const [loginUrl, setLoginUrl] = (0, import_react17.useState)(null);
2917
- const [emailStatus, setEmailStatus] = (0, import_react17.useState)("idle");
2918
- const lastCheckedEmail = (0, import_react17.useRef)("");
2919
- (0, import_react17.useEffect)(() => {
2957
+ const [touchedName, setTouchedName] = (0, import_react18.useState)(false);
2958
+ const [touchedEmail, setTouchedEmail] = (0, import_react18.useState)(false);
2959
+ const [touchedEmailConfirm, setTouchedEmailConfirm] = (0, import_react18.useState)(false);
2960
+ const [touchedPhone, setTouchedPhone] = (0, import_react18.useState)(false);
2961
+ const [touchedCpf, setTouchedCpf] = (0, import_react18.useState)(false);
2962
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react18.useState)(false);
2963
+ const [submitting, setSubmitting] = (0, import_react18.useState)(false);
2964
+ const [error, setError] = (0, import_react18.useState)(null);
2965
+ const [emailTaken, setEmailTaken] = (0, import_react18.useState)(false);
2966
+ const [loginUrl, setLoginUrl] = (0, import_react18.useState)(null);
2967
+ const [emailStatus, setEmailStatus] = (0, import_react18.useState)("idle");
2968
+ const lastCheckedEmail = (0, import_react18.useRef)("");
2969
+ (0, import_react18.useEffect)(() => {
2920
2970
  if (!email || !EMAIL_RE.test(email)) {
2921
2971
  setEmailStatus("idle");
2922
2972
  return;
@@ -2934,27 +2984,27 @@ function useCheckoutForm(args) {
2934
2984
  }, CHECK_DEBOUNCE_MS);
2935
2985
  return () => clearTimeout(timer);
2936
2986
  }, [email, auth]);
2937
- const validateName = (0, import_react17.useMemo)(() => {
2987
+ const validateName = (0, import_react18.useMemo)(() => {
2938
2988
  if (name.length === 0) return null;
2939
2989
  if (name.trim().length < 2) return "Nome muito curto.";
2940
2990
  return null;
2941
2991
  }, [name]);
2942
- const validateEmail = (0, import_react17.useMemo)(() => {
2992
+ const validateEmail = (0, import_react18.useMemo)(() => {
2943
2993
  if (email.length === 0) return null;
2944
2994
  if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
2945
2995
  return null;
2946
2996
  }, [email]);
2947
- const validateEmailConfirm = (0, import_react17.useMemo)(() => {
2997
+ const validateEmailConfirm = (0, import_react18.useMemo)(() => {
2948
2998
  if (emailConfirm.length === 0) return null;
2949
2999
  if (emailConfirm !== email) return "Os e-mails n\xE3o coincidem.";
2950
3000
  return null;
2951
3001
  }, [emailConfirm, email]);
2952
- const validatePhone = (0, import_react17.useMemo)(() => {
3002
+ const validatePhone = (0, import_react18.useMemo)(() => {
2953
3003
  if (phone.length === 0) return null;
2954
3004
  if (!PHONE_RE.test(phone)) return "Telefone inv\xE1lido.";
2955
3005
  return null;
2956
3006
  }, [phone]);
2957
- const validateCpf = (0, import_react17.useMemo)(() => {
3007
+ const validateCpf = (0, import_react18.useMemo)(() => {
2958
3008
  if (cpf.length === 0) return null;
2959
3009
  const digits = cpf.replace(/\D/g, "");
2960
3010
  if (digits.length !== 11) return "CPF deve ter 11 d\xEDgitos.";
@@ -2969,7 +3019,7 @@ function useCheckoutForm(args) {
2969
3019
  const cpfError = touchedCpf || formSubmitAttempted ? validateCpf : null;
2970
3020
  const phoneOk = method === "pix-auto" ? phone === "" || PHONE_RE.test(phone) : PHONE_RE.test(phone);
2971
3021
  const canSubmit = name.trim().length >= 2 && EMAIL_RE.test(email) && (emailConfirm === "" || emailConfirm === email) && phoneOk && validateCpf === null && cpf.replace(/\D/g, "").length === 11 && emailStatus !== "exists" && !submitting && (method !== "card" || card.number.length >= 12 && card.ccv.length >= 3 && card.expiryMonth.length >= 1 && card.expiryYear.length >= 2 && card.holderName.length >= 1);
2972
- const submit = (0, import_react17.useCallback)(async () => {
3022
+ const submit = (0, import_react18.useCallback)(async () => {
2973
3023
  setFormSubmitAttempted(true);
2974
3024
  setError(null);
2975
3025
  setEmailTaken(false);
@@ -2998,10 +3048,16 @@ function useCheckoutForm(args) {
2998
3048
  name: card.holderName || name.trim(),
2999
3049
  email,
3000
3050
  cpfCnpj: cpf.replace(/\D/g, ""),
3001
- // Empty postal/address: backend defaults if Asaas requires.
3002
- // Apps that need full address should override via custom form.
3003
- postalCode: "00000000",
3004
- addressNumber: "0",
3051
+ // Plan-V 0.28.4 Asaas's `creditCardHolderInfo.postalCode`
3052
+ // rejects all-zeros with `tokenize_failed:invalid_holderInfo`
3053
+ // ("O CEP informado é inválido."). The default CheckoutPage
3054
+ // doesn't collect CEP from the user (reference design skipped
3055
+ // it), so ship a known-valid placeholder. Apps that need real
3056
+ // customer addresses must override CheckoutPageDefault and
3057
+ // collect CEP — see personalburn's PaywallStepPagamento for
3058
+ // the pattern.
3059
+ postalCode: "01001000",
3060
+ addressNumber: "100",
3005
3061
  phone
3006
3062
  }
3007
3063
  };
@@ -3019,7 +3075,7 @@ function useCheckoutForm(args) {
3019
3075
  const result = await auth.subscribeAnonymous(args2);
3020
3076
  return result;
3021
3077
  } catch (err) {
3022
- if (err instanceof import_sdk12.EmailTakenError) {
3078
+ if (err instanceof import_sdk13.EmailTakenError) {
3023
3079
  setEmailTaken(true);
3024
3080
  setLoginUrl(err.loginUrl);
3025
3081
  setEmailStatus("exists");
@@ -3076,1867 +3132,1868 @@ function mod11(digits, len) {
3076
3132
  }
3077
3133
 
3078
3134
  // src/hooks/usePlan.ts
3079
- var import_sdk13 = require("@hook-sdk/sdk");
3135
+ var import_sdk14 = require("@hook-sdk/sdk");
3080
3136
  function usePlan() {
3081
- const { plan } = (0, import_sdk13.useHook)();
3137
+ const { plan } = (0, import_sdk14.useHook)();
3082
3138
  return plan;
3083
3139
  }
3084
3140
 
3085
- // src/defaults/CheckoutPageDefault.tsx
3086
- var import_jsx_runtime27 = require("react/jsx-runtime");
3087
- var INTENT_KEY = "hook:paywall:intent";
3088
- var PIX_PAYLOAD_KEY = "hook:paywall:pix-pending";
3089
- function readIntent() {
3090
- if (typeof window === "undefined") return {};
3091
- try {
3092
- const raw = sessionStorage.getItem(INTENT_KEY);
3093
- if (!raw) return {};
3094
- return JSON.parse(raw);
3095
- } catch {
3096
- return {};
3097
- }
3098
- }
3099
- function formatBrl(cents) {
3100
- return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(cents / 100);
3141
+ // src/components/paywall/Paywall.tsx
3142
+ var import_react21 = require("react");
3143
+ var import_sdk15 = require("@hook-sdk/sdk");
3144
+
3145
+ // src/utils/price.ts
3146
+ function formatBRL(cents) {
3147
+ if (cents === null || cents === void 0) return "";
3148
+ const reais = cents / 100;
3149
+ return new Intl.NumberFormat("pt-BR", {
3150
+ style: "currency",
3151
+ currency: "BRL"
3152
+ }).format(reais);
3101
3153
  }
3102
- function formatCardNumber(v) {
3103
- const digits = v.replace(/\D/g, "").slice(0, 16);
3104
- return digits.replace(/(.{4})/g, "$1 ").trim();
3154
+ function monthlyFromYearly(yearlyCents) {
3155
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
3156
+ return Math.round(yearlyCents / 12);
3105
3157
  }
3106
- function formatExpiryMmAa(v) {
3107
- const d = v.replace(/\D/g, "").slice(0, 4);
3108
- if (d.length < 3) return d;
3109
- return d.slice(0, 2) + "/" + d.slice(2);
3158
+ function dailyFromYearly(yearlyCents) {
3159
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
3160
+ return Math.round(yearlyCents / 365);
3110
3161
  }
3111
- function parseExpiryMmAa(v) {
3112
- const d = v.replace(/\D/g, "");
3113
- return { month: d.slice(0, 2), year: d.slice(2, 4) };
3162
+ function computeAnchorCents(baseCents, multiplier) {
3163
+ if (multiplier === null || multiplier === void 0) return null;
3164
+ if (!Number.isFinite(multiplier)) return null;
3165
+ if (multiplier <= 1) return null;
3166
+ return Math.round(baseCents * multiplier);
3114
3167
  }
3115
- function formatCpf(v) {
3116
- const d = v.replace(/\D/g, "").slice(0, 11);
3117
- if (d.length <= 3) return d;
3118
- if (d.length <= 6) return d.slice(0, 3) + "." + d.slice(3);
3119
- if (d.length <= 9) return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6);
3120
- return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6, 9) + "-" + d.slice(9);
3168
+ function discountPercent(anchorCents, realCents) {
3169
+ if (anchorCents <= realCents) return 0;
3170
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
3121
3171
  }
3122
- function detectCardBrand(num) {
3123
- const n = num.replace(/\s/g, "");
3124
- if (/^4/.test(n)) return "VISA";
3125
- if (/^(5[1-5]|2[2-7])/.test(n)) return "MASTER";
3126
- if (/^3[47]/.test(n)) return "AMEX";
3127
- if (/^(4011|4312|4389|4514|6011|6362|6363)/.test(n)) return "ELO";
3128
- if (/^(606282|3841)/.test(n)) return "HIPER";
3129
- return "";
3172
+
3173
+ // src/components/paywall/PaywallProvider.tsx
3174
+ var import_react19 = require("react");
3175
+ var import_jsx_runtime28 = require("react/jsx-runtime");
3176
+ var PaywallContext = (0, import_react19.createContext)(null);
3177
+ function PaywallProvider({ children }) {
3178
+ const state = usePaywallState();
3179
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(PaywallContext.Provider, { value: state, children });
3130
3180
  }
3131
- function CheckoutPageDefault() {
3132
- const navigate = (0, import_react_router_dom3.useNavigate)();
3133
- const plan = usePlan();
3134
- const intent = (0, import_react18.useMemo)(readIntent, []);
3135
- const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3136
- const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3137
- const form = useCheckoutForm({ defaultMethod, defaultCycle });
3138
- const [expiryMmAa, setExpiryMmAa] = (0, import_react18.useState)("");
3139
- (0, import_react18.useEffect)(() => {
3140
- const { month, year } = parseExpiryMmAa(expiryMmAa);
3141
- if (month !== form.card.expiryMonth || year !== form.card.expiryYear) {
3142
- form.setCard({ expiryMonth: month, expiryYear: year });
3143
- }
3144
- }, [expiryMmAa]);
3145
- (0, import_react18.useEffect)(() => {
3146
- if (form.emailTaken && form.loginUrl) {
3147
- const t = setTimeout(() => navigate(form.loginUrl), 1200);
3148
- return () => clearTimeout(t);
3149
- }
3150
- }, [form.emailTaken, form.loginUrl, navigate]);
3151
- const planInfo = plan.data ? {
3152
- priceCents: plan.data.priceCents,
3153
- yearlyPriceCents: plan.data.yearlyPriceCents,
3154
- trialDays: plan.data.trialDays
3155
- } : null;
3156
- const annual = form.cycle === "YEARLY";
3157
- const cyclePrice = (0, import_react18.useMemo)(() => {
3158
- if (!planInfo) return null;
3159
- return annual ? planInfo.yearlyPriceCents ?? planInfo.priceCents * 12 : planInfo.priceCents;
3160
- }, [planInfo, annual]);
3161
- const monthlyText = (0, import_react18.useMemo)(() => {
3162
- if (!planInfo) return "";
3163
- const monthly = annual && planInfo.yearlyPriceCents ? Math.round(planInfo.yearlyPriceCents / 12) : planInfo.priceCents;
3164
- return formatBrl(monthly);
3165
- }, [planInfo, annual]);
3166
- const todayCents = (0, import_react18.useMemo)(() => {
3167
- if (form.method === "pix-auto") return cyclePrice ?? 0;
3168
- const trialDays2 = planInfo?.trialDays ?? 0;
3169
- if (trialDays2 > 0) return 0;
3170
- return cyclePrice ?? 0;
3171
- }, [form.method, cyclePrice, planInfo]);
3172
- const todayAmount = formatBrl(todayCents);
3173
- const cyclePriceText = cyclePrice !== null ? formatBrl(cyclePrice) : "";
3174
- const annualSavingsCents = (0, import_react18.useMemo)(() => {
3175
- if (!planInfo || !planInfo.yearlyPriceCents) return 0;
3176
- return planInfo.priceCents * 12 - planInfo.yearlyPriceCents;
3177
- }, [planInfo]);
3178
- const trialDays = planInfo?.trialDays ?? 7;
3179
- const cardBrand = detectCardBrand(form.card.number);
3180
- async function onSubmit(e) {
3181
- e.preventDefault();
3182
- const result = await form.submit();
3183
- if (!result) return;
3184
- if (form.method === "pix-auto" && result.pix_qr_payload) {
3185
- try {
3186
- sessionStorage.setItem(
3187
- PIX_PAYLOAD_KEY,
3188
- JSON.stringify({
3189
- payload: result.pix_qr_payload,
3190
- base64: result.pix_qr_base64 ?? null,
3191
- subscriptionId: result.subscription_id,
3192
- pixAuthorizationId: result.pix_authorization_id ?? null
3193
- })
3194
- );
3195
- } catch {
3196
- }
3197
- navigate(result.redirect.replace(/^.*\/app\/[^/]+/, ""));
3198
- return;
3199
- }
3200
- navigate(result.redirect.replace(/^.*\/app\/[^/]+/, "") || "/");
3181
+
3182
+ // src/components/paywall/usePaywallContext.ts
3183
+ var import_react20 = require("react");
3184
+ function usePaywallContext() {
3185
+ const ctx = (0, import_react20.useContext)(PaywallContext);
3186
+ if (!ctx) {
3187
+ throw new Error("usePaywallContext must be used within <PaywallProvider>");
3201
3188
  }
3202
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "flex-1 flex flex-col bg-background min-h-0", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("form", { onSubmit, className: "flex-1 overflow-y-auto", children: [
3203
- form.emailTaken ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "px-5 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { role: "alert", className: "rounded-2xl bg-destructive/10 p-4 text-sm text-destructive border border-destructive/20", children: [
3204
- "Esse e-mail j\xE1 tem conta nesse app.",
3205
- " ",
3206
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("a", { href: form.loginUrl ?? "/signin", className: "underline font-semibold", children: "Entrar agora" })
3207
- ] }) }) : null,
3208
- form.error ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "px-5 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("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,
3209
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "px-5 pt-4", children: form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "rounded-2xl bg-card border-[1.5px] border-foreground p-3.5", children: [
3210
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [
3211
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ShieldIcon, { className: "w-4 h-4" }),
3212
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-sm font-bold", children: "Voc\xEA N\xC3O ser\xE1 cobrada hoje" })
3213
- ] }),
3214
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex justify-between items-baseline text-sm text-muted-foreground", children: [
3215
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: "R$ 0,00 agora" }),
3216
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "opacity-50", children: "\xB7" }),
3217
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
3218
- monthlyText,
3219
- "/m\xEAs ap\xF3s ",
3220
- trialDays,
3221
- " dias"
3222
- ] })
3223
- ] }),
3224
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "mt-2.5 text-[11px] text-muted-foreground flex items-center gap-1.5", children: [
3225
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BellIcon, { className: "w-2.5 h-2.5" }),
3226
- "Avisamos por email 2 dias antes da primeira cobran\xE7a"
3227
- ] })
3228
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "rounded-2xl p-3.5 bg-emerald-50 border-[1.5px] border-emerald-600/60", children: [
3229
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-center gap-2 mb-2 text-emerald-900", children: [
3230
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(ShieldIcon, { className: "w-4 h-4" }),
3231
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-sm font-bold", children: [
3232
- "Garantia incondicional de ",
3233
- trialDays,
3234
- " dias"
3235
- ] })
3236
- ] }),
3237
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-sm text-emerald-900 leading-snug", children: [
3238
- "Voc\xEA paga ",
3239
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("b", { children: todayAmount }),
3240
- " agora via Pix.",
3241
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("br", {}),
3242
- "N\xE3o gostou em ",
3243
- trialDays,
3244
- " dias? Devolvemos ",
3245
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("b", { children: "100%" }),
3246
- " sem perguntas \u2014 direto pelo app."
3247
- ] })
3248
- ] }) }),
3249
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("section", { className: "px-5 pt-5", children: [
3250
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("h2", { className: "font-display text-2xl mb-3.5 leading-tight text-foreground", children: "Quase l\xE1." }),
3251
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "Email" }),
3252
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3253
- FieldInput,
3254
- {
3255
- type: "email",
3256
- inputMode: "email",
3257
- autoComplete: "email",
3258
- autoCapitalize: "none",
3259
- autoCorrect: "off",
3260
- spellCheck: false,
3261
- placeholder: "seu@email.com",
3262
- value: form.email,
3263
- onChange: form.setEmail,
3264
- onBlur: form.markEmailTouched,
3265
- error: form.emailError,
3266
- valid: form.emailStatus === "available"
3267
- }
3268
- ),
3269
- !form.emailError && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" }),
3270
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-3" }),
3271
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "Nome completo" }),
3272
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3273
- FieldInput,
3274
- {
3275
- type: "text",
3276
- autoComplete: "name",
3277
- placeholder: "como est\xE1 no documento",
3278
- value: form.name,
3279
- onChange: form.setName,
3280
- onBlur: form.markNameTouched,
3281
- error: form.nameError,
3282
- valid: !!form.name && !form.nameError
3283
- }
3284
- ),
3285
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-3" }),
3286
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "CPF" }),
3287
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3288
- FieldInput,
3289
- {
3290
- type: "text",
3291
- inputMode: "numeric",
3292
- placeholder: "000.000.000-00",
3293
- value: form.cpf,
3294
- onChange: (v) => form.setCpf(formatCpf(v)),
3295
- onBlur: form.markCpfTouched,
3296
- error: form.cpfError,
3297
- valid: !!form.cpf && !form.cpfError
3298
- }
3299
- ),
3300
- form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3301
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-3" }),
3302
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "Telefone" }),
3303
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3304
- FieldInput,
3305
- {
3306
- type: "tel",
3307
- inputMode: "tel",
3308
- autoComplete: "tel",
3309
- placeholder: "(11) 99999-9999",
3310
- value: form.phone,
3311
- onChange: form.setPhone,
3312
- onBlur: form.markPhoneTouched,
3313
- error: form.phoneError,
3314
- valid: !!form.phone && !form.phoneError
3315
- }
3316
- ),
3317
- !form.phoneError && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
3318
- ] }) : null
3319
- ] }),
3320
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("section", { className: "px-5 pt-5", children: [
3321
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "Forma de pagamento" }),
3322
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { role: "tablist", className: "flex gap-1.5 bg-muted p-1 rounded-xl", children: [
3323
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3324
- TabButton,
3325
- {
3326
- active: form.method === "card",
3327
- onClick: () => form.setMethod("card"),
3328
- icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(CardIcon, { className: "w-3.5 h-3.5" }),
3329
- label: "Cart\xE3o",
3330
- subtitle: trialDays > 0 ? `${trialDays} dias gr\xE1tis` : "pague hoje",
3331
- subtitleActiveClass: "text-emerald-700"
3332
- }
3333
- ),
3334
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3335
- TabButton,
3336
- {
3337
- active: form.method === "pix-auto",
3338
- onClick: () => form.setMethod("pix-auto"),
3339
- icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(PixIcon, { className: "w-3.5 h-3.5" }),
3340
- label: "Pix",
3341
- subtitle: `pague hoje \xB7 garantia ${trialDays}d`,
3342
- subtitleActiveClass: "text-foreground/70"
3343
- }
3344
- )
3345
- ] })
3346
- ] }),
3347
- form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("section", { className: "px-5 pt-3.5", children: [
3348
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "N\xFAmero do cart\xE3o" }),
3349
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "relative", children: [
3350
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3351
- FieldInput,
3352
- {
3353
- type: "text",
3354
- inputMode: "numeric",
3355
- autoComplete: "cc-number",
3356
- placeholder: "0000 0000 0000 0000",
3357
- value: form.card.number,
3358
- onChange: (v) => form.setCard({ number: formatCardNumber(v) }),
3359
- style: cardBrand ? { paddingRight: "4.5rem" } : void 0
3360
- }
3361
- ),
3362
- cardBrand && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("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 })
3363
- ] }),
3364
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-3" }),
3365
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex gap-2.5", children: [
3366
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex-1", children: [
3367
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "Validade" }),
3368
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3369
- FieldInput,
3370
- {
3371
- type: "text",
3372
- inputMode: "numeric",
3373
- autoComplete: "cc-exp",
3374
- placeholder: "MM/AA",
3375
- value: expiryMmAa,
3376
- onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v))
3377
- }
3378
- )
3379
- ] }),
3380
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex-1", children: [
3381
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "CVV" }),
3382
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3383
- FieldInput,
3384
- {
3385
- type: "text",
3386
- inputMode: "numeric",
3387
- autoComplete: "cc-csc",
3388
- placeholder: "3 d\xEDgitos",
3389
- value: form.card.ccv,
3390
- onChange: (v) => form.setCard({ ccv: v.replace(/\D/g, "").slice(0, 4) })
3391
- }
3392
- )
3393
- ] })
3394
- ] }),
3395
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-3" }),
3396
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldLabel, { children: "Nome no cart\xE3o" }),
3397
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3398
- FieldInput,
3399
- {
3400
- type: "text",
3401
- autoComplete: "cc-name",
3402
- placeholder: "como est\xE1 no cart\xE3o",
3403
- value: form.card.holderName,
3404
- onChange: (v) => form.setCard({ holderName: v })
3405
- }
3406
- )
3407
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("section", { className: "px-5 pt-3.5", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "rounded-2xl bg-card border border-border p-3.5 flex gap-3.5 items-center", children: [
3408
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "w-[72px] h-[72px] rounded-xl shrink-0 border-2 border-foreground relative overflow-hidden bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("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__ */ (0, import_jsx_runtime27.jsx)(PixIcon, { className: "w-3.5 h-3.5 text-foreground" }) }) }),
3409
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex-1", children: [
3410
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-xs font-bold uppercase tracking-wider text-muted-foreground", children: "pagamento em segundos" }),
3411
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-sm text-foreground mt-1 leading-snug", children: [
3412
- "Geramos seu ",
3413
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("b", { children: "QR Pix" }),
3414
- " no pr\xF3ximo passo. Pague pelo app do banco e seu acesso libera ",
3415
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("b", { children: "imediatamente" }),
3416
- "."
3417
- ] })
3418
- ] })
3419
- ] }) }),
3420
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("section", { className: "px-5 pt-5", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "bg-muted rounded-2xl p-4", children: [
3421
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex justify-between mb-2.5", children: [
3422
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { children: [
3423
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-sm font-semibold text-foreground", children: annual ? "Plano Anual" : "Plano Mensal" }),
3424
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-[11px] text-muted-foreground", children: "Coach" })
3425
- ] }),
3426
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-right", children: [
3427
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-sm font-bold text-foreground", children: [
3428
- cyclePriceText,
3429
- "/",
3430
- annual ? "ano" : "m\xEAs"
3431
- ] }),
3432
- annual && annualSavingsCents > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-[11px] text-emerald-700 font-semibold", children: [
3433
- "economia ",
3434
- formatBrl(annualSavingsCents)
3435
- ] })
3436
- ] })
3437
- ] }),
3438
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-px bg-border my-3" }),
3439
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex justify-between items-baseline", children: [
3440
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { children: [
3441
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-sm font-bold text-foreground", children: "Voc\xEA paga hoje" }),
3442
- form.method === "card" && trialDays > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-[11px] text-muted-foreground mt-0.5", children: [
3443
- "cobran\xE7a inicia no dia ",
3444
- trialDays
3445
- ] }),
3446
- form.method === "pix-auto" && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-[11px] text-emerald-700 mt-0.5 font-semibold", children: [
3447
- "reembolso garantido at\xE9 o dia ",
3448
- trialDays
3449
- ] })
3450
- ] }),
3451
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-2xl font-bold font-display tracking-tight text-foreground", children: todayAmount })
3452
- ] })
3453
- ] }) }),
3454
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "h-4" }),
3455
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "sticky bottom-0 px-5 pt-3.5 pb-6 bg-gradient-to-b from-transparent to-background", children: [
3456
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3457
- "button",
3458
- {
3459
- type: "submit",
3460
- disabled: !form.canSubmit,
3461
- 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",
3462
- children: form.submitting ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3463
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Spinner2, {}),
3464
- " ",
3465
- form.method === "pix-auto" ? "Gerando QR\u2026" : "Confirmando\u2026"
3466
- ] }) : form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3467
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LockIcon, { className: "w-3.5 h-3.5" }),
3468
- " Confirmar e come\xE7ar gr\xE1tis"
3469
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3470
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(PixIcon, { className: "w-3.5 h-3.5" }),
3471
- " Gerar QR \xB7 pagar ",
3472
- todayAmount
3473
- ] })
3474
- }
3475
- ),
3476
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "text-center mt-2.5 text-xs text-muted-foreground", children: [
3477
- "Ao continuar, voc\xEA concorda com nossos ",
3478
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("u", { children: "Termos" }),
3479
- ". Pagamento seguro Asaas."
3480
- ] }),
3481
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "mt-3 flex items-center justify-center gap-3.5 text-[11px] text-muted-foreground", children: [
3482
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
3483
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(LockIcon, { className: "w-2.5 h-2.5 opacity-60" }),
3484
- " SSL 256-bit"
3485
- ] }),
3486
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "w-px h-2.5 bg-border" }),
3487
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { children: "Pagamento via Asaas" }),
3488
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "w-px h-2.5 bg-border" }),
3489
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
3490
- "Garantia ",
3491
- trialDays,
3492
- " dias"
3493
- ] })
3494
- ] })
3495
- ] })
3496
- ] }) });
3497
- }
3498
- function FieldLabel({ children }) {
3499
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-1.5", children });
3189
+ return ctx;
3500
3190
  }
3501
- function FieldInput(props) {
3502
- const baseClass = "w-full px-4 rounded-xl bg-card text-base text-foreground outline-none border-[1.5px] transition-colors";
3503
- const stateClass = props.error ? "border-destructive focus:border-destructive" : props.valid ? "border-emerald-600 focus:border-emerald-700" : "border-border focus:border-foreground";
3504
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3505
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3506
- "input",
3191
+
3192
+ // src/components/paywall/PaywallMethodTabs.tsx
3193
+ var import_jsx_runtime29 = require("react/jsx-runtime");
3194
+ function PaywallMethodTabs({
3195
+ labels,
3196
+ className,
3197
+ tabClassName,
3198
+ tabActiveClassName
3199
+ }) {
3200
+ const { methods, selectedMethod, selectMethod } = usePaywallContext();
3201
+ if (methods.length < 2) return null;
3202
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", className, children: methods.map((m) => {
3203
+ const active = m === selectedMethod;
3204
+ const label = labels[m] ?? m;
3205
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3206
+ "button",
3507
3207
  {
3508
- type: props.type ?? "text",
3509
- inputMode: props.inputMode,
3510
- autoComplete: props.autoComplete,
3511
- autoCapitalize: props.autoCapitalize,
3512
- autoCorrect: props.autoCorrect,
3513
- spellCheck: props.spellCheck,
3514
- placeholder: props.placeholder,
3515
- value: props.value,
3516
- onChange: (e) => props.onChange(e.target.value),
3517
- onBlur: props.onBlur,
3518
- style: { height: "52px", ...props.style },
3519
- className: `${baseClass} ${stateClass}`
3520
- }
3521
- ),
3522
- props.error ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "mt-1.5 text-xs text-destructive font-medium", children: props.error }) : null
3523
- ] });
3208
+ type: "button",
3209
+ role: "tab",
3210
+ "aria-selected": active,
3211
+ "aria-controls": `paywall-tab-${m}`,
3212
+ tabIndex: active ? 0 : -1,
3213
+ onClick: () => selectMethod(m),
3214
+ className: [tabClassName, active ? tabActiveClassName : ""].filter(Boolean).join(" "),
3215
+ children: label
3216
+ },
3217
+ m
3218
+ );
3219
+ }) });
3524
3220
  }
3525
- function FieldHint({ children }) {
3526
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "mt-1.5 text-xs text-muted-foreground", children });
3221
+
3222
+ // src/components/paywall/PaywallMethodContent.tsx
3223
+ var import_jsx_runtime30 = require("react/jsx-runtime");
3224
+ function PaywallMethodContent({ copy, className, rowClassName }) {
3225
+ const { selectedMethod, hasConsumedTrial } = usePaywallContext();
3226
+ const useCardConsumed = selectedMethod === "card" && hasConsumedTrial && copy.cardConsumedTrial;
3227
+ const rows = useCardConsumed ? copy.cardConsumedTrial.bodyRows : selectedMethod === "pix-auto" || selectedMethod === "pix-once" ? copy.pix.bodyRows : copy.card.bodyRows;
3228
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { role: "tabpanel", id: `paywall-tab-${selectedMethod}`, className, children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: rowClassName, children: row }, i)) });
3527
3229
  }
3528
- function TabButton({ active, onClick, icon, label, subtitle, subtitleActiveClass }) {
3529
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
3530
- "button",
3230
+
3231
+ // src/components/paywall/PaywallCyclePicker.tsx
3232
+ var import_jsx_runtime31 = require("react/jsx-runtime");
3233
+ var VARIANT_CLASSES = {
3234
+ default: { card: "", cardSelected: "" },
3235
+ "premium-gold": {
3236
+ card: "",
3237
+ cardSelected: "border-2 border-yellow-400/80 ring-2 ring-yellow-400/20"
3238
+ },
3239
+ "pink-pill": {
3240
+ card: "rounded-2xl",
3241
+ cardSelected: "border-2 border-pink-500"
3242
+ }
3243
+ };
3244
+ function PaywallCyclePicker({
3245
+ labels,
3246
+ className,
3247
+ cardClassName,
3248
+ cardSelectedClassName,
3249
+ anchorClassName,
3250
+ variant = "default",
3251
+ render
3252
+ }) {
3253
+ const ctx = usePaywallContext();
3254
+ const { cycle: selected, setCycle, plan, anchorPriceCents } = ctx;
3255
+ const cycles = ["MONTHLY", "YEARLY"];
3256
+ if (render) {
3257
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className, children: render({ cycles, selected, setCycle, plan, anchorPriceCents }) });
3258
+ }
3259
+ if (cycles.length < 2) return null;
3260
+ const v = VARIANT_CLASSES[variant];
3261
+ const composedCardClassName = [v.card, cardClassName].filter(Boolean).join(" ");
3262
+ const composedCardSelectedClassName = [v.cardSelected, cardSelectedClassName].filter(Boolean).join(" ");
3263
+ const monthlyCents = plan?.monthlyCents ?? 0;
3264
+ const yearlyCents = plan?.yearlyCents ?? 0;
3265
+ const anchorMonthly = plan?.anchorMonthlyCents ?? null;
3266
+ const anchorYearly = plan?.anchorYearlyCents ?? null;
3267
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
3268
+ "div",
3531
3269
  {
3532
- type: "button",
3533
- role: "tab",
3534
- "aria-selected": active,
3535
- onClick,
3536
- 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"}`,
3537
- style: { minHeight: 56 },
3538
- children: [
3539
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
3540
- icon,
3541
- " ",
3542
- label
3543
- ] }),
3544
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: `text-[10px] font-medium ${active ? subtitleActiveClass : "text-muted-foreground/70"}`, children: subtitle })
3545
- ]
3270
+ role: "radiogroup",
3271
+ "aria-label": "Ciclo de cobran\xE7a",
3272
+ className: ["flex flex-row gap-2", className].filter(Boolean).join(" "),
3273
+ children: cycles.map((c) => {
3274
+ const active = c === selected;
3275
+ const label = c === "YEARLY" ? labels.annualLabel : labels.monthlyLabel;
3276
+ const suffix = c === "YEARLY" ? labels.annualSuffix : labels.monthlySuffix;
3277
+ const mainCents = c === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents;
3278
+ const anchorCents = c === "YEARLY" ? anchorYearly : anchorMonthly;
3279
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
3280
+ "button",
3281
+ {
3282
+ type: "button",
3283
+ role: "radio",
3284
+ "aria-checked": active,
3285
+ onClick: () => setCycle(c),
3286
+ className: [
3287
+ "flex flex-col items-center gap-0.5",
3288
+ composedCardClassName,
3289
+ active ? composedCardSelectedClassName : ""
3290
+ ].filter(Boolean).join(" "),
3291
+ children: [
3292
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "font-bold text-base leading-tight", children: formatBRL(mainCents) }),
3293
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-xs opacity-70 leading-tight", children: suffix }),
3294
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "text-xs opacity-60 leading-tight", children: label }),
3295
+ anchorCents != null && anchorCents > mainCents ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: anchorClassName ?? "text-xs opacity-50", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("s", { children: formatBRL(anchorCents) }) }) : null
3296
+ ]
3297
+ },
3298
+ c
3299
+ );
3300
+ })
3546
3301
  }
3547
3302
  );
3548
3303
  }
3549
- function CardIcon({ className }) {
3550
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinejoin: "round", "aria-hidden": "true", children: [
3551
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "2.5" }),
3552
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M2.5 10h19" })
3553
- ] });
3554
- }
3555
- function PixIcon({ className }) {
3556
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M12 2L2 12l10 10 10-10L12 2zm0 4.83L17.17 12 12 17.17 6.83 12 12 6.83z" }) });
3557
- }
3558
- function LockIcon({ className }) {
3559
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3560
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2.5" }),
3561
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M7.5 10V7a4.5 4.5 0 119 0v3" })
3562
- ] });
3563
- }
3564
- function ShieldIcon({ className }) {
3565
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3566
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M12 2.5l8 3v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10v-6l8-3z" }),
3567
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M9 12l2 2 4-4" })
3568
- ] });
3569
- }
3570
- function BellIcon({ className }) {
3571
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3572
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M6 17V11a6 6 0 1112 0v6l1.5 2H4.5L6 17z" }),
3573
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M10 21a2 2 0 004 0" })
3574
- ] });
3575
- }
3576
- function Spinner2() {
3577
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3578
- "span",
3304
+
3305
+ // src/components/paywall/Paywall.tsx
3306
+ var import_jsx_runtime32 = require("react/jsx-runtime");
3307
+ var NBSP = "\xA0";
3308
+ function Paywall({
3309
+ copy,
3310
+ themeClasses = {},
3311
+ slots = {},
3312
+ onBeforeCheckout
3313
+ }) {
3314
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(PaywallProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3315
+ PaywallInner,
3579
3316
  {
3580
- className: "w-4 h-4 rounded-full border-2 border-white/40 border-t-white",
3581
- style: { animation: "spin 0.7s linear infinite" }
3317
+ copy,
3318
+ themeClasses,
3319
+ slots,
3320
+ onBeforeCheckout
3582
3321
  }
3583
- );
3584
- }
3585
-
3586
- // src/defaults/PixWaitingPageDefault.tsx
3587
- var import_react19 = require("react");
3588
- var import_react_router_dom4 = require("react-router-dom");
3589
- var import_sdk14 = require("@hook-sdk/sdk");
3590
- var import_jsx_runtime28 = require("react/jsx-runtime");
3591
- var PIX_PAYLOAD_KEY2 = "hook:paywall:pix-pending";
3592
- var TIMEOUT_MS = 30 * 60 * 1e3;
3593
- function readPixPayload() {
3594
- if (typeof window === "undefined") return null;
3595
- try {
3596
- const raw = sessionStorage.getItem(PIX_PAYLOAD_KEY2);
3597
- if (!raw) return null;
3598
- return JSON.parse(raw);
3599
- } catch {
3600
- return null;
3601
- }
3322
+ ) });
3602
3323
  }
3603
- function clearPixPayload() {
3604
- if (typeof window === "undefined") return;
3605
- try {
3606
- sessionStorage.removeItem(PIX_PAYLOAD_KEY2);
3607
- } catch {
3608
- }
3609
- }
3610
- function PixWaitingPageDefault() {
3611
- const navigate = (0, import_react_router_dom4.useNavigate)();
3612
- const { subscription } = (0, import_sdk14.useHook)();
3613
- const payload = (0, import_react19.useMemo)(readPixPayload, []);
3614
- const [copied, setCopied] = (0, import_react19.useState)(false);
3615
- const [timedOut, setTimedOut] = (0, import_react19.useState)(false);
3616
- (0, import_react19.useEffect)(() => {
3617
- if (!payload) navigate("/paywall/checkout", { replace: true });
3618
- }, [payload, navigate]);
3619
- (0, import_react19.useEffect)(() => {
3620
- const t = setTimeout(() => setTimedOut(true), TIMEOUT_MS);
3621
- return () => clearTimeout(t);
3622
- }, []);
3623
- const hasAccess = subscription.hasAccess;
3624
- (0, import_react19.useEffect)(() => {
3625
- if (hasAccess) {
3626
- clearPixPayload();
3627
- navigate("/", { replace: true });
3324
+ function PaywallInner({
3325
+ copy,
3326
+ themeClasses = {},
3327
+ slots = {},
3328
+ onBeforeCheckout
3329
+ }) {
3330
+ const { track: track2 } = (0, import_sdk15.useHook)();
3331
+ const s = usePaywallContext();
3332
+ const priceLabel = formatBRL(s.currentPriceCents).replace(new RegExp(NBSP, "g"), " ");
3333
+ const trialDaysCardLabel = String(s.trialDaysCard);
3334
+ const ctaLabel = (0, import_react21.useMemo)(() => {
3335
+ if (s.isFree) return copy.freeCta ?? "Come\xE7ar agora";
3336
+ if (s.selectedMethod === "card") {
3337
+ if (s.hasConsumedTrial && copy.cardConsumedTrial) {
3338
+ return interp(copy.cardConsumedTrial.ctaTemplate, {
3339
+ price: priceLabel,
3340
+ days: trialDaysCardLabel
3341
+ });
3342
+ }
3343
+ if (s.trialDaysCard > 0) {
3344
+ return interp(copy.card.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
3345
+ }
3346
+ return copy.cardConsumedTrial ? interp(copy.cardConsumedTrial.ctaTemplate, {
3347
+ price: priceLabel,
3348
+ days: trialDaysCardLabel
3349
+ }) : `Assinar por ${priceLabel}`;
3628
3350
  }
3629
- }, [hasAccess, navigate]);
3630
- async function copyPayload() {
3631
- if (!payload?.payload) return;
3632
- try {
3633
- await navigator.clipboard.writeText(payload.payload);
3634
- setCopied(true);
3635
- setTimeout(() => setCopied(false), 2e3);
3636
- } catch {
3351
+ return interp(copy.pix.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
3352
+ }, [
3353
+ s.isFree,
3354
+ s.selectedMethod,
3355
+ s.hasConsumedTrial,
3356
+ s.trialDaysCard,
3357
+ copy,
3358
+ priceLabel,
3359
+ trialDaysCardLabel
3360
+ ]);
3361
+ const switchHint = (0, import_react21.useMemo)(() => {
3362
+ if (s.methods.length < 2) return void 0;
3363
+ return s.selectedMethod === "card" ? copy.card.switchHint : copy.pix.switchHint;
3364
+ }, [s.methods.length, s.selectedMethod, copy]);
3365
+ (0, import_react21.useEffect)(() => {
3366
+ if (!s.initialLoadComplete) return;
3367
+ track2("paywall_view", {
3368
+ default_method: s.selectedMethod,
3369
+ default_cycle: s.cycle,
3370
+ available_methods: s.methods
3371
+ });
3372
+ }, [s.initialLoadComplete]);
3373
+ const handleCta = async () => {
3374
+ track2("paywall_cta_clicked", {
3375
+ method: s.selectedMethod,
3376
+ cycle: s.cycle,
3377
+ price_cents: s.currentPriceCents,
3378
+ had_consumed_trial: s.hasConsumedTrial
3379
+ });
3380
+ if (onBeforeCheckout) {
3381
+ await onBeforeCheckout(s.selectedMethod, s.cycle);
3382
+ return;
3637
3383
  }
3638
- }
3639
- if (!payload) return null;
3640
- if (timedOut) {
3641
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-10 text-center bg-background space-y-4", children: [
3642
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h1", { className: "font-display text-2xl text-foreground", children: "PIX expirado" }),
3643
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-muted-foreground", children: "O tempo pra pagar acabou. Gere um novo PIX." }),
3644
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3645
- "button",
3646
- {
3647
- onClick: () => {
3648
- clearPixPayload();
3649
- navigate("/paywall/checkout", { replace: true });
3650
- },
3651
- className: "rounded-xl bg-primary px-6 py-3 text-base font-semibold text-primary-foreground",
3652
- children: "Tentar novamente"
3653
- }
3654
- )
3655
- ] });
3656
- }
3657
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex-1 flex flex-col items-center px-6 py-8 bg-background space-y-6", children: [
3658
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("header", { className: "text-center space-y-2", children: [
3659
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h1", { className: "font-display text-2xl text-foreground", children: "Pague o PIX" }),
3660
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("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." })
3661
- ] }),
3662
- payload.base64 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3663
- "img",
3384
+ await s.submit();
3385
+ };
3386
+ const ctaTheme = s.selectedMethod === "card" ? themeClasses.ctaCard : themeClasses.ctaPix;
3387
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: themeClasses.container, children: [
3388
+ slots.heroSlot,
3389
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("h1", { className: themeClasses.headline, children: copy.headline }),
3390
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ul", { children: copy.features.map((f) => /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("li", { className: themeClasses.feature, children: [
3391
+ "\u2713 ",
3392
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { children: f })
3393
+ ] }, f)) }),
3394
+ copy.socialProof ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: themeClasses.socialProof, children: copy.socialProof }) : null,
3395
+ slots.cyclePickerSlot ?? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3396
+ PaywallCyclePicker,
3664
3397
  {
3665
- src: `data:image/png;base64,${payload.base64}`,
3666
- alt: "QR Code PIX",
3667
- className: "w-64 h-64 rounded-2xl border border-border bg-card p-2"
3398
+ labels: copy.cycle,
3399
+ cardClassName: themeClasses.cycleCard,
3400
+ cardSelectedClassName: themeClasses.cycleCardSelected,
3401
+ anchorClassName: themeClasses.anchorPrice
3668
3402
  }
3669
- ) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("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" }),
3670
- payload.payload ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3671
- "button",
3403
+ ),
3404
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3405
+ PaywallMethodTabs,
3672
3406
  {
3673
- onClick: copyPayload,
3674
- className: "w-full max-w-xs rounded-xl border border-border bg-card px-4 py-3 text-sm font-medium text-foreground",
3675
- children: copied ? "\u2713 Copiado!" : "Copiar c\xF3digo PIX"
3407
+ labels: { "pix-auto": copy.pix.tabLabel, card: copy.card.tabLabel },
3408
+ className: themeClasses.tabs,
3409
+ tabClassName: themeClasses.tab,
3410
+ tabActiveClassName: themeClasses.tabActive
3676
3411
  }
3677
- ) : null,
3678
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
3679
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "inline-block w-2 h-2 rounded-full bg-primary animate-pulse" }),
3680
- "Aguardando pagamento\u2026"
3681
- ] }),
3682
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("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." })
3683
- ] });
3684
- }
3685
-
3686
- // src/hooks/useLoginForm.ts
3687
- var import_react20 = require("react");
3688
- var import_sdk15 = require("@hook-sdk/sdk");
3689
- var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3690
- var MIN_PASSWORD = 8;
3691
- function useLoginForm() {
3692
- const { auth } = (0, import_sdk15.useHook)();
3693
- const [email, setEmail] = (0, import_react20.useState)("");
3694
- const [password, setPassword] = (0, import_react20.useState)("");
3695
- const [submitting, setSubmitting] = (0, import_react20.useState)(false);
3696
- const [error, setError] = (0, import_react20.useState)(null);
3697
- const [touchedEmail, setTouchedEmail] = (0, import_react20.useState)(false);
3698
- const [touchedPassword, setTouchedPassword] = (0, import_react20.useState)(false);
3699
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react20.useState)(false);
3700
- const validateEmail = (0, import_react20.useMemo)(() => {
3701
- if (email.length === 0) return null;
3702
- if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
3703
- return null;
3704
- }, [email]);
3705
- const validatePassword = (0, import_react20.useMemo)(() => {
3706
- if (password.length === 0) return null;
3707
- if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
3708
- return null;
3709
- }, [password]);
3710
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3711
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3712
- const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
3713
- const submit = (0, import_react20.useCallback)(async () => {
3714
- setFormSubmitAttempted(true);
3715
- if (!canSubmit) return false;
3716
- setSubmitting(true);
3717
- setError(null);
3718
- try {
3719
- await auth.login({ email, password });
3720
- return true;
3721
- } catch (err) {
3722
- setError(mapSdkError(err));
3723
- return false;
3724
- } finally {
3725
- setSubmitting(false);
3726
- }
3727
- }, [auth, email, password, canSubmit]);
3728
- return {
3729
- email,
3730
- setEmail,
3731
- emailError,
3732
- markEmailTouched: () => setTouchedEmail(true),
3733
- password,
3734
- setPassword,
3735
- passwordError,
3736
- markPasswordTouched: () => setTouchedPassword(true),
3737
- formSubmitAttempted,
3738
- submit,
3739
- submitting,
3740
- canSubmit,
3741
- error,
3742
- loginWithGoogle: () => auth.loginWithGoogle()
3743
- };
3744
- }
3745
-
3746
- // src/hooks/useSignupForm.ts
3747
- var import_react21 = require("react");
3748
- var import_sdk16 = require("@hook-sdk/sdk");
3749
- var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3750
- var MIN_PASSWORD2 = 8;
3751
- function useSignupForm() {
3752
- const { auth } = (0, import_sdk16.useHook)();
3753
- const [name, setName] = (0, import_react21.useState)("");
3754
- const [email, setEmail] = (0, import_react21.useState)("");
3755
- const [password, setPassword] = (0, import_react21.useState)("");
3756
- const [submitting, setSubmitting] = (0, import_react21.useState)(false);
3757
- const [error, setError] = (0, import_react21.useState)(null);
3758
- const [touchedName, setTouchedName] = (0, import_react21.useState)(false);
3759
- const [touchedEmail, setTouchedEmail] = (0, import_react21.useState)(false);
3760
- const [touchedPassword, setTouchedPassword] = (0, import_react21.useState)(false);
3761
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react21.useState)(false);
3762
- const validateName = (0, import_react21.useMemo)(() => {
3763
- if (name.length === 0) return null;
3764
- if (name.trim().length < 2) return "Nome muito curto.";
3765
- return null;
3766
- }, [name]);
3767
- const validateEmail = (0, import_react21.useMemo)(() => {
3768
- if (email.length === 0) return null;
3769
- if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
3770
- return null;
3771
- }, [email]);
3772
- const validatePassword = (0, import_react21.useMemo)(() => {
3773
- if (password.length === 0) return null;
3774
- if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
3775
- return null;
3776
- }, [password]);
3777
- const nameError = touchedName || formSubmitAttempted ? validateName : null;
3778
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3779
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3780
- const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
3781
- const submit = (0, import_react21.useCallback)(async () => {
3782
- setFormSubmitAttempted(true);
3783
- if (!canSubmit) return false;
3784
- setSubmitting(true);
3785
- setError(null);
3786
- try {
3787
- await auth.signup({ name, email, password });
3788
- return true;
3789
- } catch (err) {
3790
- setError(mapSdkError(err));
3791
- return false;
3792
- } finally {
3793
- setSubmitting(false);
3794
- }
3795
- }, [auth, name, email, password, canSubmit]);
3796
- return {
3797
- name,
3798
- setName,
3799
- nameError,
3800
- markNameTouched: () => setTouchedName(true),
3801
- email,
3802
- setEmail,
3803
- emailError,
3804
- markEmailTouched: () => setTouchedEmail(true),
3805
- password,
3806
- setPassword,
3807
- passwordError,
3808
- markPasswordTouched: () => setTouchedPassword(true),
3809
- formSubmitAttempted,
3810
- submit,
3811
- submitting,
3812
- canSubmit,
3813
- error,
3814
- loginWithGoogle: () => auth.loginWithGoogle()
3815
- };
3816
- }
3817
-
3818
- // src/hooks/useForgotForm.ts
3819
- var import_react22 = require("react");
3820
- var import_sdk17 = require("@hook-sdk/sdk");
3821
- var EMAIL_RE4 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3822
- function useForgotForm() {
3823
- const { auth } = (0, import_sdk17.useHook)();
3824
- const [email, setEmail] = (0, import_react22.useState)("");
3825
- const [submitting, setSubmitting] = (0, import_react22.useState)(false);
3826
- const [sent, setSent] = (0, import_react22.useState)(false);
3827
- const [error, setError] = (0, import_react22.useState)(null);
3828
- const [touchedEmail, setTouchedEmail] = (0, import_react22.useState)(false);
3829
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react22.useState)(false);
3830
- const validateEmail = (0, import_react22.useMemo)(() => {
3831
- if (email.length === 0) return null;
3832
- if (!EMAIL_RE4.test(email)) return "Formato de e-mail inv\xE1lido.";
3833
- return null;
3834
- }, [email]);
3835
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3836
- const canSubmit = email.length > 0 && validateEmail === null && !submitting;
3837
- const submit = (0, import_react22.useCallback)(async () => {
3838
- setFormSubmitAttempted(true);
3839
- if (!canSubmit) return false;
3840
- setSubmitting(true);
3841
- setError(null);
3842
- try {
3843
- await auth.forgot({ email });
3844
- setSent(true);
3845
- return true;
3846
- } catch (err) {
3847
- setError(mapSdkError(err));
3848
- return false;
3849
- } finally {
3850
- setSubmitting(false);
3851
- }
3852
- }, [auth, email, canSubmit]);
3853
- return {
3854
- email,
3855
- setEmail,
3856
- emailError,
3857
- markEmailTouched: () => setTouchedEmail(true),
3858
- formSubmitAttempted,
3859
- submit,
3860
- submitting,
3861
- canSubmit,
3862
- sent,
3863
- error
3864
- };
3865
- }
3866
-
3867
- // src/hooks/useResetForm.ts
3868
- var import_react23 = require("react");
3869
- var import_sdk18 = require("@hook-sdk/sdk");
3870
- var MIN_PASSWORD3 = 12;
3871
- function useResetForm() {
3872
- const { auth } = (0, import_sdk18.useHook)();
3873
- const [token, setToken] = (0, import_react23.useState)(null);
3874
- const [password, setPassword] = (0, import_react23.useState)("");
3875
- const [confirm, setConfirm] = (0, import_react23.useState)("");
3876
- const [submitting, setSubmitting] = (0, import_react23.useState)(false);
3877
- const [done, setDone] = (0, import_react23.useState)(false);
3878
- const [error, setError] = (0, import_react23.useState)(null);
3879
- const [touchedPassword, setTouchedPassword] = (0, import_react23.useState)(false);
3880
- const [touchedConfirm, setTouchedConfirm] = (0, import_react23.useState)(false);
3881
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react23.useState)(false);
3882
- (0, import_react23.useEffect)(() => {
3883
- if (typeof window === "undefined") return;
3884
- const params = new URLSearchParams(window.location.search);
3885
- const t = params.get("token");
3886
- setToken(t && t.length > 0 ? t : null);
3887
- }, []);
3888
- const validatePassword = (0, import_react23.useMemo)(() => {
3889
- if (password.length === 0) return null;
3890
- if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
3891
- return null;
3892
- }, [password]);
3893
- const validateConfirm = (0, import_react23.useMemo)(() => {
3894
- if (confirm.length === 0) return null;
3895
- if (confirm !== password) return "Senhas n\xE3o coincidem.";
3896
- return null;
3897
- }, [confirm, password]);
3898
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3899
- const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
3900
- const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
3901
- const submit = (0, import_react23.useCallback)(async () => {
3902
- setFormSubmitAttempted(true);
3903
- if (!canSubmit || token === null) return;
3904
- setSubmitting(true);
3905
- setError(null);
3906
- try {
3907
- await auth.reset({ token, newPassword: password });
3908
- setDone(true);
3909
- if (typeof window !== "undefined") {
3910
- const url = new URL(window.location.href);
3911
- url.searchParams.delete("token");
3912
- url.searchParams.delete("screen");
3913
- window.history.replaceState({}, "", url.toString());
3412
+ ),
3413
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3414
+ PaywallMethodContent,
3415
+ {
3416
+ copy: {
3417
+ pix: interpolateCopy(copy.pix, priceLabel, trialDaysCardLabel),
3418
+ card: interpolateCopy(copy.card, priceLabel, trialDaysCardLabel),
3419
+ cardConsumedTrial: copy.cardConsumedTrial ? {
3420
+ bodyRows: copy.cardConsumedTrial.bodyRows.map(
3421
+ (r) => interp(r, { price: priceLabel, days: trialDaysCardLabel })
3422
+ ),
3423
+ ctaTemplate: copy.cardConsumedTrial.ctaTemplate
3424
+ } : void 0
3425
+ },
3426
+ className: themeClasses.tabContent,
3427
+ rowClassName: themeClasses.tabContentRow
3914
3428
  }
3915
- } catch (err) {
3916
- setError(mapSdkError(err));
3917
- } finally {
3918
- setSubmitting(false);
3919
- }
3920
- }, [auth, token, password, canSubmit]);
3921
- return {
3922
- token,
3923
- password,
3924
- setPassword,
3925
- passwordError,
3926
- markPasswordTouched: () => setTouchedPassword(true),
3927
- confirm,
3928
- setConfirm,
3929
- confirmError,
3930
- markConfirmTouched: () => setTouchedConfirm(true),
3931
- formSubmitAttempted,
3932
- submit,
3933
- submitting,
3934
- canSubmit,
3935
- done,
3936
- error
3937
- };
3938
- }
3939
-
3940
- // src/utils/price.ts
3941
- function formatBRL(cents) {
3942
- if (cents === null || cents === void 0) return "";
3943
- const reais = cents / 100;
3944
- return new Intl.NumberFormat("pt-BR", {
3945
- style: "currency",
3946
- currency: "BRL"
3947
- }).format(reais);
3948
- }
3949
- function monthlyFromYearly(yearlyCents) {
3950
- if (yearlyCents === null || yearlyCents === void 0) return 0;
3951
- return Math.round(yearlyCents / 12);
3952
- }
3953
- function dailyFromYearly(yearlyCents) {
3954
- if (yearlyCents === null || yearlyCents === void 0) return 0;
3955
- return Math.round(yearlyCents / 365);
3956
- }
3957
- function computeAnchorCents(baseCents, multiplier) {
3958
- if (multiplier === null || multiplier === void 0) return null;
3959
- if (!Number.isFinite(multiplier)) return null;
3960
- if (multiplier <= 1) return null;
3961
- return Math.round(baseCents * multiplier);
3962
- }
3963
- function discountPercent(anchorCents, realCents) {
3964
- if (anchorCents <= realCents) return 0;
3965
- return Math.floor((anchorCents - realCents) / anchorCents * 100);
3966
- }
3967
-
3968
- // src/hooks/useAuthPrimitives.ts
3969
- var import_react24 = require("react");
3970
- var import_sdk19 = require("@hook-sdk/sdk");
3971
- var warned = false;
3972
- function useAuthPrimitives() {
3973
- const { auth } = (0, import_sdk19.useHook)();
3974
- (0, import_react24.useEffect)(() => {
3975
- if (!warned && process.env.NODE_ENV !== "production") {
3976
- warned = true;
3977
- console.warn(
3978
- "[@hook-sdk/template] useAuthPrimitives() \xE9 escape hatch. Pra login/signup/forgot, use useLoginForm/useSignupForm/useForgotForm. Docs: docs/19-golden-template.md#escape-hatch"
3979
- );
3980
- }
3981
- }, []);
3982
- return {
3983
- login: auth.login,
3984
- signup: auth.signup,
3985
- logout: auth.logout,
3986
- logoutAll: auth.logoutAll,
3987
- forgot: auth.forgot,
3988
- resendVerify: auth.resendVerify,
3989
- changePassword: auth.changePassword,
3990
- changeEmail: auth.changeEmail,
3991
- refresh: auth.refresh
3992
- };
3429
+ ),
3430
+ slots.beforeCtaSlot,
3431
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { children: [
3432
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3433
+ "button",
3434
+ {
3435
+ type: "button",
3436
+ onClick: () => {
3437
+ void handleCta();
3438
+ },
3439
+ disabled: s.submitting,
3440
+ className: ctaTheme,
3441
+ children: s.submitting ? "Abrindo checkout\u2026" : ctaLabel
3442
+ }
3443
+ ),
3444
+ switchHint ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: themeClasses.switchHint, children: switchHint }) : null,
3445
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: themeClasses.trustLine, children: copy.trustLine })
3446
+ ] })
3447
+ ] });
3993
3448
  }
3994
-
3995
- // src/hooks/useAuth.ts
3996
- var import_sdk20 = require("@hook-sdk/sdk");
3997
- function useAuth() {
3998
- const { user, authStatus, auth } = (0, import_sdk20.useHook)();
3999
- return {
4000
- user,
4001
- authStatus,
4002
- refresh: auth.refresh
4003
- };
3449
+ function interp(tpl, vars) {
3450
+ return tpl.replace(/\{(\w+)\}/g, (_m, k) => vars[k] ?? "");
4004
3451
  }
4005
-
4006
- // src/index.ts
4007
- var import_sdk25 = require("@hook-sdk/sdk");
4008
-
4009
- // src/hooks/useSubscription.ts
4010
- var import_sdk21 = require("@hook-sdk/sdk");
4011
- function useSubscription() {
4012
- const { subscription } = (0, import_sdk21.useHook)();
3452
+ function interpolateCopy(m, price, days) {
4013
3453
  return {
4014
- status: subscription.status()
3454
+ tabLabel: m.tabLabel,
3455
+ bodyRows: m.bodyRows.map((r) => interp(r, { price, days })),
3456
+ ctaTemplate: m.ctaTemplate,
3457
+ switchHint: m.switchHint
4015
3458
  };
4016
3459
  }
4017
3460
 
4018
- // src/hooks/useReminders.ts
4019
- var import_react25 = require("react");
4020
- var import_sdk22 = require("@hook-sdk/sdk");
4021
- function useReminders() {
4022
- const { push } = (0, import_sdk22.useHook)();
4023
- const r = push.reminders;
4024
- const [reminders, setReminders] = (0, import_react25.useState)([]);
4025
- const [loading, setLoading] = (0, import_react25.useState)(true);
4026
- const reload = (0, import_react25.useCallback)(async () => {
4027
- setLoading(true);
4028
- try {
4029
- const next = await r.list();
4030
- setReminders(next);
4031
- } finally {
4032
- setLoading(false);
4033
- }
4034
- }, [r]);
4035
- (0, import_react25.useEffect)(() => {
4036
- void reload();
4037
- }, [reload]);
4038
- const setReminder = (0, import_react25.useCallback)(async (input) => {
4039
- await r.set(input);
4040
- await reload();
4041
- }, [r, reload]);
4042
- const deleteReminder = (0, import_react25.useCallback)(async (slot) => {
4043
- await r.delete(slot);
4044
- await reload();
4045
- }, [r, reload]);
4046
- const schedule = (0, import_react25.useCallback)(async (items) => {
4047
- return r.schedule(items);
4048
- }, [r]);
4049
- const setFallbacks = (0, import_react25.useCallback)(async (items) => {
4050
- return r.setFallbacks(items);
4051
- }, [r]);
4052
- return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
3461
+ // src/components/paywall/PaywallCta.tsx
3462
+ var import_jsx_runtime33 = require("react/jsx-runtime");
3463
+ function PaywallCta({
3464
+ ctaLabel,
3465
+ loadingLabel,
3466
+ switchHint,
3467
+ trustLine,
3468
+ className,
3469
+ buttonClassName,
3470
+ switchHintClassName,
3471
+ trustClassName
3472
+ }) {
3473
+ const { submit, submitting } = usePaywallContext();
3474
+ const label = submitting && loadingLabel ? loadingLabel : ctaLabel;
3475
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className, children: [
3476
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
3477
+ "button",
3478
+ {
3479
+ type: "button",
3480
+ onClick: () => {
3481
+ void submit();
3482
+ },
3483
+ disabled: submitting,
3484
+ className: buttonClassName,
3485
+ children: label
3486
+ }
3487
+ ),
3488
+ switchHint ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: switchHintClassName, children: switchHint }) : null,
3489
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: trustClassName, children: trustLine })
3490
+ ] });
4053
3491
  }
4054
3492
 
4055
- // src/hooks/useToast.ts
4056
- var import_react26 = require("react");
4057
- function useToast() {
4058
- const [items, setItems] = (0, import_react26.useState)([]);
4059
- const show = (0, import_react26.useCallback)((message, kind = "info") => {
4060
- const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4061
- setItems((prev) => [...prev, { id, message, kind }]);
4062
- setTimeout(() => {
4063
- setItems((prev) => prev.filter((t) => t.id !== id));
4064
- }, 4e3);
4065
- }, []);
4066
- const dismiss = (0, import_react26.useCallback)((id) => {
4067
- setItems((prev) => prev.filter((t) => t.id !== id));
4068
- }, []);
4069
- return { items, show, dismiss };
3493
+ // src/components/paywall/blocks/PaywallEyebrow.tsx
3494
+ var import_jsx_runtime34 = require("react/jsx-runtime");
3495
+ var DEFAULT_EYEBROW_CLASSES = "text-xs uppercase tracking-widest font-semibold opacity-70";
3496
+ function PaywallEyebrow({ text, className }) {
3497
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: [DEFAULT_EYEBROW_CLASSES, className].filter(Boolean).join(" "), children: text });
4070
3498
  }
4071
3499
 
4072
- // src/RouteBoundary.tsx
4073
- var import_react_router_dom5 = require("react-router-dom");
4074
- var import_jsx_runtime29 = require("react/jsx-runtime");
4075
- function RouteBoundary({ children }) {
4076
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_react_router_dom5.Routes, { children: [
4077
- children,
4078
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_react_router_dom5.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(DefaultNotFound, {}) })
4079
- ] });
3500
+ // src/components/paywall/blocks/PaywallHero.tsx
3501
+ var import_jsx_runtime35 = require("react/jsx-runtime");
3502
+ var DEFAULT_GRADIENT = "absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent";
3503
+ function PaywallHero({
3504
+ src,
3505
+ alt = "",
3506
+ headline,
3507
+ aspectRatio = "16/9",
3508
+ gradientClassName,
3509
+ className,
3510
+ headlineClassName,
3511
+ imgClassName,
3512
+ render
3513
+ }) {
3514
+ if (render) {
3515
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className, children: render({ src, headline }) });
3516
+ }
3517
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
3518
+ "div",
3519
+ {
3520
+ className: ["relative overflow-hidden", className].filter(Boolean).join(" "),
3521
+ style: { aspectRatio },
3522
+ children: [
3523
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3524
+ "img",
3525
+ {
3526
+ src,
3527
+ alt,
3528
+ className: ["absolute inset-0 w-full h-full object-cover", imgClassName].filter(Boolean).join(" ")
3529
+ }
3530
+ ),
3531
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: gradientClassName ?? DEFAULT_GRADIENT, "aria-hidden": "true" }),
3532
+ headline ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3533
+ "h1",
3534
+ {
3535
+ className: ["absolute bottom-0 left-0 right-0 p-4 text-white font-bold text-2xl", headlineClassName].filter(Boolean).join(" "),
3536
+ children: headline
3537
+ }
3538
+ ) : null
3539
+ ]
3540
+ }
3541
+ );
4080
3542
  }
4081
- function DefaultNotFound() {
4082
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
3543
+
3544
+ // src/components/paywall/blocks/PaywallHeadline.tsx
3545
+ var import_jsx_runtime36 = require("react/jsx-runtime");
3546
+ var DEFAULT_HEADLINE_CLASSES = "text-2xl font-bold leading-tight";
3547
+ function PaywallHeadline({ text, className, as = "h1" }) {
3548
+ const Tag = as;
3549
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(Tag, { className: [DEFAULT_HEADLINE_CLASSES, className].filter(Boolean).join(" "), children: text });
4083
3550
  }
4084
3551
 
4085
- // src/PreAuthShell.tsx
4086
- var import_react_router_dom6 = require("react-router-dom");
4087
- var import_jsx_runtime30 = require("react/jsx-runtime");
4088
- function PreAuthShell({
4089
- basename,
4090
- testRouter,
4091
- testInitialEntries,
4092
- children
3552
+ // src/components/paywall/blocks/PaywallPriceHeadline.tsx
3553
+ var import_jsx_runtime37 = require("react/jsx-runtime");
3554
+ var DEFAULT_CLASS = "text-2xl font-bold leading-tight";
3555
+ var CYCLE_LABEL = {
3556
+ MONTHLY: "mensal",
3557
+ YEARLY: "anual"
3558
+ };
3559
+ function PaywallPriceHeadline({
3560
+ template,
3561
+ className,
3562
+ as = "h1",
3563
+ render
4093
3564
  }) {
4094
- if (testRouter === "memory") {
4095
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_router_dom6.MemoryRouter, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_router_dom6.Routes, { children }) });
3565
+ const { cycle, currentMonthlyEquivCents, plan } = usePaywallContext();
3566
+ const yearlyCents = plan?.yearlyCents ?? null;
3567
+ const pricePerDay = formatBRL(dailyFromYearly(yearlyCents));
3568
+ const monthlyEquiv = currentMonthlyEquivCents ?? 0;
3569
+ const cycleLabel = CYCLE_LABEL[cycle] ?? cycle.toLowerCase();
3570
+ const rootClasses = [DEFAULT_CLASS, className].filter(Boolean).join(" ");
3571
+ if (render) {
3572
+ const RootTag2 = as;
3573
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(RootTag2, { className: [className].filter(Boolean).join(" ") || void 0, children: render({ pricePerDay, currentMonthlyEquivCents: monthlyEquiv, cycle }) });
4096
3574
  }
4097
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_router_dom6.BrowserRouter, { basename, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_router_dom6.Routes, { children }) });
3575
+ const text = template.replaceAll("{pricePerDay}", pricePerDay).replaceAll("{currentMonthlyEquiv}", formatBRL(monthlyEquiv)).replaceAll("{cycle}", cycleLabel);
3576
+ const RootTag = as;
3577
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(RootTag, { className: rootClasses, children: text });
4098
3578
  }
4099
3579
 
4100
- // src/OnboardingFlow.tsx
4101
- var import_react28 = require("react");
4102
- var import_sdk23 = require("@hook-sdk/sdk");
4103
-
4104
- // src/hooks/useOnboardingStep.ts
4105
- var import_react27 = require("react");
4106
- var OnboardingStepContext = (0, import_react27.createContext)(null);
4107
- function useOnboardingStep() {
4108
- const ctx = (0, import_react27.useContext)(OnboardingStepContext);
4109
- if (!ctx) {
4110
- throw new Error(
4111
- "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
4112
- );
3580
+ // src/components/paywall/blocks/PaywallCountdown.tsx
3581
+ var import_react22 = require("react");
3582
+ var import_jsx_runtime38 = require("react/jsx-runtime");
3583
+ var DEFAULT_COUNTDOWN_CLASSES = "font-mono tabular-nums";
3584
+ function resolveDeadlineMs(deadline) {
3585
+ if (deadline instanceof Date) return deadline.getTime();
3586
+ if (typeof deadline === "string") return new Date(deadline).getTime();
3587
+ const { sessionStorageKey, durationMs } = deadline;
3588
+ if (typeof window === "undefined" || typeof window.sessionStorage === "undefined") {
3589
+ return Date.now() + durationMs;
3590
+ }
3591
+ const stored = window.sessionStorage.getItem(sessionStorageKey);
3592
+ const parsed = stored ? Number.parseInt(stored, 10) : NaN;
3593
+ const now = Date.now();
3594
+ if (!Number.isFinite(parsed) || parsed < now) {
3595
+ const target = now + durationMs;
3596
+ window.sessionStorage.setItem(sessionStorageKey, String(target));
3597
+ return target;
3598
+ }
3599
+ return parsed;
3600
+ }
3601
+ function computeRemaining(deadlineMs) {
3602
+ const diff = Math.max(0, deadlineMs - Date.now());
3603
+ const totalSeconds = Math.floor(diff / 1e3);
3604
+ const h = Math.floor(totalSeconds / 3600);
3605
+ const m = Math.floor(totalSeconds % 3600 / 60);
3606
+ const s = totalSeconds % 60;
3607
+ return { h, m, s, expired: diff === 0 };
3608
+ }
3609
+ function pad(n) {
3610
+ return String(n).padStart(2, "0");
3611
+ }
3612
+ function PaywallCountdown({
3613
+ deadline,
3614
+ format = "h:m:s",
3615
+ onExpire,
3616
+ className,
3617
+ render
3618
+ }) {
3619
+ const deadlineMsRef = (0, import_react22.useRef)(null);
3620
+ if (deadlineMsRef.current === null) {
3621
+ deadlineMsRef.current = resolveDeadlineMs(deadline);
3622
+ }
3623
+ const [state, setState] = (0, import_react22.useState)(() => computeRemaining(deadlineMsRef.current));
3624
+ const expiredCalledRef = (0, import_react22.useRef)(false);
3625
+ (0, import_react22.useEffect)(() => {
3626
+ if (state.expired) {
3627
+ if (!expiredCalledRef.current) {
3628
+ expiredCalledRef.current = true;
3629
+ onExpire?.();
3630
+ }
3631
+ return;
3632
+ }
3633
+ const tick = () => {
3634
+ const next = computeRemaining(deadlineMsRef.current);
3635
+ setState(next);
3636
+ if (next.expired && !expiredCalledRef.current) {
3637
+ expiredCalledRef.current = true;
3638
+ onExpire?.();
3639
+ }
3640
+ };
3641
+ const id = setInterval(tick, 1e3);
3642
+ return () => clearInterval(id);
3643
+ }, [state.expired]);
3644
+ if (render) {
3645
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className, children: render(state) });
4113
3646
  }
4114
- return ctx;
3647
+ const formatted = format === "h:m:s" ? `${pad(state.h)}:${pad(state.m)}:${pad(state.s)}` : `${pad(state.h * 60 + state.m)}:${pad(state.s)}`;
3648
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: [DEFAULT_COUNTDOWN_CLASSES, className].filter(Boolean).join(" "), children: formatted });
4115
3649
  }
4116
3650
 
4117
- // src/OnboardingFlow.tsx
4118
- var import_jsx_runtime31 = require("react/jsx-runtime");
4119
- var isFilled = (v) => v != null && v !== "";
4120
- var CURRENT_STEP_FIELD = "currentStep";
4121
- function readPersistedStepIdx(draft) {
4122
- const raw = draft[CURRENT_STEP_FIELD];
4123
- return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
4124
- }
4125
- function OnboardingFlow({
4126
- steps,
4127
- screens,
4128
- onComplete,
4129
- persistKey
3651
+ // src/components/paywall/blocks/PaywallFeatures.tsx
3652
+ var import_jsx_runtime39 = require("react/jsx-runtime");
3653
+ function PaywallFeatures({
3654
+ items,
3655
+ IconComponent,
3656
+ className,
3657
+ itemClassName,
3658
+ iconClassName,
3659
+ render,
3660
+ renderItem
4130
3661
  }) {
4131
- const [draft, setDraft, status] = (0, import_sdk23.usePersistedState)(persistKey, {});
4132
- const draftRef = (0, import_react28.useRef)(draft);
4133
- draftRef.current = draft;
4134
- const idx = readPersistedStepIdx(draft);
4135
- const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
4136
- const setIdx = (0, import_react28.useCallback)(
4137
- (n) => {
4138
- setDraft((prev) => {
4139
- const prevIdx = readPersistedStepIdx(prev);
4140
- const nextIdx = typeof n === "function" ? n(prevIdx) : n;
4141
- return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
4142
- });
4143
- },
4144
- [setDraft]
4145
- );
4146
- const setValue = (0, import_react28.useCallback)(
4147
- (patch) => {
4148
- draftRef.current = { ...draftRef.current, ...patch };
4149
- setDraft((prev) => ({ ...prev, ...patch }));
4150
- },
4151
- [setDraft]
4152
- );
4153
- const step = steps[clampedIdx];
4154
- const hookCtx = (0, import_sdk23.useHook)();
4155
- const track2 = typeof hookCtx.track === "function" ? hookCtx.track : void 0;
4156
- (0, import_react28.useEffect)(() => {
4157
- if (status.loading) return;
4158
- if (!step) return;
4159
- if (!track2) return;
4160
- track2("onboarding_step_viewed", {
4161
- step: step.id,
4162
- step_index: clampedIdx,
4163
- total_steps: steps.length
4164
- });
4165
- }, [step?.id, clampedIdx, steps.length, status.loading, track2]);
4166
- const valid = (0, import_react28.useMemo)(
4167
- () => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
4168
- [draft, step]
4169
- );
4170
- const next = (0, import_react28.useCallback)(() => {
4171
- if (!step) return;
4172
- const current = draftRef.current;
4173
- const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
4174
- if (!validNow) return;
4175
- if (clampedIdx + 1 >= steps.length) {
4176
- onComplete(current);
4177
- } else {
4178
- setIdx(clampedIdx + 1);
4179
- }
4180
- }, [clampedIdx, onComplete, step, steps.length, setIdx]);
4181
- const prevStep = (0, import_react28.useCallback)(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
4182
- const ctx = (0, import_react28.useMemo)(
4183
- () => ({
4184
- stepIndex: clampedIdx,
4185
- totalSteps: steps.length,
4186
- value: draft,
4187
- setValue,
4188
- valid,
4189
- next,
4190
- prev: prevStep
4191
- }),
4192
- [clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
4193
- );
4194
- if (status.loading) {
4195
- return null;
4196
- }
4197
- if (!step) {
4198
- throw new Error(
4199
- `[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
4200
- );
3662
+ if (render) {
3663
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className, children: render({ items }) });
4201
3664
  }
4202
- const Screen = screens[step.screen];
4203
- if (!Screen) {
4204
- throw new Error(
4205
- `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
4206
- );
3665
+ if (renderItem) {
3666
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("li", { children: renderItem(item, idx) }, idx)) });
4207
3667
  }
4208
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Screen, {}) });
3668
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("li", { className: itemClassName, children: [
3669
+ IconComponent ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(IconComponent, { className: iconClassName }) : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: iconClassName, "aria-hidden": "true", children: "\u2713" }),
3670
+ /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { children: item })
3671
+ ] }, idx)) });
4209
3672
  }
4210
3673
 
4211
- // src/hooks/useFeature.ts
4212
- function useFeature(name) {
4213
- const config = useAppConfig();
4214
- return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
3674
+ // src/components/paywall/blocks/PaywallFeaturesCard.tsx
3675
+ var import_jsx_runtime40 = require("react/jsx-runtime");
3676
+ var DEFAULT_CARD_CLASSES = "rounded-xl border p-4";
3677
+ function PaywallFeaturesCard({
3678
+ title,
3679
+ items,
3680
+ className,
3681
+ cardClassName,
3682
+ titleClassName,
3683
+ itemClassName,
3684
+ renderItem
3685
+ }) {
3686
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: [DEFAULT_CARD_CLASSES, cardClassName].filter(Boolean).join(" "), children: [
3687
+ title ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: ["font-semibold mb-2", titleClassName].filter(Boolean).join(" "), children: title }) : null,
3688
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("ul", { children: items.map(
3689
+ (item, idx) => renderItem ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { children: renderItem(item, idx) }, idx) : /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("li", { className: itemClassName, children: [
3690
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { "aria-hidden": "true", children: "\u2022" }),
3691
+ " ",
3692
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { children: item })
3693
+ ] }, idx)
3694
+ ) })
3695
+ ] }) });
4215
3696
  }
4216
3697
 
4217
- // src/components/paywall/Paywall.tsx
4218
- var import_react31 = require("react");
4219
- var import_sdk24 = require("@hook-sdk/sdk");
4220
-
4221
- // src/components/paywall/PaywallProvider.tsx
4222
- var import_react29 = require("react");
4223
- var import_jsx_runtime32 = require("react/jsx-runtime");
4224
- var PaywallContext = (0, import_react29.createContext)(null);
4225
- function PaywallProvider({ children }) {
4226
- const state = usePaywallState();
4227
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(PaywallContext.Provider, { value: state, children });
3698
+ // src/components/paywall/blocks/PaywallTrophyBadge.tsx
3699
+ var import_jsx_runtime41 = require("react/jsx-runtime");
3700
+ 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";
3701
+ var FLOATING_CLASSES = "absolute top-2 right-2 z-10 shadow-md";
3702
+ function PaywallTrophyBadge({
3703
+ text,
3704
+ className,
3705
+ iconClassName,
3706
+ floating = false,
3707
+ render
3708
+ }) {
3709
+ if (render) {
3710
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className, children: render({ text }) });
3711
+ }
3712
+ const rootClasses = [
3713
+ DEFAULT_CHIP_CLASSES,
3714
+ floating ? FLOATING_CLASSES : "",
3715
+ className
3716
+ ].filter(Boolean).join(" ");
3717
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: rootClasses, children: [
3718
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: iconClassName, "aria-hidden": "true", children: "\u{1F3C6}" }),
3719
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: text })
3720
+ ] });
4228
3721
  }
4229
3722
 
4230
- // src/components/paywall/usePaywallContext.ts
4231
- var import_react30 = require("react");
4232
- function usePaywallContext() {
4233
- const ctx = (0, import_react30.useContext)(PaywallContext);
4234
- if (!ctx) {
4235
- throw new Error("usePaywallContext must be used within <PaywallProvider>");
3723
+ // src/components/paywall/blocks/PaywallAnchorPrice.tsx
3724
+ var import_jsx_runtime42 = require("react/jsx-runtime");
3725
+ var DEFAULT_CLASS2 = "text-sm opacity-60 line-through";
3726
+ function PaywallAnchorPrice({
3727
+ className,
3728
+ render
3729
+ }) {
3730
+ const { anchorPriceCents, cycle } = usePaywallContext();
3731
+ if (anchorPriceCents === null || anchorPriceCents === void 0 || anchorPriceCents <= 0) {
3732
+ return null;
4236
3733
  }
4237
- return ctx;
3734
+ void cycle;
3735
+ const formatted = formatBRL(anchorPriceCents);
3736
+ const rootClasses = [DEFAULT_CLASS2, className].filter(Boolean).join(" ");
3737
+ if (render) {
3738
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: className || void 0, children: render({ anchorCents: anchorPriceCents, formatted }) });
3739
+ }
3740
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: rootClasses, children: formatted });
4238
3741
  }
4239
3742
 
4240
- // src/components/paywall/PaywallMethodTabs.tsx
4241
- var import_jsx_runtime33 = require("react/jsx-runtime");
4242
- function PaywallMethodTabs({
4243
- labels,
3743
+ // src/components/paywall/blocks/PaywallTestimonials.tsx
3744
+ var import_jsx_runtime43 = require("react/jsx-runtime");
3745
+ var DEFAULT_ROOT = "flex gap-3 overflow-x-auto snap-x snap-mandatory pb-2";
3746
+ var DEFAULT_CARD = "snap-start shrink-0 w-72 rounded-2xl border p-4 flex flex-col gap-2";
3747
+ var DEFAULT_AVATAR = "w-10 h-10 rounded-full object-cover";
3748
+ var DEFAULT_QUOTE = "text-sm leading-snug";
3749
+ var DEFAULT_NAME = "text-xs font-semibold opacity-80";
3750
+ var DEFAULT_STARS = "text-yellow-500 text-sm";
3751
+ function clampStars(n) {
3752
+ return Math.max(0, Math.min(5, Math.round(n)));
3753
+ }
3754
+ function PaywallTestimonials({
3755
+ items,
4244
3756
  className,
4245
- tabClassName,
4246
- tabActiveClassName
3757
+ cardClassName,
3758
+ avatarClassName,
3759
+ quoteClassName,
3760
+ nameClassName,
3761
+ starsClassName,
3762
+ renderItem
4247
3763
  }) {
4248
- const { methods, selectedMethod, selectMethod } = usePaywallContext();
4249
- if (methods.length < 2) return null;
4250
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", className, children: methods.map((m) => {
4251
- const active = m === selectedMethod;
4252
- const label = labels[m] ?? m;
4253
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
4254
- "button",
4255
- {
4256
- type: "button",
4257
- role: "tab",
4258
- "aria-selected": active,
4259
- "aria-controls": `paywall-tab-${m}`,
4260
- tabIndex: active ? 0 : -1,
4261
- onClick: () => selectMethod(m),
4262
- className: [tabClassName, active ? tabActiveClassName : ""].filter(Boolean).join(" "),
4263
- children: label
4264
- },
4265
- m
4266
- );
3764
+ const rootClasses = [DEFAULT_ROOT, className].filter(Boolean).join(" ");
3765
+ const cardClasses = [DEFAULT_CARD, cardClassName].filter(Boolean).join(" ");
3766
+ const avatarClasses = [DEFAULT_AVATAR, avatarClassName].filter(Boolean).join(" ");
3767
+ const quoteClasses = [DEFAULT_QUOTE, quoteClassName].filter(Boolean).join(" ");
3768
+ const nameClasses = [DEFAULT_NAME, nameClassName].filter(Boolean).join(" ");
3769
+ const starsClasses = [DEFAULT_STARS, starsClassName].filter(Boolean).join(" ");
3770
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: rootClasses, children: items.map((item, idx) => {
3771
+ if (renderItem) return renderItem(item, idx);
3772
+ const filled = clampStars(item.stars);
3773
+ const empty = 5 - filled;
3774
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: cardClasses, children: [
3775
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "flex items-center gap-2", children: [
3776
+ item.avatar ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
3777
+ "img",
3778
+ {
3779
+ src: item.avatar,
3780
+ alt: "",
3781
+ loading: "lazy",
3782
+ className: avatarClasses,
3783
+ "aria-hidden": "true"
3784
+ }
3785
+ ) : null,
3786
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: nameClasses, children: item.name })
3787
+ ] }),
3788
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: starsClasses, "aria-label": `${filled} de 5 estrelas`, children: [
3789
+ "\u2605".repeat(filled),
3790
+ "\u2606".repeat(empty)
3791
+ ] }),
3792
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("p", { className: quoteClasses, children: item.quote })
3793
+ ] }, idx);
4267
3794
  }) });
4268
3795
  }
4269
3796
 
4270
- // src/components/paywall/PaywallMethodContent.tsx
4271
- var import_jsx_runtime34 = require("react/jsx-runtime");
4272
- function PaywallMethodContent({ copy, className, rowClassName }) {
4273
- const { selectedMethod, hasConsumedTrial } = usePaywallContext();
4274
- const useCardConsumed = selectedMethod === "card" && hasConsumedTrial && copy.cardConsumedTrial;
4275
- const rows = useCardConsumed ? copy.cardConsumedTrial.bodyRows : selectedMethod === "pix-auto" || selectedMethod === "pix-once" ? copy.pix.bodyRows : copy.card.bodyRows;
4276
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { role: "tabpanel", id: `paywall-tab-${selectedMethod}`, className, children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: rowClassName, children: row }, i)) });
3797
+ // src/components/paywall/blocks/PaywallStatsRow.tsx
3798
+ var import_jsx_runtime44 = require("react/jsx-runtime");
3799
+ var DEFAULT_ROOT2 = "grid grid-cols-3 gap-4";
3800
+ var DEFAULT_CELL = "flex flex-col items-center text-center";
3801
+ var DEFAULT_VALUE = "text-2xl font-bold";
3802
+ var DEFAULT_LABEL = "text-xs opacity-70";
3803
+ function PaywallStatsRow({
3804
+ stats,
3805
+ className,
3806
+ cellClassName,
3807
+ valueClassName,
3808
+ labelClassName,
3809
+ renderCell
3810
+ }) {
3811
+ const rootClasses = [DEFAULT_ROOT2, className].filter(Boolean).join(" ");
3812
+ const cellClasses = [DEFAULT_CELL, cellClassName].filter(Boolean).join(" ");
3813
+ const valueClasses = [DEFAULT_VALUE, valueClassName].filter(Boolean).join(" ");
3814
+ const labelClasses = [DEFAULT_LABEL, labelClassName].filter(Boolean).join(" ");
3815
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: rootClasses, children: stats.map((stat, idx) => {
3816
+ if (renderCell) return renderCell(stat, idx);
3817
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: cellClasses, children: [
3818
+ stat.icon ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { "aria-hidden": "true", children: stat.icon }) : null,
3819
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: valueClasses, children: stat.value }),
3820
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: labelClasses, children: stat.label })
3821
+ ] }, idx);
3822
+ }) });
4277
3823
  }
4278
3824
 
4279
- // src/components/paywall/PaywallCyclePicker.tsx
4280
- var import_jsx_runtime35 = require("react/jsx-runtime");
4281
- var VARIANT_CLASSES = {
4282
- default: { card: "", cardSelected: "" },
4283
- "premium-gold": {
4284
- card: "",
4285
- cardSelected: "border-2 border-yellow-400/80 ring-2 ring-yellow-400/20"
4286
- },
4287
- "pink-pill": {
4288
- card: "rounded-2xl",
4289
- cardSelected: "border-2 border-pink-500"
4290
- }
3825
+ // src/components/paywall/blocks/PaywallFinePrint.tsx
3826
+ var import_jsx_runtime45 = require("react/jsx-runtime");
3827
+ var DEFAULT_CLASS3 = "text-xs opacity-60 leading-snug";
3828
+ var CYCLE_LABEL2 = {
3829
+ MONTHLY: "mensal",
3830
+ YEARLY: "anual"
4291
3831
  };
4292
- function PaywallCyclePicker({
4293
- labels,
3832
+ function PaywallFinePrint({
3833
+ template,
4294
3834
  className,
4295
- cardClassName,
4296
- cardSelectedClassName,
4297
- anchorClassName,
4298
- variant = "default",
4299
3835
  render
4300
3836
  }) {
4301
- const ctx = usePaywallContext();
4302
- const { cycle: selected, setCycle, plan, anchorPriceCents } = ctx;
4303
- const cycles = ["MONTHLY", "YEARLY"];
3837
+ const {
3838
+ currentPriceCents,
3839
+ cycle,
3840
+ trialDaysCard,
3841
+ trialDaysPix,
3842
+ selectedMethod
3843
+ } = usePaywallContext();
3844
+ const trialDays = selectedMethod === "card" ? trialDaysCard : trialDaysPix;
3845
+ const cycleLabel = CYCLE_LABEL2[cycle] ?? cycle.toLowerCase();
3846
+ const priceFormatted = formatBRL(currentPriceCents ?? 0);
3847
+ const rootClasses = [DEFAULT_CLASS3, className].filter(Boolean).join(" ");
4304
3848
  if (render) {
4305
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className, children: render({ cycles, selected, setCycle, plan, anchorPriceCents }) });
3849
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { className: className || void 0, children: render({
3850
+ currentPriceCents: currentPriceCents ?? 0,
3851
+ cycle,
3852
+ trialDays: trialDays ?? 0,
3853
+ selectedMethod
3854
+ }) });
4306
3855
  }
4307
- if (cycles.length < 2) return null;
4308
- const v = VARIANT_CLASSES[variant];
4309
- const composedCardClassName = [v.card, cardClassName].filter(Boolean).join(" ");
4310
- const composedCardSelectedClassName = [v.cardSelected, cardSelectedClassName].filter(Boolean).join(" ");
4311
- const monthlyCents = plan?.monthlyCents ?? 0;
4312
- const yearlyCents = plan?.yearlyCents ?? 0;
4313
- const anchorMonthly = plan?.anchorMonthlyCents ?? null;
4314
- const anchorYearly = plan?.anchorYearlyCents ?? null;
4315
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
4316
- "div",
4317
- {
4318
- role: "radiogroup",
4319
- "aria-label": "Ciclo de cobran\xE7a",
4320
- className: ["flex flex-row gap-2", className].filter(Boolean).join(" "),
4321
- children: cycles.map((c) => {
4322
- const active = c === selected;
4323
- const label = c === "YEARLY" ? labels.annualLabel : labels.monthlyLabel;
4324
- const suffix = c === "YEARLY" ? labels.annualSuffix : labels.monthlySuffix;
4325
- const mainCents = c === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents;
4326
- const anchorCents = c === "YEARLY" ? anchorYearly : anchorMonthly;
4327
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
4328
- "button",
4329
- {
4330
- type: "button",
4331
- role: "radio",
4332
- "aria-checked": active,
4333
- onClick: () => setCycle(c),
4334
- className: [
4335
- "flex flex-col items-center gap-0.5",
4336
- composedCardClassName,
4337
- active ? composedCardSelectedClassName : ""
4338
- ].filter(Boolean).join(" "),
4339
- children: [
4340
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "font-bold text-base leading-tight", children: formatBRL(mainCents) }),
4341
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-xs opacity-70 leading-tight", children: suffix }),
4342
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-xs opacity-60 leading-tight", children: label }),
4343
- anchorCents != null && anchorCents > mainCents ? /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: anchorClassName ?? "text-xs opacity-50", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("s", { children: formatBRL(anchorCents) }) }) : null
4344
- ]
4345
- },
4346
- c
4347
- );
4348
- })
4349
- }
4350
- );
3856
+ const text = template.replaceAll("{price}", priceFormatted).replaceAll("{trialDays}", String(trialDays ?? 0)).replaceAll("{cycle}", cycleLabel);
3857
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { className: rootClasses, children: text });
4351
3858
  }
4352
3859
 
4353
- // src/components/paywall/Paywall.tsx
4354
- var import_jsx_runtime36 = require("react/jsx-runtime");
4355
- var NBSP = "\xA0";
4356
- function Paywall({
4357
- copy,
4358
- themeClasses = {},
4359
- slots = {},
4360
- onBeforeCheckout
3860
+ // src/components/paywall/blocks/PaywallTrustLine.tsx
3861
+ var import_jsx_runtime46 = require("react/jsx-runtime");
3862
+ var DEFAULT_ROOT3 = "flex items-center gap-3";
3863
+ var DEFAULT_ITEM = "flex items-center gap-1.5 text-xs";
3864
+ function PaywallTrustLine({
3865
+ items,
3866
+ className,
3867
+ itemClassName,
3868
+ renderItem
4361
3869
  }) {
4362
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(PaywallProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4363
- PaywallInner,
4364
- {
4365
- copy,
4366
- themeClasses,
4367
- slots,
4368
- onBeforeCheckout
4369
- }
4370
- ) });
3870
+ const rootClasses = [DEFAULT_ROOT3, className].filter(Boolean).join(" ");
3871
+ const itemClasses = [DEFAULT_ITEM, itemClassName].filter(Boolean).join(" ");
3872
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className: rootClasses, children: items.map((item, idx) => {
3873
+ if (renderItem) return renderItem(item, idx);
3874
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("span", { className: itemClasses, children: [
3875
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { "aria-hidden": "true", children: item.icon }),
3876
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: item.text })
3877
+ ] }, idx);
3878
+ }) });
4371
3879
  }
4372
- function PaywallInner({
4373
- copy,
4374
- themeClasses = {},
4375
- slots = {},
4376
- onBeforeCheckout
3880
+
3881
+ // src/components/paywall/blocks/PaywallStickyFooter.tsx
3882
+ var import_jsx_runtime47 = require("react/jsx-runtime");
3883
+ var DEFAULT_CLASSES = "sticky bottom-0 left-0 right-0 bg-background";
3884
+ var SAFE_AREA_CLASS = "pb-[env(safe-area-inset-bottom)]";
3885
+ function PaywallStickyFooter({
3886
+ children,
3887
+ className,
3888
+ safeAreaInsets = true
4377
3889
  }) {
4378
- const { track: track2 } = (0, import_sdk24.useHook)();
4379
- const s = usePaywallContext();
4380
- const priceLabel = formatBRL(s.currentPriceCents).replace(new RegExp(NBSP, "g"), " ");
4381
- const trialDaysCardLabel = String(s.trialDaysCard);
4382
- const ctaLabel = (0, import_react31.useMemo)(() => {
4383
- if (s.isFree) return copy.freeCta ?? "Come\xE7ar agora";
4384
- if (s.selectedMethod === "card") {
4385
- if (s.hasConsumedTrial && copy.cardConsumedTrial) {
4386
- return interp(copy.cardConsumedTrial.ctaTemplate, {
4387
- price: priceLabel,
4388
- days: trialDaysCardLabel
4389
- });
4390
- }
4391
- if (s.trialDaysCard > 0) {
4392
- return interp(copy.card.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
4393
- }
4394
- return copy.cardConsumedTrial ? interp(copy.cardConsumedTrial.ctaTemplate, {
4395
- price: priceLabel,
4396
- days: trialDaysCardLabel
4397
- }) : `Assinar por ${priceLabel}`;
3890
+ const classes = [DEFAULT_CLASSES, safeAreaInsets ? SAFE_AREA_CLASS : null, className].filter(Boolean).join(" ");
3891
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: classes, children });
3892
+ }
3893
+
3894
+ // src/defaults/CheckoutPageDefault.tsx
3895
+ var import_jsx_runtime48 = require("react/jsx-runtime");
3896
+ var INTENT_KEY = "hook:paywall:intent";
3897
+ var PIX_PAYLOAD_KEY = "hook:paywall:pix-pending";
3898
+ function readIntent() {
3899
+ if (typeof window === "undefined") return {};
3900
+ try {
3901
+ const raw = sessionStorage.getItem(INTENT_KEY);
3902
+ if (!raw) return {};
3903
+ return JSON.parse(raw);
3904
+ } catch {
3905
+ return {};
3906
+ }
3907
+ }
3908
+ function formatBrl(cents) {
3909
+ return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(cents / 100);
3910
+ }
3911
+ function formatCardNumber(v) {
3912
+ const digits = v.replace(/\D/g, "").slice(0, 16);
3913
+ return digits.replace(/(.{4})/g, "$1 ").trim();
3914
+ }
3915
+ function formatExpiryMmAa(v) {
3916
+ const d = v.replace(/\D/g, "").slice(0, 4);
3917
+ if (d.length < 3) return d;
3918
+ return d.slice(0, 2) + "/" + d.slice(2);
3919
+ }
3920
+ function parseExpiryMmAa(v) {
3921
+ const d = v.replace(/\D/g, "");
3922
+ return { month: d.slice(0, 2), year: d.slice(2, 4) };
3923
+ }
3924
+ function formatCpf(v) {
3925
+ const d = v.replace(/\D/g, "").slice(0, 11);
3926
+ if (d.length <= 3) return d;
3927
+ if (d.length <= 6) return d.slice(0, 3) + "." + d.slice(3);
3928
+ if (d.length <= 9) return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6);
3929
+ return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6, 9) + "-" + d.slice(9);
3930
+ }
3931
+ function detectCardBrand(num) {
3932
+ const n = num.replace(/\s/g, "");
3933
+ if (/^4/.test(n)) return "VISA";
3934
+ if (/^(5[1-5]|2[2-7])/.test(n)) return "MASTER";
3935
+ if (/^3[47]/.test(n)) return "AMEX";
3936
+ if (/^(4011|4312|4389|4514|6011|6362|6363)/.test(n)) return "ELO";
3937
+ if (/^(606282|3841)/.test(n)) return "HIPER";
3938
+ return "";
3939
+ }
3940
+ function CheckoutPageDefault() {
3941
+ const navigate = (0, import_react_router_dom3.useNavigate)();
3942
+ const plan = usePlan();
3943
+ const intent = (0, import_react23.useMemo)(readIntent, []);
3944
+ const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3945
+ const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3946
+ const form = useCheckoutForm({ defaultMethod, defaultCycle });
3947
+ const [expiryMmAa, setExpiryMmAa] = (0, import_react23.useState)("");
3948
+ (0, import_react23.useEffect)(() => {
3949
+ const { month, year } = parseExpiryMmAa(expiryMmAa);
3950
+ if (month !== form.card.expiryMonth || year !== form.card.expiryYear) {
3951
+ form.setCard({ expiryMonth: month, expiryYear: year });
4398
3952
  }
4399
- return interp(copy.pix.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
4400
- }, [
4401
- s.isFree,
4402
- s.selectedMethod,
4403
- s.hasConsumedTrial,
4404
- s.trialDaysCard,
4405
- copy,
4406
- priceLabel,
4407
- trialDaysCardLabel
4408
- ]);
4409
- const switchHint = (0, import_react31.useMemo)(() => {
4410
- if (s.methods.length < 2) return void 0;
4411
- return s.selectedMethod === "card" ? copy.card.switchHint : copy.pix.switchHint;
4412
- }, [s.methods.length, s.selectedMethod, copy]);
4413
- (0, import_react31.useEffect)(() => {
4414
- if (!s.initialLoadComplete) return;
4415
- track2("paywall_view", {
4416
- default_method: s.selectedMethod,
4417
- default_cycle: s.cycle,
4418
- available_methods: s.methods
4419
- });
4420
- }, [s.initialLoadComplete]);
4421
- const handleCta = async () => {
4422
- track2("paywall_cta_clicked", {
4423
- method: s.selectedMethod,
4424
- cycle: s.cycle,
4425
- price_cents: s.currentPriceCents,
4426
- had_consumed_trial: s.hasConsumedTrial
4427
- });
4428
- if (onBeforeCheckout) {
4429
- await onBeforeCheckout(s.selectedMethod, s.cycle);
3953
+ }, [expiryMmAa]);
3954
+ (0, import_react23.useEffect)(() => {
3955
+ if (form.emailTaken && form.loginUrl) {
3956
+ const t = setTimeout(() => navigate(form.loginUrl), 1200);
3957
+ return () => clearTimeout(t);
3958
+ }
3959
+ }, [form.emailTaken, form.loginUrl, navigate]);
3960
+ const planInfo = plan.data ? {
3961
+ priceCents: plan.data.priceCents,
3962
+ yearlyPriceCents: plan.data.yearlyPriceCents,
3963
+ trialDays: plan.data.trialDays
3964
+ } : null;
3965
+ const annual = form.cycle === "YEARLY";
3966
+ const cyclePrice = (0, import_react23.useMemo)(() => {
3967
+ if (!planInfo) return null;
3968
+ return annual ? planInfo.yearlyPriceCents ?? planInfo.priceCents * 12 : planInfo.priceCents;
3969
+ }, [planInfo, annual]);
3970
+ const monthlyText = (0, import_react23.useMemo)(() => {
3971
+ if (!planInfo) return "";
3972
+ const monthly = annual && planInfo.yearlyPriceCents ? Math.round(planInfo.yearlyPriceCents / 12) : planInfo.priceCents;
3973
+ return formatBrl(monthly);
3974
+ }, [planInfo, annual]);
3975
+ const todayCents = (0, import_react23.useMemo)(() => {
3976
+ if (form.method === "pix-auto") return cyclePrice ?? 0;
3977
+ const trialDays2 = planInfo?.trialDays ?? 0;
3978
+ if (trialDays2 > 0) return 0;
3979
+ return cyclePrice ?? 0;
3980
+ }, [form.method, cyclePrice, planInfo]);
3981
+ const todayAmount = formatBrl(todayCents);
3982
+ const cyclePriceText = cyclePrice !== null ? formatBrl(cyclePrice) : "";
3983
+ const annualSavingsCents = (0, import_react23.useMemo)(() => {
3984
+ if (!planInfo || !planInfo.yearlyPriceCents) return 0;
3985
+ return planInfo.priceCents * 12 - planInfo.yearlyPriceCents;
3986
+ }, [planInfo]);
3987
+ const trialDays = planInfo?.trialDays ?? 7;
3988
+ const cardBrand = detectCardBrand(form.card.number);
3989
+ async function onSubmit(e) {
3990
+ e.preventDefault();
3991
+ const result = await form.submit();
3992
+ if (!result) return;
3993
+ if (form.method === "pix-auto" && result.pix_qr_payload) {
3994
+ try {
3995
+ sessionStorage.setItem(
3996
+ PIX_PAYLOAD_KEY,
3997
+ JSON.stringify({
3998
+ payload: result.pix_qr_payload,
3999
+ base64: result.pix_qr_base64 ?? null,
4000
+ subscriptionId: result.subscription_id,
4001
+ pixAuthorizationId: result.pix_authorization_id ?? null
4002
+ })
4003
+ );
4004
+ } catch {
4005
+ }
4006
+ navigate(result.redirect.replace(/^.*\/app\/[^/]+/, ""));
4430
4007
  return;
4431
4008
  }
4432
- await s.submit();
4433
- };
4434
- const ctaTheme = s.selectedMethod === "card" ? themeClasses.ctaCard : themeClasses.ctaPix;
4435
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: themeClasses.container, children: [
4436
- slots.heroSlot,
4437
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h1", { className: themeClasses.headline, children: copy.headline }),
4438
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { children: copy.features.map((f) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { className: themeClasses.feature, children: [
4439
- "\u2713 ",
4440
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { children: f })
4441
- ] }, f)) }),
4442
- copy.socialProof ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: themeClasses.socialProof, children: copy.socialProof }) : null,
4443
- slots.cyclePickerSlot ?? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4444
- PaywallCyclePicker,
4445
- {
4446
- labels: copy.cycle,
4447
- cardClassName: themeClasses.cycleCard,
4448
- cardSelectedClassName: themeClasses.cycleCardSelected,
4449
- anchorClassName: themeClasses.anchorPrice
4450
- }
4451
- ),
4452
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4453
- PaywallMethodTabs,
4454
- {
4455
- labels: { "pix-auto": copy.pix.tabLabel, card: copy.card.tabLabel },
4456
- className: themeClasses.tabs,
4457
- tabClassName: themeClasses.tab,
4458
- tabActiveClassName: themeClasses.tabActive
4459
- }
4460
- ),
4461
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4462
- PaywallMethodContent,
4463
- {
4464
- copy: {
4465
- pix: interpolateCopy(copy.pix, priceLabel, trialDaysCardLabel),
4466
- card: interpolateCopy(copy.card, priceLabel, trialDaysCardLabel),
4467
- cardConsumedTrial: copy.cardConsumedTrial ? {
4468
- bodyRows: copy.cardConsumedTrial.bodyRows.map(
4469
- (r) => interp(r, { price: priceLabel, days: trialDaysCardLabel })
4470
- ),
4471
- ctaTemplate: copy.cardConsumedTrial.ctaTemplate
4472
- } : void 0
4473
- },
4474
- className: themeClasses.tabContent,
4475
- rowClassName: themeClasses.tabContentRow
4476
- }
4477
- ),
4478
- slots.beforeCtaSlot,
4479
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { children: [
4480
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4009
+ navigate(result.redirect.replace(/^.*\/app\/[^/]+/, "") || "/");
4010
+ }
4011
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "flex-1 flex flex-col bg-background min-h-0", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("form", { onSubmit, className: "flex-1 flex flex-col min-h-0", children: [
4012
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex-1 overflow-y-auto pb-4", children: [
4013
+ form.emailTaken ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "px-5 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { role: "alert", className: "rounded-2xl bg-destructive/10 p-4 text-sm text-destructive border border-destructive/20", children: [
4014
+ "Esse e-mail j\xE1 tem conta nesse app.",
4015
+ " ",
4016
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("a", { href: form.loginUrl ?? "/signin", className: "underline font-semibold", children: "Entrar agora" })
4017
+ ] }) }) : null,
4018
+ form.error ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "px-5 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("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,
4019
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "px-5 pt-4", children: form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "rounded-2xl bg-card border-[1.5px] border-foreground p-3.5", children: [
4020
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [
4021
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ShieldIcon, { className: "w-4 h-4" }),
4022
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "text-sm font-bold", children: "Voc\xEA N\xC3O ser\xE1 cobrada hoje" })
4023
+ ] }),
4024
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex justify-between items-baseline text-sm text-muted-foreground", children: [
4025
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { children: "R$ 0,00 agora" }),
4026
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "opacity-50", children: "\xB7" }),
4027
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { children: [
4028
+ monthlyText,
4029
+ "/m\xEAs ap\xF3s ",
4030
+ trialDays,
4031
+ " dias"
4032
+ ] })
4033
+ ] }),
4034
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "mt-2.5 text-[11px] text-muted-foreground flex items-center gap-1.5", children: [
4035
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(BellIcon, { className: "w-2.5 h-2.5" }),
4036
+ "Avisamos por email 2 dias antes da primeira cobran\xE7a"
4037
+ ] })
4038
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "rounded-2xl p-3.5 bg-emerald-50 border-[1.5px] border-emerald-600/60", children: [
4039
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-2 mb-2 text-emerald-900", children: [
4040
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(ShieldIcon, { className: "w-4 h-4" }),
4041
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-sm font-bold", children: [
4042
+ "Garantia incondicional de ",
4043
+ trialDays,
4044
+ " dias"
4045
+ ] })
4046
+ ] }),
4047
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-sm text-emerald-900 leading-snug", children: [
4048
+ "Voc\xEA paga hoje via Pix.",
4049
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("br", {}),
4050
+ "N\xE3o gostou em ",
4051
+ trialDays,
4052
+ " dias? Devolvemos ",
4053
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("b", { children: "100%" }),
4054
+ " sem perguntas \u2014 direto pelo app."
4055
+ ] })
4056
+ ] }) }),
4057
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("section", { className: "px-5 pt-5", children: [
4058
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("h2", { className: "font-display text-2xl mb-3.5 leading-tight text-foreground", children: "Quase l\xE1." }),
4059
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "Email" }),
4060
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4061
+ FieldInput,
4062
+ {
4063
+ type: "email",
4064
+ inputMode: "email",
4065
+ autoComplete: "email",
4066
+ autoCapitalize: "none",
4067
+ autoCorrect: "off",
4068
+ spellCheck: false,
4069
+ placeholder: "seu@email.com",
4070
+ value: form.email,
4071
+ onChange: form.setEmail,
4072
+ onBlur: form.markEmailTouched,
4073
+ error: form.emailError,
4074
+ valid: form.emailStatus === "available"
4075
+ }
4076
+ ),
4077
+ !form.emailError && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" }),
4078
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "h-3" }),
4079
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "Nome completo" }),
4080
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4081
+ FieldInput,
4082
+ {
4083
+ type: "text",
4084
+ autoComplete: "name",
4085
+ placeholder: "como est\xE1 no documento",
4086
+ value: form.name,
4087
+ onChange: form.setName,
4088
+ onBlur: form.markNameTouched,
4089
+ error: form.nameError,
4090
+ valid: !!form.name && !form.nameError
4091
+ }
4092
+ ),
4093
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "h-3" }),
4094
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "CPF" }),
4095
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4096
+ FieldInput,
4097
+ {
4098
+ type: "text",
4099
+ inputMode: "numeric",
4100
+ placeholder: "000.000.000-00",
4101
+ value: form.cpf,
4102
+ onChange: (v) => form.setCpf(formatCpf(v)),
4103
+ onBlur: form.markCpfTouched,
4104
+ error: form.cpfError,
4105
+ valid: !!form.cpf && !form.cpfError
4106
+ }
4107
+ ),
4108
+ form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
4109
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "h-3" }),
4110
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "Telefone" }),
4111
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4112
+ FieldInput,
4113
+ {
4114
+ type: "tel",
4115
+ inputMode: "tel",
4116
+ autoComplete: "tel",
4117
+ placeholder: "(11) 99999-9999",
4118
+ value: form.phone,
4119
+ onChange: form.setPhone,
4120
+ onBlur: form.markPhoneTouched,
4121
+ error: form.phoneError,
4122
+ valid: !!form.phone && !form.phoneError
4123
+ }
4124
+ ),
4125
+ !form.phoneError && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
4126
+ ] }) : null
4127
+ ] }),
4128
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("section", { className: "px-5 pt-5", children: [
4129
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "Forma de pagamento" }),
4130
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { role: "tablist", className: "flex gap-1.5 bg-muted p-1 rounded-xl", children: [
4131
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4132
+ TabButton,
4133
+ {
4134
+ active: form.method === "card",
4135
+ onClick: () => form.setMethod("card"),
4136
+ icon: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(CardIcon, { className: "w-3.5 h-3.5" }),
4137
+ label: "Cart\xE3o",
4138
+ subtitle: trialDays > 0 ? `${trialDays} dias gr\xE1tis` : "pague hoje",
4139
+ subtitleActiveClass: "text-emerald-700"
4140
+ }
4141
+ ),
4142
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4143
+ TabButton,
4144
+ {
4145
+ active: form.method === "pix-auto",
4146
+ onClick: () => form.setMethod("pix-auto"),
4147
+ icon: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(PixIcon, { className: "w-3.5 h-3.5" }),
4148
+ label: "Pix",
4149
+ subtitle: `pague hoje \xB7 garantia ${trialDays}d`,
4150
+ subtitleActiveClass: "text-foreground/70"
4151
+ }
4152
+ )
4153
+ ] })
4154
+ ] }),
4155
+ form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("section", { className: "px-5 pt-3.5", children: [
4156
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "N\xFAmero do cart\xE3o" }),
4157
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "relative", children: [
4158
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4159
+ FieldInput,
4160
+ {
4161
+ type: "text",
4162
+ inputMode: "numeric",
4163
+ autoComplete: "cc-number",
4164
+ placeholder: "0000 0000 0000 0000",
4165
+ value: form.card.number,
4166
+ onChange: (v) => form.setCard({ number: formatCardNumber(v) }),
4167
+ style: cardBrand ? { paddingRight: "4.5rem" } : void 0
4168
+ }
4169
+ ),
4170
+ cardBrand && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("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 })
4171
+ ] }),
4172
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "h-3" }),
4173
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex gap-2.5", children: [
4174
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex-1", children: [
4175
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "Validade" }),
4176
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4177
+ FieldInput,
4178
+ {
4179
+ type: "text",
4180
+ inputMode: "numeric",
4181
+ autoComplete: "cc-exp",
4182
+ placeholder: "MM/AA",
4183
+ value: expiryMmAa,
4184
+ onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v))
4185
+ }
4186
+ )
4187
+ ] }),
4188
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex-1", children: [
4189
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "CVV" }),
4190
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4191
+ FieldInput,
4192
+ {
4193
+ type: "text",
4194
+ inputMode: "numeric",
4195
+ autoComplete: "cc-csc",
4196
+ placeholder: "3 d\xEDgitos",
4197
+ value: form.card.ccv,
4198
+ onChange: (v) => form.setCard({ ccv: v.replace(/\D/g, "").slice(0, 4) })
4199
+ }
4200
+ )
4201
+ ] })
4202
+ ] }),
4203
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "h-3" }),
4204
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "Nome no cart\xE3o" }),
4205
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4206
+ FieldInput,
4207
+ {
4208
+ type: "text",
4209
+ autoComplete: "cc-name",
4210
+ placeholder: "como est\xE1 no cart\xE3o",
4211
+ value: form.card.holderName,
4212
+ onChange: (v) => form.setCard({ holderName: v })
4213
+ }
4214
+ )
4215
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("section", { className: "px-5 pt-3.5", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "rounded-2xl bg-card border border-border p-3.5 flex gap-3.5 items-center", children: [
4216
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "w-[72px] h-[72px] rounded-xl shrink-0 border-2 border-foreground relative overflow-hidden bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("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__ */ (0, import_jsx_runtime48.jsx)(PixIcon, { className: "w-3.5 h-3.5 text-foreground" }) }) }),
4217
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex-1", children: [
4218
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "text-xs font-bold uppercase tracking-wider text-muted-foreground", children: "pagamento em segundos" }),
4219
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-sm text-foreground mt-1 leading-snug", children: [
4220
+ "Geramos seu ",
4221
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("b", { children: "QR Pix" }),
4222
+ " no pr\xF3ximo passo. Pague pelo app do banco e seu acesso libera ",
4223
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("b", { children: "imediatamente" }),
4224
+ "."
4225
+ ] })
4226
+ ] })
4227
+ ] }) }),
4228
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("section", { className: "px-5 pt-5", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "bg-muted rounded-2xl p-4", children: [
4229
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex justify-between mb-2.5", children: [
4230
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
4231
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "text-sm font-semibold text-foreground", children: annual ? "Plano Anual" : "Plano Mensal" }),
4232
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "text-[11px] text-muted-foreground", children: "Coach" })
4233
+ ] }),
4234
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-right", children: [
4235
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-sm font-bold text-foreground", children: [
4236
+ cyclePriceText,
4237
+ "/",
4238
+ annual ? "ano" : "m\xEAs"
4239
+ ] }),
4240
+ annual && annualSavingsCents > 0 && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-[11px] text-emerald-700 font-semibold", children: [
4241
+ "economia ",
4242
+ formatBrl(annualSavingsCents)
4243
+ ] })
4244
+ ] })
4245
+ ] }),
4246
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "h-px bg-border my-3" }),
4247
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex justify-between items-baseline", children: [
4248
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
4249
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "text-sm font-bold text-foreground", children: "Voc\xEA paga hoje" }),
4250
+ form.method === "card" && trialDays > 0 && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-[11px] text-muted-foreground mt-0.5", children: [
4251
+ "cobran\xE7a inicia no dia ",
4252
+ trialDays
4253
+ ] }),
4254
+ form.method === "pix-auto" && /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-[11px] text-emerald-700 mt-0.5 font-semibold", children: [
4255
+ "reembolso garantido at\xE9 o dia ",
4256
+ trialDays
4257
+ ] })
4258
+ ] }),
4259
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "text-2xl font-bold font-display tracking-tight text-foreground", children: todayAmount })
4260
+ ] })
4261
+ ] }) })
4262
+ ] }),
4263
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(PaywallStickyFooter, { className: "px-5 pt-3.5 pb-5 border-t border-border", children: [
4264
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4481
4265
  "button",
4482
4266
  {
4483
- type: "button",
4484
- onClick: () => {
4485
- void handleCta();
4486
- },
4487
- disabled: s.submitting,
4488
- className: ctaTheme,
4489
- children: s.submitting ? "Abrindo checkout\u2026" : ctaLabel
4267
+ type: "submit",
4268
+ disabled: !form.canSubmit,
4269
+ 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",
4270
+ children: form.submitting ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
4271
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(Spinner2, {}),
4272
+ " ",
4273
+ form.method === "pix-auto" ? "Gerando QR\u2026" : "Confirmando\u2026"
4274
+ ] }) : form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
4275
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(LockIcon, { className: "w-3.5 h-3.5" }),
4276
+ " Confirmar e come\xE7ar gr\xE1tis"
4277
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
4278
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(PixIcon, { className: "w-3.5 h-3.5" }),
4279
+ " Gerar QR Pix"
4280
+ ] })
4490
4281
  }
4491
4282
  ),
4492
- switchHint ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: themeClasses.switchHint, children: switchHint }) : null,
4493
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: themeClasses.trustLine, children: copy.trustLine })
4283
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "text-center mt-2.5 text-xs text-muted-foreground", children: [
4284
+ "Ao continuar, voc\xEA concorda com nossos ",
4285
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("u", { children: "Termos" }),
4286
+ ". Pagamento seguro Asaas."
4287
+ ] }),
4288
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "mt-3 flex items-center justify-center gap-3.5 text-[11px] text-muted-foreground", children: [
4289
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
4290
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(LockIcon, { className: "w-2.5 h-2.5 opacity-60" }),
4291
+ " SSL 256-bit"
4292
+ ] }),
4293
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "w-px h-2.5 bg-border" }),
4294
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { children: "Pagamento via Asaas" }),
4295
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "w-px h-2.5 bg-border" }),
4296
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { children: [
4297
+ "Garantia ",
4298
+ trialDays,
4299
+ " dias"
4300
+ ] })
4301
+ ] })
4494
4302
  ] })
4495
- ] });
4496
- }
4497
- function interp(tpl, vars) {
4498
- return tpl.replace(/\{(\w+)\}/g, (_m, k) => vars[k] ?? "");
4303
+ ] }) });
4499
4304
  }
4500
- function interpolateCopy(m, price, days) {
4501
- return {
4502
- tabLabel: m.tabLabel,
4503
- bodyRows: m.bodyRows.map((r) => interp(r, { price, days })),
4504
- ctaTemplate: m.ctaTemplate,
4505
- switchHint: m.switchHint
4506
- };
4305
+ function FieldLabel({ children }) {
4306
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-1.5", children });
4507
4307
  }
4508
-
4509
- // src/components/paywall/PaywallCta.tsx
4510
- var import_jsx_runtime37 = require("react/jsx-runtime");
4511
- function PaywallCta({
4512
- ctaLabel,
4513
- loadingLabel,
4514
- switchHint,
4515
- trustLine,
4516
- className,
4517
- buttonClassName,
4518
- switchHintClassName,
4519
- trustClassName
4520
- }) {
4521
- const { submit, submitting } = usePaywallContext();
4522
- const label = submitting && loadingLabel ? loadingLabel : ctaLabel;
4523
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className, children: [
4524
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4525
- "button",
4308
+ function FieldInput(props) {
4309
+ const baseClass = "w-full px-4 rounded-xl bg-card text-base text-foreground outline-none border-[1.5px] transition-colors";
4310
+ const stateClass = props.error ? "border-destructive focus:border-destructive" : props.valid ? "border-emerald-600 focus:border-emerald-700" : "border-border focus:border-foreground";
4311
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
4312
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4313
+ "input",
4526
4314
  {
4527
- type: "button",
4528
- onClick: () => {
4529
- void submit();
4530
- },
4531
- disabled: submitting,
4532
- className: buttonClassName,
4533
- children: label
4315
+ type: props.type ?? "text",
4316
+ inputMode: props.inputMode,
4317
+ autoComplete: props.autoComplete,
4318
+ autoCapitalize: props.autoCapitalize,
4319
+ autoCorrect: props.autoCorrect,
4320
+ spellCheck: props.spellCheck,
4321
+ placeholder: props.placeholder,
4322
+ value: props.value,
4323
+ onChange: (e) => props.onChange(e.target.value),
4324
+ onBlur: props.onBlur,
4325
+ style: { height: "52px", ...props.style },
4326
+ className: `${baseClass} ${stateClass}`
4534
4327
  }
4535
4328
  ),
4536
- switchHint ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: switchHintClassName, children: switchHint }) : null,
4537
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: trustClassName, children: trustLine })
4329
+ props.error ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "mt-1.5 text-xs text-destructive font-medium", children: props.error }) : null
4538
4330
  ] });
4539
4331
  }
4540
-
4541
- // src/components/paywall/blocks/PaywallEyebrow.tsx
4542
- var import_jsx_runtime38 = require("react/jsx-runtime");
4543
- var DEFAULT_EYEBROW_CLASSES = "text-xs uppercase tracking-widest font-semibold opacity-70";
4544
- function PaywallEyebrow({ text, className }) {
4545
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: [DEFAULT_EYEBROW_CLASSES, className].filter(Boolean).join(" "), children: text });
4332
+ function FieldHint({ children }) {
4333
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "mt-1.5 text-xs text-muted-foreground", children });
4546
4334
  }
4547
-
4548
- // src/components/paywall/blocks/PaywallHero.tsx
4549
- var import_jsx_runtime39 = require("react/jsx-runtime");
4550
- var DEFAULT_GRADIENT = "absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent";
4551
- function PaywallHero({
4552
- src,
4553
- alt = "",
4554
- headline,
4555
- aspectRatio = "16/9",
4556
- gradientClassName,
4557
- className,
4558
- headlineClassName,
4559
- imgClassName,
4560
- render
4561
- }) {
4562
- if (render) {
4563
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className, children: render({ src, headline }) });
4564
- }
4565
- return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
4566
- "div",
4335
+ function TabButton({ active, onClick, icon, label, subtitle, subtitleActiveClass }) {
4336
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
4337
+ "button",
4567
4338
  {
4568
- className: ["relative overflow-hidden", className].filter(Boolean).join(" "),
4569
- style: { aspectRatio },
4339
+ type: "button",
4340
+ role: "tab",
4341
+ "aria-selected": active,
4342
+ onClick,
4343
+ 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"}`,
4344
+ style: { minHeight: 56 },
4570
4345
  children: [
4571
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4572
- "img",
4573
- {
4574
- src,
4575
- alt,
4576
- className: ["absolute inset-0 w-full h-full object-cover", imgClassName].filter(Boolean).join(" ")
4577
- }
4578
- ),
4579
- /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className: gradientClassName ?? DEFAULT_GRADIENT, "aria-hidden": "true" }),
4580
- headline ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
4581
- "h1",
4582
- {
4583
- className: ["absolute bottom-0 left-0 right-0 p-4 text-white font-bold text-2xl", headlineClassName].filter(Boolean).join(" "),
4584
- children: headline
4585
- }
4586
- ) : null
4346
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
4347
+ icon,
4348
+ " ",
4349
+ label
4350
+ ] }),
4351
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: `text-[10px] font-medium ${active ? subtitleActiveClass : "text-muted-foreground/70"}`, children: subtitle })
4587
4352
  ]
4588
4353
  }
4589
4354
  );
4590
4355
  }
4591
-
4592
- // src/components/paywall/blocks/PaywallHeadline.tsx
4593
- var import_jsx_runtime40 = require("react/jsx-runtime");
4594
- var DEFAULT_HEADLINE_CLASSES = "text-2xl font-bold leading-tight";
4595
- function PaywallHeadline({ text, className, as = "h1" }) {
4596
- const Tag = as;
4597
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Tag, { className: [DEFAULT_HEADLINE_CLASSES, className].filter(Boolean).join(" "), children: text });
4356
+ function CardIcon({ className }) {
4357
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinejoin: "round", "aria-hidden": "true", children: [
4358
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "2.5" }),
4359
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("path", { d: "M2.5 10h19" })
4360
+ ] });
4361
+ }
4362
+ function PixIcon({ className }) {
4363
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("path", { d: "M12 2L2 12l10 10 10-10L12 2zm0 4.83L17.17 12 12 17.17 6.83 12 12 6.83z" }) });
4364
+ }
4365
+ function LockIcon({ className }) {
4366
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
4367
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2.5" }),
4368
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("path", { d: "M7.5 10V7a4.5 4.5 0 119 0v3" })
4369
+ ] });
4370
+ }
4371
+ function ShieldIcon({ className }) {
4372
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
4373
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("path", { d: "M12 2.5l8 3v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10v-6l8-3z" }),
4374
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("path", { d: "M9 12l2 2 4-4" })
4375
+ ] });
4376
+ }
4377
+ function BellIcon({ className }) {
4378
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
4379
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("path", { d: "M6 17V11a6 6 0 1112 0v6l1.5 2H4.5L6 17z" }),
4380
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("path", { d: "M10 21a2 2 0 004 0" })
4381
+ ] });
4382
+ }
4383
+ function Spinner2() {
4384
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4385
+ "span",
4386
+ {
4387
+ className: "w-4 h-4 rounded-full border-2 border-white/40 border-t-white",
4388
+ style: { animation: "spin 0.7s linear infinite" }
4389
+ }
4390
+ );
4598
4391
  }
4599
4392
 
4600
- // src/components/paywall/blocks/PaywallPriceHeadline.tsx
4601
- var import_jsx_runtime41 = require("react/jsx-runtime");
4602
- var DEFAULT_CLASS = "text-2xl font-bold leading-tight";
4603
- var CYCLE_LABEL = {
4604
- MONTHLY: "mensal",
4605
- YEARLY: "anual"
4606
- };
4607
- function PaywallPriceHeadline({
4608
- template,
4609
- className,
4610
- as = "h1",
4611
- render
4612
- }) {
4613
- const { cycle, currentMonthlyEquivCents, plan } = usePaywallContext();
4614
- const yearlyCents = plan?.yearlyCents ?? null;
4615
- const pricePerDay = formatBRL(dailyFromYearly(yearlyCents));
4616
- const monthlyEquiv = currentMonthlyEquivCents ?? 0;
4617
- const cycleLabel = CYCLE_LABEL[cycle] ?? cycle.toLowerCase();
4618
- const rootClasses = [DEFAULT_CLASS, className].filter(Boolean).join(" ");
4619
- if (render) {
4620
- const RootTag2 = as;
4621
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(RootTag2, { className: [className].filter(Boolean).join(" ") || void 0, children: render({ pricePerDay, currentMonthlyEquivCents: monthlyEquiv, cycle }) });
4393
+ // src/defaults/PixWaitingPageDefault.tsx
4394
+ var import_react24 = require("react");
4395
+ var import_react_router_dom4 = require("react-router-dom");
4396
+ var import_sdk16 = require("@hook-sdk/sdk");
4397
+ var import_jsx_runtime49 = require("react/jsx-runtime");
4398
+ var PIX_PAYLOAD_KEY2 = "hook:paywall:pix-pending";
4399
+ var TIMEOUT_MS = 30 * 60 * 1e3;
4400
+ function readPixPayload() {
4401
+ if (typeof window === "undefined") return null;
4402
+ try {
4403
+ const raw = sessionStorage.getItem(PIX_PAYLOAD_KEY2);
4404
+ if (!raw) return null;
4405
+ return JSON.parse(raw);
4406
+ } catch {
4407
+ return null;
4622
4408
  }
4623
- const text = template.replaceAll("{pricePerDay}", pricePerDay).replaceAll("{currentMonthlyEquiv}", formatBRL(monthlyEquiv)).replaceAll("{cycle}", cycleLabel);
4624
- const RootTag = as;
4625
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(RootTag, { className: rootClasses, children: text });
4626
4409
  }
4627
-
4628
- // src/components/paywall/blocks/PaywallCountdown.tsx
4629
- var import_react32 = require("react");
4630
- var import_jsx_runtime42 = require("react/jsx-runtime");
4631
- var DEFAULT_COUNTDOWN_CLASSES = "font-mono tabular-nums";
4632
- function resolveDeadlineMs(deadline) {
4633
- if (deadline instanceof Date) return deadline.getTime();
4634
- if (typeof deadline === "string") return new Date(deadline).getTime();
4635
- const { sessionStorageKey, durationMs } = deadline;
4636
- if (typeof window === "undefined" || typeof window.sessionStorage === "undefined") {
4637
- return Date.now() + durationMs;
4410
+ function clearPixPayload() {
4411
+ if (typeof window === "undefined") return;
4412
+ try {
4413
+ sessionStorage.removeItem(PIX_PAYLOAD_KEY2);
4414
+ } catch {
4638
4415
  }
4639
- const stored = window.sessionStorage.getItem(sessionStorageKey);
4640
- const parsed = stored ? Number.parseInt(stored, 10) : NaN;
4641
- const now = Date.now();
4642
- if (!Number.isFinite(parsed) || parsed < now) {
4643
- const target = now + durationMs;
4644
- window.sessionStorage.setItem(sessionStorageKey, String(target));
4645
- return target;
4416
+ }
4417
+ function PixWaitingPageDefault() {
4418
+ const navigate = (0, import_react_router_dom4.useNavigate)();
4419
+ const { subscription } = (0, import_sdk16.useHook)();
4420
+ const payload = (0, import_react24.useMemo)(readPixPayload, []);
4421
+ const [copied, setCopied] = (0, import_react24.useState)(false);
4422
+ const [timedOut, setTimedOut] = (0, import_react24.useState)(false);
4423
+ (0, import_react24.useEffect)(() => {
4424
+ if (!payload) navigate("/paywall/checkout", { replace: true });
4425
+ }, [payload, navigate]);
4426
+ (0, import_react24.useEffect)(() => {
4427
+ window.scrollTo(0, 0);
4428
+ }, []);
4429
+ (0, import_react24.useEffect)(() => {
4430
+ const t = setTimeout(() => setTimedOut(true), TIMEOUT_MS);
4431
+ return () => clearTimeout(t);
4432
+ }, []);
4433
+ const hasAccess = subscription.hasAccess;
4434
+ (0, import_react24.useEffect)(() => {
4435
+ if (hasAccess) {
4436
+ clearPixPayload();
4437
+ navigate("/", { replace: true });
4438
+ }
4439
+ }, [hasAccess, navigate]);
4440
+ async function copyPayload() {
4441
+ if (!payload?.payload) return;
4442
+ try {
4443
+ await navigator.clipboard.writeText(payload.payload);
4444
+ setCopied(true);
4445
+ setTimeout(() => setCopied(false), 2e3);
4446
+ } catch {
4447
+ }
4646
4448
  }
4647
- return parsed;
4449
+ if (!payload) return null;
4450
+ if (timedOut) {
4451
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-10 text-center bg-background space-y-4", children: [
4452
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("h1", { className: "font-display text-2xl text-foreground", children: "PIX expirado" }),
4453
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "text-sm text-muted-foreground", children: "O tempo pra pagar acabou. Gere um novo PIX." }),
4454
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4455
+ "button",
4456
+ {
4457
+ onClick: () => {
4458
+ clearPixPayload();
4459
+ navigate("/paywall/checkout", { replace: true });
4460
+ },
4461
+ className: "rounded-xl bg-primary px-6 py-3 text-base font-semibold text-primary-foreground",
4462
+ children: "Tentar novamente"
4463
+ }
4464
+ )
4465
+ ] });
4466
+ }
4467
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex-1 flex flex-col items-center px-6 py-8 bg-background space-y-6", children: [
4468
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("header", { className: "text-center space-y-2", children: [
4469
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("h1", { className: "font-display text-2xl text-foreground", children: "Pague o PIX" }),
4470
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("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." })
4471
+ ] }),
4472
+ payload.base64 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4473
+ "img",
4474
+ {
4475
+ src: `data:image/png;base64,${payload.base64}`,
4476
+ alt: "QR Code PIX",
4477
+ className: "w-64 h-64 rounded-2xl border border-border bg-card p-2"
4478
+ }
4479
+ ) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("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" }),
4480
+ payload.payload ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
4481
+ "button",
4482
+ {
4483
+ onClick: copyPayload,
4484
+ className: "w-full max-w-xs rounded-xl border border-border bg-card px-4 py-3 text-sm font-medium text-foreground",
4485
+ children: copied ? "\u2713 Copiado!" : "Copiar c\xF3digo PIX"
4486
+ }
4487
+ ) : null,
4488
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
4489
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "inline-block w-2 h-2 rounded-full bg-primary animate-pulse" }),
4490
+ "Aguardando pagamento\u2026"
4491
+ ] }),
4492
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("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." })
4493
+ ] });
4648
4494
  }
4649
- function computeRemaining(deadlineMs) {
4650
- const diff = Math.max(0, deadlineMs - Date.now());
4651
- const totalSeconds = Math.floor(diff / 1e3);
4652
- const h = Math.floor(totalSeconds / 3600);
4653
- const m = Math.floor(totalSeconds % 3600 / 60);
4654
- const s = totalSeconds % 60;
4655
- return { h, m, s, expired: diff === 0 };
4495
+
4496
+ // src/hooks/useLoginForm.ts
4497
+ var import_react25 = require("react");
4498
+ var import_sdk17 = require("@hook-sdk/sdk");
4499
+ var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4500
+ var MIN_PASSWORD = 8;
4501
+ function useLoginForm() {
4502
+ const { auth } = (0, import_sdk17.useHook)();
4503
+ const [email, setEmail] = (0, import_react25.useState)("");
4504
+ const [password, setPassword] = (0, import_react25.useState)("");
4505
+ const [submitting, setSubmitting] = (0, import_react25.useState)(false);
4506
+ const [error, setError] = (0, import_react25.useState)(null);
4507
+ const [touchedEmail, setTouchedEmail] = (0, import_react25.useState)(false);
4508
+ const [touchedPassword, setTouchedPassword] = (0, import_react25.useState)(false);
4509
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react25.useState)(false);
4510
+ const validateEmail = (0, import_react25.useMemo)(() => {
4511
+ if (email.length === 0) return null;
4512
+ if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
4513
+ return null;
4514
+ }, [email]);
4515
+ const validatePassword = (0, import_react25.useMemo)(() => {
4516
+ if (password.length === 0) return null;
4517
+ if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
4518
+ return null;
4519
+ }, [password]);
4520
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
4521
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
4522
+ const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
4523
+ const submit = (0, import_react25.useCallback)(async () => {
4524
+ setFormSubmitAttempted(true);
4525
+ if (!canSubmit) return false;
4526
+ setSubmitting(true);
4527
+ setError(null);
4528
+ try {
4529
+ await auth.login({ email, password });
4530
+ return true;
4531
+ } catch (err) {
4532
+ setError(mapSdkError(err));
4533
+ return false;
4534
+ } finally {
4535
+ setSubmitting(false);
4536
+ }
4537
+ }, [auth, email, password, canSubmit]);
4538
+ return {
4539
+ email,
4540
+ setEmail,
4541
+ emailError,
4542
+ markEmailTouched: () => setTouchedEmail(true),
4543
+ password,
4544
+ setPassword,
4545
+ passwordError,
4546
+ markPasswordTouched: () => setTouchedPassword(true),
4547
+ formSubmitAttempted,
4548
+ submit,
4549
+ submitting,
4550
+ canSubmit,
4551
+ error,
4552
+ loginWithGoogle: () => auth.loginWithGoogle()
4553
+ };
4554
+ }
4555
+
4556
+ // src/hooks/useSignupForm.ts
4557
+ var import_react26 = require("react");
4558
+ var import_sdk18 = require("@hook-sdk/sdk");
4559
+ var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4560
+ var MIN_PASSWORD2 = 8;
4561
+ function useSignupForm() {
4562
+ const { auth } = (0, import_sdk18.useHook)();
4563
+ const [name, setName] = (0, import_react26.useState)("");
4564
+ const [email, setEmail] = (0, import_react26.useState)("");
4565
+ const [password, setPassword] = (0, import_react26.useState)("");
4566
+ const [submitting, setSubmitting] = (0, import_react26.useState)(false);
4567
+ const [error, setError] = (0, import_react26.useState)(null);
4568
+ const [touchedName, setTouchedName] = (0, import_react26.useState)(false);
4569
+ const [touchedEmail, setTouchedEmail] = (0, import_react26.useState)(false);
4570
+ const [touchedPassword, setTouchedPassword] = (0, import_react26.useState)(false);
4571
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react26.useState)(false);
4572
+ const validateName = (0, import_react26.useMemo)(() => {
4573
+ if (name.length === 0) return null;
4574
+ if (name.trim().length < 2) return "Nome muito curto.";
4575
+ return null;
4576
+ }, [name]);
4577
+ const validateEmail = (0, import_react26.useMemo)(() => {
4578
+ if (email.length === 0) return null;
4579
+ if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
4580
+ return null;
4581
+ }, [email]);
4582
+ const validatePassword = (0, import_react26.useMemo)(() => {
4583
+ if (password.length === 0) return null;
4584
+ if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
4585
+ return null;
4586
+ }, [password]);
4587
+ const nameError = touchedName || formSubmitAttempted ? validateName : null;
4588
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
4589
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
4590
+ const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
4591
+ const submit = (0, import_react26.useCallback)(async () => {
4592
+ setFormSubmitAttempted(true);
4593
+ if (!canSubmit) return false;
4594
+ setSubmitting(true);
4595
+ setError(null);
4596
+ try {
4597
+ await auth.signup({ name, email, password });
4598
+ return true;
4599
+ } catch (err) {
4600
+ setError(mapSdkError(err));
4601
+ return false;
4602
+ } finally {
4603
+ setSubmitting(false);
4604
+ }
4605
+ }, [auth, name, email, password, canSubmit]);
4606
+ return {
4607
+ name,
4608
+ setName,
4609
+ nameError,
4610
+ markNameTouched: () => setTouchedName(true),
4611
+ email,
4612
+ setEmail,
4613
+ emailError,
4614
+ markEmailTouched: () => setTouchedEmail(true),
4615
+ password,
4616
+ setPassword,
4617
+ passwordError,
4618
+ markPasswordTouched: () => setTouchedPassword(true),
4619
+ formSubmitAttempted,
4620
+ submit,
4621
+ submitting,
4622
+ canSubmit,
4623
+ error,
4624
+ loginWithGoogle: () => auth.loginWithGoogle()
4625
+ };
4656
4626
  }
4657
- function pad(n) {
4658
- return String(n).padStart(2, "0");
4627
+
4628
+ // src/hooks/useForgotForm.ts
4629
+ var import_react27 = require("react");
4630
+ var import_sdk19 = require("@hook-sdk/sdk");
4631
+ var EMAIL_RE4 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4632
+ function useForgotForm() {
4633
+ const { auth } = (0, import_sdk19.useHook)();
4634
+ const [email, setEmail] = (0, import_react27.useState)("");
4635
+ const [submitting, setSubmitting] = (0, import_react27.useState)(false);
4636
+ const [sent, setSent] = (0, import_react27.useState)(false);
4637
+ const [error, setError] = (0, import_react27.useState)(null);
4638
+ const [touchedEmail, setTouchedEmail] = (0, import_react27.useState)(false);
4639
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react27.useState)(false);
4640
+ const validateEmail = (0, import_react27.useMemo)(() => {
4641
+ if (email.length === 0) return null;
4642
+ if (!EMAIL_RE4.test(email)) return "Formato de e-mail inv\xE1lido.";
4643
+ return null;
4644
+ }, [email]);
4645
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
4646
+ const canSubmit = email.length > 0 && validateEmail === null && !submitting;
4647
+ const submit = (0, import_react27.useCallback)(async () => {
4648
+ setFormSubmitAttempted(true);
4649
+ if (!canSubmit) return false;
4650
+ setSubmitting(true);
4651
+ setError(null);
4652
+ try {
4653
+ await auth.forgot({ email });
4654
+ setSent(true);
4655
+ return true;
4656
+ } catch (err) {
4657
+ setError(mapSdkError(err));
4658
+ return false;
4659
+ } finally {
4660
+ setSubmitting(false);
4661
+ }
4662
+ }, [auth, email, canSubmit]);
4663
+ return {
4664
+ email,
4665
+ setEmail,
4666
+ emailError,
4667
+ markEmailTouched: () => setTouchedEmail(true),
4668
+ formSubmitAttempted,
4669
+ submit,
4670
+ submitting,
4671
+ canSubmit,
4672
+ sent,
4673
+ error
4674
+ };
4659
4675
  }
4660
- function PaywallCountdown({
4661
- deadline,
4662
- format = "h:m:s",
4663
- onExpire,
4664
- className,
4665
- render
4666
- }) {
4667
- const deadlineMsRef = (0, import_react32.useRef)(null);
4668
- if (deadlineMsRef.current === null) {
4669
- deadlineMsRef.current = resolveDeadlineMs(deadline);
4670
- }
4671
- const [state, setState] = (0, import_react32.useState)(() => computeRemaining(deadlineMsRef.current));
4672
- const expiredCalledRef = (0, import_react32.useRef)(false);
4673
- (0, import_react32.useEffect)(() => {
4674
- if (state.expired) {
4675
- if (!expiredCalledRef.current) {
4676
- expiredCalledRef.current = true;
4677
- onExpire?.();
4676
+
4677
+ // src/hooks/useResetForm.ts
4678
+ var import_react28 = require("react");
4679
+ var import_sdk20 = require("@hook-sdk/sdk");
4680
+ var MIN_PASSWORD3 = 8;
4681
+ function useResetForm() {
4682
+ const { auth } = (0, import_sdk20.useHook)();
4683
+ const [token, setToken] = (0, import_react28.useState)(null);
4684
+ const [password, setPassword] = (0, import_react28.useState)("");
4685
+ const [confirm, setConfirm] = (0, import_react28.useState)("");
4686
+ const [submitting, setSubmitting] = (0, import_react28.useState)(false);
4687
+ const [done, setDone] = (0, import_react28.useState)(false);
4688
+ const [error, setError] = (0, import_react28.useState)(null);
4689
+ const [touchedPassword, setTouchedPassword] = (0, import_react28.useState)(false);
4690
+ const [touchedConfirm, setTouchedConfirm] = (0, import_react28.useState)(false);
4691
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react28.useState)(false);
4692
+ (0, import_react28.useEffect)(() => {
4693
+ if (typeof window === "undefined") return;
4694
+ const params = new URLSearchParams(window.location.search);
4695
+ const t = params.get("token");
4696
+ setToken(t && t.length > 0 ? t : null);
4697
+ }, []);
4698
+ const validatePassword = (0, import_react28.useMemo)(() => {
4699
+ if (password.length === 0) return null;
4700
+ if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
4701
+ return null;
4702
+ }, [password]);
4703
+ const validateConfirm = (0, import_react28.useMemo)(() => {
4704
+ if (confirm.length === 0) return null;
4705
+ if (confirm !== password) return "Senhas n\xE3o coincidem.";
4706
+ return null;
4707
+ }, [confirm, password]);
4708
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
4709
+ const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
4710
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
4711
+ const submit = (0, import_react28.useCallback)(async () => {
4712
+ setFormSubmitAttempted(true);
4713
+ if (!canSubmit || token === null) return;
4714
+ setSubmitting(true);
4715
+ setError(null);
4716
+ try {
4717
+ await auth.reset({ token, newPassword: password });
4718
+ setDone(true);
4719
+ if (typeof window !== "undefined") {
4720
+ const url = new URL(window.location.href);
4721
+ url.searchParams.delete("token");
4722
+ url.searchParams.delete("screen");
4723
+ window.history.replaceState({}, "", url.toString());
4678
4724
  }
4679
- return;
4725
+ } catch (err) {
4726
+ setError(mapSdkError(err));
4727
+ } finally {
4728
+ setSubmitting(false);
4680
4729
  }
4681
- const tick = () => {
4682
- const next = computeRemaining(deadlineMsRef.current);
4683
- setState(next);
4684
- if (next.expired && !expiredCalledRef.current) {
4685
- expiredCalledRef.current = true;
4686
- onExpire?.();
4687
- }
4688
- };
4689
- const id = setInterval(tick, 1e3);
4690
- return () => clearInterval(id);
4691
- }, [state.expired]);
4692
- if (render) {
4693
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className, children: render(state) });
4694
- }
4695
- const formatted = format === "h:m:s" ? `${pad(state.h)}:${pad(state.m)}:${pad(state.s)}` : `${pad(state.h * 60 + state.m)}:${pad(state.s)}`;
4696
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { className: [DEFAULT_COUNTDOWN_CLASSES, className].filter(Boolean).join(" "), children: formatted });
4730
+ }, [auth, token, password, canSubmit]);
4731
+ return {
4732
+ token,
4733
+ password,
4734
+ setPassword,
4735
+ passwordError,
4736
+ markPasswordTouched: () => setTouchedPassword(true),
4737
+ confirm,
4738
+ setConfirm,
4739
+ confirmError,
4740
+ markConfirmTouched: () => setTouchedConfirm(true),
4741
+ formSubmitAttempted,
4742
+ submit,
4743
+ submitting,
4744
+ canSubmit,
4745
+ done,
4746
+ error
4747
+ };
4697
4748
  }
4698
4749
 
4699
- // src/components/paywall/blocks/PaywallFeatures.tsx
4700
- var import_jsx_runtime43 = require("react/jsx-runtime");
4701
- function PaywallFeatures({
4702
- items,
4703
- IconComponent,
4704
- className,
4705
- itemClassName,
4706
- iconClassName,
4707
- render,
4708
- renderItem
4709
- }) {
4710
- if (render) {
4711
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className, children: render({ items }) });
4712
- }
4713
- if (renderItem) {
4714
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("li", { children: renderItem(item, idx) }, idx)) });
4715
- }
4716
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("li", { className: itemClassName, children: [
4717
- IconComponent ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(IconComponent, { className: iconClassName }) : /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: iconClassName, "aria-hidden": "true", children: "\u2713" }),
4718
- /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { children: item })
4719
- ] }, idx)) });
4750
+ // src/hooks/useAuthPrimitives.ts
4751
+ var import_react29 = require("react");
4752
+ var import_sdk21 = require("@hook-sdk/sdk");
4753
+ var warned = false;
4754
+ function useAuthPrimitives() {
4755
+ const { auth } = (0, import_sdk21.useHook)();
4756
+ (0, import_react29.useEffect)(() => {
4757
+ if (!warned && process.env.NODE_ENV !== "production") {
4758
+ warned = true;
4759
+ console.warn(
4760
+ "[@hook-sdk/template] useAuthPrimitives() \xE9 escape hatch. Pra login/signup/forgot, use useLoginForm/useSignupForm/useForgotForm. Docs: docs/19-golden-template.md#escape-hatch"
4761
+ );
4762
+ }
4763
+ }, []);
4764
+ return {
4765
+ login: auth.login,
4766
+ signup: auth.signup,
4767
+ logout: auth.logout,
4768
+ logoutAll: auth.logoutAll,
4769
+ forgot: auth.forgot,
4770
+ resendVerify: auth.resendVerify,
4771
+ changePassword: auth.changePassword,
4772
+ changeEmail: auth.changeEmail,
4773
+ refresh: auth.refresh
4774
+ };
4720
4775
  }
4721
4776
 
4722
- // src/components/paywall/blocks/PaywallFeaturesCard.tsx
4723
- var import_jsx_runtime44 = require("react/jsx-runtime");
4724
- var DEFAULT_CARD_CLASSES = "rounded-xl border p-4";
4725
- function PaywallFeaturesCard({
4726
- title,
4727
- items,
4728
- className,
4729
- cardClassName,
4730
- titleClassName,
4731
- itemClassName,
4732
- renderItem
4733
- }) {
4734
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: [DEFAULT_CARD_CLASSES, cardClassName].filter(Boolean).join(" "), children: [
4735
- title ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: ["font-semibold mb-2", titleClassName].filter(Boolean).join(" "), children: title }) : null,
4736
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { children: items.map(
4737
- (item, idx) => renderItem ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { children: renderItem(item, idx) }, idx) : /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { className: itemClassName, children: [
4738
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { "aria-hidden": "true", children: "\u2022" }),
4739
- " ",
4740
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { children: item })
4741
- ] }, idx)
4742
- ) })
4743
- ] }) });
4777
+ // src/hooks/useAuth.ts
4778
+ var import_sdk22 = require("@hook-sdk/sdk");
4779
+ function useAuth() {
4780
+ const { user, authStatus, auth } = (0, import_sdk22.useHook)();
4781
+ return {
4782
+ user,
4783
+ authStatus,
4784
+ refresh: auth.refresh
4785
+ };
4744
4786
  }
4745
4787
 
4746
- // src/components/paywall/blocks/PaywallTrophyBadge.tsx
4747
- var import_jsx_runtime45 = require("react/jsx-runtime");
4748
- 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";
4749
- var FLOATING_CLASSES = "absolute top-2 right-2 z-10 shadow-md";
4750
- function PaywallTrophyBadge({
4751
- text,
4752
- className,
4753
- iconClassName,
4754
- floating = false,
4755
- render
4756
- }) {
4757
- if (render) {
4758
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className, children: render({ text }) });
4759
- }
4760
- const rootClasses = [
4761
- DEFAULT_CHIP_CLASSES,
4762
- floating ? FLOATING_CLASSES : "",
4763
- className
4764
- ].filter(Boolean).join(" ");
4765
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: rootClasses, children: [
4766
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { className: iconClassName, "aria-hidden": "true", children: "\u{1F3C6}" }),
4767
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { children: text })
4768
- ] });
4788
+ // src/index.ts
4789
+ var import_sdk26 = require("@hook-sdk/sdk");
4790
+
4791
+ // src/hooks/useSubscription.ts
4792
+ var import_sdk23 = require("@hook-sdk/sdk");
4793
+ function useSubscription() {
4794
+ const { subscription } = (0, import_sdk23.useHook)();
4795
+ return {
4796
+ status: subscription.status()
4797
+ };
4769
4798
  }
4770
4799
 
4771
- // src/components/paywall/blocks/PaywallAnchorPrice.tsx
4772
- var import_jsx_runtime46 = require("react/jsx-runtime");
4773
- var DEFAULT_CLASS2 = "text-sm opacity-60 line-through";
4774
- function PaywallAnchorPrice({
4775
- className,
4776
- render
4777
- }) {
4778
- const { anchorPriceCents, cycle } = usePaywallContext();
4779
- if (anchorPriceCents === null || anchorPriceCents === void 0 || anchorPriceCents <= 0) {
4780
- return null;
4781
- }
4782
- void cycle;
4783
- const formatted = formatBRL(anchorPriceCents);
4784
- const rootClasses = [DEFAULT_CLASS2, className].filter(Boolean).join(" ");
4785
- if (render) {
4786
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: className || void 0, children: render({ anchorCents: anchorPriceCents, formatted }) });
4787
- }
4788
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: rootClasses, children: formatted });
4800
+ // src/hooks/useReminders.ts
4801
+ var import_react30 = require("react");
4802
+ var import_sdk24 = require("@hook-sdk/sdk");
4803
+ function useReminders() {
4804
+ const { push } = (0, import_sdk24.useHook)();
4805
+ const r = push.reminders;
4806
+ const [reminders, setReminders] = (0, import_react30.useState)([]);
4807
+ const [loading, setLoading] = (0, import_react30.useState)(true);
4808
+ const reload = (0, import_react30.useCallback)(async () => {
4809
+ setLoading(true);
4810
+ try {
4811
+ const next = await r.list();
4812
+ setReminders(next);
4813
+ } finally {
4814
+ setLoading(false);
4815
+ }
4816
+ }, [r]);
4817
+ (0, import_react30.useEffect)(() => {
4818
+ void reload();
4819
+ }, [reload]);
4820
+ const setReminder = (0, import_react30.useCallback)(async (input) => {
4821
+ await r.set(input);
4822
+ await reload();
4823
+ }, [r, reload]);
4824
+ const deleteReminder = (0, import_react30.useCallback)(async (slot) => {
4825
+ await r.delete(slot);
4826
+ await reload();
4827
+ }, [r, reload]);
4828
+ const schedule = (0, import_react30.useCallback)(async (items) => {
4829
+ return r.schedule(items);
4830
+ }, [r]);
4831
+ const setFallbacks = (0, import_react30.useCallback)(async (items) => {
4832
+ return r.setFallbacks(items);
4833
+ }, [r]);
4834
+ return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
4789
4835
  }
4790
4836
 
4791
- // src/components/paywall/blocks/PaywallTestimonials.tsx
4792
- var import_jsx_runtime47 = require("react/jsx-runtime");
4793
- var DEFAULT_ROOT = "flex gap-3 overflow-x-auto snap-x snap-mandatory pb-2";
4794
- var DEFAULT_CARD = "snap-start shrink-0 w-72 rounded-2xl border p-4 flex flex-col gap-2";
4795
- var DEFAULT_AVATAR = "w-10 h-10 rounded-full object-cover";
4796
- var DEFAULT_QUOTE = "text-sm leading-snug";
4797
- var DEFAULT_NAME = "text-xs font-semibold opacity-80";
4798
- var DEFAULT_STARS = "text-yellow-500 text-sm";
4799
- function clampStars(n) {
4800
- return Math.max(0, Math.min(5, Math.round(n)));
4837
+ // src/hooks/useToast.ts
4838
+ var import_react31 = require("react");
4839
+ function useToast() {
4840
+ const [items, setItems] = (0, import_react31.useState)([]);
4841
+ const show = (0, import_react31.useCallback)((message, kind = "info") => {
4842
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4843
+ setItems((prev) => [...prev, { id, message, kind }]);
4844
+ setTimeout(() => {
4845
+ setItems((prev) => prev.filter((t) => t.id !== id));
4846
+ }, 4e3);
4847
+ }, []);
4848
+ const dismiss = (0, import_react31.useCallback)((id) => {
4849
+ setItems((prev) => prev.filter((t) => t.id !== id));
4850
+ }, []);
4851
+ return { items, show, dismiss };
4801
4852
  }
4802
- function PaywallTestimonials({
4803
- items,
4804
- className,
4805
- cardClassName,
4806
- avatarClassName,
4807
- quoteClassName,
4808
- nameClassName,
4809
- starsClassName,
4810
- renderItem
4811
- }) {
4812
- const rootClasses = [DEFAULT_ROOT, className].filter(Boolean).join(" ");
4813
- const cardClasses = [DEFAULT_CARD, cardClassName].filter(Boolean).join(" ");
4814
- const avatarClasses = [DEFAULT_AVATAR, avatarClassName].filter(Boolean).join(" ");
4815
- const quoteClasses = [DEFAULT_QUOTE, quoteClassName].filter(Boolean).join(" ");
4816
- const nameClasses = [DEFAULT_NAME, nameClassName].filter(Boolean).join(" ");
4817
- const starsClasses = [DEFAULT_STARS, starsClassName].filter(Boolean).join(" ");
4818
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: rootClasses, children: items.map((item, idx) => {
4819
- if (renderItem) return renderItem(item, idx);
4820
- const filled = clampStars(item.stars);
4821
- const empty = 5 - filled;
4822
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: cardClasses, children: [
4823
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "flex items-center gap-2", children: [
4824
- item.avatar ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
4825
- "img",
4826
- {
4827
- src: item.avatar,
4828
- alt: "",
4829
- loading: "lazy",
4830
- className: avatarClasses,
4831
- "aria-hidden": "true"
4832
- }
4833
- ) : null,
4834
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { className: nameClasses, children: item.name })
4835
- ] }),
4836
- /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: starsClasses, "aria-label": `${filled} de 5 estrelas`, children: [
4837
- "\u2605".repeat(filled),
4838
- "\u2606".repeat(empty)
4839
- ] }),
4840
- /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: quoteClasses, children: item.quote })
4841
- ] }, idx);
4842
- }) });
4853
+
4854
+ // src/RouteBoundary.tsx
4855
+ var import_react_router_dom5 = require("react-router-dom");
4856
+ var import_jsx_runtime50 = require("react/jsx-runtime");
4857
+ function RouteBoundary({ children }) {
4858
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_react_router_dom5.Routes, { children: [
4859
+ children,
4860
+ /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_react_router_dom5.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(DefaultNotFound, {}) })
4861
+ ] });
4862
+ }
4863
+ function DefaultNotFound() {
4864
+ return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
4843
4865
  }
4844
4866
 
4845
- // src/components/paywall/blocks/PaywallStatsRow.tsx
4846
- var import_jsx_runtime48 = require("react/jsx-runtime");
4847
- var DEFAULT_ROOT2 = "grid grid-cols-3 gap-4";
4848
- var DEFAULT_CELL = "flex flex-col items-center text-center";
4849
- var DEFAULT_VALUE = "text-2xl font-bold";
4850
- var DEFAULT_LABEL = "text-xs opacity-70";
4851
- function PaywallStatsRow({
4852
- stats,
4853
- className,
4854
- cellClassName,
4855
- valueClassName,
4856
- labelClassName,
4857
- renderCell
4867
+ // src/PreAuthShell.tsx
4868
+ var import_react_router_dom6 = require("react-router-dom");
4869
+ var import_jsx_runtime51 = require("react/jsx-runtime");
4870
+ function PreAuthShell({
4871
+ basename,
4872
+ testRouter,
4873
+ testInitialEntries,
4874
+ children
4858
4875
  }) {
4859
- const rootClasses = [DEFAULT_ROOT2, className].filter(Boolean).join(" ");
4860
- const cellClasses = [DEFAULT_CELL, cellClassName].filter(Boolean).join(" ");
4861
- const valueClasses = [DEFAULT_VALUE, valueClassName].filter(Boolean).join(" ");
4862
- const labelClasses = [DEFAULT_LABEL, labelClassName].filter(Boolean).join(" ");
4863
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: rootClasses, children: stats.map((stat, idx) => {
4864
- if (renderCell) return renderCell(stat, idx);
4865
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: cellClasses, children: [
4866
- stat.icon ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { "aria-hidden": "true", children: stat.icon }) : null,
4867
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: valueClasses, children: stat.value }),
4868
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: labelClasses, children: stat.label })
4869
- ] }, idx);
4870
- }) });
4876
+ if (testRouter === "memory") {
4877
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_router_dom6.MemoryRouter, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_router_dom6.Routes, { children }) });
4878
+ }
4879
+ return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_router_dom6.BrowserRouter, { basename, children: /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_react_router_dom6.Routes, { children }) });
4871
4880
  }
4872
4881
 
4873
- // src/components/paywall/blocks/PaywallFinePrint.tsx
4874
- var import_jsx_runtime49 = require("react/jsx-runtime");
4875
- var DEFAULT_CLASS3 = "text-xs opacity-60 leading-snug";
4876
- var CYCLE_LABEL2 = {
4877
- MONTHLY: "mensal",
4878
- YEARLY: "anual"
4879
- };
4880
- function PaywallFinePrint({
4881
- template,
4882
- className,
4883
- render
4884
- }) {
4885
- const {
4886
- currentPriceCents,
4887
- cycle,
4888
- trialDaysCard,
4889
- trialDaysPix,
4890
- selectedMethod
4891
- } = usePaywallContext();
4892
- const trialDays = selectedMethod === "card" ? trialDaysCard : trialDaysPix;
4893
- const cycleLabel = CYCLE_LABEL2[cycle] ?? cycle.toLowerCase();
4894
- const priceFormatted = formatBRL(currentPriceCents ?? 0);
4895
- const rootClasses = [DEFAULT_CLASS3, className].filter(Boolean).join(" ");
4896
- if (render) {
4897
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: className || void 0, children: render({
4898
- currentPriceCents: currentPriceCents ?? 0,
4899
- cycle,
4900
- trialDays: trialDays ?? 0,
4901
- selectedMethod
4902
- }) });
4882
+ // src/OnboardingFlow.tsx
4883
+ var import_react33 = require("react");
4884
+ var import_sdk25 = require("@hook-sdk/sdk");
4885
+
4886
+ // src/hooks/useOnboardingStep.ts
4887
+ var import_react32 = require("react");
4888
+ var OnboardingStepContext = (0, import_react32.createContext)(null);
4889
+ function useOnboardingStep() {
4890
+ const ctx = (0, import_react32.useContext)(OnboardingStepContext);
4891
+ if (!ctx) {
4892
+ throw new Error(
4893
+ "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
4894
+ );
4903
4895
  }
4904
- const text = template.replaceAll("{price}", priceFormatted).replaceAll("{trialDays}", String(trialDays ?? 0)).replaceAll("{cycle}", cycleLabel);
4905
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: rootClasses, children: text });
4896
+ return ctx;
4906
4897
  }
4907
4898
 
4908
- // src/components/paywall/blocks/PaywallTrustLine.tsx
4909
- var import_jsx_runtime50 = require("react/jsx-runtime");
4910
- var DEFAULT_ROOT3 = "flex items-center gap-3";
4911
- var DEFAULT_ITEM = "flex items-center gap-1.5 text-xs";
4912
- function PaywallTrustLine({
4913
- items,
4914
- className,
4915
- itemClassName,
4916
- renderItem
4899
+ // src/OnboardingFlow.tsx
4900
+ var import_jsx_runtime52 = require("react/jsx-runtime");
4901
+ var isFilled = (v) => v != null && v !== "";
4902
+ var CURRENT_STEP_FIELD = "currentStep";
4903
+ function readPersistedStepIdx(draft) {
4904
+ const raw = draft[CURRENT_STEP_FIELD];
4905
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
4906
+ }
4907
+ function OnboardingFlow({
4908
+ steps,
4909
+ screens,
4910
+ onComplete,
4911
+ persistKey
4917
4912
  }) {
4918
- const rootClasses = [DEFAULT_ROOT3, className].filter(Boolean).join(" ");
4919
- const itemClasses = [DEFAULT_ITEM, itemClassName].filter(Boolean).join(" ");
4920
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: rootClasses, children: items.map((item, idx) => {
4921
- if (renderItem) return renderItem(item, idx);
4922
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("span", { className: itemClasses, children: [
4923
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { "aria-hidden": "true", children: item.icon }),
4924
- /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { children: item.text })
4925
- ] }, idx);
4926
- }) });
4913
+ const [draft, setDraft, status] = (0, import_sdk25.usePersistedState)(persistKey, {});
4914
+ const draftRef = (0, import_react33.useRef)(draft);
4915
+ draftRef.current = draft;
4916
+ const idx = readPersistedStepIdx(draft);
4917
+ const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
4918
+ const setIdx = (0, import_react33.useCallback)(
4919
+ (n) => {
4920
+ setDraft((prev) => {
4921
+ const prevIdx = readPersistedStepIdx(prev);
4922
+ const nextIdx = typeof n === "function" ? n(prevIdx) : n;
4923
+ return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
4924
+ });
4925
+ },
4926
+ [setDraft]
4927
+ );
4928
+ const setValue = (0, import_react33.useCallback)(
4929
+ (patch) => {
4930
+ draftRef.current = { ...draftRef.current, ...patch };
4931
+ setDraft((prev) => ({ ...prev, ...patch }));
4932
+ },
4933
+ [setDraft]
4934
+ );
4935
+ const step = steps[clampedIdx];
4936
+ const hookCtx = (0, import_sdk25.useHook)();
4937
+ const track2 = typeof hookCtx.track === "function" ? hookCtx.track : void 0;
4938
+ (0, import_react33.useEffect)(() => {
4939
+ if (status.loading) return;
4940
+ if (!step) return;
4941
+ if (!track2) return;
4942
+ track2("onboarding_step_viewed", {
4943
+ step: step.id,
4944
+ step_index: clampedIdx,
4945
+ total_steps: steps.length
4946
+ });
4947
+ }, [step?.id, clampedIdx, steps.length, status.loading, track2]);
4948
+ const valid = (0, import_react33.useMemo)(
4949
+ () => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
4950
+ [draft, step]
4951
+ );
4952
+ const next = (0, import_react33.useCallback)(() => {
4953
+ if (!step) return;
4954
+ const current = draftRef.current;
4955
+ const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
4956
+ if (!validNow) return;
4957
+ if (clampedIdx + 1 >= steps.length) {
4958
+ onComplete(current);
4959
+ } else {
4960
+ setIdx(clampedIdx + 1);
4961
+ }
4962
+ }, [clampedIdx, onComplete, step, steps.length, setIdx]);
4963
+ const prevStep = (0, import_react33.useCallback)(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
4964
+ const ctx = (0, import_react33.useMemo)(
4965
+ () => ({
4966
+ stepIndex: clampedIdx,
4967
+ totalSteps: steps.length,
4968
+ value: draft,
4969
+ setValue,
4970
+ valid,
4971
+ next,
4972
+ prev: prevStep
4973
+ }),
4974
+ [clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
4975
+ );
4976
+ if (status.loading) {
4977
+ return null;
4978
+ }
4979
+ if (!step) {
4980
+ throw new Error(
4981
+ `[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
4982
+ );
4983
+ }
4984
+ const Screen = screens[step.screen];
4985
+ if (!Screen) {
4986
+ throw new Error(
4987
+ `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
4988
+ );
4989
+ }
4990
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(Screen, {}) });
4927
4991
  }
4928
4992
 
4929
- // src/components/paywall/blocks/PaywallStickyFooter.tsx
4930
- var import_jsx_runtime51 = require("react/jsx-runtime");
4931
- var DEFAULT_CLASSES = "sticky bottom-0 left-0 right-0 bg-background";
4932
- var SAFE_AREA_CLASS = "pb-[env(safe-area-inset-bottom)]";
4933
- function PaywallStickyFooter({
4934
- children,
4935
- className,
4936
- safeAreaInsets = true
4937
- }) {
4938
- const classes = [DEFAULT_CLASSES, safeAreaInsets ? SAFE_AREA_CLASS : null, className].filter(Boolean).join(" ");
4939
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: classes, children });
4993
+ // src/hooks/useFeature.ts
4994
+ function useFeature(name) {
4995
+ const config = useAppConfig();
4996
+ return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
4940
4997
  }
4941
4998
  // Annotate the CommonJS export names for ESM import in node:
4942
4999
  0 && (module.exports = {