@hook-sdk/template 0.28.4 → 0.28.6

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
@@ -2892,7 +2892,7 @@ function EmptyState({ title, description, action }) {
2892
2892
  }
2893
2893
 
2894
2894
  // src/defaults/CheckoutPageDefault.tsx
2895
- var import_react19 = require("react");
2895
+ var import_react23 = require("react");
2896
2896
  var import_react_router_dom3 = require("react-router-dom");
2897
2897
 
2898
2898
  // src/hooks/useCheckoutForm.ts
@@ -3138,1861 +3138,1862 @@ function usePlan() {
3138
3138
  return plan;
3139
3139
  }
3140
3140
 
3141
- // src/defaults/CheckoutPageDefault.tsx
3142
- var import_jsx_runtime28 = require("react/jsx-runtime");
3143
- var INTENT_KEY = "hook:paywall:intent";
3144
- var PIX_PAYLOAD_KEY = "hook:paywall:pix-pending";
3145
- function readIntent() {
3146
- if (typeof window === "undefined") return {};
3147
- try {
3148
- const raw = sessionStorage.getItem(INTENT_KEY);
3149
- if (!raw) return {};
3150
- return JSON.parse(raw);
3151
- } catch {
3152
- return {};
3153
- }
3154
- }
3155
- function formatBrl(cents) {
3156
- 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);
3157
3153
  }
3158
- function formatCardNumber(v) {
3159
- const digits = v.replace(/\D/g, "").slice(0, 16);
3160
- 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);
3161
3157
  }
3162
- function formatExpiryMmAa(v) {
3163
- const d = v.replace(/\D/g, "").slice(0, 4);
3164
- if (d.length < 3) return d;
3165
- 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);
3166
3161
  }
3167
- function parseExpiryMmAa(v) {
3168
- const d = v.replace(/\D/g, "");
3169
- 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);
3170
3167
  }
3171
- function formatCpf(v) {
3172
- const d = v.replace(/\D/g, "").slice(0, 11);
3173
- if (d.length <= 3) return d;
3174
- if (d.length <= 6) return d.slice(0, 3) + "." + d.slice(3);
3175
- if (d.length <= 9) return d.slice(0, 3) + "." + d.slice(3, 6) + "." + d.slice(6);
3176
- 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);
3177
3171
  }
3178
- function detectCardBrand(num) {
3179
- const n = num.replace(/\s/g, "");
3180
- if (/^4/.test(n)) return "VISA";
3181
- if (/^(5[1-5]|2[2-7])/.test(n)) return "MASTER";
3182
- if (/^3[47]/.test(n)) return "AMEX";
3183
- if (/^(4011|4312|4389|4514|6011|6362|6363)/.test(n)) return "ELO";
3184
- if (/^(606282|3841)/.test(n)) return "HIPER";
3185
- 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 });
3186
3180
  }
