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