@hook-sdk/template 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -62,7 +62,7 @@ module.exports = __toCommonJS(index_exports);
62
62
 
63
63
  // src/AppRoot.tsx
64
64
  var import_react15 = require("react");
65
- var import_sdk9 = require("@hook-sdk/sdk");
65
+ var import_sdk10 = require("@hook-sdk/sdk");
66
66
 
67
67
  // src/internal/TemplateConfigContext.tsx
68
68
  var import_react = require("react");
@@ -157,50 +157,66 @@ function AuthGate({ Login, Signup, Forgot, Reset, children }) {
157
157
  var import_react3 = require("react");
158
158
  var import_sdk2 = require("@hook-sdk/sdk");
159
159
  function usePaywallState() {
160
- const { subscription } = (0, import_sdk2.useHook)();
160
+ const { subscription, plan } = (0, import_sdk2.useHook)();
161
161
  const [opening, setOpening] = (0, import_react3.useState)(false);
162
162
  const [error, setError] = (0, import_react3.useState)(null);
163
163
  const [pixPending, setPixPending] = (0, import_react3.useState)(null);
164
164
  const status = subscription.status();
165
165
  const daysLeftInTrial = subscription.daysLeftInTrial();
166
166
  const initialLoadComplete = subscription.initialLoadComplete;
167
+ const availableMethods = (0, import_react3.useMemo)(
168
+ () => ["card", "pix-auto"],
169
+ []
170
+ );
171
+ const priceCents = plan.data?.priceCents ?? 0;
172
+ const yearlyPriceCents = plan.data?.yearlyPriceCents ?? null;
173
+ const monthlyEquivalent = (0, import_react3.useCallback)(
174
+ (cycle) => {
175
+ if (cycle === "YEARLY" && yearlyPriceCents) {
176
+ return Math.round(yearlyPriceCents / 12);
177
+ }
178
+ return priceCents;
179
+ },
180
+ [priceCents, yearlyPriceCents]
181
+ );
167
182
  const checkout = (0, import_react3.useCallback)(
168
183
  async (args) => {
169
184
  setOpening(true);
170
185
  setError(null);
171
186
  setPixPending(null);
172
- const method = args.method ?? "card";
173
- const cycle = args.cycle ?? "MONTHLY";
174
187
  try {
175
- if (method === "card") {
176
- const result = await subscription.checkoutCard({ cpf: args.cpf, cycle });
177
- window.location.href = result.invoiceUrl;
178
- return;
179
- }
180
- if (method === "pix-auto") {
181
- const result = await subscription.checkoutPixAuto({ cpf: args.cpf, cycle });
182
- setPixPending({
183
- method: "pix-auto",
184
- qrCodePayload: result.qrCodePayload,
185
- qrCodeBase64: result.qrCodeBase64,
186
- expiresAt: null,
187
- paid: false
188
+ if (args.method === "card") {
189
+ if (!args.card || !args.holderInfo) {
190
+ throw new Error('card and holderInfo are required when method is "card"');
191
+ }
192
+ await subscription.checkout({
193
+ method: "card",
194
+ cycle: args.cycle,
195
+ cpf: args.cpf,
196
+ card: args.card,
197
+ holderInfo: args.holderInfo,
198
+ ...args.remoteIp ? { remoteIp: args.remoteIp } : {}
188
199
  });
200
+ await subscription.refresh();
189
201
  setOpening(false);
190
202
  return;
191
203
  }
192
- if (method === "pix-once") {
193
- const result = await subscription.checkoutPixOnce({ cpf: args.cpf, cycle });
194
- setPixPending({
195
- method: "pix-once",
196
- qrCodePayload: result.qrCodePayload,
197
- qrCodeBase64: result.qrCodeBase64,
198
- expiresAt: result.expiresAt,
199
- paid: false
200
- });
201
- setOpening(false);
202
- return;
204
+ const result = await subscription.checkout({
205
+ method: "pix-auto",
206
+ cycle: args.cycle,
207
+ cpf: args.cpf
208
+ });
209
+ if (result.method !== "pix-auto") {
210
+ throw new Error(`unexpected checkout result method: ${result.method}`);
203
211
  }
212
+ setPixPending({
213
+ method: "pix-auto",
214
+ qrCodePayload: result.qrCodePayload,
215
+ qrCodeBase64: result.qrCodeBase64,
216
+ expiresAt: null,
217
+ paid: false
218
+ });
219
+ setOpening(false);
204
220
  } catch (err) {
205
221
  setError(err);
206
222
  setOpening(false);
@@ -253,7 +269,9 @@ function usePaywallState() {
253
269
  opening,
254
270
  error,
255
271
  pixPending,
256
- dismissPix
272
+ dismissPix,
273
+ availableMethods,
274
+ monthlyEquivalent
257
275
  };
258
276
  }
259
277
 
@@ -1549,9 +1567,10 @@ function InstallGate({ children }) {
1549
1567
  if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1550
1568
  if (installState.isInstalled) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1551
1569
  if (installState.variant === "desktop") {
1570
+ const showBanner = !installState.isDismissedSession && !installState.isDismissedPermanent;
1552
1571
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1553
1572
  children,
1554
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DesktopVariant, { state: installState, actions: installState })
1573
+ showBanner && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DesktopVariant, { state: installState, actions: installState })
1555
1574
  ] });
1556
1575
  }
1557
1576
  if (!shouldBlock) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
@@ -2197,33 +2216,429 @@ function DefaultResetScreen({ onNavigate }) {
2197
2216
 
2198
2217
  // src/defaults/DefaultPaywall.tsx
2199
2218
  var import_react14 = require("react");
2219
+
2220
+ // src/hooks/usePlan.ts
2221
+ var import_sdk9 = require("@hook-sdk/sdk");
2222
+ function usePlan() {
2223
+ const { plan } = (0, import_sdk9.useHook)();
2224
+ return plan;
2225
+ }
2226
+
2227
+ // src/utils/price.ts
2228
+ function formatBRL(cents) {
2229
+ if (cents === null || cents === void 0) return "";
2230
+ const reais = cents / 100;
2231
+ return new Intl.NumberFormat("pt-BR", {
2232
+ style: "currency",
2233
+ currency: "BRL"
2234
+ }).format(reais);
2235
+ }
2236
+ function monthlyFromYearly(yearlyCents) {
2237
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2238
+ return Math.round(yearlyCents / 12);
2239
+ }
2240
+ function dailyFromYearly(yearlyCents) {
2241
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2242
+ return Math.round(yearlyCents / 365);
2243
+ }
2244
+ function computeAnchorCents(baseCents, multiplier) {
2245
+ if (multiplier === null || multiplier === void 0) return null;
2246
+ if (!Number.isFinite(multiplier)) return null;
2247
+ if (multiplier <= 1) return null;
2248
+ return Math.round(baseCents * multiplier);
2249
+ }
2250
+ function discountPercent(anchorCents, realCents) {
2251
+ if (anchorCents <= realCents) return 0;
2252
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
2253
+ }
2254
+
2255
+ // src/defaults/DefaultPaywall.tsx
2200
2256
  var import_jsx_runtime24 = require("react/jsx-runtime");
2201
2257
  function DefaultPaywall() {
2202
2258
  const config = useTemplateConfig();
2203
- const { checkout, opening, error } = usePaywallState();
2259
+ const plan = usePlan();
2260
+ const {
2261
+ checkout,
2262
+ opening,
2263
+ error,
2264
+ availableMethods,
2265
+ monthlyEquivalent,
2266
+ pixPending,
2267
+ dismissPix
2268
+ } = usePaywallState();
2204
2269
  const p = config.subscription.paywall_config;
2270
+ const paywallCfg = plan.data?.paywallConfig ?? {};
2271
+ const anchorMultiplier = paywallCfg.anchorMultiplier ?? p.anchorMultiplier;
2272
+ const anchorPriceCents = paywallCfg.anchorPriceCents ?? p.anchorPriceCents;
2273
+ const [cycle, setCycle] = (0, import_react14.useState)("MONTHLY");
2274
+ const [method, setMethod] = (0, import_react14.useState)("card");
2205
2275
  const [cpf, setCpf] = (0, import_react14.useState)("");
2276
+ const [cardNumber, setCardNumber] = (0, import_react14.useState)("");
2277
+ const [cardHolderName, setCardHolderName] = (0, import_react14.useState)("");
2278
+ const [cardExpiryMonth, setCardExpiryMonth] = (0, import_react14.useState)("");
2279
+ const [cardExpiryYear, setCardExpiryYear] = (0, import_react14.useState)("");
2280
+ const [cardCcv, setCardCcv] = (0, import_react14.useState)("");
2281
+ const [holderName, setHolderName] = (0, import_react14.useState)("");
2282
+ const [holderEmail, setHolderEmail] = (0, import_react14.useState)("");
2283
+ const [holderPostalCode, setHolderPostalCode] = (0, import_react14.useState)("");
2284
+ const [holderAddressNumber, setHolderAddressNumber] = (0, import_react14.useState)("");
2285
+ const [holderPhone, setHolderPhone] = (0, import_react14.useState)("");
2286
+ const trialDays = plan.data?.trialDays ?? 0;
2287
+ const monthlyCents = plan.data?.priceCents ?? 0;
2288
+ const yearlyCents = plan.data?.yearlyPriceCents ?? null;
2289
+ const activeCents = cycle === "YEARLY" && yearlyCents ? monthlyEquivalent("YEARLY") : monthlyCents;
2290
+ const pct = (0, import_react14.useMemo)(() => {
2291
+ if (!yearlyCents || !monthlyCents) return 0;
2292
+ const derived = Math.round((1 - yearlyCents / 12 / monthlyCents) * 100);
2293
+ return Math.max(0, derived);
2294
+ }, [monthlyCents, yearlyCents]);
2295
+ const anchorBaseCents = cycle === "YEARLY" && yearlyCents ? monthlyFromYearly(yearlyCents) : monthlyCents;
2296
+ const anchorCents = computeAnchorCents(anchorBaseCents, anchorMultiplier) ?? (anchorPriceCents && anchorPriceCents > anchorBaseCents ? anchorPriceCents : null);
2297
+ const anchorDiscount = anchorCents ? discountPercent(anchorCents, activeCents) : 0;
2206
2298
  const cpfDigits = cpf.replace(/\D/g, "");
2207
- const canCheckout = cpfDigits.length === 11 && !opening;
2208
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("main", { style: { padding: 24, maxWidth: 440, margin: "0 auto", textAlign: "center" }, children: [
2299
+ const cardFieldsFilled = cardNumber.replace(/\s/g, "").length >= 13 && cardHolderName.trim().length > 0 && cardExpiryMonth.length === 2 && cardExpiryYear.length >= 2 && cardCcv.length >= 3 && holderName.trim().length > 0 && /.+@.+\..+/.test(holderEmail) && holderPostalCode.replace(/\D/g, "").length === 8 && holderAddressNumber.trim().length > 0;
2300
+ const canCheckout = cpfDigits.length === 11 && !opening && (method === "pix-auto" || cardFieldsFilled);
2301
+ const ctaLabel = (0, import_react14.useMemo)(() => {
2302
+ if (opening) return "Abrindo\u2026";
2303
+ if (trialDays > 0) return `Comece trial de ${trialDays} dias gr\xE1tis`;
2304
+ return p.cta ?? "Assinar agora";
2305
+ }, [opening, trialDays, p.cta]);
2306
+ const footer = (0, import_react14.useMemo)(() => {
2307
+ if (trialDays > 0) {
2308
+ return `Sem cobran\xE7a agora. Cobran\xE7a autom\xE1tica em ${trialDays} dias. Cancele quando quiser.`;
2309
+ }
2310
+ return "Cobran\xE7a imediata. Cancele quando quiser.";
2311
+ }, [trialDays]);
2312
+ const yearSuffix = cardExpiryYear.length === 2 ? `20${cardExpiryYear}` : cardExpiryYear;
2313
+ const phoneDigits = holderPhone.replace(/\D/g, "");
2314
+ const postalDigits = holderPostalCode.replace(/\D/g, "");
2315
+ const submit = () => {
2316
+ if (method === "card") {
2317
+ void checkout({
2318
+ cpf: cpfDigits,
2319
+ cycle,
2320
+ method: "card",
2321
+ card: {
2322
+ number: cardNumber.replace(/\s/g, ""),
2323
+ holderName: cardHolderName.trim(),
2324
+ expiryMonth: cardExpiryMonth,
2325
+ expiryYear: yearSuffix,
2326
+ ccv: cardCcv
2327
+ },
2328
+ holderInfo: {
2329
+ name: holderName.trim(),
2330
+ email: holderEmail.trim(),
2331
+ cpfCnpj: cpfDigits,
2332
+ postalCode: postalDigits,
2333
+ addressNumber: holderAddressNumber.trim(),
2334
+ ...phoneDigits ? { phone: phoneDigits } : {}
2335
+ }
2336
+ });
2337
+ return;
2338
+ }
2339
+ void checkout({ cpf: cpfDigits, cycle, method: "pix-auto" });
2340
+ };
2341
+ const inputStyle = {
2342
+ width: "100%",
2343
+ padding: 10,
2344
+ fontSize: 14,
2345
+ borderRadius: 8,
2346
+ border: "1px solid #ccc",
2347
+ boxSizing: "border-box"
2348
+ };
2349
+ const labelStyle = { display: "block", fontSize: 13, opacity: 0.75, marginBottom: 4 };
2350
+ const fieldGroup = { marginBottom: 10, textAlign: "left" };
2351
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("main", { style: { padding: 24, maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
2209
2352
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h1", { style: { marginBottom: 8 }, children: p.title }),
2210
2353
  p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: p.subtitle }),
2211
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", marginBottom: 24 }, children: p.benefits.map((b) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
2354
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2355
+ "div",
2356
+ {
2357
+ role: "group",
2358
+ "aria-label": "Per\xEDodo de cobran\xE7a",
2359
+ style: { display: "flex", gap: 8, marginBottom: 16 },
2360
+ children: [
2361
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2362
+ "button",
2363
+ {
2364
+ type: "button",
2365
+ "aria-pressed": cycle === "MONTHLY",
2366
+ onClick: () => setCycle("MONTHLY"),
2367
+ style: {
2368
+ flex: 1,
2369
+ padding: 12,
2370
+ borderRadius: 8,
2371
+ border: cycle === "MONTHLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2372
+ background: cycle === "MONTHLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2373
+ cursor: "pointer"
2374
+ },
2375
+ children: "Mensal"
2376
+ }
2377
+ ),
2378
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2379
+ "button",
2380
+ {
2381
+ type: "button",
2382
+ "aria-pressed": cycle === "YEARLY",
2383
+ onClick: () => setCycle("YEARLY"),
2384
+ disabled: !yearlyCents,
2385
+ style: {
2386
+ flex: 1,
2387
+ padding: 12,
2388
+ borderRadius: 8,
2389
+ border: cycle === "YEARLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2390
+ background: cycle === "YEARLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2391
+ cursor: yearlyCents ? "pointer" : "not-allowed",
2392
+ opacity: yearlyCents ? 1 : 0.5
2393
+ },
2394
+ children: [
2395
+ "Anual",
2396
+ pct > 0 ? ` \u2212${pct}%` : ""
2397
+ ]
2398
+ }
2399
+ )
2400
+ ]
2401
+ }
2402
+ ),
2403
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { marginBottom: 8 }, children: [
2404
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 32, fontWeight: 700, lineHeight: 1 }, children: [
2405
+ formatBRL(activeCents),
2406
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { fontSize: 16, fontWeight: 400, opacity: 0.7 }, children: "/m\xEAs" })
2407
+ ] }),
2408
+ anchorCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 14, opacity: 0.6, marginTop: 4 }, children: [
2409
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { textDecoration: "line-through" }, children: formatBRL(anchorCents) }),
2410
+ anchorDiscount > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { style: { marginLeft: 6 }, children: [
2411
+ "\u2212",
2412
+ anchorDiscount,
2413
+ "%"
2414
+ ] })
2415
+ ] }),
2416
+ cycle === "YEARLY" && yearlyCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 12, opacity: 0.6, marginTop: 4 }, children: [
2417
+ "Cobrado ",
2418
+ formatBRL(yearlyCents),
2419
+ " por ano"
2420
+ ] })
2421
+ ] }),
2422
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", margin: "24px 0" }, children: p.benefits.map((b) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
2212
2423
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
2213
2424
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { children: b })
2214
2425
  ] }, b)) }),