3187
- function CheckoutPageDefault() {
3188
- const navigate = (0, import_react_router_dom3.useNavigate)();
3189
- const plan = usePlan();
3190
- const intent = (0, import_react19.useMemo)(readIntent, []);
3191
- const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3192
- const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3193
- const form = useCheckoutForm({ defaultMethod, defaultCycle });
3194
- const [expiryMmAa, setExpiryMmAa] = (0, import_react19.useState)("");
3195
- (0, import_react19.useEffect)(() => {
3196
- const { month, year } = parseExpiryMmAa(expiryMmAa);
3197
- if (month !== form.card.expiryMonth || year !== form.card.expiryYear) {
3198
- form.setCard({ expiryMonth: month, expiryYear: year });
3199
- }
3200
- }, [expiryMmAa]);
3201
- (0, import_react19.useEffect)(() => {
3202
- if (form.emailTaken && form.loginUrl) {
3203
- const t = setTimeout(() => navigate(form.loginUrl), 1200);
3204
- return () => clearTimeout(t);
3205
- }
3206
- }, [form.emailTaken, form.loginUrl, navigate]);
3207
- const planInfo = plan.data ? {
3208
- priceCents: plan.data.priceCents,
3209
- yearlyPriceCents: plan.data.yearlyPriceCents,
3210
- trialDays: plan.data.trialDays
3211
- } : null;
3212
- const annual = form.cycle === "YEARLY";
3213
- const cyclePrice = (0, import_react19.useMemo)(() => {
3214
- if (!planInfo) return null;
3215
- return annual ? planInfo.yearlyPriceCents ?? planInfo.priceCents * 12 : planInfo.priceCents;
3216
- }, [planInfo, annual]);
3217
- const monthlyText = (0, import_react19.useMemo)(() => {
3218
- if (!planInfo) return "";
3219
- const monthly = annual && planInfo.yearlyPriceCents ? Math.round(planInfo.yearlyPriceCents / 12) : planInfo.priceCents;
3220
- return formatBrl(monthly);
3221
- }, [planInfo, annual]);
3222
- const todayCents = (0, import_react19.useMemo)(() => {
3223
- if (form.method === "pix-auto") return cyclePrice ?? 0;
3224
- const trialDays2 = planInfo?.trialDays ?? 0;
3225
- if (trialDays2 > 0) return 0;
3226
- return cyclePrice ?? 0;
3227
- }, [form.method, cyclePrice, planInfo]);
3228
- const todayAmount = formatBrl(todayCents);
3229
- const cyclePriceText = cyclePrice !== null ? formatBrl(cyclePrice) : "";
3230
- const annualSavingsCents = (0, import_react19.useMemo)(() => {
3231
- if (!planInfo || !planInfo.yearlyPriceCents) return 0;
3232
- return planInfo.priceCents * 12 - planInfo.yearlyPriceCents;
3233
- }, [planInfo]);
3234
- const trialDays = planInfo?.trialDays ?? 7;
3235
- const cardBrand = detectCardBrand(form.card.number);
3236
- async function onSubmit(e) {
3237
- e.preventDefault();
3238
- const result = await form.submit();
3239
- if (!result) return;
3240
- if (form.method === "pix-auto" && result.pix_qr_payload) {
3241
- try {
3242
- sessionStorage.setItem(
3243
- PIX_PAYLOAD_KEY,
3244
- JSON.stringify({
3245
- payload: result.pix_qr_payload,
3246
- base64: result.pix_qr_base64 ?? null,
3247
- subscriptionId: result.subscription_id,
3248
- pixAuthorizationId: result.pix_authorization_id ?? null
3249
- })
3250
- );
3251
- } catch {
3252
- }
3253
- navigate(result.redirect.replace(/^.*\/app\/[^/]+/, ""));
3254
- return;
3255
- }
3256
- 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>");
3257
3188
  }
3258
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "flex-1 flex flex-col bg-background min-h-0", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("form", { onSubmit, className: "flex-1 overflow-y-auto", children: [
3259
- form.emailTaken ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "px-5 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { role: "alert", className: "rounded-2xl bg-destructive/10 p-4 text-sm text-destructive border border-destructive/20", children: [
3260
- "Esse e-mail j\xE1 tem conta nesse app.",
3261
- " ",
3262
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("a", { href: form.loginUrl ?? "/signin", className: "underline font-semibold", children: "Entrar agora" })
3263
- ] }) }) : null,
3264
- form.error ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "px-5 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime28.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,
3265
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "px-5 pt-4", children: form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "rounded-2xl bg-card border-[1.5px] border-foreground p-3.5", children: [
3266
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center gap-2 mb-2", children: [
3267
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ShieldIcon, { className: "w-4 h-4" }),
3268
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-sm font-bold", children: "Voc\xEA N\xC3O ser\xE1 cobrada hoje" })
3269
- ] }),
3270
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex justify-between items-baseline text-sm text-muted-foreground", children: [
3271
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { children: "R$ 0,00 agora" }),
3272
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "opacity-50", children: "\xB7" }),
3273
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
3274
- monthlyText,
3275
- "/m\xEAs ap\xF3s ",
3276
- trialDays,
3277
- " dias"
3278
- ] })
3279
- ] }),
3280
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "mt-2.5 text-[11px] text-muted-foreground flex items-center gap-1.5", children: [
3281
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(BellIcon, { className: "w-2.5 h-2.5" }),
3282
- "Avisamos por email 2 dias antes da primeira cobran\xE7a"
3283
- ] })
3284
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "rounded-2xl p-3.5 bg-emerald-50 border-[1.5px] border-emerald-600/60", children: [
3285
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex items-center gap-2 mb-2 text-emerald-900", children: [
3286
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ShieldIcon, { className: "w-4 h-4" }),
3287
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-sm font-bold", children: [
3288
- "Garantia incondicional de ",
3289
- trialDays,
3290
- " dias"
3291
- ] })
3292
- ] }),
3293
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-sm text-emerald-900 leading-snug", children: [
3294
- "Voc\xEA paga ",
3295
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("b", { children: todayAmount }),
3296
- " agora via Pix.",
3297
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("br", {}),
3298
- "N\xE3o gostou em ",
3299
- trialDays,
3300
- " dias? Devolvemos ",
3301
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("b", { children: "100%" }),
3302
- " sem perguntas \u2014 direto pelo app."
3303
- ] })
3304
- ] }) }),
3305
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { className: "px-5 pt-5", children: [
3306
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { className: "font-display text-2xl mb-3.5 leading-tight text-foreground", children: "Quase l\xE1." }),
3307
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "Email" }),
3308
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3309
- FieldInput,
3310
- {
3311
- type: "email",
3312
- inputMode: "email",
3313
- autoComplete: "email",
3314
- autoCapitalize: "none",
3315
- autoCorrect: "off",
3316
- spellCheck: false,
3317
- placeholder: "seu@email.com",
3318
- value: form.email,
3319
- onChange: form.setEmail,
3320
- onBlur: form.markEmailTouched,
3321
- error: form.emailError,
3322
- valid: form.emailStatus === "available"
3323
- }
3324
- ),
3325
- !form.emailError && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" }),
3326
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "h-3" }),
3327
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "Nome completo" }),
3328
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3329
- FieldInput,
3330
- {
3331
- type: "text",
3332
- autoComplete: "name",
3333
- placeholder: "como est\xE1 no documento",
3334
- value: form.name,
3335
- onChange: form.setName,
3336
- onBlur: form.markNameTouched,
3337
- error: form.nameError,
3338
- valid: !!form.name && !form.nameError
3339
- }
3340
- ),
3341
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "h-3" }),
3342
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "CPF" }),
3343
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3344
- FieldInput,
3345
- {
3346
- type: "text",
3347
- inputMode: "numeric",
3348
- placeholder: "000.000.000-00",
3349
- value: form.cpf,
3350
- onChange: (v) => form.setCpf(formatCpf(v)),
3351
- onBlur: form.markCpfTouched,
3352
- error: form.cpfError,
3353
- valid: !!form.cpf && !form.cpfError
3354
- }
3355
- ),
3356
- form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3357
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "h-3" }),
3358
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "Telefone" }),
3359
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3360
- FieldInput,
3361
- {
3362
- type: "tel",
3363
- inputMode: "tel",
3364
- autoComplete: "tel",
3365
- placeholder: "(11) 99999-9999",
3366
- value: form.phone,
3367
- onChange: form.setPhone,
3368
- onBlur: form.markPhoneTouched,
3369
- error: form.phoneError,
3370
- valid: !!form.phone && !form.phoneError
3371
- }
3372
- ),
3373
- !form.phoneError && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
3374
- ] }) : null
3375
- ] }),
3376
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { className: "px-5 pt-5", children: [
3377
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "Forma de pagamento" }),
3378
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { role: "tablist", className: "flex gap-1.5 bg-muted p-1 rounded-xl", children: [
3379
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3380
- TabButton,
3381
- {
3382
- active: form.method === "card",
3383
- onClick: () => form.setMethod("card"),
3384
- icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(CardIcon, { className: "w-3.5 h-3.5" }),
3385
- label: "Cart\xE3o",
3386
- subtitle: trialDays > 0 ? `${trialDays} dias gr\xE1tis` : "pague hoje",
3387
- subtitleActiveClass: "text-emerald-700"
3388
- }
3389
- ),
3390
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3391
- TabButton,
3392
- {
3393
- active: form.method === "pix-auto",
3394
- onClick: () => form.setMethod("pix-auto"),
3395
- icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(PixIcon, { className: "w-3.5 h-3.5" }),
3396
- label: "Pix",
3397
- subtitle: `pague hoje \xB7 garantia ${trialDays}d`,
3398
- subtitleActiveClass: "text-foreground/70"
3399
- }
3400
- )
3401
- ] })
3402
- ] }),
3403
- form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { className: "px-5 pt-3.5", children: [
3404
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "N\xFAmero do cart\xE3o" }),
3405
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "relative", children: [
3406
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3407
- FieldInput,
3408
- {
3409
- type: "text",
3410
- inputMode: "numeric",
3411
- autoComplete: "cc-number",
3412
- placeholder: "0000 0000 0000 0000",
3413
- value: form.card.number,
3414
- onChange: (v) => form.setCard({ number: formatCardNumber(v) }),
3415
- style: cardBrand ? { paddingRight: "4.5rem" } : void 0
3416
- }
3417
- ),
3418
- cardBrand && /* @__PURE__ */ (0, import_jsx_runtime28.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 })
3419
- ] }),
3420
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "h-3" }),
3421
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex gap-2.5", children: [
3422
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex-1", children: [
3423
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "Validade" }),
3424
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3425
- FieldInput,
3426
- {
3427
- type: "text",
3428
- inputMode: "numeric",
3429
- autoComplete: "cc-exp",
3430
- placeholder: "MM/AA",
3431
- value: expiryMmAa,
3432
- onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v))
3433
- }
3434
- )
3435
- ] }),
3436
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex-1", children: [
3437
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "CVV" }),
3438
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3439
- FieldInput,
3440
- {
3441
- type: "text",
3442
- inputMode: "numeric",
3443
- autoComplete: "cc-csc",
3444
- placeholder: "3 d\xEDgitos",
3445
- value: form.card.ccv,
3446
- onChange: (v) => form.setCard({ ccv: v.replace(/\D/g, "").slice(0, 4) })
3447
- }
3448
- )
3449
- ] })
3450
- ] }),
3451
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "h-3" }),
3452
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(FieldLabel, { children: "Nome no cart\xE3o" }),
3453
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3454
- FieldInput,
3455
- {
3456
- type: "text",
3457
- autoComplete: "cc-name",
3458
- placeholder: "como est\xE1 no cart\xE3o",
3459
- value: form.card.holderName,
3460
- onChange: (v) => form.setCard({ holderName: v })
3461
- }
3462
- )
3463
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("section", { className: "px-5 pt-3.5", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "rounded-2xl bg-card border border-border p-3.5 flex gap-3.5 items-center", children: [
3464
- /* @__PURE__ */ (0, import_jsx_runtime28.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_runtime28.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_runtime28.jsx)(PixIcon, { className: "w-3.5 h-3.5 text-foreground" }) }) }),
3465
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex-1", children: [
3466
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-xs font-bold uppercase tracking-wider text-muted-foreground", children: "pagamento em segundos" }),
3467
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-sm text-foreground mt-1 leading-snug", children: [
3468
- "Geramos seu ",
3469
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("b", { children: "QR Pix" }),
3470
- " no pr\xF3ximo passo. Pague pelo app do banco e seu acesso libera ",
3471
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("b", { children: "imediatamente" }),
3472
- "."
3473
- ] })
3474
- ] })
3475
- ] }) }),
3476
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("section", { className: "px-5 pt-5", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "bg-muted rounded-2xl p-4", children: [
3477
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex justify-between mb-2.5", children: [
3478
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { children: [
3479
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-sm font-semibold text-foreground", children: annual ? "Plano Anual" : "Plano Mensal" }),
3480
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-[11px] text-muted-foreground", children: "Coach" })
3481
- ] }),
3482
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-right", children: [
3483
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-sm font-bold text-foreground", children: [
3484
- cyclePriceText,
3485
- "/",
3486
- annual ? "ano" : "m\xEAs"
3487
- ] }),
3488
- annual && annualSavingsCents > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-[11px] text-emerald-700 font-semibold", children: [
3489
- "economia ",
3490
- formatBrl(annualSavingsCents)
3491
- ] })
3492
- ] })
3493
- ] }),
3494
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "h-px bg-border my-3" }),
3495
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex justify-between items-baseline", children: [
3496
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { children: [
3497
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-sm font-bold text-foreground", children: "Voc\xEA paga hoje" }),
3498
- form.method === "card" && trialDays > 0 && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-[11px] text-muted-foreground mt-0.5", children: [
3499
- "cobran\xE7a inicia no dia ",
3500
- trialDays
3501
- ] }),
3502
- form.method === "pix-auto" && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-[11px] text-emerald-700 mt-0.5 font-semibold", children: [
3503
- "reembolso garantido at\xE9 o dia ",
3504
- trialDays
3505
- ] })
3506
- ] }),
3507
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-2xl font-bold font-display tracking-tight text-foreground", children: todayAmount })
3508
- ] })
3509
- ] }) }),
3510
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "h-4" }),
3511
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "sticky bottom-0 px-5 pt-3.5 pb-6 bg-gradient-to-b from-transparent to-background", children: [
3512
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3513
- "button",
3514
- {
3515
- type: "submit",
3516
- disabled: !form.canSubmit,
3517
- 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",
3518
- children: form.submitting ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3519
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Spinner2, {}),
3520
- " ",
3521
- form.method === "pix-auto" ? "Gerando QR\u2026" : "Confirmando\u2026"
3522
- ] }) : form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3523
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(LockIcon, { className: "w-3.5 h-3.5" }),
3524
- " Confirmar e come\xE7ar gr\xE1tis"
3525
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3526
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(PixIcon, { className: "w-3.5 h-3.5" }),
3527
- " Gerar QR \xB7 pagar ",
3528
- todayAmount
3529
- ] })
3530
- }
3531
- ),
3532
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "text-center mt-2.5 text-xs text-muted-foreground", children: [
3533
- "Ao continuar, voc\xEA concorda com nossos ",
3534
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("u", { children: "Termos" }),
3535
- ". Pagamento seguro Asaas."
3536
- ] }),
3537
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "mt-3 flex items-center justify-center gap-3.5 text-[11px] text-muted-foreground", children: [
3538
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
3539
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(LockIcon, { className: "w-2.5 h-2.5 opacity-60" }),
3540
- " SSL 256-bit"
3541
- ] }),
3542
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "w-px h-2.5 bg-border" }),
3543
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { children: "Pagamento via Asaas" }),
3544
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "w-px h-2.5 bg-border" }),
3545
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { children: [
3546
- "Garantia ",
3547
- trialDays,
3548
- " dias"
3549
- ] })
3550
- ] })
3551
- ] })
3552
- ] }) });
3553
- }
3554
- function FieldLabel({ children }) {
3555
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-1.5", children });
3189
+ return ctx;
3556
3190
  }
3557
- function FieldInput(props) {
3558
- const baseClass = "w-full px-4 rounded-xl bg-card text-base text-foreground outline-none border-[1.5px] transition-colors";
3559
- const stateClass = props.error ? "border-destructive focus:border-destructive" : props.valid ? "border-emerald-600 focus:border-emerald-700" : "border-border focus:border-foreground";
3560
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_jsx_runtime28.Fragment, { children: [
3561
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3562
- "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",
3563
3207
  {
3564
- type: props.type ?? "text",
3565
- inputMode: props.inputMode,
3566
- autoComplete: props.autoComplete,
3567
- autoCapitalize: props.autoCapitalize,
3568
- autoCorrect: props.autoCorrect,
3569
- spellCheck: props.spellCheck,
3570
- placeholder: props.placeholder,
3571
- value: props.value,
3572
- onChange: (e) => props.onChange(e.target.value),
3573
- onBlur: props.onBlur,
3574
- style: { height: "52px", ...props.style },
3575
- className: `${baseClass} ${stateClass}`
3576
- }
3577
- ),
3578
- props.error ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "mt-1.5 text-xs text-destructive font-medium", children: props.error }) : null
3579
- ] });
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
+ }) });
3580
3220
  }
3581
- function FieldHint({ children }) {
3582
- return /* @__PURE__ */ (0, import_jsx_runtime28.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)) });
3583
3229
  }
3584
- function TabButton({ active, onClick, icon, label, subtitle, subtitleActiveClass }) {
3585
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
3586
- "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",
3587
3269
  {
3588
- type: "button",
3589
- role: "tab",
3590
- "aria-selected": active,
3591
- onClick,
3592
- 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"}`,
3593
- style: { minHeight: 56 },
3594
- children: [
3595
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
3596
- icon,
3597
- " ",
3598
- label
3599
- ] }),
3600
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: `text-[10px] font-medium ${active ? subtitleActiveClass : "text-muted-foreground/70"}`, children: subtitle })
3601
- ]
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
+ })
3602
3301
  }
3603
3302
  );
3604
3303
  }
