@hook-sdk/template 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/AppRoot.tsx
2
2
  import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef3, useState as useState12 } from "react";
3
- import { useHook as useHook8 } from "@hook-sdk/sdk";
3
+ import { useHook as useHook9 } from "@hook-sdk/sdk";
4
4
 
5
5
  // src/internal/TemplateConfigContext.tsx
6
6
  import { createContext, useContext, useMemo } from "react";
@@ -92,53 +92,69 @@ function AuthGate({ Login, Signup, Forgot, Reset, children }) {
92
92
  }
93
93
 
94
94
  // src/hooks/usePaywallState.ts
95
- import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } from "react";
95
+ import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
96
96
  import { useHook as useHook2 } from "@hook-sdk/sdk";
97
97
  function usePaywallState() {
98
- const { subscription } = useHook2();
98
+ const { subscription, plan } = useHook2();
99
99
  const [opening, setOpening] = useState2(false);
100
100
  const [error, setError] = useState2(null);
101
101
  const [pixPending, setPixPending] = useState2(null);
102
102
  const status = subscription.status();
103
103
  const daysLeftInTrial = subscription.daysLeftInTrial();
104
104
  const initialLoadComplete = subscription.initialLoadComplete;
105
+ const availableMethods = useMemo2(
106
+ () => ["card", "pix-auto"],
107
+ []
108
+ );
109
+ const priceCents = plan.data?.priceCents ?? 0;
110
+ const yearlyPriceCents = plan.data?.yearlyPriceCents ?? null;
111
+ const monthlyEquivalent = useCallback(
112
+ (cycle) => {
113
+ if (cycle === "YEARLY" && yearlyPriceCents) {
114
+ return Math.round(yearlyPriceCents / 12);
115
+ }
116
+ return priceCents;
117
+ },
118
+ [priceCents, yearlyPriceCents]
119
+ );
105
120
  const checkout = useCallback(
106
121
  async (args) => {
107
122
  setOpening(true);
108
123
  setError(null);
109
124
  setPixPending(null);
110
- const method = args.method ?? "card";
111
- const cycle = args.cycle ?? "MONTHLY";
112
125
  try {
113
- if (method === "card") {
114
- const result = await subscription.checkoutCard({ cpf: args.cpf, cycle });
115
- window.location.href = result.invoiceUrl;
116
- return;
117
- }
118
- if (method === "pix-auto") {
119
- const result = await subscription.checkoutPixAuto({ cpf: args.cpf, cycle });
120
- setPixPending({
121
- method: "pix-auto",
122
- qrCodePayload: result.qrCodePayload,
123
- qrCodeBase64: result.qrCodeBase64,
124
- expiresAt: null,
125
- paid: false
126
+ if (args.method === "card") {
127
+ if (!args.card || !args.holderInfo) {
128
+ throw new Error('card and holderInfo are required when method is "card"');
129
+ }
130
+ await subscription.checkout({
131
+ method: "card",
132
+ cycle: args.cycle,
133
+ cpf: args.cpf,
134
+ card: args.card,
135
+ holderInfo: args.holderInfo,
136
+ ...args.remoteIp ? { remoteIp: args.remoteIp } : {}
126
137
  });
138
+ await subscription.refresh();
127
139
  setOpening(false);
128
140
  return;
129
141
  }
130
- if (method === "pix-once") {
131
- const result = await subscription.checkoutPixOnce({ cpf: args.cpf, cycle });
132
- setPixPending({
133
- method: "pix-once",
134
- qrCodePayload: result.qrCodePayload,
135
- qrCodeBase64: result.qrCodeBase64,
136
- expiresAt: result.expiresAt,
137
- paid: false
138
- });
139
- setOpening(false);
140
- return;
142
+ const result = await subscription.checkout({
143
+ method: "pix-auto",
144
+ cycle: args.cycle,
145
+ cpf: args.cpf
146
+ });
147
+ if (result.method !== "pix-auto") {
148
+ throw new Error(`unexpected checkout result method: ${result.method}`);
141
149
  }
150
+ setPixPending({
151
+ method: "pix-auto",
152
+ qrCodePayload: result.qrCodePayload,
153
+ qrCodeBase64: result.qrCodeBase64,
154
+ expiresAt: null,
155
+ paid: false
156
+ });
157
+ setOpening(false);
142
158
  } catch (err) {
143
159
  setError(err);
144
160
  setOpening(false);
@@ -191,7 +207,9 @@ function usePaywallState() {
191
207
  opening,
192
208
  error,
193
209
  pixPending,
194
- dismissPix
210
+ dismissPix,
211
+ availableMethods,
212
+ monthlyEquivalent
195
213
  };
196
214
  }
197
215
 
@@ -1534,7 +1552,7 @@ var ErrorBoundary = class extends Component {
1534
1552
  };
1535
1553
 
1536
1554
  // src/hooks/useLoginForm.ts
1537
- import { useCallback as useCallback3, useMemo as useMemo2, useState as useState6 } from "react";
1555
+ import { useCallback as useCallback3, useMemo as useMemo3, useState as useState6 } from "react";
1538
1556
  import { useHook as useHook4 } from "@hook-sdk/sdk";
1539
1557
 
1540
1558
  // src/errors.ts
@@ -1575,12 +1593,12 @@ function useLoginForm() {
1575
1593
  const [password, setPassword] = useState6("");
1576
1594
  const [submitting, setSubmitting] = useState6(false);
1577
1595
  const [error, setError] = useState6(null);
1578
- const emailError = useMemo2(() => {
1596
+ const emailError = useMemo3(() => {
1579
1597
  if (email.length === 0) return null;
1580
1598
  if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
1581
1599
  return null;
1582
1600
  }, [email]);
1583
- const passwordError = useMemo2(() => {
1601
+ const passwordError = useMemo3(() => {
1584
1602
  if (password.length === 0) return null;
1585
1603
  if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
1586
1604
  return null;
@@ -1840,7 +1858,7 @@ function DefaultLoginScreen({ onNavigate }) {
1840
1858
  }
1841
1859
 
1842
1860
  // src/hooks/useSignupForm.ts
1843
- import { useCallback as useCallback4, useMemo as useMemo3, useState as useState8 } from "react";
1861
+ import { useCallback as useCallback4, useMemo as useMemo4, useState as useState8 } from "react";
1844
1862
  import { useHook as useHook5 } from "@hook-sdk/sdk";
1845
1863
  var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1846
1864
  var MIN_PASSWORD2 = 8;
@@ -1851,17 +1869,17 @@ function useSignupForm() {
1851
1869
  const [password, setPassword] = useState8("");
1852
1870
  const [submitting, setSubmitting] = useState8(false);
1853
1871
  const [error, setError] = useState8(null);
1854
- const nameError = useMemo3(() => {
1872
+ const nameError = useMemo4(() => {
1855
1873
  if (name.length === 0) return null;
1856
1874
  if (name.trim().length < 2) return "Nome muito curto.";
1857
1875
  return null;
1858
1876
  }, [name]);
1859
- const emailError = useMemo3(() => {
1877
+ const emailError = useMemo4(() => {
1860
1878
  if (email.length === 0) return null;
1861
1879
  if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
1862
1880
  return null;
1863
1881
  }, [email]);
1864
- const passwordError = useMemo3(() => {
1882
+ const passwordError = useMemo4(() => {
1865
1883
  if (password.length === 0) return null;
1866
1884
  if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
1867
1885
  return null;
@@ -1955,7 +1973,7 @@ function DefaultSignupScreen({ onNavigate }) {
1955
1973
  }
1956
1974
 
1957
1975
  // src/hooks/useForgotForm.ts
1958
- import { useCallback as useCallback5, useMemo as useMemo4, useState as useState9 } from "react";
1976
+ import { useCallback as useCallback5, useMemo as useMemo5, useState as useState9 } from "react";
1959
1977
  import { useHook as useHook6 } from "@hook-sdk/sdk";
1960
1978
  var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1961
1979
  function useForgotForm() {
@@ -1964,7 +1982,7 @@ function useForgotForm() {
1964
1982
  const [submitting, setSubmitting] = useState9(false);
1965
1983
  const [sent, setSent] = useState9(false);
1966
1984
  const [error, setError] = useState9(null);
1967
- const emailError = useMemo4(() => {
1985
+ const emailError = useMemo5(() => {
1968
1986
  if (email.length === 0) return null;
1969
1987
  if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
1970
1988
  return null;
@@ -2029,7 +2047,7 @@ function DefaultForgotScreen({ onNavigate }) {
2029
2047
  }
2030
2048
 
2031
2049
  // src/hooks/useResetForm.ts
2032
- import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo5, useState as useState10 } from "react";
2050
+ import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo6, useState as useState10 } from "react";
2033
2051
  import { useHook as useHook7 } from "@hook-sdk/sdk";
2034
2052
  var MIN_PASSWORD3 = 12;
2035
2053
  function useResetForm() {
@@ -2046,12 +2064,12 @@ function useResetForm() {
2046
2064
  const t = params.get("token");
2047
2065
  setToken(t && t.length > 0 ? t : null);
2048
2066
  }, []);
2049
- const passwordError = useMemo5(() => {
2067
+ const passwordError = useMemo6(() => {
2050
2068
  if (password.length === 0) return null;
2051
2069
  if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2052
2070
  return null;
2053
2071
  }, [password]);
2054
- const confirmError = useMemo5(() => {
2072
+ const confirmError = useMemo6(() => {
2055
2073
  if (confirm.length === 0) return null;
2056
2074
  if (confirm !== password) return "Senhas n\xE3o coincidem.";
2057
2075
  return null;
@@ -2135,34 +2153,430 @@ function DefaultResetScreen({ onNavigate }) {
2135
2153
  }
2136
2154
 
2137
2155
  // src/defaults/DefaultPaywall.tsx
2138
- import { useState as useState11 } from "react";
2139
- import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
2156
+ import { useMemo as useMemo7, useState as useState11 } from "react";
2157
+
2158
+ // src/hooks/usePlan.ts
2159
+ import { useHook as useHook8 } from "@hook-sdk/sdk";
2160
+ function usePlan() {
2161
+ const { plan } = useHook8();
2162
+ return plan;
2163
+ }
2164
+
2165
+ // src/utils/price.ts
2166
+ function formatBRL(cents) {
2167
+ if (cents === null || cents === void 0) return "";
2168
+ const reais = cents / 100;
2169
+ return new Intl.NumberFormat("pt-BR", {
2170
+ style: "currency",
2171
+ currency: "BRL"
2172
+ }).format(reais);
2173
+ }
2174
+ function monthlyFromYearly(yearlyCents) {
2175
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2176
+ return Math.round(yearlyCents / 12);
2177
+ }
2178
+ function dailyFromYearly(yearlyCents) {
2179
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2180
+ return Math.round(yearlyCents / 365);
2181
+ }
2182
+ function computeAnchorCents(baseCents, multiplier) {
2183
+ if (multiplier === null || multiplier === void 0) return null;
2184
+ if (!Number.isFinite(multiplier)) return null;
2185
+ if (multiplier <= 1) return null;
2186
+ return Math.round(baseCents * multiplier);
2187
+ }
2188
+ function discountPercent(anchorCents, realCents) {
2189
+ if (anchorCents <= realCents) return 0;
2190
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
2191
+ }
2192
+
2193
+ // src/defaults/DefaultPaywall.tsx
2194
+ import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
2140
2195
  function DefaultPaywall() {
2141
2196
  const config = useTemplateConfig();
2142
- const { checkout, opening, error } = usePaywallState();
2197
+ const plan = usePlan();
2198
+ const {
2199
+ checkout,
2200
+ opening,
2201
+ error,
2202
+ availableMethods,
2203
+ monthlyEquivalent,
2204
+ pixPending,
2205
+ dismissPix
2206
+ } = usePaywallState();
2143
2207
  const p = config.subscription.paywall_config;
2208
+ const paywallCfg = plan.data?.paywallConfig ?? {};
2209
+ const anchorMultiplier = paywallCfg.anchorMultiplier ?? p.anchorMultiplier;
2210
+ const anchorPriceCents = paywallCfg.anchorPriceCents ?? p.anchorPriceCents;
2211
+ const [cycle, setCycle] = useState11("MONTHLY");
2212
+ const [method, setMethod] = useState11("card");
2144
2213
  const [cpf, setCpf] = useState11("");
2214
+ const [cardNumber, setCardNumber] = useState11("");
2215
+ const [cardHolderName, setCardHolderName] = useState11("");
2216
+ const [cardExpiryMonth, setCardExpiryMonth] = useState11("");
2217
+ const [cardExpiryYear, setCardExpiryYear] = useState11("");
2218
+ const [cardCcv, setCardCcv] = useState11("");
2219
+ const [holderName, setHolderName] = useState11("");
2220
+ const [holderEmail, setHolderEmail] = useState11("");
2221
+ const [holderPostalCode, setHolderPostalCode] = useState11("");
2222
+ const [holderAddressNumber, setHolderAddressNumber] = useState11("");
2223
+ const [holderPhone, setHolderPhone] = useState11("");
2224
+ const trialDays = plan.data?.trialDays ?? 0;
2225
+ const monthlyCents = plan.data?.priceCents ?? 0;
2226
+ const yearlyCents = plan.data?.yearlyPriceCents ?? null;
2227
+ const activeCents = cycle === "YEARLY" && yearlyCents ? monthlyEquivalent("YEARLY") : monthlyCents;
2228
+ const pct = useMemo7(() => {
2229
+ if (!yearlyCents || !monthlyCents) return 0;
2230
+ const derived = Math.round((1 - yearlyCents / 12 / monthlyCents) * 100);
2231
+ return Math.max(0, derived);
2232
+ }, [monthlyCents, yearlyCents]);
2233
+ const anchorBaseCents = cycle === "YEARLY" && yearlyCents ? monthlyFromYearly(yearlyCents) : monthlyCents;
2234
+ const anchorCents = computeAnchorCents(anchorBaseCents, anchorMultiplier) ?? (anchorPriceCents && anchorPriceCents > anchorBaseCents ? anchorPriceCents : null);
2235
+ const anchorDiscount = anchorCents ? discountPercent(anchorCents, activeCents) : 0;
2145
2236
  const cpfDigits = cpf.replace(/\D/g, "");
2146
- const canCheckout = cpfDigits.length === 11 && !opening;
2147
- return /* @__PURE__ */ jsxs18("main", { style: { padding: 24, maxWidth: 440, margin: "0 auto", textAlign: "center" }, children: [
2237
+ const cardFieldsFilled = cardNumber.replace(/\s/g, "").length >= 13 && cardHolderName.trim().length > 0 && cardExpiryMonth.length === 2 && cardExpiryYear.length >= 2 && cardCcv.length >= 3 && holderName.trim().length > 0 && /.+@.+\..+/.test(holderEmail) && holderPostalCode.replace(/\D/g, "").length === 8 && holderAddressNumber.trim().length > 0;
2238
+ const canCheckout = cpfDigits.length === 11 && !opening && (method === "pix-auto" || cardFieldsFilled);
2239
+ const ctaLabel = useMemo7(() => {
2240
+ if (opening) return "Abrindo\u2026";
2241
+ if (trialDays > 0) return `Comece trial de ${trialDays} dias gr\xE1tis`;
2242
+ return p.cta ?? "Assinar agora";
2243
+ }, [opening, trialDays, p.cta]);
2244
+ const footer = useMemo7(() => {
2245
+ if (trialDays > 0) {
2246
+ return `Sem cobran\xE7a agora. Cobran\xE7a autom\xE1tica em ${trialDays} dias. Cancele quando quiser.`;
2247
+ }
2248
+ return "Cobran\xE7a imediata. Cancele quando quiser.";
2249
+ }, [trialDays]);
2250
+ const yearSuffix = cardExpiryYear.length === 2 ? `20${cardExpiryYear}` : cardExpiryYear;
2251
+ const phoneDigits = holderPhone.replace(/\D/g, "");
2252
+ const postalDigits = holderPostalCode.replace(/\D/g, "");
2253
+ const submit = () => {
2254
+ if (method === "card") {
2255
+ void checkout({
2256
+ cpf: cpfDigits,
2257
+ cycle,
2258
+ method: "card",
2259
+ card: {
2260
+ number: cardNumber.replace(/\s/g, ""),
2261
+ holderName: cardHolderName.trim(),
2262
+ expiryMonth: cardExpiryMonth,
2263
+ expiryYear: yearSuffix,
2264
+ ccv: cardCcv
2265
+ },
2266
+ holderInfo: {
2267
+ name: holderName.trim(),
2268
+ email: holderEmail.trim(),
2269
+ cpfCnpj: cpfDigits,
2270
+ postalCode: postalDigits,
2271
+ addressNumber: holderAddressNumber.trim(),
2272
+ ...phoneDigits ? { phone: phoneDigits } : {}
2273
+ }
2274
+ });
2275
+ return;
2276
+ }
2277
+ void checkout({ cpf: cpfDigits, cycle, method: "pix-auto" });
2278
+ };
2279
+ const inputStyle = {
2280
+ width: "100%",
2281
+ padding: 10,
2282
+ fontSize: 14,
2283
+ borderRadius: 8,
2284
+ border: "1px solid #ccc",
2285
+ boxSizing: "border-box"
2286
+ };
2287
+ const labelStyle = { display: "block", fontSize: 13, opacity: 0.75, marginBottom: 4 };
2288
+ const fieldGroup = { marginBottom: 10, textAlign: "left" };
2289
+ return /* @__PURE__ */ jsxs18("main", { style: { padding: 24, maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
2148
2290
  /* @__PURE__ */ jsx24("h1", { style: { marginBottom: 8 }, children: p.title }),
2149
2291
  p.subtitle && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.7, marginBottom: 24 }, children: p.subtitle }),
2150
- /* @__PURE__ */ jsx24("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", marginBottom: 24 }, children: p.benefits.map((b) => /* @__PURE__ */ jsxs18("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
2292
+ /* @__PURE__ */ jsxs18(
2293
+ "div",
2294
+ {
2295
+ role: "group",
2296
+ "aria-label": "Per\xEDodo de cobran\xE7a",
2297
+ style: { display: "flex", gap: 8, marginBottom: 16 },
2298
+ children: [
2299
+ /* @__PURE__ */ jsx24(
2300
+ "button",
2301
+ {
2302
+ type: "button",
2303
+ "aria-pressed": cycle === "MONTHLY",
2304
+ onClick: () => setCycle("MONTHLY"),
2305
+ style: {
2306
+ flex: 1,
2307
+ padding: 12,
2308
+ borderRadius: 8,
2309
+ border: cycle === "MONTHLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2310
+ background: cycle === "MONTHLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2311
+ cursor: "pointer"
2312
+ },
2313
+ children: "Mensal"
2314
+ }
2315
+ ),
2316
+ /* @__PURE__ */ jsxs18(
2317
+ "button",
2318
+ {
2319
+ type: "button",
2320
+ "aria-pressed": cycle === "YEARLY",
2321
+ onClick: () => setCycle("YEARLY"),
2322
+ disabled: !yearlyCents,
2323
+ style: {
2324
+ flex: 1,
2325
+ padding: 12,
2326
+ borderRadius: 8,
2327
+ border: cycle === "YEARLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2328
+ background: cycle === "YEARLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2329
+ cursor: yearlyCents ? "pointer" : "not-allowed",
2330
+ opacity: yearlyCents ? 1 : 0.5
2331
+ },
2332
+ children: [
2333
+ "Anual",
2334
+ pct > 0 ? ` \u2212${pct}%` : ""
2335
+ ]
2336
+ }
2337
+ )
2338
+ ]
2339
+ }
2340
+ ),
2341
+ /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 8 }, children: [
2342
+ /* @__PURE__ */ jsxs18("div", { style: { fontSize: 32, fontWeight: 700, lineHeight: 1 }, children: [
2343
+ formatBRL(activeCents),
2344
+ /* @__PURE__ */ jsx24("span", { style: { fontSize: 16, fontWeight: 400, opacity: 0.7 }, children: "/m\xEAs" })
2345
+ ] }),
2346
+ anchorCents && /* @__PURE__ */ jsxs18("div", { style: { fontSize: 14, opacity: 0.6, marginTop: 4 }, children: [
2347
+ /* @__PURE__ */ jsx24("span", { style: { textDecoration: "line-through" }, children: formatBRL(anchorCents) }),
2348
+ anchorDiscount > 0 && /* @__PURE__ */ jsxs18("span", { style: { marginLeft: 6 }, children: [
2349
+ "\u2212",
2350
+ anchorDiscount,
2351
+ "%"
2352
+ ] })
2353
+ ] }),
2354
+ cycle === "YEARLY" && yearlyCents && /* @__PURE__ */ jsxs18("div", { style: { fontSize: 12, opacity: 0.6, marginTop: 4 }, children: [
2355
+ "Cobrado ",
2356
+ formatBRL(yearlyCents),
2357
+ " por ano"
2358
+ ] })
2359
+ ] }),
2360
+ /* @__PURE__ */ jsx24("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", margin: "24px 0" }, children: p.benefits.map((b) => /* @__PURE__ */ jsxs18("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
2151
2361
  /* @__PURE__ */ jsx24("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
2152
2362
  /* @__PURE__ */ jsx24("span", { children: b })
2153
2363
  ] }, b)) }),
2154
- /* @__PURE__ */ jsxs18("div", { style: { textAlign: "left", marginBottom: 16 }, children: [
2155
- /* @__PURE__ */ jsx24("label", { style: { display: "block", fontSize: 14, opacity: 0.7, marginBottom: 4 }, children: "Seu CPF (pra emiss\xE3o de recibo)" }),
2364
+ /* @__PURE__ */ jsx24("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", style: { display: "flex", gap: 8, marginBottom: 16 }, children: availableMethods.map((m) => /* @__PURE__ */ jsx24(
2365
+ "button",
2366
+ {
2367
+ type: "button",
2368
+ role: "tab",
2369
+ "aria-selected": method === m,
2370
+ onClick: () => setMethod(m),
2371
+ style: {
2372
+ flex: 1,
2373
+ padding: 10,
2374
+ borderRadius: 8,
2375
+ border: method === m ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2376
+ background: method === m ? "var(--hook-color-primary-soft, #eef)" : "white",
2377
+ cursor: "pointer"
2378
+ },
2379
+ children: m === "card" ? "\u{1F4B3} Cart\xE3o" : "\u{1F4F1} Pix Autom\xE1tico"
2380
+ },
2381
+ m
2382
+ )) }),
2383
+ method === "card" && /* @__PURE__ */ jsxs18(
2384
+ "fieldset",
2385
+ {
2386
+ "data-testid": "paywall-card-form",
2387
+ style: { border: "none", padding: 0, marginBottom: 16 },
2388
+ children: [
2389
+ /* @__PURE__ */ jsx24("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8 }, children: "Dados do cart\xE3o" }),
2390
+ /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2391
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-number", style: labelStyle, children: "N\xFAmero do cart\xE3o" }),
2392
+ /* @__PURE__ */ jsx24(
2393
+ "input",
2394
+ {
2395
+ id: "pw-card-number",
2396
+ "data-testid": "pw-card-number",
2397
+ type: "text",
2398
+ inputMode: "numeric",
2399
+ autoComplete: "cc-number",
2400
+ placeholder: "0000 0000 0000 0000",
2401
+ value: cardNumber,
2402
+ onChange: (e) => setCardNumber(e.target.value),
2403
+ style: inputStyle
2404
+ }
2405
+ )
2406
+ ] }),
2407
+ /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2408
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-holder", style: labelStyle, children: "Nome impresso no cart\xE3o" }),
2409
+ /* @__PURE__ */ jsx24(
2410
+ "input",
2411
+ {
2412
+ id: "pw-card-holder",
2413
+ "data-testid": "pw-card-holder",
2414
+ type: "text",
2415
+ autoComplete: "cc-name",
2416
+ placeholder: "NOME SOBRENOME",
2417
+ value: cardHolderName,
2418
+ onChange: (e) => setCardHolderName(e.target.value),
2419
+ style: inputStyle
2420
+ }
2421
+ )
2422
+ ] }),
2423
+ /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2424
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2425
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-exp-m", style: labelStyle, children: "M\xEAs" }),
2426
+ /* @__PURE__ */ jsx24(
2427
+ "input",
2428
+ {
2429
+ id: "pw-card-exp-m",
2430
+ "data-testid": "pw-card-exp-m",
2431
+ type: "text",
2432
+ inputMode: "numeric",
2433
+ autoComplete: "cc-exp-month",
2434
+ placeholder: "MM",
2435
+ maxLength: 2,
2436
+ value: cardExpiryMonth,
2437
+ onChange: (e) => setCardExpiryMonth(e.target.value.replace(/\D/g, "")),
2438
+ style: inputStyle
2439
+ }
2440
+ )
2441
+ ] }),
2442
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2443
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-exp-y", style: labelStyle, children: "Ano" }),
2444
+ /* @__PURE__ */ jsx24(
2445
+ "input",
2446
+ {
2447
+ id: "pw-card-exp-y",
2448
+ "data-testid": "pw-card-exp-y",
2449
+ type: "text",
2450
+ inputMode: "numeric",
2451
+ autoComplete: "cc-exp-year",
2452
+ placeholder: "AA",
2453
+ maxLength: 4,
2454
+ value: cardExpiryYear,
2455
+ onChange: (e) => setCardExpiryYear(e.target.value.replace(/\D/g, "")),
2456
+ style: inputStyle
2457
+ }
2458
+ )
2459
+ ] }),
2460
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2461
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-cvv", style: labelStyle, children: "CVV" }),
2462
+ /* @__PURE__ */ jsx24(
2463
+ "input",
2464
+ {
2465
+ id: "pw-card-cvv",
2466
+ "data-testid": "pw-card-cvv",
2467
+ type: "text",
2468
+ inputMode: "numeric",
2469
+ autoComplete: "cc-csc",
2470
+ placeholder: "123",
2471
+ maxLength: 4,
2472
+ value: cardCcv,
2473
+ onChange: (e) => setCardCcv(e.target.value.replace(/\D/g, "")),
2474
+ style: inputStyle
2475
+ }
2476
+ )
2477
+ ] })
2478
+ ] }),
2479
+ /* @__PURE__ */ jsx24("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8, marginTop: 8 }, children: "Dados do titular" }),
2480
+ /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2481
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-name", style: labelStyle, children: "Nome completo" }),
2482
+ /* @__PURE__ */ jsx24(
2483
+ "input",
2484
+ {
2485
+ id: "pw-holder-name",
2486
+ "data-testid": "pw-holder-name",
2487
+ type: "text",
2488
+ autoComplete: "name",
2489
+ placeholder: "Nome Sobrenome",
2490
+ value: holderName,
2491
+ onChange: (e) => setHolderName(e.target.value),
2492
+ style: inputStyle
2493
+ }
2494
+ )
2495
+ ] }),
2496
+ /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2497
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-email", style: labelStyle, children: "E-mail" }),
2498
+ /* @__PURE__ */ jsx24(
2499
+ "input",
2500
+ {
2501
+ id: "pw-holder-email",
2502
+ "data-testid": "pw-holder-email",
2503
+ type: "email",
2504
+ autoComplete: "email",
2505
+ placeholder: "voce@email.com",
2506
+ value: holderEmail,
2507
+ onChange: (e) => setHolderEmail(e.target.value),
2508
+ style: inputStyle
2509
+ }
2510
+ )
2511
+ ] }),
2512
+ /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2513
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2514
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-cep", style: labelStyle, children: "CEP" }),
2515
+ /* @__PURE__ */ jsx24(
2516
+ "input",
2517
+ {
2518
+ id: "pw-holder-cep",
2519
+ "data-testid": "pw-holder-cep",
2520
+ type: "text",
2521
+ inputMode: "numeric",
2522
+ autoComplete: "postal-code",
2523
+ placeholder: "00000-000",
2524
+ value: holderPostalCode,
2525
+ onChange: (e) => setHolderPostalCode(e.target.value),
2526
+ style: inputStyle
2527
+ }
2528
+ )
2529
+ ] }),
2530
+ /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2531
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-addr-n", style: labelStyle, children: "N\xFAmero" }),
2532
+ /* @__PURE__ */ jsx24(
2533
+ "input",
2534
+ {
2535
+ id: "pw-holder-addr-n",
2536
+ "data-testid": "pw-holder-addr-n",
2537
+ type: "text",
2538
+ inputMode: "numeric",
2539
+ placeholder: "123",
2540
+ value: holderAddressNumber,
2541
+ onChange: (e) => setHolderAddressNumber(e.target.value),
2542
+ style: inputStyle
2543
+ }
2544
+ )
2545
+ ] })
2546
+ ] }),
2547
+ /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2548
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-phone", style: labelStyle, children: "Telefone (opcional)" }),
2549
+ /* @__PURE__ */ jsx24(
2550
+ "input",
2551
+ {
2552
+ id: "pw-holder-phone",
2553
+ "data-testid": "pw-holder-phone",
2554
+ type: "tel",
2555
+ inputMode: "tel",
2556
+ autoComplete: "tel",
2557
+ placeholder: "(11) 99999-9999",
2558
+ value: holderPhone,
2559
+ onChange: (e) => setHolderPhone(e.target.value),
2560
+ style: inputStyle
2561
+ }
2562
+ )
2563
+ ] })
2564
+ ]
2565
+ }
2566
+ ),
2567
+ /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2568
+ /* @__PURE__ */ jsx24("label", { htmlFor: "pw-cpf", style: labelStyle, children: "Seu CPF" }),
2156
2569
  /* @__PURE__ */ jsx24(
2157
2570
  "input",
2158
2571
  {
2572
+ id: "pw-cpf",
2159
2573
  "data-testid": "paywall-cpf",
2160
2574
  type: "text",
2161
2575
  inputMode: "numeric",
2162
2576
  placeholder: "000.000.000-00",
2163
2577
  value: cpf,
2164
2578
  onChange: (e) => setCpf(e.target.value),
2165
- style: { width: "100%", padding: 10, fontSize: 14, borderRadius: 8, border: "1px solid #ccc" }
2579
+ style: inputStyle
2166
2580
  }
2167
2581
  )
2168
2582
  ] }),