2215
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { textAlign: "left", marginBottom: 16 }, children: [
2216
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { style: { display: "block", fontSize: 14, opacity: 0.7, marginBottom: 4 }, children: "Seu CPF (pra emiss\xE3o de recibo)" }),
2426
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", style: { display: "flex", gap: 8, marginBottom: 16 }, children: availableMethods.map((m) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2427
+ "button",
2428
+ {
2429
+ type: "button",
2430
+ role: "tab",
2431
+ "aria-selected": method === m,
2432
+ onClick: () => setMethod(m),
2433
+ style: {
2434
+ flex: 1,
2435
+ padding: 10,
2436
+ borderRadius: 8,
2437
+ border: method === m ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2438
+ background: method === m ? "var(--hook-color-primary-soft, #eef)" : "white",
2439
+ cursor: "pointer"
2440
+ },
2441
+ children: m === "card" ? "\u{1F4B3} Cart\xE3o" : "\u{1F4F1} Pix Autom\xE1tico"
2442
+ },
2443
+ m
2444
+ )) }),
2445
+ method === "card" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2446
+ "fieldset",
2447
+ {
2448
+ "data-testid": "paywall-card-form",
2449
+ style: { border: "none", padding: 0, marginBottom: 16 },
2450
+ children: [
2451
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8 }, children: "Dados do cart\xE3o" }),
2452
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2453
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-number", style: labelStyle, children: "N\xFAmero do cart\xE3o" }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2455
+ "input",
2456
+ {
2457
+ id: "pw-card-number",
2458
+ "data-testid": "pw-card-number",
2459
+ type: "text",
2460
+ inputMode: "numeric",
2461
+ autoComplete: "cc-number",
2462
+ placeholder: "0000 0000 0000 0000",
2463
+ value: cardNumber,
2464
+ onChange: (e) => setCardNumber(e.target.value),
2465
+ style: inputStyle
2466
+ }
2467
+ )
2468
+ ] }),
2469
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2470
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-holder", style: labelStyle, children: "Nome impresso no cart\xE3o" }),
2471
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2472
+ "input",
2473
+ {
2474
+ id: "pw-card-holder",
2475
+ "data-testid": "pw-card-holder",
2476
+ type: "text",
2477
+ autoComplete: "cc-name",
2478
+ placeholder: "NOME SOBRENOME",
2479
+ value: cardHolderName,
2480
+ onChange: (e) => setCardHolderName(e.target.value),
2481
+ style: inputStyle
2482
+ }
2483
+ )
2484
+ ] }),
2485
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2486
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2487
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-m", style: labelStyle, children: "M\xEAs" }),
2488
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2489
+ "input",
2490
+ {
2491
+ id: "pw-card-exp-m",
2492
+ "data-testid": "pw-card-exp-m",
2493
+ type: "text",
2494
+ inputMode: "numeric",
2495
+ autoComplete: "cc-exp-month",
2496
+ placeholder: "MM",
2497
+ maxLength: 2,
2498
+ value: cardExpiryMonth,
2499
+ onChange: (e) => setCardExpiryMonth(e.target.value.replace(/\D/g, "")),
2500
+ style: inputStyle
2501
+ }
2502
+ )
2503
+ ] }),
2504
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2505
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-y", style: labelStyle, children: "Ano" }),
2506
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2507
+ "input",
2508
+ {
2509
+ id: "pw-card-exp-y",
2510
+ "data-testid": "pw-card-exp-y",
2511
+ type: "text",
2512
+ inputMode: "numeric",
2513
+ autoComplete: "cc-exp-year",
2514
+ placeholder: "AA",
2515
+ maxLength: 4,
2516
+ value: cardExpiryYear,
2517
+ onChange: (e) => setCardExpiryYear(e.target.value.replace(/\D/g, "")),
2518
+ style: inputStyle
2519
+ }
2520
+ )
2521
+ ] }),
2522
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2523
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-cvv", style: labelStyle, children: "CVV" }),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2525
+ "input",
2526
+ {
2527
+ id: "pw-card-cvv",
2528
+ "data-testid": "pw-card-cvv",
2529
+ type: "text",
2530
+ inputMode: "numeric",
2531
+ autoComplete: "cc-csc",
2532
+ placeholder: "123",
2533
+ maxLength: 4,
2534
+ value: cardCcv,
2535
+ onChange: (e) => setCardCcv(e.target.value.replace(/\D/g, "")),
2536
+ style: inputStyle
2537
+ }
2538
+ )
2539
+ ] })
2540
+ ] }),
2541
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8, marginTop: 8 }, children: "Dados do titular" }),
2542
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2543
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-name", style: labelStyle, children: "Nome completo" }),
2544
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2545
+ "input",
2546
+ {
2547
+ id: "pw-holder-name",
2548
+ "data-testid": "pw-holder-name",
2549
+ type: "text",
2550
+ autoComplete: "name",
2551
+ placeholder: "Nome Sobrenome",
2552
+ value: holderName,
2553
+ onChange: (e) => setHolderName(e.target.value),
2554
+ style: inputStyle
2555
+ }
2556
+ )
2557
+ ] }),
2558
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2559
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-email", style: labelStyle, children: "E-mail" }),
2560
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2561
+ "input",
2562
+ {
2563
+ id: "pw-holder-email",
2564
+ "data-testid": "pw-holder-email",
2565
+ type: "email",
2566
+ autoComplete: "email",
2567
+ placeholder: "voce@email.com",
2568
+ value: holderEmail,
2569
+ onChange: (e) => setHolderEmail(e.target.value),
2570
+ style: inputStyle
2571
+ }
2572
+ )
2573
+ ] }),
2574
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2575
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2576
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-cep", style: labelStyle, children: "CEP" }),
2577
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2578
+ "input",
2579
+ {
2580
+ id: "pw-holder-cep",
2581
+ "data-testid": "pw-holder-cep",
2582
+ type: "text",
2583
+ inputMode: "numeric",
2584
+ autoComplete: "postal-code",
2585
+ placeholder: "00000-000",
2586
+ value: holderPostalCode,
2587
+ onChange: (e) => setHolderPostalCode(e.target.value),
2588
+ style: inputStyle
2589
+ }
2590
+ )
2591
+ ] }),
2592
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2593
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-addr-n", style: labelStyle, children: "N\xFAmero" }),
2594
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2595
+ "input",
2596
+ {
2597
+ id: "pw-holder-addr-n",
2598
+ "data-testid": "pw-holder-addr-n",
2599
+ type: "text",
2600
+ inputMode: "numeric",
2601
+ placeholder: "123",
2602
+ value: holderAddressNumber,
2603
+ onChange: (e) => setHolderAddressNumber(e.target.value),
2604
+ style: inputStyle
2605
+ }
2606
+ )
2607
+ ] })
2608
+ ] }),
2609
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2610
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-phone", style: labelStyle, children: "Telefone (opcional)" }),
2611
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2612
+ "input",
2613
+ {
2614
+ id: "pw-holder-phone",
2615
+ "data-testid": "pw-holder-phone",
2616
+ type: "tel",
2617
+ inputMode: "tel",
2618
+ autoComplete: "tel",
2619
+ placeholder: "(11) 99999-9999",
2620
+ value: holderPhone,
2621
+ onChange: (e) => setHolderPhone(e.target.value),
2622
+ style: inputStyle
2623
+ }
2624
+ )
2625
+ ] })
2626
+ ]
2627
+ }
2628
+ ),
2629
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2630
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-cpf", style: labelStyle, children: "Seu CPF" }),
2217
2631
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2218
2632
  "input",