3605
- function CardIcon({ className }) {
3606
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinejoin: "round", "aria-hidden": "true", children: [
3607
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("rect", { x: "2.5", y: "5.5", width: "19", height: "13", rx: "2.5" }),
3608
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { d: "M2.5 10h19" })
3609
- ] });
3610
- }
3611
- function PixIcon({ className }) {
3612
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { d: "M12 2L2 12l10 10 10-10L12 2zm0 4.83L17.17 12 12 17.17 6.83 12 12 6.83z" }) });
3613
- }
3614
- function LockIcon({ className }) {
3615
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3616
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2.5" }),
3617
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { d: "M7.5 10V7a4.5 4.5 0 119 0v3" })
3618
- ] });
3619
- }
3620
- function ShieldIcon({ className }) {
3621
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3622
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { d: "M12 2.5l8 3v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10v-6l8-3z" }),
3623
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { d: "M9 12l2 2 4-4" })
3624
- ] });
3625
- }
3626
- function BellIcon({ className }) {
3627
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
3628
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { d: "M6 17V11a6 6 0 1112 0v6l1.5 2H4.5L6 17z" }),
3629
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("path", { d: "M10 21a2 2 0 004 0" })
3630
- ] });
3631
- }
3632
- function Spinner2() {
3633
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
3634
- "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,
3635
3316
  {
3636
- className: "w-4 h-4 rounded-full border-2 border-white/40 border-t-white",
3637
- style: { animation: "spin 0.7s linear infinite" }
3317
+ copy,
3318
+ themeClasses,
3319
+ slots,
3320
+ onBeforeCheckout
3638
3321
  }
3639
- );
3640
- }
3641
-
3642
- // src/defaults/PixWaitingPageDefault.tsx
3643
- var import_react20 = require("react");
3644
- var import_react_router_dom4 = require("react-router-dom");
3645
- var import_sdk15 = require("@hook-sdk/sdk");
3646
- var import_jsx_runtime29 = require("react/jsx-runtime");
3647
- var PIX_PAYLOAD_KEY2 = "hook:paywall:pix-pending";
3648
- var TIMEOUT_MS = 30 * 60 * 1e3;
3649
- function readPixPayload() {
3650
- if (typeof window === "undefined") return null;
3651
- try {
3652
- const raw = sessionStorage.getItem(PIX_PAYLOAD_KEY2);
3653
- if (!raw) return null;
3654
- return JSON.parse(raw);
3655
- } catch {
3656
- return null;
3657
- }
3658
- }
3659
- function clearPixPayload() {
3660
- if (typeof window === "undefined") return;
3661
- try {
3662
- sessionStorage.removeItem(PIX_PAYLOAD_KEY2);
3663
- } catch {
3664
- }
3322
+ ) });
3665
3323
  }
3666
- function PixWaitingPageDefault() {
3667
- const navigate = (0, import_react_router_dom4.useNavigate)();
3668
- const { subscription } = (0, import_sdk15.useHook)();
3669
- const payload = (0, import_react20.useMemo)(readPixPayload, []);
3670
- const [copied, setCopied] = (0, import_react20.useState)(false);
3671
- const [timedOut, setTimedOut] = (0, import_react20.useState)(false);
3672
- (0, import_react20.useEffect)(() => {
3673
- if (!payload) navigate("/paywall/checkout", { replace: true });
3674
- }, [payload, navigate]);
3675
- (0, import_react20.useEffect)(() => {
3676
- const t = setTimeout(() => setTimedOut(true), TIMEOUT_MS);
3677
- return () => clearTimeout(t);
3678
- }, []);
3679
- const hasAccess = subscription.hasAccess;
3680
- (0, import_react20.useEffect)(() => {
3681
- if (hasAccess) {
3682
- clearPixPayload();
3683
- navigate("/", { replace: true });
3684
- }
3685
- }, [hasAccess, navigate]);
3686
- async function copyPayload() {
3687
- if (!payload?.payload) return;
3688
- try {
3689
- await navigator.clipboard.writeText(payload.payload);
3690
- setCopied(true);
3691
- setTimeout(() => setCopied(false), 2e3);
3692
- } catch {
3693
- }
3694
- }
3695
- if (!payload) return null;
3696
- if (timedOut) {
3697
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-10 text-center bg-background space-y-4", children: [
3698
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("h1", { className: "font-display text-2xl text-foreground", children: "PIX expirado" }),
3699
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-sm text-muted-foreground", children: "O tempo pra pagar acabou. Gere um novo PIX." }),
3700
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3701
- "button",
3702
- {
3703
- onClick: () => {
3704
- clearPixPayload();
3705
- navigate("/paywall/checkout", { replace: true });
3706
- },
3707
- className: "rounded-xl bg-primary px-6 py-3 text-base font-semibold text-primary-foreground",
3708
- children: "Tentar novamente"
3709
- }
3710
- )
3711
- ] });
3712
- }
3713
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex-1 flex flex-col items-center px-6 py-8 bg-background space-y-6", children: [
3714
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("header", { className: "text-center space-y-2", children: [
3715
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("h1", { className: "font-display text-2xl text-foreground", children: "Pague o PIX" }),
3716
- /* @__PURE__ */ (0, import_jsx_runtime29.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." })
3717
- ] }),
3718
- payload.base64 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3719
- "img",
3720
- {
3721
- src: `data:image/png;base64,${payload.base64}`,
3722
- alt: "QR Code PIX",
3723
- className: "w-64 h-64 rounded-2xl border border-border bg-card p-2"
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
+ });
3724
3342
  }
3725
- ) : /* @__PURE__ */ (0, import_jsx_runtime29.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" }),
3726
- payload.payload ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
3727
- "button",
3728
- {
3729
- onClick: copyPayload,
3730
- className: "w-full max-w-xs rounded-xl border border-border bg-card px-4 py-3 text-sm font-medium text-foreground",
3731
- children: copied ? "\u2713 Copiado!" : "Copiar c\xF3digo PIX"
3343
+ if (s.trialDaysCard > 0) {
3344
+ return interp(copy.card.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
3732
3345
  }
3733
- ) : null,
3734
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
3735
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "inline-block w-2 h-2 rounded-full bg-primary animate-pulse" }),
3736
- "Aguardando pagamento\u2026"
3737
- ] }),
3738
- /* @__PURE__ */ (0, import_jsx_runtime29.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." })
3739
- ] });
3740
- }
3741
-
3742
- // src/hooks/useLoginForm.ts
3743
- var import_react21 = require("react");
3744
- var import_sdk16 = require("@hook-sdk/sdk");
3745
- var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3746
- var MIN_PASSWORD = 8;
3747
- function useLoginForm() {
3748
- const { auth } = (0, import_sdk16.useHook)();
3749
- const [email, setEmail] = (0, import_react21.useState)("");
3750
- const [password, setPassword] = (0, import_react21.useState)("");
3751
- const [submitting, setSubmitting] = (0, import_react21.useState)(false);
3752
- const [error, setError] = (0, import_react21.useState)(null);
3753
- const [touchedEmail, setTouchedEmail] = (0, import_react21.useState)(false);
3754
- const [touchedPassword, setTouchedPassword] = (0, import_react21.useState)(false);
3755
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react21.useState)(false);
3756
- const validateEmail = (0, import_react21.useMemo)(() => {
3757
- if (email.length === 0) return null;
3758
- if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
3759
- return null;
3760
- }, [email]);
3761
- const validatePassword = (0, import_react21.useMemo)(() => {
3762
- if (password.length === 0) return null;
3763
- if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
3764
- return null;
3765
- }, [password]);
3766
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3767
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3768
- const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
3769
- const submit = (0, import_react21.useCallback)(async () => {
3770
- setFormSubmitAttempted(true);
3771
- if (!canSubmit) return false;
3772
- setSubmitting(true);
3773
- setError(null);
3774
- try {
3775
- await auth.login({ email, password });
3776
- return true;
3777
- } catch (err) {
3778
- setError(mapSdkError(err));
3779
- return false;
3780
- } finally {
3781
- setSubmitting(false);
3782
- }
3783
- }, [auth, email, password, canSubmit]);
3784
- return {
3785
- email,
3786
- setEmail,
3787
- emailError,
3788
- markEmailTouched: () => setTouchedEmail(true),
3789
- password,
3790
- setPassword,
3791
- passwordError,
3792
- markPasswordTouched: () => setTouchedPassword(true),
3793
- formSubmitAttempted,
3794
- submit,
3795
- submitting,
3796
- canSubmit,
3797
- error,
3798
- loginWithGoogle: () => auth.loginWithGoogle()
3799
- };
3800
- }
3801
-
3802
- // src/hooks/useSignupForm.ts
3803
- var import_react22 = require("react");
3804
- var import_sdk17 = require("@hook-sdk/sdk");
3805
- var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3806
- var MIN_PASSWORD2 = 8;
3807
- function useSignupForm() {
3808
- const { auth } = (0, import_sdk17.useHook)();
3809
- const [name, setName] = (0, import_react22.useState)("");
3810
- const [email, setEmail] = (0, import_react22.useState)("");
3811
- const [password, setPassword] = (0, import_react22.useState)("");
3812
- const [submitting, setSubmitting] = (0, import_react22.useState)(false);
3813
- const [error, setError] = (0, import_react22.useState)(null);
3814
- const [touchedName, setTouchedName] = (0, import_react22.useState)(false);
3815
- const [touchedEmail, setTouchedEmail] = (0, import_react22.useState)(false);
3816
- const [touchedPassword, setTouchedPassword] = (0, import_react22.useState)(false);
3817
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react22.useState)(false);
3818
- const validateName = (0, import_react22.useMemo)(() => {
3819
- if (name.length === 0) return null;
3820
- if (name.trim().length < 2) return "Nome muito curto.";
3821
- return null;
3822
- }, [name]);
3823
- const validateEmail = (0, import_react22.useMemo)(() => {
3824
- if (email.length === 0) return null;
3825
- if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
3826
- return null;
3827
- }, [email]);
3828
- const validatePassword = (0, import_react22.useMemo)(() => {
3829
- if (password.length === 0) return null;
3830
- if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
3831
- return null;
3832
- }, [password]);
3833
- const nameError = touchedName || formSubmitAttempted ? validateName : null;
3834
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3835
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3836
- const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === 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.signup({ name, email, password });
3844
- return true;
3845
- } catch (err) {
3846
- setError(mapSdkError(err));
3847
- return false;
3848
- } finally {
3849
- setSubmitting(false);
3346
+ return copy.cardConsumedTrial ? interp(copy.cardConsumedTrial.ctaTemplate, {
3347
+ price: priceLabel,
3348
+ days: trialDaysCardLabel
3349
+ }) : `Assinar por ${priceLabel}`;
3850
3350
  }