@@ -2172,7 +2586,7 @@ function DefaultPaywall() {
2172
2586
  {
2173
2587
  "data-testid": "paywall-cta",
2174
2588
  type: "button",
2175
- onClick: () => void checkout({ cpf: cpfDigits }),
2589
+ onClick: submit,
2176
2590
  disabled: !canCheckout,
2177
2591
  style: {
2178
2592
  width: "100%",
@@ -2183,21 +2597,101 @@ function DefaultPaywall() {
2183
2597
  borderRadius: 8,
2184
2598
  opacity: canCheckout ? 1 : 0.5,
2185
2599
  fontSize: 16,
2186
- fontWeight: 600
2600
+ fontWeight: 600,
2601
+ cursor: canCheckout ? "pointer" : "not-allowed"
2187
2602
  },
2188
- children: opening ? "Abrindo..." : p.cta
2603
+ children: ctaLabel
2189
2604
  }
2190
2605
  ),
2191
- p.priceHint && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.6, marginTop: 12 }, children: p.priceHint }),
2192
- p.footerNote && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.5, marginTop: 16, fontSize: 12 }, children: p.footerNote })
2606
+ /* @__PURE__ */ jsx24("p", { style: { opacity: 0.55, marginTop: 16, fontSize: 12 }, children: footer }),
2607
+ p.priceHint && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.6, marginTop: 8, fontSize: 12 }, children: p.priceHint }),
2608
+ p.footerNote && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.5, marginTop: 8, fontSize: 12 }, children: p.footerNote }),
2609
+ pixPending && /* @__PURE__ */ jsx24(
2610
+ "div",
2611
+ {
2612
+ "data-testid": "paywall-pix-modal",
2613
+ role: "dialog",
2614
+ "aria-label": "Pagamento Pix pendente",
2615
+ style: {
2616
+ position: "fixed",
2617
+ inset: 0,
2618
+ background: "rgba(0,0,0,0.6)",
2619
+ display: "flex",
2620
+ alignItems: "center",
2621
+ justifyContent: "center",
2622
+ padding: 24,
2623
+ zIndex: 1e3
2624
+ },
2625
+ children: /* @__PURE__ */ jsxs18("div", { style: {
2626
+ background: "#fff",
2627
+ borderRadius: 12,
2628
+ padding: 24,
2629
+ maxWidth: 360,
2630
+ width: "100%",
2631
+ textAlign: "center"
2632
+ }, children: [
2633
+ pixPending.paid ? /* @__PURE__ */ jsxs18(Fragment6, { children: [
2634
+ /* @__PURE__ */ jsx24("h2", { style: { marginTop: 0 }, children: "Pagamento confirmado!" }),
2635
+ /* @__PURE__ */ jsx24("p", { style: { opacity: 0.7 }, children: "Liberando acesso\u2026" })
2636
+ ] }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
2637
+ /* @__PURE__ */ jsx24("h2", { style: { marginTop: 0, fontSize: 18 }, children: "Pague com Pix Autom\xE1tico" }),
2638
+ /* @__PURE__ */ jsx24("p", { style: { fontSize: 13, opacity: 0.75 }, children: "Escaneie o QR Code no app do seu banco pra autorizar o d\xE9bito recorrente." }),
2639
+ pixPending.qrCodeBase64 && /* @__PURE__ */ jsx24(
2640
+ "img",
2641
+ {
2642
+ "data-testid": "pix-qr-image",
2643
+ alt: "QR Code Pix",
2644
+ src: `data:image/png;base64,${pixPending.qrCodeBase64}`,
2645
+ style: { width: "100%", maxWidth: 240, margin: "12px auto", display: "block" }
2646
+ }
2647
+ ),
2648
+ pixPending.qrCodePayload && /* @__PURE__ */ jsx24(
2649
+ "textarea",
2650
+ {
2651
+ "data-testid": "pix-qr-payload",
2652
+ readOnly: true,
2653
+ value: pixPending.qrCodePayload,
2654
+ style: {
2655
+ width: "100%",
2656
+ minHeight: 72,
2657
+ padding: 8,
2658
+ fontSize: 11,
2659
+ fontFamily: "monospace",
2660
+ borderRadius: 6,
2661
+ border: "1px solid #ccc",
2662
+ resize: "none"
2663
+ }
2664
+ }
2665
+ ),
2666
+ /* @__PURE__ */ jsx24("p", { style: { fontSize: 11, opacity: 0.55, marginTop: 12 }, children: "Aguardando confirma\xE7\xE3o do banco\u2026 Pode levar alguns segundos." })
2667
+ ] }),
2668
+ /* @__PURE__ */ jsx24(
2669
+ "button",
2670
+ {
2671
+ type: "button",
2672
+ onClick: dismissPix,
2673
+ style: {
2674
+ marginTop: 16,
2675
+ padding: "8px 16px",
2676
+ border: "1px solid #ccc",
2677
+ borderRadius: 6,
2678
+ background: "white",
2679
+ cursor: "pointer"
2680
+ },
2681
+ children: "Fechar"
2682
+ }
2683
+ )
2684
+ ] })
2685
+ }
2686
+ )
2193
2687
  ] });