2219
2633
  {
2634
+ id: "pw-cpf",
2220
2635
  "data-testid": "paywall-cpf",
2221
2636
  type: "text",
2222
2637
  inputMode: "numeric",
2223
2638
  placeholder: "000.000.000-00",
2224
2639
  value: cpf,
2225
2640
  onChange: (e) => setCpf(e.target.value),
2226
- style: { width: "100%", padding: 10, fontSize: 14, borderRadius: 8, border: "1px solid #ccc" }
2641
+ style: inputStyle
2227
2642
  }
2228
2643
  )
2229
2644
  ] }),
@@ -2233,7 +2648,7 @@ function DefaultPaywall() {
2233
2648
  {
2234
2649
  "data-testid": "paywall-cta",
2235
2650
  type: "button",
2236
- onClick: () => void checkout({ cpf: cpfDigits }),
2651
+ onClick: submit,
2237
2652
  disabled: !canCheckout,
2238
2653
  style: {
2239
2654
  width: "100%",
@@ -2244,13 +2659,93 @@ function DefaultPaywall() {
2244
2659
  borderRadius: 8,
2245
2660
  opacity: canCheckout ? 1 : 0.5,
2246
2661
  fontSize: 16,
2247
- fontWeight: 600
2662
+ fontWeight: 600,
2663
+ cursor: canCheckout ? "pointer" : "not-allowed"
2248
2664
  },
2249
- children: opening ? "Abrindo..." : p.cta
2665
+ children: ctaLabel
2250
2666
  }
2251
2667
  ),
2252
- p.priceHint && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.6, marginTop: 12 }, children: p.priceHint }),
2253
- p.footerNote && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.5, marginTop: 16, fontSize: 12 }, children: p.footerNote })
2668
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.55, marginTop: 16, fontSize: 12 }, children: footer }),
2669
+ p.priceHint && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.6, marginTop: 8, fontSize: 12 }, children: p.priceHint }),
2670
+ p.footerNote && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.5, marginTop: 8, fontSize: 12 }, children: p.footerNote }),
2671
+ pixPending && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2672
+ "div",
2673
+ {
2674
+ "data-testid": "paywall-pix-modal",
2675
+ role: "dialog",
2676
+ "aria-label": "Pagamento Pix pendente",
2677
+ style: {
2678
+ position: "fixed",
2679
+ inset: 0,
2680
+ background: "rgba(0,0,0,0.6)",
2681
+ display: "flex",
2682
+ alignItems: "center",
2683
+ justifyContent: "center",
2684
+ padding: 24,
2685
+ zIndex: 1e3
2686
+ },
2687
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: {
2688
+ background: "#fff",
2689
+ borderRadius: 12,
2690
+ padding: 24,
2691
+ maxWidth: 360,
2692
+ width: "100%",
2693
+ textAlign: "center"
2694
+ }, children: [
2695
+ pixPending.paid ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2696
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0 }, children: "Pagamento confirmado!" }),
2697
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.7 }, children: "Liberando acesso\u2026" })
2698
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2699
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0, fontSize: 18 }, children: "Pague com Pix Autom\xE1tico" }),
2700
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { fontSize: 13, opacity: 0.75 }, children: "Escaneie o QR Code no app do seu banco pra autorizar o d\xE9bito recorrente." }),
2701
+ pixPending.qrCodeBase64 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2702
+ "img",
2703
+ {
2704
+ "data-testid": "pix-qr-image",
2705
+ alt: "QR Code Pix",
2706
+ src: `data:image/png;base64,${pixPending.qrCodeBase64}`,
2707
+ style: { width: "100%", maxWidth: 240, margin: "12px auto", display: "block" }
2708
+ }
2709
+ ),
2710
+ pixPending.qrCodePayload && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2711
+ "textarea",
2712
+ {
2713
+ "data-testid": "pix-qr-payload",
2714
+ readOnly: true,
2715
+ value: pixPending.qrCodePayload,
2716
+ style: {
2717
+ width: "100%",
2718
+ minHeight: 72,
2719
+ padding: 8,
2720
+ fontSize: 11,
2721
+ fontFamily: "monospace",
2722
+ borderRadius: 6,
2723
+ border: "1px solid #ccc",
2724
+ resize: "none"
2725
+ }
2726
+ }
2727
+ ),
2728
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { fontSize: 11, opacity: 0.55, marginTop: 12 }, children: "Aguardando confirma\xE7\xE3o do banco\u2026 Pode levar alguns segundos." })
2729
+ ] }),
2730
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2731
+ "button",
2732
+ {
2733
+ type: "button",
2734
+ onClick: dismissPix,
2735
+ style: {
2736
+ marginTop: 16,
2737
+ padding: "8px 16px",
2738
+ border: "1px solid #ccc",
2739
+ borderRadius: 6,
2740
+ background: "white",
2741
+ cursor: "pointer"
2742
+ },
2743
+ children: "Fechar"
2744
+ }
2745
+ )
2746
+ ] })
2747
+ }
2748
+ )
2254
2749
  ] });