3851
- }, [auth, name, email, password, canSubmit]);
3852
- return {
3853
- name,
3854
- setName,
3855
- nameError,
3856
- markNameTouched: () => setTouchedName(true),
3857
- email,
3858
- setEmail,
3859
- emailError,
3860
- markEmailTouched: () => setTouchedEmail(true),
3861
- password,
3862
- setPassword,
3863
- passwordError,
3864
- markPasswordTouched: () => setTouchedPassword(true),
3865
- formSubmitAttempted,
3866
- submit,
3867
- submitting,
3868
- canSubmit,
3869
- error,
3870
- loginWithGoogle: () => auth.loginWithGoogle()
3871
- };
3872
- }
3873
-
3874
- // src/hooks/useForgotForm.ts
3875
- var import_react23 = require("react");
3876
- var import_sdk18 = require("@hook-sdk/sdk");
3877
- var EMAIL_RE4 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3878
- function useForgotForm() {
3879
- const { auth } = (0, import_sdk18.useHook)();
3880
- const [email, setEmail] = (0, import_react23.useState)("");
3881
- const [submitting, setSubmitting] = (0, import_react23.useState)(false);
3882
- const [sent, setSent] = (0, import_react23.useState)(false);
3883
- const [error, setError] = (0, import_react23.useState)(null);
3884
- const [touchedEmail, setTouchedEmail] = (0, import_react23.useState)(false);
3885
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react23.useState)(false);
3886
- const validateEmail = (0, import_react23.useMemo)(() => {
3887
- if (email.length === 0) return null;
3888
- if (!EMAIL_RE4.test(email)) return "Formato de e-mail inv\xE1lido.";
3889
- return null;
3890
- }, [email]);
3891
- const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3892
- const canSubmit = email.length > 0 && validateEmail === null && !submitting;
3893
- const submit = (0, import_react23.useCallback)(async () => {
3894
- setFormSubmitAttempted(true);
3895
- if (!canSubmit) return false;
3896
- setSubmitting(true);
3897
- setError(null);
3898
- try {
3899
- await auth.forgot({ email });
3900
- setSent(true);
3901
- return true;
3902
- } catch (err) {
3903
- setError(mapSdkError(err));
3904
- return false;
3905
- } finally {
3906
- setSubmitting(false);
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;
3907
3383
  }
3908
- }, [auth, email, canSubmit]);
3909
- return {
3910
- email,
3911
- setEmail,
3912
- emailError,
3913
- markEmailTouched: () => setTouchedEmail(true),
3914
- formSubmitAttempted,
3915
- submit,
3916
- submitting,
3917
- canSubmit,
3918
- sent,
3919
- error
3384
+ await s.submit();
3920
3385
  };
3921
- }
3922
-
3923
- // src/hooks/useResetForm.ts
3924
- var import_react24 = require("react");
3925
- var import_sdk19 = require("@hook-sdk/sdk");
3926
- var MIN_PASSWORD3 = 8;
3927
- function useResetForm() {
3928
- const { auth } = (0, import_sdk19.useHook)();
3929
- const [token, setToken] = (0, import_react24.useState)(null);
3930
- const [password, setPassword] = (0, import_react24.useState)("");
3931
- const [confirm, setConfirm] = (0, import_react24.useState)("");
3932
- const [submitting, setSubmitting] = (0, import_react24.useState)(false);
3933
- const [done, setDone] = (0, import_react24.useState)(false);
3934
- const [error, setError] = (0, import_react24.useState)(null);
3935
- const [touchedPassword, setTouchedPassword] = (0, import_react24.useState)(false);
3936
- const [touchedConfirm, setTouchedConfirm] = (0, import_react24.useState)(false);
3937
- const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react24.useState)(false);
3938
- (0, import_react24.useEffect)(() => {
3939
- if (typeof window === "undefined") return;
3940
- const params = new URLSearchParams(window.location.search);
3941
- const t = params.get("token");
3942
- setToken(t && t.length > 0 ? t : null);
3943
- }, []);
3944
- const validatePassword = (0, import_react24.useMemo)(() => {
3945
- if (password.length === 0) return null;
3946
- if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
3947
- return null;
3948
- }, [password]);
3949
- const validateConfirm = (0, import_react24.useMemo)(() => {
3950
- if (confirm.length === 0) return null;
3951
- if (confirm !== password) return "Senhas n\xE3o coincidem.";
3952
- return null;
3953
- }, [confirm, password]);
3954
- const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
3955
- const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
3956
- const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
3957
- const submit = (0, import_react24.useCallback)(async () => {
3958
- setFormSubmitAttempted(true);
3959
- if (!canSubmit || token === null) return;
3960
- setSubmitting(true);
3961
- setError(null);
3962
- try {
3963
- await auth.reset({ token, newPassword: password });
3964
- setDone(true);
3965
- if (typeof window !== "undefined") {
3966
- const url = new URL(window.location.href);
3967
- url.searchParams.delete("token");
3968
- url.searchParams.delete("screen");
3969
- window.history.replaceState({}, "", url.toString());
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,
3397
+ {
3398
+ labels: copy.cycle,
3399
+ cardClassName: themeClasses.cycleCard,
3400
+ cardSelectedClassName: themeClasses.cycleCardSelected,
3401
+ anchorClassName: themeClasses.anchorPrice
3970
3402
  }
3971
- } catch (err) {
3972
- setError(mapSdkError(err));
3973
- } finally {
3974
- setSubmitting(false);
3975
- }
3976
- }, [auth, token, password, canSubmit]);
3977
- return {
3978
- token,
3979
- password,
3980
- setPassword,
3981
- passwordError,
3982
- markPasswordTouched: () => setTouchedPassword(true),
3983
- confirm,
3984
- setConfirm,
3985
- confirmError,
3986
- markConfirmTouched: () => setTouchedConfirm(true),
3987
- formSubmitAttempted,
3988
- submit,
3989
- submitting,
3990
- canSubmit,
3991
- done,
3992
- error
3993
- };
3994
- }
3995
-
3996
- // src/utils/price.ts
3997
- function formatBRL(cents) {
3998
- if (cents === null || cents === void 0) return "";
3999
- const reais = cents / 100;
4000
- return new Intl.NumberFormat("pt-BR", {
4001
- style: "currency",
4002
- currency: "BRL"
4003
- }).format(reais);
4004
- }
4005
- function monthlyFromYearly(yearlyCents) {
4006
- if (yearlyCents === null || yearlyCents === void 0) return 0;
4007
- return Math.round(yearlyCents / 12);
4008
- }
4009
- function dailyFromYearly(yearlyCents) {
4010
- if (yearlyCents === null || yearlyCents === void 0) return 0;
4011
- return Math.round(yearlyCents / 365);
4012
- }
4013
- function computeAnchorCents(baseCents, multiplier) {
4014
- if (multiplier === null || multiplier === void 0) return null;
4015
- if (!Number.isFinite(multiplier)) return null;
4016
- if (multiplier <= 1) return null;
4017
- return Math.round(baseCents * multiplier);
4018
- }
4019
- function discountPercent(anchorCents, realCents) {
4020
- if (anchorCents <= realCents) return 0;
4021
- return Math.floor((anchorCents - realCents) / anchorCents * 100);
4022
- }
4023
-
4024
- // src/hooks/useAuthPrimitives.ts
4025
- var import_react25 = require("react");
4026
- var import_sdk20 = require("@hook-sdk/sdk");
4027
- var warned = false;
4028
- function useAuthPrimitives() {
4029
- const { auth } = (0, import_sdk20.useHook)();
4030
- (0, import_react25.useEffect)(() => {
4031
- if (!warned && process.env.NODE_ENV !== "production") {
4032
- warned = true;
4033
- console.warn(
4034
- "[@hook-sdk/template] useAuthPrimitives() \xE9 escape hatch. Pra login/signup/forgot, use useLoginForm/useSignupForm/useForgotForm. Docs: docs/19-golden-template.md#escape-hatch"
4035
- );
4036
- }
4037
- }, []);
4038
- return {
4039
- login: auth.login,
4040
- signup: auth.signup,
4041
- logout: auth.logout,
4042
- logoutAll: auth.logoutAll,
4043
- forgot: auth.forgot,
4044
- resendVerify: auth.resendVerify,
4045
- changePassword: auth.changePassword,
4046
- changeEmail: auth.changeEmail,
4047
- refresh: auth.refresh
4048
- };
4049
- }
4050
-
4051
- // src/hooks/useAuth.ts
4052
- var import_sdk21 = require("@hook-sdk/sdk");
4053
- function useAuth() {
4054
- const { user, authStatus, auth } = (0, import_sdk21.useHook)();
4055
- return {
4056
- user,
4057
- authStatus,
4058
- refresh: auth.refresh
4059
- };
3403
+ ),
3404
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
3405
+ PaywallMethodTabs,
3406
+ {
3407
+ labels: { "pix-auto": copy.pix.tabLabel, card: copy.card.tabLabel },
3408
+ className: themeClasses.tabs,
3409
+ tabClassName: themeClasses.tab,
3410
+ tabActiveClassName: themeClasses.tabActive
3411
+ }
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
3428
+ }
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
+ ] });
4060
3448
  }