2194
2688
  }
2195
2689
 
2196
2690
  // src/AppRoot.tsx
2197
- import { Fragment as Fragment6, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
2691
+ import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
2198
2692
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2199
2693
  function PaymentReturnHandler({ children }) {
2200
- const { subscription } = useHook8();
2694
+ const { subscription } = useHook9();
2201
2695
  const subRef = useRef3(subscription);
2202
2696
  subRef.current = subscription;
2203
2697
  const runIdRef = useRef3(0);
@@ -2265,7 +2759,7 @@ function PaymentReturnHandler({ children }) {
2265
2759
  )
2266
2760
  ] }) });
2267
2761
  }
2268
- return /* @__PURE__ */ jsx25(Fragment6, { children });
2762
+ return /* @__PURE__ */ jsx25(Fragment7, { children });
2269
2763
  }
2270
2764
  var overlayStyle2 = {
2271
2765
  position: "fixed",
@@ -2306,7 +2800,7 @@ function AppRoot({
2306
2800
 
2307
2801
  // src/hooks/usePush.ts
2308
2802
  import { useCallback as useCallback8, useEffect as useEffect9, useState as useState13 } from "react";
2309
- import { useHook as useHook9 } from "@hook-sdk/sdk";
2803
+ import { useHook as useHook10 } from "@hook-sdk/sdk";
2310
2804
  function detectIosNeedsInstall() {
2311
2805
  if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2312
2806
  const ua = navigator.userAgent || "";
@@ -2332,7 +2826,7 @@ function deriveState(push) {
2332
2826
  return { kind: "prompt" };
2333
2827
  }
2334
2828
  function usePush() {
2335
- const { push } = useHook9();
2829
+ const { push } = useHook10();
2336
2830
  const [state, setState] = useState13(() => deriveState(push));
2337
2831
  useEffect9(() => {
2338
2832
  setState(deriveState(push));
@@ -2414,41 +2908,6 @@ function EmptyState({ title, description, action }) {
2414
2908
  ] });
2415
2909
  }
2416
2910
 
2417
- // src/hooks/usePlan.ts
2418
- import { useHook as useHook10 } from "@hook-sdk/sdk";
2419
- function usePlan() {
2420
- const { plan } = useHook10();
2421
- return plan;
2422
- }
2423
-
2424
- // src/utils/price.ts
2425
- function formatBRL(cents) {
2426
- if (cents === null || cents === void 0) return "";
2427
- const reais = cents / 100;
2428
- return new Intl.NumberFormat("pt-BR", {
2429
- style: "currency",
2430
- currency: "BRL"
2431
- }).format(reais);
2432
- }
2433
- function monthlyFromYearly(yearlyCents) {
2434
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2435
- return Math.round(yearlyCents / 12);
2436
- }
2437
- function dailyFromYearly(yearlyCents) {
2438
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2439
- return Math.round(yearlyCents / 365);
2440
- }
2441
- function computeAnchorCents(baseCents, multiplier) {
2442
- if (multiplier === null || multiplier === void 0) return null;
2443
- if (!Number.isFinite(multiplier)) return null;
2444
- if (multiplier <= 1) return null;
2445
- return Math.round(baseCents * multiplier);
2446
- }
2447
- function discountPercent(anchorCents, realCents) {
2448
- if (anchorCents <= realCents) return 0;
2449
- return Math.floor((anchorCents - realCents) / anchorCents * 100);
2450
- }
2451
-
2452
2911
  // src/hooks/useAuthPrimitives.ts
2453
2912
  import { useEffect as useEffect10 } from "react";
2454
2913
  import { useHook as useHook11 } from "@hook-sdk/sdk";