2255
2750
  }
2256
2751
 
@@ -2258,7 +2753,7 @@ function DefaultPaywall() {
2258
2753
  var import_jsx_runtime25 = require("react/jsx-runtime");
2259
2754
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2260
2755
  function PaymentReturnHandler({ children }) {
2261
- const { subscription } = (0, import_sdk9.useHook)();
2756
+ const { subscription } = (0, import_sdk10.useHook)();
2262
2757
  const subRef = (0, import_react15.useRef)(subscription);
2263
2758
  subRef.current = subscription;
2264
2759
  const runIdRef = (0, import_react15.useRef)(0);
@@ -2367,7 +2862,7 @@ function AppRoot({
2367
2862
 
2368
2863
  // src/hooks/usePush.ts
2369
2864
  var import_react16 = require("react");
2370
- var import_sdk10 = require("@hook-sdk/sdk");
2865
+ var import_sdk11 = require("@hook-sdk/sdk");
2371
2866
  function detectIosNeedsInstall() {
2372
2867
  if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2373
2868
  const ua = navigator.userAgent || "";
@@ -2393,7 +2888,7 @@ function deriveState(push) {
2393
2888
  return { kind: "prompt" };
2394
2889
  }
2395
2890
  function usePush() {
2396
- const { push } = (0, import_sdk10.useHook)();
2891
+ const { push } = (0, import_sdk11.useHook)();
2397
2892
  const [state, setState] = (0, import_react16.useState)(() => deriveState(push));
2398
2893
  (0, import_react16.useEffect)(() => {
2399
2894
  setState(deriveState(push));
@@ -2475,41 +2970,6 @@ function EmptyState({ title, description, action }) {
2475
2970
  ] });
2476
2971
  }
2477
2972
 
2478
- // src/hooks/usePlan.ts
2479
- var import_sdk11 = require("@hook-sdk/sdk");
2480
- function usePlan() {
2481
- const { plan } = (0, import_sdk11.useHook)();
2482
- return plan;
2483
- }
2484
-
2485
- // src/utils/price.ts
2486
- function formatBRL(cents) {
2487
- if (cents === null || cents === void 0) return "";
2488
- const reais = cents / 100;
2489
- return new Intl.NumberFormat("pt-BR", {
2490
- style: "currency",
2491
- currency: "BRL"
2492
- }).format(reais);
2493
- }
2494
- function monthlyFromYearly(yearlyCents) {
2495
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2496
- return Math.round(yearlyCents / 12);
2497
- }
2498
- function dailyFromYearly(yearlyCents) {
2499
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2500
- return Math.round(yearlyCents / 365);
2501
- }
2502
- function computeAnchorCents(baseCents, multiplier) {
2503
- if (multiplier === null || multiplier === void 0) return null;
2504
- if (!Number.isFinite(multiplier)) return null;
2505
- if (multiplier <= 1) return null;
2506
- return Math.round(baseCents * multiplier);
2507
- }
2508
- function discountPercent(anchorCents, realCents) {
2509
- if (anchorCents <= realCents) return 0;
2510
- return Math.floor((anchorCents - realCents) / anchorCents * 100);
2511
- }
2512
-
2513
2973
  // src/hooks/useAuthPrimitives.ts
2514
2974
  var import_react17 = require("react");
2515
2975
  var import_sdk12 = require("@hook-sdk/sdk");