4061
-
4062
- // src/index.ts
4063
- var import_sdk26 = require("@hook-sdk/sdk");
4064
-
4065
- // src/hooks/useSubscription.ts
4066
- var import_sdk22 = require("@hook-sdk/sdk");
4067
- function useSubscription() {
4068
- const { subscription } = (0, import_sdk22.useHook)();
3449
+ function interp(tpl, vars) {
3450
+ return tpl.replace(/\{(\w+)\}/g, (_m, k) => vars[k] ?? "");
3451
+ }
3452
+ function interpolateCopy(m, price, days) {
4069
3453
  return {
4070
- 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
4071
3458
  };
4072
3459
  }
4073
3460
 
4074
- // src/hooks/useReminders.ts
4075
- var import_react26 = require("react");
4076
- var import_sdk23 = require("@hook-sdk/sdk");
4077
- function useReminders() {
4078
- const { push } = (0, import_sdk23.useHook)();
4079
- const r = push.reminders;
4080
- const [reminders, setReminders] = (0, import_react26.useState)([]);
4081
- const [loading, setLoading] = (0, import_react26.useState)(true);
4082
- const reload = (0, import_react26.useCallback)(async () => {
4083
- setLoading(true);
4084
- try {
4085
- const next = await r.list();
4086
- setReminders(next);
4087
- } finally {
4088
- setLoading(false);
4089
- }
4090
- }, [r]);
4091
- (0, import_react26.useEffect)(() => {
4092
- void reload();
4093
- }, [reload]);
4094
- const setReminder = (0, import_react26.useCallback)(async (input) => {
4095
- await r.set(input);
4096
- await reload();
4097
- }, [r, reload]);
4098
- const deleteReminder = (0, import_react26.useCallback)(async (slot) => {
4099
- await r.delete(slot);
4100
- await reload();
4101
- }, [r, reload]);
4102
- const schedule = (0, import_react26.useCallback)(async (items) => {
4103
- return r.schedule(items);
4104
- }, [r]);
4105
- const setFallbacks = (0, import_react26.useCallback)(async (items) => {
4106
- return r.setFallbacks(items);
4107
- }, [r]);
4108
- return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
4109
- }
4110
-
4111
- // src/hooks/useToast.ts
4112
- var import_react27 = require("react");
4113
- function useToast() {
4114
- const [items, setItems] = (0, import_react27.useState)([]);
4115
- const show = (0, import_react27.useCallback)((message, kind = "info") => {
4116
- const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4117
- setItems((prev) => [...prev, { id, message, kind }]);
4118
- setTimeout(() => {
4119
- setItems((prev) => prev.filter((t) => t.id !== id));
4120
- }, 4e3);
4121
- }, []);
4122
- const dismiss = (0, import_react27.useCallback)((id) => {
4123
- setItems((prev) => prev.filter((t) => t.id !== id));
4124
- }, []);
4125
- return { items, show, dismiss };
4126
- }
4127
-
4128
- // src/RouteBoundary.tsx
4129
- var import_react_router_dom5 = require("react-router-dom");
4130
- var import_jsx_runtime30 = require("react/jsx-runtime");
4131
- function RouteBoundary({ children }) {
4132
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_react_router_dom5.Routes, { children: [
4133
- children,
4134
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_react_router_dom5.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(DefaultNotFound, {}) })
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 })
4135
3490
  ] });
4136
3491
  }
4137
- function DefaultNotFound() {
4138
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
3492
+
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 });
4139
3498
  }
4140
3499
 
4141
- // src/PreAuthShell.tsx
4142
- var import_react_router_dom6 = require("react-router-dom");
4143
- var import_jsx_runtime31 = require("react/jsx-runtime");
4144
- function PreAuthShell({
4145
- basename,
4146
- testRouter,
4147
- testInitialEntries,
4148
- children
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
4149
3513
  }) {
4150
- if (testRouter === "memory") {
4151
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_router_dom6.MemoryRouter, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_router_dom6.Routes, { children }) });
3514
+ if (render) {
3515
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className, children: render({ src, headline }) });
4152
3516
  }
4153
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_router_dom6.BrowserRouter, { basename, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_react_router_dom6.Routes, { children }) });
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
+ );
4154
3542
  }
4155
3543
 
4156
- // src/OnboardingFlow.tsx
4157
- var import_react29 = require("react");
4158
- var import_sdk24 = require("@hook-sdk/sdk");
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 });
3550
+ }
4159
3551
 
4160
- // src/hooks/useOnboardingStep.ts
4161
- var import_react28 = require("react");
4162
- var OnboardingStepContext = (0, import_react28.createContext)(null);
4163
- function useOnboardingStep() {
4164
- const ctx = (0, import_react28.useContext)(OnboardingStepContext);
4165
- if (!ctx) {
4166
- throw new Error(
4167
- "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
4168
- );
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
3564
+ }) {
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 }) });
4169
3574
  }
4170
- return ctx;
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 });
4171
3578
  }
4172
3579
 
4173
- // src/OnboardingFlow.tsx
4174
- var import_jsx_runtime32 = require("react/jsx-runtime");
4175
- var isFilled = (v) => v != null && v !== "";
4176
- var CURRENT_STEP_FIELD = "currentStep";
4177
- function readPersistedStepIdx(draft) {
4178
- const raw = draft[CURRENT_STEP_FIELD];
4179
- return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
4180
- }
4181
- function OnboardingFlow({
4182
- steps,
4183
- screens,
4184
- onComplete,
4185
- persistKey
4186
- }) {
4187
- const [draft, setDraft, status] = (0, import_sdk24.usePersistedState)(persistKey, {});
4188
- const draftRef = (0, import_react29.useRef)(draft);
4189
- draftRef.current = draft;
4190
- const idx = readPersistedStepIdx(draft);
4191
- const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
4192
- const setIdx = (0, import_react29.useCallback)(
4193
- (n) => {
4194
- setDraft((prev) => {
4195
- const prevIdx = readPersistedStepIdx(prev);
4196
- const nextIdx = typeof n === "function" ? n(prevIdx) : n;
4197
- return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
4198
- });
4199
- },
4200
- [setDraft]
4201
- );
4202
- const setValue = (0, import_react29.useCallback)(
4203
- (patch) => {
4204
- draftRef.current = { ...draftRef.current, ...patch };
4205
- setDraft((prev) => ({ ...prev, ...patch }));
4206
- },
4207
- [setDraft]
4208
- );
4209
- const step = steps[clampedIdx];
4210
- const hookCtx = (0, import_sdk24.useHook)();
4211
- const track2 = typeof hookCtx.track === "function" ? hookCtx.track : void 0;
4212
- (0, import_react29.useEffect)(() => {
4213
- if (status.loading) return;
4214
- if (!step) return;
4215
- if (!track2) return;
4216
- track2("onboarding_step_viewed", {
4217
- step: step.id,
4218
- step_index: clampedIdx,
4219
- total_steps: steps.length
4220
- });
4221
- }, [step?.id, clampedIdx, steps.length, status.loading, track2]);
4222
- const valid = (0, import_react29.useMemo)(
4223
- () => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
4224
- [draft, step]
4225
- );
4226
- const next = (0, import_react29.useCallback)(() => {
4227
- if (!step) return;
4228
- const current = draftRef.current;
4229
- const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
4230
- if (!validNow) return;
4231
- if (clampedIdx + 1 >= steps.length) {
4232
- onComplete(current);
4233
- } else {
4234
- setIdx(clampedIdx + 1);
4235
- }
4236
- }, [clampedIdx, onComplete, step, steps.length, setIdx]);
4237
- const prevStep = (0, import_react29.useCallback)(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
4238
- const ctx = (0, import_react29.useMemo)(
4239
- () => ({
4240
- stepIndex: clampedIdx,
4241
- totalSteps: steps.length,
4242
- value: draft,
4243
- setValue,
4244
- valid,
4245
- next,
4246
- prev: prevStep
4247
- }),
4248
- [clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
4249
- );
4250
- if (status.loading) {
4251
- return null;
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;
4252
3590
  }
4253
- if (!step) {
4254
- throw new Error(
4255
- `[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
4256
- );
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;
4257
3598
  }
4258
- const Screen = screens[step.screen];
4259
- if (!Screen) {
4260
- throw new Error(
4261
- `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
4262
- );
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) });
4263
3646
  }
4264
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Screen, {}) });
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 });
4265
3649
  }
4266
3650
 
4267
- // src/hooks/useFeature.ts
4268
- function useFeature(name) {
4269
- const config = useAppConfig();
4270
- return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
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
3661
+ }) {
3662
+ if (render) {
3663
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("div", { className, children: render({ items }) });
3664
+ }
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)) });
3667
+ }
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)) });
4271
3672
  }
4272
3673
 
4273
- // src/components/paywall/Paywall.tsx
4274
- var import_react32 = require("react");
4275
- var import_sdk25 = require("@hook-sdk/sdk");
4276
-
4277
- // src/components/paywall/PaywallProvider.tsx
4278
- var import_react30 = require("react");
4279
- var import_jsx_runtime33 = require("react/jsx-runtime");
4280
- var PaywallContext = (0, import_react30.createContext)(null);
4281
- function PaywallProvider({ children }) {
4282
- const state = usePaywallState();
4283
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(PaywallContext.Provider, { value: state, children });
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
+ ] }) });
4284
3696
  }
4285
3697
 
4286
- // src/components/paywall/usePaywallContext.ts
4287
- var import_react31 = require("react");
4288
- function usePaywallContext() {
4289
- const ctx = (0, import_react31.useContext)(PaywallContext);
4290
- if (!ctx) {
4291
- throw new Error("usePaywallContext must be used within <PaywallProvider>");
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 }) });
4292
3711
  }
4293
- return ctx;
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
+ ] });
4294
3721
  }
4295
3722
 
4296
- // src/components/paywall/PaywallMethodTabs.tsx
4297
- var import_jsx_runtime34 = require("react/jsx-runtime");
4298
- function PaywallMethodTabs({
4299
- labels,
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({
4300
3727
  className,
4301
- tabClassName,
4302
- tabActiveClassName
3728
+ render
4303
3729
  }) {
4304
- const { methods, selectedMethod, selectMethod } = usePaywallContext();
4305
- if (methods.length < 2) return null;
4306
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", className, children: methods.map((m) => {
4307
- const active = m === selectedMethod;
4308
- const label = labels[m] ?? m;
4309
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
4310
- "button",
4311
- {
4312
- type: "button",
4313
- role: "tab",
4314
- "aria-selected": active,
4315
- "aria-controls": `paywall-tab-${m}`,
4316
- tabIndex: active ? 0 : -1,
4317
- onClick: () => selectMethod(m),
4318
- className: [tabClassName, active ? tabActiveClassName : ""].filter(Boolean).join(" "),
4319
- children: label
4320
- },
4321
- m
4322
- );
4323
- }) });
3730
+ const { anchorPriceCents, cycle } = usePaywallContext();
3731
+ if (anchorPriceCents === null || anchorPriceCents === void 0 || anchorPriceCents <= 0) {
3732
+ return null;
3733
+ }
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 });
4324
3741
  }
4325
3742
 
4326
- // src/components/paywall/PaywallMethodContent.tsx
4327
- var import_jsx_runtime35 = require("react/jsx-runtime");
4328
- function PaywallMethodContent({ copy, className, rowClassName }) {
4329
- const { selectedMethod, hasConsumedTrial } = usePaywallContext();
4330
- const useCardConsumed = selectedMethod === "card" && hasConsumedTrial && copy.cardConsumedTrial;
4331
- const rows = useCardConsumed ? copy.cardConsumedTrial.bodyRows : selectedMethod === "pix-auto" || selectedMethod === "pix-once" ? copy.pix.bodyRows : copy.card.bodyRows;
4332
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "tabpanel", id: `paywall-tab-${selectedMethod}`, className, children: rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: rowClassName, children: row }, i)) });
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,
3756
+ className,
3757
+ cardClassName,
3758
+ avatarClassName,
3759
+ quoteClassName,
3760
+ nameClassName,
3761
+ starsClassName,
3762
+ renderItem
3763
+ }) {
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);
3794
+ }) });
4333
3795
  }
4334
3796
 
4335
- // src/components/paywall/PaywallCyclePicker.tsx
4336
- var import_jsx_runtime36 = require("react/jsx-runtime");
4337
- var VARIANT_CLASSES = {
4338
- default: { card: "", cardSelected: "" },
4339
- "premium-gold": {
4340
- card: "",
4341
- cardSelected: "border-2 border-yellow-400/80 ring-2 ring-yellow-400/20"
4342
- },
4343
- "pink-pill": {
4344
- card: "rounded-2xl",
4345
- cardSelected: "border-2 border-pink-500"
4346
- }
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
+ }) });
3823
+ }
3824
+
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"
4347
3831
  };
4348
- function PaywallCyclePicker({
4349
- labels,
3832
+ function PaywallFinePrint({
3833
+ template,
4350
3834
  className,
4351
- cardClassName,
4352
- cardSelectedClassName,
4353
- anchorClassName,
4354
- variant = "default",
4355
3835
  render
4356
3836
  }) {
4357
- const ctx = usePaywallContext();
4358
- const { cycle: selected, setCycle, plan, anchorPriceCents } = ctx;
4359
- 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(" ");
4360
3848
  if (render) {
4361
- return /* @__PURE__ */ (0, import_jsx_runtime36.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
+ }) });
4362
3855
  }
4363
- if (cycles.length < 2) return null;
4364
- const v = VARIANT_CLASSES[variant];
4365
- const composedCardClassName = [v.card, cardClassName].filter(Boolean).join(" ");
4366
- const composedCardSelectedClassName = [v.cardSelected, cardSelectedClassName].filter(Boolean).join(" ");
4367
- const monthlyCents = plan?.monthlyCents ?? 0;
4368
- const yearlyCents = plan?.yearlyCents ?? 0;
4369
- const anchorMonthly = plan?.anchorMonthlyCents ?? null;
4370
- const anchorYearly = plan?.anchorYearlyCents ?? null;
4371
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
4372
- "div",
4373
- {
4374
- role: "radiogroup",
4375
- "aria-label": "Ciclo de cobran\xE7a",
4376
- className: ["flex flex-row gap-2", className].filter(Boolean).join(" "),
4377
- children: cycles.map((c) => {
4378
- const active = c === selected;
4379
- const label = c === "YEARLY" ? labels.annualLabel : labels.monthlyLabel;
4380
- const suffix = c === "YEARLY" ? labels.annualSuffix : labels.monthlySuffix;
4381
- const mainCents = c === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents;
4382
- const anchorCents = c === "YEARLY" ? anchorYearly : anchorMonthly;
4383
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
4384
- "button",
4385
- {
4386
- type: "button",
4387
- role: "radio",
4388
- "aria-checked": active,
4389
- onClick: () => setCycle(c),
4390
- className: [
4391
- "flex flex-col items-center gap-0.5",
4392
- composedCardClassName,
4393
- active ? composedCardSelectedClassName : ""
4394
- ].filter(Boolean).join(" "),
4395
- children: [
4396
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "font-bold text-base leading-tight", children: formatBRL(mainCents) }),
4397
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-xs opacity-70 leading-tight", children: suffix }),
4398
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-xs opacity-60 leading-tight", children: label }),
4399
- anchorCents != null && anchorCents > mainCents ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: anchorClassName ?? "text-xs opacity-50", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("s", { children: formatBRL(anchorCents) }) }) : null
4400
- ]
4401
- },
4402
- c
4403
- );
4404
- })
4405
- }
4406
- );
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 });
4407
3858
  }
4408
3859
 
4409
- // src/components/paywall/Paywall.tsx
4410
- var import_jsx_runtime37 = require("react/jsx-runtime");
4411
- var NBSP = "\xA0";
4412
- function Paywall({
4413
- copy,
4414
- themeClasses = {},
4415
- slots = {},
4416
- 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
4417
3869
  }) {
4418
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(PaywallProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4419
- PaywallInner,
4420
- {
4421
- copy,
4422
- themeClasses,
4423
- slots,
4424
- onBeforeCheckout
4425
- }
4426
- ) });
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
+ }) });
4427
3879
  }
4428
- function PaywallInner({
4429
- copy,
4430
- themeClasses = {},
4431
- slots = {},
4432
- 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
4433
3889
  }) {
4434
- const { track: track2 } = (0, import_sdk25.useHook)();
4435
- const s = usePaywallContext();
4436
- const priceLabel = formatBRL(s.currentPriceCents).replace(new RegExp(NBSP, "g"), " ");
4437
- const trialDaysCardLabel = String(s.trialDaysCard);
4438
- const ctaLabel = (0, import_react32.useMemo)(() => {
4439
- if (s.isFree) return copy.freeCta ?? "Come\xE7ar agora";
4440
- if (s.selectedMethod === "card") {
4441
- if (s.hasConsumedTrial && copy.cardConsumedTrial) {
4442
- return interp(copy.cardConsumedTrial.ctaTemplate, {
4443
- price: priceLabel,
4444
- days: trialDaysCardLabel
4445
- });
4446
- }
4447
- if (s.trialDaysCard > 0) {
4448
- return interp(copy.card.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
4449
- }
4450
- return copy.cardConsumedTrial ? interp(copy.cardConsumedTrial.ctaTemplate, {
4451
- price: priceLabel,
4452
- days: trialDaysCardLabel
4453
- }) : `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 });
4454
3952
  }
4455
- return interp(copy.pix.ctaTemplate, { price: priceLabel, days: trialDaysCardLabel });
4456
- }, [
4457
- s.isFree,
4458
- s.selectedMethod,
4459
- s.hasConsumedTrial,
4460
- s.trialDaysCard,
4461
- copy,
4462
- priceLabel,
4463
- trialDaysCardLabel
4464
- ]);
4465
- const switchHint = (0, import_react32.useMemo)(() => {
4466
- if (s.methods.length < 2) return void 0;
4467
- return s.selectedMethod === "card" ? copy.card.switchHint : copy.pix.switchHint;
4468
- }, [s.methods.length, s.selectedMethod, copy]);
4469
- (0, import_react32.useEffect)(() => {
4470
- if (!s.initialLoadComplete) return;
4471
- track2("paywall_view", {
4472
- default_method: s.selectedMethod,
4473
- default_cycle: s.cycle,
4474
- available_methods: s.methods
4475
- });
4476
- }, [s.initialLoadComplete]);
4477
- const handleCta = async () => {
4478
- track2("paywall_cta_clicked", {
4479
- method: s.selectedMethod,
4480
- cycle: s.cycle,
4481
- price_cents: s.currentPriceCents,
4482
- had_consumed_trial: s.hasConsumedTrial
4483
- });
4484
- if (onBeforeCheckout) {
4485
- 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\/[^/]+/, ""));
4486
4007
  return;
4487
4008
  }
4488
- await s.submit();
4489
- };
4490
- const ctaTheme = s.selectedMethod === "card" ? themeClasses.ctaCard : themeClasses.ctaPix;
4491
- return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: themeClasses.container, children: [
4492
- slots.heroSlot,
4493
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("h1", { className: themeClasses.headline, children: copy.headline }),
4494
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { children: copy.features.map((f) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: themeClasses.feature, children: [
4495
- "\u2713 ",
4496
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: f })
4497
- ] }, f)) }),
4498
- copy.socialProof ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: themeClasses.socialProof, children: copy.socialProof }) : null,
4499
- slots.cyclePickerSlot ?? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4500
- PaywallCyclePicker,
4501
- {
4502
- labels: copy.cycle,
4503
- cardClassName: themeClasses.cycleCard,
4504
- cardSelectedClassName: themeClasses.cycleCardSelected,
4505
- anchorClassName: themeClasses.anchorPrice
4506
- }
4507
- ),
4508
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4509
- PaywallMethodTabs,
4510
- {
4511
- labels: { "pix-auto": copy.pix.tabLabel, card: copy.card.tabLabel },
4512
- className: themeClasses.tabs,
4513
- tabClassName: themeClasses.tab,
4514
- tabActiveClassName: themeClasses.tabActive
4515
- }
4516
- ),
4517
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
4518
- PaywallMethodContent,
4519
- {
4520
- copy: {
4521
- pix: interpolateCopy(copy.pix, priceLabel, trialDaysCardLabel),
4522
- card: interpolateCopy(copy.card, priceLabel, trialDaysCardLabel),
4523
- cardConsumedTrial: copy.cardConsumedTrial ? {
4524
- bodyRows: copy.cardConsumedTrial.bodyRows.map(
4525
- (r) => interp(r, { price: priceLabel, days: trialDaysCardLabel })
4526
- ),
4527
- ctaTemplate: copy.cardConsumedTrial.ctaTemplate
4528
- } : void 0
4529
- },
4530
- className: themeClasses.tabContent,
4531
- rowClassName: themeClasses.tabContentRow
4532
- }
4533
- ),
4534
- slots.beforeCtaSlot,
4535
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { children: [
4536
- /* @__PURE__ */ (0, import_jsx_runtime37.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
+ form.method !== "pix-auto" && /* @__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)(
4537
4265
  "button",
4538
4266
  {
4539
- type: "button",
4540
- onClick: () => {
4541
- void handleCta();
4542
- },
4543
- disabled: s.submitting,
4544
- className: ctaTheme,
4545
- 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 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
+ ] })
4546
4281
  }
4547
4282
  ),
4548
- switchHint ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: themeClasses.switchHint, children: switchHint }) : null,
4549
- /* @__PURE__ */ (0, import_jsx_runtime37.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
+ ] })
4550
4302
  ] })
4551
- ] });
4552
- }
4553
- function interp(tpl, vars) {
4554
- return tpl.replace(/\{(\w+)\}/g, (_m, k) => vars[k] ?? "");
4303
+ ] }) });
4555
4304
  }
4556
- function interpolateCopy(m, price, days) {
4557
- return {
4558
- tabLabel: m.tabLabel,
4559
- bodyRows: m.bodyRows.map((r) => interp(r, { price, days })),
4560
- ctaTemplate: m.ctaTemplate,
4561
- switchHint: m.switchHint
4562
- };
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 });
4563
4307
  }
4564
-
4565
- // src/components/paywall/PaywallCta.tsx
4566
- var import_jsx_runtime38 = require("react/jsx-runtime");
4567
- function PaywallCta({
4568
- ctaLabel,
4569
- loadingLabel,
4570
- switchHint,
4571
- trustLine,
4572
- className,
4573
- buttonClassName,
4574
- switchHintClassName,
4575
- trustClassName
4576
- }) {
4577
- const { submit, submitting } = usePaywallContext();
4578
- const label = submitting && loadingLabel ? loadingLabel : ctaLabel;
4579
- return /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className, children: [
4580
- /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
4581
- "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",
4582
4314
  {
4583
- type: "button",
4584
- onClick: () => {
4585
- void submit();
4586
- },
4587
- disabled: submitting,
4588
- className: buttonClassName,
4589
- 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}`
4590
4327
  }
4591
4328
  ),
4592
- switchHint ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("p", { className: switchHintClassName, children: switchHint }) : null,
4593
- /* @__PURE__ */ (0, import_jsx_runtime38.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
4594
4330
  ] });
4595
4331
  }
4596
-
4597
- // src/components/paywall/blocks/PaywallEyebrow.tsx
4598
- var import_jsx_runtime39 = require("react/jsx-runtime");
4599
- var DEFAULT_EYEBROW_CLASSES = "text-xs uppercase tracking-widest font-semibold opacity-70";
4600
- function PaywallEyebrow({ text, className }) {
4601
- return /* @__PURE__ */ (0, import_jsx_runtime39.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 });
4602
4334
  }
4603
-
4604
- // src/components/paywall/blocks/PaywallHero.tsx
4605
- var import_jsx_runtime40 = require("react/jsx-runtime");
4606
- var DEFAULT_GRADIENT = "absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent";
4607
- function PaywallHero({
4608
- src,
4609
- alt = "",
4610
- headline,
4611
- aspectRatio = "16/9",
4612
- gradientClassName,
4613
- className,
4614
- headlineClassName,
4615
- imgClassName,
4616
- render
4617
- }) {
4618
- if (render) {
4619
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className, children: render({ src, headline }) });
4620
- }
4621
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
4622
- "div",
4335
+ function TabButton({ active, onClick, icon, label, subtitle, subtitleActiveClass }) {
4336
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(
4337
+ "button",
4338
+ {
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 },
4345
+ children: [
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 })
4352
+ ]
4353
+ }
4354
+ );
4355
+ }
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",
4623
4386
  {
4624
- className: ["relative overflow-hidden", className].filter(Boolean).join(" "),
4625
- style: { aspectRatio },
4626
- children: [
4627
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
4628
- "img",
4629
- {
4630
- src,
4631
- alt,
4632
- className: ["absolute inset-0 w-full h-full object-cover", imgClassName].filter(Boolean).join(" ")
4633
- }
4634
- ),
4635
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: gradientClassName ?? DEFAULT_GRADIENT, "aria-hidden": "true" }),
4636
- headline ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
4637
- "h1",
4638
- {
4639
- className: ["absolute bottom-0 left-0 right-0 p-4 text-white font-bold text-2xl", headlineClassName].filter(Boolean).join(" "),
4640
- children: headline
4641
- }
4642
- ) : null
4643
- ]
4387
+ className: "w-4 h-4 rounded-full border-2 border-white/40 border-t-white",
4388
+ style: { animation: "spin 0.7s linear infinite" }
4644
4389
  }
4645
4390
  );
4646
4391
  }
4647
4392
 
4648
- // src/components/paywall/blocks/PaywallHeadline.tsx
4649
- var import_jsx_runtime41 = require("react/jsx-runtime");
4650
- var DEFAULT_HEADLINE_CLASSES = "text-2xl font-bold leading-tight";
4651
- function PaywallHeadline({ text, className, as = "h1" }) {
4652
- const Tag = as;
4653
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Tag, { className: [DEFAULT_HEADLINE_CLASSES, className].filter(Boolean).join(" "), children: text });
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;
4408
+ }
4654
4409
  }
4655
-
4656
- // src/components/paywall/blocks/PaywallPriceHeadline.tsx
4657
- var import_jsx_runtime42 = require("react/jsx-runtime");
4658
- var DEFAULT_CLASS = "text-2xl font-bold leading-tight";
4659
- var CYCLE_LABEL = {
4660
- MONTHLY: "mensal",
4661
- YEARLY: "anual"
4662
- };
4663
- function PaywallPriceHeadline({
4664
- template,
4665
- className,
4666
- as = "h1",
4667
- render
4668
- }) {
4669
- const { cycle, currentMonthlyEquivCents, plan } = usePaywallContext();
4670
- const yearlyCents = plan?.yearlyCents ?? null;
4671
- const pricePerDay = formatBRL(dailyFromYearly(yearlyCents));
4672
- const monthlyEquiv = currentMonthlyEquivCents ?? 0;
4673
- const cycleLabel = CYCLE_LABEL[cycle] ?? cycle.toLowerCase();
4674
- const rootClasses = [DEFAULT_CLASS, className].filter(Boolean).join(" ");
4675
- if (render) {
4676
- const RootTag2 = as;
4677
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RootTag2, { className: [className].filter(Boolean).join(" ") || void 0, children: render({ pricePerDay, currentMonthlyEquivCents: monthlyEquiv, cycle }) });
4410
+ function clearPixPayload() {
4411
+ if (typeof window === "undefined") return;
4412
+ try {
4413
+ sessionStorage.removeItem(PIX_PAYLOAD_KEY2);
4414
+ } catch {
4678
4415
  }
4679
- const text = template.replaceAll("{pricePerDay}", pricePerDay).replaceAll("{currentMonthlyEquiv}", formatBRL(monthlyEquiv)).replaceAll("{cycle}", cycleLabel);
4680
- const RootTag = as;
4681
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RootTag, { className: rootClasses, children: text });
4682
4416
  }
4683
-
4684
- // src/components/paywall/blocks/PaywallCountdown.tsx
4685
- var import_react33 = require("react");
4686
- var import_jsx_runtime43 = require("react/jsx-runtime");
4687
- var DEFAULT_COUNTDOWN_CLASSES = "font-mono tabular-nums";
4688
- function resolveDeadlineMs(deadline) {
4689
- if (deadline instanceof Date) return deadline.getTime();
4690
- if (typeof deadline === "string") return new Date(deadline).getTime();
4691
- const { sessionStorageKey, durationMs } = deadline;
4692
- if (typeof window === "undefined" || typeof window.sessionStorage === "undefined") {
4693
- return Date.now() + durationMs;
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
+ }
4694
4448
  }
4695
- const stored = window.sessionStorage.getItem(sessionStorageKey);
4696
- const parsed = stored ? Number.parseInt(stored, 10) : NaN;
4697
- const now = Date.now();
4698
- if (!Number.isFinite(parsed) || parsed < now) {
4699
- const target = now + durationMs;
4700
- window.sessionStorage.setItem(sessionStorageKey, String(target));
4701
- return target;
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
+ ] });
4702
4466
  }
4703
- return parsed;
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
+ ] });
4704
4494
  }
4705
- function computeRemaining(deadlineMs) {
4706
- const diff = Math.max(0, deadlineMs - Date.now());
4707
- const totalSeconds = Math.floor(diff / 1e3);
4708
- const h = Math.floor(totalSeconds / 3600);
4709
- const m = Math.floor(totalSeconds % 3600 / 60);
4710
- const s = totalSeconds % 60;
4711
- 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
+ };
4712
4626
  }
4713
- function pad(n) {
4714
- 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
+ };
4715
4675
  }
4716
- function PaywallCountdown({
4717
- deadline,
4718
- format = "h:m:s",
4719
- onExpire,
4720
- className,
4721
- render
4722
- }) {
4723
- const deadlineMsRef = (0, import_react33.useRef)(null);
4724
- if (deadlineMsRef.current === null) {
4725
- deadlineMsRef.current = resolveDeadlineMs(deadline);
4726
- }
4727
- const [state, setState] = (0, import_react33.useState)(() => computeRemaining(deadlineMsRef.current));
4728
- const expiredCalledRef = (0, import_react33.useRef)(false);
4729
- (0, import_react33.useEffect)(() => {
4730
- if (state.expired) {
4731
- if (!expiredCalledRef.current) {
4732
- expiredCalledRef.current = true;
4733
- 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());
4734
4724
  }
4735
- return;
4725
+ } catch (err) {
4726
+ setError(mapSdkError(err));
4727
+ } finally {
4728
+ setSubmitting(false);
4736
4729
  }
4737
- const tick = () => {
4738
- const next = computeRemaining(deadlineMsRef.current);
4739
- setState(next);
4740
- if (next.expired && !expiredCalledRef.current) {
4741
- expiredCalledRef.current = true;
4742
- onExpire?.();
4743
- }
4744
- };
4745
- const id = setInterval(tick, 1e3);
4746
- return () => clearInterval(id);
4747
- }, [state.expired]);
4748
- if (render) {
4749
- return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className, children: render(state) });
4750
- }
4751
- const formatted = format === "h:m:s" ? `${pad(state.h)}:${pad(state.m)}:${pad(state.s)}` : `${pad(state.h * 60 + state.m)}:${pad(state.s)}`;
4752
- return /* @__PURE__ */ (0, import_jsx_runtime43.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
+ };
4753
4748
  }
4754
4749
 
4755
- // src/components/paywall/blocks/PaywallFeatures.tsx
4756
- var import_jsx_runtime44 = require("react/jsx-runtime");
4757
- function PaywallFeatures({
4758
- items,
4759
- IconComponent,
4760
- className,
4761
- itemClassName,
4762
- iconClassName,
4763
- render,
4764
- renderItem
4765
- }) {
4766
- if (render) {
4767
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className, children: render({ items }) });
4768
- }
4769
- if (renderItem) {
4770
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("li", { children: renderItem(item, idx) }, idx)) });
4771
- }
4772
- return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("ul", { className, children: items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("li", { className: itemClassName, children: [
4773
- IconComponent ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(IconComponent, { className: iconClassName }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: iconClassName, "aria-hidden": "true", children: "\u2713" }),
4774
- /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { children: item })
4775
- ] }, 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
+ };
4776
4775
  }
4777
4776
 
4778
- // src/components/paywall/blocks/PaywallFeaturesCard.tsx
4779
- var import_jsx_runtime45 = require("react/jsx-runtime");
4780
- var DEFAULT_CARD_CLASSES = "rounded-xl border p-4";
4781
- function PaywallFeaturesCard({
4782
- title,
4783
- items,
4784
- className,
4785
- cardClassName,
4786
- titleClassName,
4787
- itemClassName,
4788
- renderItem
4789
- }) {
4790
- return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("div", { className: [DEFAULT_CARD_CLASSES, cardClassName].filter(Boolean).join(" "), children: [
4791
- title ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { className: ["font-semibold mb-2", titleClassName].filter(Boolean).join(" "), children: title }) : null,
4792
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ul", { children: items.map(
4793
- (item, idx) => renderItem ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("li", { children: renderItem(item, idx) }, idx) : /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { className: itemClassName, children: [
4794
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { "aria-hidden": "true", children: "\u2022" }),
4795
- " ",
4796
- /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { children: item })
4797
- ] }, idx)
4798
- ) })
4799
- ] }) });
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
+ };
4800
4786
  }
4801
4787
 
4802
- // src/components/paywall/blocks/PaywallTrophyBadge.tsx
4803
- var import_jsx_runtime46 = require("react/jsx-runtime");
4804
- 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";
4805
- var FLOATING_CLASSES = "absolute top-2 right-2 z-10 shadow-md";
4806
- function PaywallTrophyBadge({
4807
- text,
4808
- className,
4809
- iconClassName,
4810
- floating = false,
4811
- render
4812
- }) {
4813
- if (render) {
4814
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { className, children: render({ text }) });
4815
- }
4816
- const rootClasses = [
4817
- DEFAULT_CHIP_CLASSES,
4818
- floating ? FLOATING_CLASSES : "",
4819
- className
4820
- ].filter(Boolean).join(" ");
4821
- return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: rootClasses, children: [
4822
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: iconClassName, "aria-hidden": "true", children: "\u{1F3C6}" }),
4823
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { children: text })
4824
- ] });
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
+ };
4825
4798
  }
4826
4799
 
4827
- // src/components/paywall/blocks/PaywallAnchorPrice.tsx
4828
- var import_jsx_runtime47 = require("react/jsx-runtime");
4829
- var DEFAULT_CLASS2 = "text-sm opacity-60 line-through";
4830
- function PaywallAnchorPrice({
4831
- className,
4832
- render
4833
- }) {
4834
- const { anchorPriceCents, cycle } = usePaywallContext();
4835
- if (anchorPriceCents === null || anchorPriceCents === void 0 || anchorPriceCents <= 0) {
4836
- return null;
4837
- }
4838
- void cycle;
4839
- const formatted = formatBRL(anchorPriceCents);
4840
- const rootClasses = [DEFAULT_CLASS2, className].filter(Boolean).join(" ");
4841
- if (render) {
4842
- return /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: className || void 0, children: render({ anchorCents: anchorPriceCents, formatted }) });
4843
- }
4844
- return /* @__PURE__ */ (0, import_jsx_runtime47.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 };
4845
4835
  }
4846
4836
 
4847
- // src/components/paywall/blocks/PaywallTestimonials.tsx
4848
- var import_jsx_runtime48 = require("react/jsx-runtime");
4849
- var DEFAULT_ROOT = "flex gap-3 overflow-x-auto snap-x snap-mandatory pb-2";
4850
- var DEFAULT_CARD = "snap-start shrink-0 w-72 rounded-2xl border p-4 flex flex-col gap-2";
4851
- var DEFAULT_AVATAR = "w-10 h-10 rounded-full object-cover";
4852
- var DEFAULT_QUOTE = "text-sm leading-snug";
4853
- var DEFAULT_NAME = "text-xs font-semibold opacity-80";
4854
- var DEFAULT_STARS = "text-yellow-500 text-sm";
4855
- function clampStars(n) {
4856
- return Math.max(0, Math.min(5, Math.round(n)));
4857
- }
4858
- function PaywallTestimonials({
4859
- items,
4860
- className,
4861
- cardClassName,
4862
- avatarClassName,
4863
- quoteClassName,
4864
- nameClassName,
4865
- starsClassName,
4866
- renderItem
4867
- }) {
4868
- const rootClasses = [DEFAULT_ROOT, className].filter(Boolean).join(" ");
4869
- const cardClasses = [DEFAULT_CARD, cardClassName].filter(Boolean).join(" ");
4870
- const avatarClasses = [DEFAULT_AVATAR, avatarClassName].filter(Boolean).join(" ");
4871
- const quoteClasses = [DEFAULT_QUOTE, quoteClassName].filter(Boolean).join(" ");
4872
- const nameClasses = [DEFAULT_NAME, nameClassName].filter(Boolean).join(" ");
4873
- const starsClasses = [DEFAULT_STARS, starsClassName].filter(Boolean).join(" ");
4874
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: rootClasses, children: items.map((item, idx) => {
4875
- if (renderItem) return renderItem(item, idx);
4876
- const filled = clampStars(item.stars);
4877
- const empty = 5 - filled;
4878
- return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: cardClasses, children: [
4879
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-2", children: [
4880
- item.avatar ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4881
- "img",
4882
- {
4883
- src: item.avatar,
4884
- alt: "",
4885
- loading: "lazy",
4886
- className: avatarClasses,
4887
- "aria-hidden": "true"
4888
- }
4889
- ) : null,
4890
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: nameClasses, children: item.name })
4891
- ] }),
4892
- /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: starsClasses, "aria-label": `${filled} de 5 estrelas`, children: [
4893
- "\u2605".repeat(filled),
4894
- "\u2606".repeat(empty)
4895
- ] }),
4896
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: quoteClasses, children: item.quote })
4897
- ] }, idx);
4898
- }) });
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 };
4899
4852
  }
4900
4853
 
4901
- // src/components/paywall/blocks/PaywallStatsRow.tsx
4902
- var import_jsx_runtime49 = require("react/jsx-runtime");
4903
- var DEFAULT_ROOT2 = "grid grid-cols-3 gap-4";
4904
- var DEFAULT_CELL = "flex flex-col items-center text-center";
4905
- var DEFAULT_VALUE = "text-2xl font-bold";
4906
- var DEFAULT_LABEL = "text-xs opacity-70";
4907
- function PaywallStatsRow({
4908
- stats,
4909
- className,
4910
- cellClassName,
4911
- valueClassName,
4912
- labelClassName,
4913
- renderCell
4914
- }) {
4915
- const rootClasses = [DEFAULT_ROOT2, className].filter(Boolean).join(" ");
4916
- const cellClasses = [DEFAULT_CELL, cellClassName].filter(Boolean).join(" ");
4917
- const valueClasses = [DEFAULT_VALUE, valueClassName].filter(Boolean).join(" ");
4918
- const labelClasses = [DEFAULT_LABEL, labelClassName].filter(Boolean).join(" ");
4919
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: rootClasses, children: stats.map((stat, idx) => {
4920
- if (renderCell) return renderCell(stat, idx);
4921
- return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: cellClasses, children: [
4922
- stat.icon ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { "aria-hidden": "true", children: stat.icon }) : null,
4923
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: valueClasses, children: stat.value }),
4924
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { className: labelClasses, children: stat.label })
4925
- ] }, idx);
4926
- }) });
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" });
4927
4865
  }
4928
4866
 
4929
- // src/components/paywall/blocks/PaywallFinePrint.tsx
4930
- var import_jsx_runtime50 = require("react/jsx-runtime");
4931
- var DEFAULT_CLASS3 = "text-xs opacity-60 leading-snug";
4932
- var CYCLE_LABEL2 = {
4933
- MONTHLY: "mensal",
4934
- YEARLY: "anual"
4935
- };
4936
- function PaywallFinePrint({
4937
- template,
4938
- className,
4939
- render
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
4940
4875
  }) {
4941
- const {
4942
- currentPriceCents,
4943
- cycle,
4944
- trialDaysCard,
4945
- trialDaysPix,
4946
- selectedMethod
4947
- } = usePaywallContext();
4948
- const trialDays = selectedMethod === "card" ? trialDaysCard : trialDaysPix;
4949
- const cycleLabel = CYCLE_LABEL2[cycle] ?? cycle.toLowerCase();
4950
- const priceFormatted = formatBRL(currentPriceCents ?? 0);
4951
- const rootClasses = [DEFAULT_CLASS3, className].filter(Boolean).join(" ");
4952
- if (render) {
4953
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("p", { className: className || void 0, children: render({
4954
- currentPriceCents: currentPriceCents ?? 0,
4955
- cycle,
4956
- trialDays: trialDays ?? 0,
4957
- selectedMethod
4958
- }) });
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 }) });
4959
4878
  }
4960
- const text = template.replaceAll("{price}", priceFormatted).replaceAll("{trialDays}", String(trialDays ?? 0)).replaceAll("{cycle}", cycleLabel);
4961
- return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("p", { className: rootClasses, children: text });
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 }) });
4962
4880
  }
4963
4881
 
4964
- // src/components/paywall/blocks/PaywallTrustLine.tsx
4965
- var import_jsx_runtime51 = require("react/jsx-runtime");
4966
- var DEFAULT_ROOT3 = "flex items-center gap-3";
4967
- var DEFAULT_ITEM = "flex items-center gap-1.5 text-xs";
4968
- function PaywallTrustLine({
4969
- items,
4970
- className,
4971
- itemClassName,
4972
- renderItem
4973
- }) {
4974
- const rootClasses = [DEFAULT_ROOT3, className].filter(Boolean).join(" ");
4975
- const itemClasses = [DEFAULT_ITEM, itemClassName].filter(Boolean).join(" ");
4976
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: rootClasses, children: items.map((item, idx) => {
4977
- if (renderItem) return renderItem(item, idx);
4978
- return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: itemClasses, children: [
4979
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { "aria-hidden": "true", children: item.icon }),
4980
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { children: item.text })
4981
- ] }, idx);
4982
- }) });
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
+ );
4895
+ }
4896
+ return ctx;
4983
4897
  }
4984
4898
 
4985
- // src/components/paywall/blocks/PaywallStickyFooter.tsx
4899
+ // src/OnboardingFlow.tsx
4986
4900
  var import_jsx_runtime52 = require("react/jsx-runtime");
4987
- var DEFAULT_CLASSES = "sticky bottom-0 left-0 right-0 bg-background";
4988
- var SAFE_AREA_CLASS = "pb-[env(safe-area-inset-bottom)]";
4989
- function PaywallStickyFooter({
4990
- children,
4991
- className,
4992
- safeAreaInsets = true
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
4993
4912
  }) {
4994
- const classes = [DEFAULT_CLASSES, safeAreaInsets ? SAFE_AREA_CLASS : null, className].filter(Boolean).join(" ");
4995
- return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("div", { className: classes, children });
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, {}) });
4991
+ }
4992
+
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);
4996
4997
  }
4997
4998
  // Annotate the CommonJS export names for ESM import in node:
4998
4999
  0 && (module.exports = {