@hook-sdk/template 0.9.1 → 0.10.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,11 +1,142 @@
1
1
  // src/AppRoot.tsx
2
- import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef3, useState as useState12 } from "react";
3
- import { useHook as useHook9 } from "@hook-sdk/sdk";
2
+ import { useMemo as useMemo3 } from "react";
3
+ import { BrowserRouter, MemoryRouter, Navigate, Route, Routes } from "react-router-dom";
4
+ import { useHook as useHook4 } from "@hook-sdk/sdk";
4
5
 
5
- // src/internal/TemplateConfigContext.tsx
6
- import { createContext, useContext, useMemo } from "react";
6
+ // src/config/AppConfigContext.tsx
7
+ import { createContext, useContext } from "react";
7
8
  import { jsx } from "react/jsx-runtime";
8
- var TemplateConfigContext = createContext(null);
9
+ var AppConfigContext = createContext(null);
10
+ function AppConfigProvider({
11
+ config,
12
+ children
13
+ }) {
14
+ return /* @__PURE__ */ jsx(AppConfigContext.Provider, { value: config, children });
15
+ }
16
+ function useAppConfig() {
17
+ const v = useContext(AppConfigContext);
18
+ if (!v) {
19
+ throw new Error(
20
+ "[hook-template] useAppConfig: AppConfigProvider missing \u2014 wrap your app in <AppRoot>"
21
+ );
22
+ }
23
+ return v;
24
+ }
25
+
26
+ // src/config/schema.ts
27
+ import { z } from "zod";
28
+ var SnakeKeyRE = /^[a-z0-9][a-z0-9_.-]{0,127}$/;
29
+ var AuthFlowSchema = z.object({
30
+ minPassword: z.number().int().min(6).max(64),
31
+ requiresEmailVerify: z.boolean(),
32
+ googleOAuth: z.boolean(),
33
+ postAuthLanding: z.string().startsWith("/"),
34
+ preAuthRoutes: z.array(z.string().startsWith("/"))
35
+ });
36
+ var PaywallNonFreeSchema = z.object({
37
+ mode: z.enum(["trial", "pay_first"]),
38
+ trialDays: z.number().int().nonnegative().optional(),
39
+ cycles: z.array(z.enum(["MONTHLY", "YEARLY"])).min(1),
40
+ prices: z.object({
41
+ monthlyCents: z.number().int().nonnegative(),
42
+ yearlyCents: z.number().int().nonnegative()
43
+ }),
44
+ anchorPrices: z.object({
45
+ monthlyCents: z.number().int().nonnegative(),
46
+ yearlyCents: z.number().int().nonnegative()
47
+ }).optional(),
48
+ checkoutMethods: z.array(z.enum(["card", "pix-auto", "pix-once"])).min(1),
49
+ requiresCpf: z.boolean(),
50
+ cancelWindowDays: z.number().int().nonnegative().optional(),
51
+ errorMessages: z.enum(["default", "custom"])
52
+ });
53
+ var PaywallFreeSchema = z.object({ mode: z.literal("free") });
54
+ var PaywallSchema = z.discriminatedUnion("mode", [PaywallNonFreeSchema, PaywallFreeSchema]);
55
+ var PersistedKeySchema = z.object({
56
+ key: z.string().regex(SnakeKeyRE, "key must be snake_case (matches /^[a-z0-9][a-z0-9_.-]{0,127}$/)"),
57
+ default: z.unknown(),
58
+ guardRegen: z.boolean().optional(),
59
+ debounceMs: z.number().int().positive().optional()
60
+ });
61
+ var OnboardingSchema = z.object({
62
+ trigger: z.enum(["pre_signup", "post_signup", "pre_signup_custom", "optional"]),
63
+ steps: z.array(
64
+ z.object({
65
+ id: z.string().regex(SnakeKeyRE),
66
+ screen: z.string(),
67
+ validates: z.array(z.string()).optional()
68
+ })
69
+ ).min(1),
70
+ persistTo: z.literal("appData"),
71
+ persistKey: z.string().regex(SnakeKeyRE)
72
+ });
73
+ var DeepLinksSchema = z.object({
74
+ passwordReset: z.string().startsWith("/").optional(),
75
+ emailVerify: z.string().startsWith("/").optional()
76
+ }).strict();
77
+ var AppConfigSchema = z.object({
78
+ slug: z.string().regex(/^[a-z0-9-]+$/),
79
+ name: z.string().min(1),
80
+ branding: z.object({ primaryColor: z.string(), logoUrl: z.string().url() }),
81
+ authFlow: AuthFlowSchema,
82
+ paywall: PaywallSchema,
83
+ persistedKeys: z.array(PersistedKeySchema),
84
+ onboarding: OnboardingSchema.optional(),
85
+ deepLinks: DeepLinksSchema.optional(),
86
+ features_enabled: z.array(z.string()).optional()
87
+ }).strict();
88
+ function parseAppConfig(input) {
89
+ const r = AppConfigSchema.safeParse(input);
90
+ if (!r.success) {
91
+ const messages = r.error.issues.map((i) => `[${i.path.join(".")}] ${i.message}`).join("\n");
92
+ throw new Error(`Invalid app.config.json:
93
+ ${messages}`);
94
+ }
95
+ return r.data;
96
+ }
97
+
98
+ // src/PersistenceRegistry.tsx
99
+ import { useEffect } from "react";
100
+ import { useHook } from "@hook-sdk/sdk";
101
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
102
+ function PersistenceRegistry({ config, children }) {
103
+ const { appData } = useHook();
104
+ useEffect(() => {
105
+ if (config.length === 0) return;
106
+ const keys = config.map((c) => c.key);
107
+ const bulk = appData.bulkRead;
108
+ bulk?.(keys).catch(() => {
109
+ });
110
+ }, [config, appData]);
111
+ return /* @__PURE__ */ jsx2(Fragment, { children });
112
+ }
113
+
114
+ // src/DeepLinkHandler.tsx
115
+ import { useEffect as useEffect2 } from "react";
116
+ import { useLocation, useNavigate } from "react-router-dom";
117
+ function DeepLinkHandler({ deepLinks }) {
118
+ const nav = useNavigate();
119
+ const loc = useLocation();
120
+ useEffect2(() => {
121
+ if (!deepLinks) return;
122
+ const params = new URLSearchParams(loc.search);
123
+ const token = params.get("token");
124
+ if (!token) return;
125
+ if (deepLinks.passwordReset && deepLinks.passwordReset.includes(":token") && loc.pathname === "/") {
126
+ nav(deepLinks.passwordReset.replace(":token", token));
127
+ return;
128
+ }
129
+ if (deepLinks.emailVerify && deepLinks.emailVerify.includes(":token") && loc.pathname === "/") {
130
+ nav(deepLinks.emailVerify.replace(":token", token));
131
+ }
132
+ }, [deepLinks, loc, nav]);
133
+ return null;
134
+ }
135
+
136
+ // src/internal/TemplateConfigContext.tsx
137
+ import { createContext as createContext2, useContext as useContext2, useMemo } from "react";
138
+ import { jsx as jsx3 } from "react/jsx-runtime";
139
+ var TemplateConfigContext = createContext2(null);
9
140
  function TemplateConfigProvider({
10
141
  config,
11
142
  children
@@ -14,10 +145,10 @@ function TemplateConfigProvider({
14
145
  ...config,
15
146
  mode: config.subscription?.mode ?? "trial"
16
147
  }), [config]);
17
- return /* @__PURE__ */ jsx(TemplateConfigContext.Provider, { value, children });
148
+ return /* @__PURE__ */ jsx3(TemplateConfigContext.Provider, { value, children });
18
149
  }
19
150
  function useTemplateConfig() {
20
- const ctx = useContext(TemplateConfigContext);
151
+ const ctx = useContext2(TemplateConfigContext);
21
152
  if (ctx === null) {
22
153
  throw new Error("useTemplateConfig must be used inside <TemplateConfigProvider>");
23
154
  }
@@ -25,7 +156,7 @@ function useTemplateConfig() {
25
156
  }
26
157
 
27
158
  // src/internal/ThemeProvider.tsx
28
- import { jsx as jsx2 } from "react/jsx-runtime";
159
+ import { jsx as jsx4 } from "react/jsx-runtime";
29
160
  function ThemeProvider({ children }) {
30
161
  const config = useTemplateConfig();
31
162
  const style = {
@@ -34,182 +165,308 @@ function ThemeProvider({ children }) {
34
165
  "--hook-color-background": config.theme.background_color
35
166
  }
36
167
  };
37
- return /* @__PURE__ */ jsx2("div", { style, children });
168
+ return /* @__PURE__ */ jsx4("div", { style, children });
38
169
  }
39
170
 
40
- // src/internal/AuthGate.tsx
41
- import { useEffect, useState } from "react";
42
-
43
- // src/hooks/useAuth.ts
44
- import { useHook } from "@hook-sdk/sdk";
45
- function useAuth() {
46
- const { user, authStatus, auth } = useHook();
47
- return {
48
- user,
49
- authStatus,
50
- refresh: auth.refresh
51
- };
52
- }
53
-
54
- // src/defaults/LoadingState.tsx
55
- import { jsx as jsx3 } from "react/jsx-runtime";
56
- function LoadingState({ message }) {
57
- return /* @__PURE__ */ jsx3("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx3("span", { children: message ?? "Carregando..." }) });
58
- }
171
+ // src/hooks/usePaywallState.ts
172
+ import { useCallback, useContext as useContext3, useMemo as useMemo2, useState } from "react";
173
+ import { useHook as useHook2 } from "@hook-sdk/sdk";
59
174
 
60
- // src/internal/AuthGate.tsx
61
- import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
62
- function detectResetFromUrl() {
63
- if (typeof window === "undefined") return false;
64
- const params = new URLSearchParams(window.location.search);
65
- return params.get("token") !== null && params.get("screen") === "reset";
66
- }
67
- function AuthGate({ Login, Signup, Forgot, Reset, children }) {
68
- const { user, authStatus } = useAuth();
69
- const [screen, setScreen] = useState(
70
- () => detectResetFromUrl() ? "reset" : "login"
71
- );
72
- useEffect(() => {
73
- if (user && screen !== "login") {
74
- setScreen("login");
75
- }
76
- }, [user, screen]);
77
- useEffect(() => {
78
- const onPop = () => {
79
- if (detectResetFromUrl()) setScreen("reset");
80
- };
81
- window.addEventListener("popstate", onPop);
82
- return () => window.removeEventListener("popstate", onPop);
83
- }, []);
84
- if (authStatus === "loading") return /* @__PURE__ */ jsx4(LoadingState, {});
85
- if (!user) {
86
- if (screen === "reset") return /* @__PURE__ */ jsx4(Reset, { onNavigate: setScreen });
87
- if (screen === "signup") return /* @__PURE__ */ jsx4(Signup, { onNavigate: setScreen });
88
- if (screen === "forgot") return /* @__PURE__ */ jsx4(Forgot, { onNavigate: setScreen });
89
- return /* @__PURE__ */ jsx4(Login, { onNavigate: setScreen });
90
- }
91
- return /* @__PURE__ */ jsx4(Fragment, { children });
175
+ // src/errors/asaas-pt-br.ts
176
+ var MAP = {
177
+ invalid_cpf: "CPF inv\xE1lido. Confira os n\xFAmeros e tente novamente.",
178
+ cpf_required: "Por favor, informe seu CPF para continuar.",
179
+ card_declined: "Cart\xE3o recusado pela operadora. Tente outro cart\xE3o ou m\xE9todo.",
180
+ insufficient_funds: "Saldo insuficiente no cart\xE3o. Tente outro m\xE9todo.",
181
+ card_expired: "Cart\xE3o expirado. Use um cart\xE3o v\xE1lido.",
182
+ invalid_card_number: "N\xFAmero de cart\xE3o inv\xE1lido.",
183
+ invalid_cvv: "C\xF3digo de seguran\xE7a (CVV) inv\xE1lido.",
184
+ invalid_expiration: "Data de validade inv\xE1lida.",
185
+ generic_decline: "Pagamento recusado. Tente novamente em instantes ou use outro m\xE9todo.",
186
+ webhook_unverified: "N\xE3o conseguimos confirmar seu pagamento. Atualize a p\xE1gina em alguns segundos.",
187
+ pix_expired: "QR Code do PIX expirou. Gere um novo.",
188
+ pix_not_paid_yet: "PIX ainda n\xE3o foi pago. Aguardando confirma\xE7\xE3o."
189
+ };
190
+ function asaasErrorMessage(code) {
191
+ return MAP[code] ?? "Ocorreu um erro inesperado. Tente novamente em instantes.";
92
192
  }
93
193
 
94
194
  // src/hooks/usePaywallState.ts
95
- import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef, useState as useState2 } from "react";
96
- import { useHook as useHook2 } from "@hook-sdk/sdk";
195
+ function isCheckoutFailure(r) {
196
+ return "ok" in r && r.ok === false;
197
+ }
198
+ var FALLBACK_PAYWALL = {
199
+ mode: "pay_first",
200
+ cycles: ["MONTHLY"],
201
+ prices: { monthlyCents: 0, yearlyCents: 0 },
202
+ checkoutMethods: ["card", "pix-auto"],
203
+ requiresCpf: true,
204
+ errorMessages: "default"
205
+ };
206
+ var isMethodAvailable = (availability, method) => availability[method] !== false;
97
207
  function usePaywallState() {
98
208
  const { subscription, plan } = useHook2();
99
- const [opening, setOpening] = useState2(false);
100
- const [error, setError] = useState2(null);
101
- const [pixPending, setPixPending] = useState2(null);
209
+ const configFromCtx = useContext3(AppConfigContext);
210
+ const paywall = configFromCtx?.paywall ?? FALLBACK_PAYWALL;
211
+ const isFree = paywall.mode === "free";
212
+ const declaredMethods = useMemo2(
213
+ () => isFree ? [] : paywall.checkoutMethods,
214
+ [isFree, paywall]
215
+ );
216
+ const availability = subscription.methodAvailability ?? {
217
+ card: null,
218
+ "pix-auto": null,
219
+ "pix-once": null
220
+ };
221
+ const methods = useMemo2(
222
+ () => declaredMethods.filter((m) => isMethodAvailable(availability, m)),
223
+ [declaredMethods, availability]
224
+ );
225
+ const defaultMethod = methods[0] ?? declaredMethods[0] ?? "card";
226
+ const [selectedMethodRaw, setSelectedMethod] = useState(defaultMethod);
227
+ const selectedMethod = methods.includes(selectedMethodRaw) ? selectedMethodRaw : methods[0] ?? selectedMethodRaw;
228
+ const initialCycle = isFree ? "MONTHLY" : paywall.cycles[0] ?? "MONTHLY";
229
+ const [cycle, setCycle] = useState(initialCycle);
230
+ const cpfRequired = !isFree && paywall.requiresCpf;
231
+ const [cpf, setCpf] = useState("");
232
+ const cpfValid = useMemo2(() => /^[0-9]{11}$/.test(cpf), [cpf]);
233
+ const [card, setCardState] = useState({
234
+ number: "",
235
+ cvv: "",
236
+ expiry: "",
237
+ holder: ""
238
+ });
239
+ const setCard = useCallback((patch) => {
240
+ setCardState((prev) => ({ ...prev, ...patch }));
241
+ }, []);
242
+ const [error, setError] = useState(null);
243
+ const [submitting, setSubmitting] = useState(false);
102
244
  const status = subscription.status();
103
245
  const daysLeftInTrial = subscription.daysLeftInTrial();
104
246
  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;
247
+ const pixPending = useMemo2(() => {
248
+ const sdkPix = subscription.pixPending;
249
+ if (!sdkPix) return null;
250
+ const liveStatus = subscription.current?.status;
251
+ const paid = liveStatus === "ACTIVE" || liveStatus === "TRIAL";
252
+ return {
253
+ method: sdkPix.method,
254
+ qrCodePayload: sdkPix.qrCodePayload,
255
+ qrCodeBase64: sdkPix.qrCodeBase64,
256
+ expiresAt: null,
257
+ paid
258
+ };
259
+ }, [subscription.pixPending, subscription.current]);
111
260
  const monthlyEquivalent = useCallback(
112
- (cycle) => {
113
- if (cycle === "YEARLY" && yearlyPriceCents) {
114
- return Math.round(yearlyPriceCents / 12);
115
- }
116
- return priceCents;
261
+ (c) => {
262
+ const monthlyCents = plan.data?.priceCents ?? (isFree ? 0 : paywall.prices.monthlyCents);
263
+ const yearlyCents = plan.data?.yearlyPriceCents ?? (isFree ? null : paywall.prices.yearlyCents);
264
+ if (c === "YEARLY" && yearlyCents) return Math.round(yearlyCents / 12);
265
+ return monthlyCents;
117
266
  },
118
- [priceCents, yearlyPriceCents]
267
+ [plan, paywall, isFree]
119
268
  );
269
+ const planDerived = useMemo2(() => {
270
+ if (isFree) return null;
271
+ const monthlyCents = paywall.prices.monthlyCents;
272
+ const yearlyCents = paywall.prices.yearlyCents;
273
+ const anchor = paywall.anchorPrices;
274
+ const discount = anchor && anchor.yearlyCents > 0 ? Math.round((1 - paywall.prices.yearlyCents / anchor.yearlyCents) * 100) : 0;
275
+ return {
276
+ monthlyCents,
277
+ yearlyCents,
278
+ anchorMonthlyCents: anchor?.monthlyCents,
279
+ anchorYearlyCents: anchor?.yearlyCents,
280
+ monthlyEquivalent: cycle === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents,
281
+ discountPercent: discount
282
+ };
283
+ }, [paywall, cycle, isFree]);
284
+ const useDefaultMessages = paywall.mode !== "free" && paywall.errorMessages === "default";
285
+ const buildError = useCallback(
286
+ (code, fallbackMessage) => ({
287
+ code,
288
+ message: fallbackMessage,
289
+ userMessage: useDefaultMessages ? asaasErrorMessage(code) : fallbackMessage
290
+ }),
291
+ [useDefaultMessages]
292
+ );
293
+ const submit = useCallback(async () => {
294
+ setSubmitting(true);
295
+ setError(null);
296
+ const methodToUse = selectedMethod;
297
+ if (!isMethodAvailable(availability, methodToUse)) {
298
+ const code = "method_unavailable";
299
+ setError(buildError(code, `method ${methodToUse} unavailable`));
300
+ setSubmitting(false);
301
+ return {
302
+ ok: false,
303
+ code: "method_unavailable",
304
+ method: methodToUse,
305
+ reason: "pre_flight_unavailable"
306
+ };
307
+ }
308
+ try {
309
+ let result;
310
+ if (methodToUse === "card") {
311
+ const [expMonthRaw = "", expYearRaw = ""] = card.expiry.split("/");
312
+ const expYearTrimmed = expYearRaw.trim();
313
+ const cardData = {
314
+ number: card.number,
315
+ holderName: card.holder,
316
+ expiryMonth: expMonthRaw.trim(),
317
+ expiryYear: expYearTrimmed.length === 2 ? `20${expYearTrimmed}` : expYearTrimmed,
318
+ ccv: card.cvv
319
+ };
320
+ const holderInfo = {
321
+ name: card.holder,
322
+ email: "",
323
+ cpfCnpj: cpf,
324
+ postalCode: "",
325
+ addressNumber: ""
326
+ };
327
+ result = await subscription.checkout({
328
+ method: "card",
329
+ cycle,
330
+ cpf,
331
+ card: cardData,
332
+ holderInfo
333
+ });
334
+ } else if (methodToUse === "pix-auto") {
335
+ result = await subscription.checkout({ method: "pix-auto", cycle, cpf });
336
+ } else {
337
+ result = await subscription.checkout({ method: "pix-once", cycle, cpf });
338
+ }
339
+ if (isCheckoutFailure(result)) {
340
+ setError(buildError(result.code, `${result.code}: ${result.reason}`));
341
+ setSubmitting(false);
342
+ return result;
343
+ }
344
+ await subscription.refresh();
345
+ setSubmitting(false);
346
+ return result;
347
+ } catch (err) {
348
+ const code = err?.code ?? "generic_decline";
349
+ const message = err instanceof Error ? err.message : String(err);
350
+ setError(buildError(code, message));
351
+ setSubmitting(false);
352
+ return void 0;
353
+ }
354
+ }, [selectedMethod, availability, subscription, cycle, cpf, card, buildError]);
120
355
  const checkout = useCallback(
121
356
  async (args) => {
122
- setOpening(true);
357
+ setSubmitting(true);
123
358
  setError(null);
124
- setPixPending(null);
125
359
  try {
126
360
  if (args.method === "card") {
127
361
  if (!args.card || !args.holderInfo) {
128
- throw new Error('card and holderInfo are required when method is "card"');
362
+ throw Object.assign(
363
+ new Error('card and holderInfo are required when method is "card"'),
364
+ { code: "validation" }
365
+ );
129
366
  }
130
- await subscription.checkout({
367
+ const sdkArgs = {
131
368
  method: "card",
132
369
  cycle: args.cycle,
133
370
  cpf: args.cpf,
134
371
  card: args.card,
135
372
  holderInfo: args.holderInfo,
136
373
  ...args.remoteIp ? { remoteIp: args.remoteIp } : {}
137
- });
374
+ };
375
+ const result2 = await subscription.checkout(sdkArgs);
376
+ if (isCheckoutFailure(result2)) {
377
+ setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
378
+ setSubmitting(false);
379
+ return;
380
+ }
138
381
  await subscription.refresh();
139
- setOpening(false);
382
+ setSubmitting(false);
383
+ return;
384
+ }
385
+ if (args.method === "pix-auto") {
386
+ const result2 = await subscription.checkout({
387
+ method: "pix-auto",
388
+ cycle: args.cycle,
389
+ cpf: args.cpf
390
+ });
391
+ if (isCheckoutFailure(result2)) {
392
+ setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
393
+ setSubmitting(false);
394
+ return;
395
+ }
396
+ setSubmitting(false);
140
397
  return;
141
398
  }
142
399
  const result = await subscription.checkout({
143
- method: "pix-auto",
400
+ method: "pix-once",
144
401
  cycle: args.cycle,
145
402
  cpf: args.cpf
146
403
  });
147
- if (result.method !== "pix-auto") {
148
- throw new Error(`unexpected checkout result method: ${result.method}`);
404
+ if (isCheckoutFailure(result)) {
405
+ setError(buildError(result.code, `${result.code}: ${result.reason}`));
406
+ setSubmitting(false);
407
+ return;
149
408
  }
150
- setPixPending({
151
- method: "pix-auto",
152
- qrCodePayload: result.qrCodePayload,
153
- qrCodeBase64: result.qrCodeBase64,
154
- expiresAt: null,
155
- paid: false
156
- });
157
- setOpening(false);
409
+ setSubmitting(false);
158
410
  } catch (err) {
159
- setError(err);
160
- setOpening(false);
411
+ const code = err?.code ?? "generic_decline";
412
+ const message = err instanceof Error ? err.message : String(err);
413
+ setError(buildError(code, message));
414
+ setSubmitting(false);
161
415
  }
162
416
  },
163
- [subscription]
417
+ [subscription, buildError]
164
418
  );
165
419
  const cancel = useCallback(async () => {
166
420
  try {
167
421
  await subscription.cancel();
168
422
  await subscription.refresh();
169
423
  } catch (err) {
170
- setError(err);
424
+ const code = err?.code ?? "generic_decline";
425
+ const message = err instanceof Error ? err.message : String(err);
426
+ setError(buildError(code, message));
171
427
  }
172
- }, [subscription]);
173
- const dismissPix = useCallback(() => setPixPending(null), []);
174
- const subRef = useRef(subscription);
175
- subRef.current = subscription;
176
- useEffect2(() => {
177
- if (!pixPending || pixPending.paid) return;
178
- let attempts = 0;
179
- const MAX_ATTEMPTS = 60;
180
- let cancelled = false;
181
- const tick = async () => {
182
- if (cancelled || attempts >= MAX_ATTEMPTS) return;
183
- attempts++;
184
- try {
185
- await subRef.current.refresh();
186
- if (cancelled) return;
187
- const s = subRef.current.status();
188
- if (s === "active" || s === "trialing") {
189
- setPixPending((prev) => prev ? { ...prev, paid: true } : prev);
190
- return;
191
- }
192
- } catch {
193
- }
194
- if (!cancelled) setTimeout(tick, 3e3);
195
- };
196
- setTimeout(tick, 3e3);
197
- return () => {
198
- cancelled = true;
199
- };
200
- }, [pixPending]);
428
+ }, [subscription, buildError]);
429
+ const cardState = useMemo2(
430
+ () => ({ ...card, set: setCard }),
431
+ [card, setCard]
432
+ );
433
+ const cpfState = useMemo2(
434
+ () => ({ required: cpfRequired, value: cpf, set: setCpf, valid: cpfValid }),
435
+ [cpfRequired, cpf, cpfValid]
436
+ );
201
437
  return {
438
+ // Subscription status (reactive, proxied from SDK)
202
439
  status,
203
440
  daysLeftInTrial,
204
441
  initialLoadComplete,
442
+ // Plan derivation from config (sync, no fetch)
443
+ plan: planDerived,
444
+ // Cycle + method selection
445
+ cycle,
446
+ setCycle,
447
+ methods,
448
+ selectedMethod,
449
+ setSelectedMethod,
450
+ // Form state
451
+ cpfState,
452
+ cardState,
453
+ // High-level + legacy actions
454
+ submit,
205
455
  checkout,
206
456
  cancel,
207
- opening,
208
- error,
457
+ // PIX state (proxied from SDK; legacy `paid`/`expiresAt` derived)
209
458
  pixPending,
210
- dismissPix,
211
- availableMethods,
212
- monthlyEquivalent
459
+ // Error + submit progress
460
+ error,
461
+ submitting,
462
+ // Backward-compat aliases (deprecated; remove in template 0.11)
463
+ opening: submitting,
464
+ availableMethods: methods,
465
+ monthlyEquivalent,
466
+ dismissPix: () => {
467
+ },
468
+ refreshPlan: () => {
469
+ }
213
470
  };
214
471
  }
215
472
 
@@ -230,51 +487,11 @@ function SubscriptionGate({ Paywall, children }) {
230
487
  return /* @__PURE__ */ jsx5(Fragment2, { children });
231
488
  }
232
489
 
233
- // src/internal/PersistedKeysPrefetch.tsx
234
- import { useEffect as useEffect3, useState as useState3 } from "react";
235
- import { useHook as useHook3 } from "@hook-sdk/sdk";
236
- import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
237
- var SAFETY_TIMEOUT_MS = 3e3;
238
- function PersistedKeysPrefetch({ children }) {
239
- const { appData } = useHook3();
240
- const config = useTemplateConfig();
241
- const hasKeys = !!config.persistedKeys && config.persistedKeys.length > 0;
242
- const [ready, setReady] = useState3(!hasKeys);
243
- useEffect3(() => {
244
- const keys = config.persistedKeys;
245
- if (!keys || keys.length === 0) {
246
- setReady(true);
247
- return;
248
- }
249
- let cancelled = false;
250
- appData.cache.startPrefetch(keys, (ks) => appData.bulkRead(ks));
251
- void appData.cache.waitForPrimed(SAFETY_TIMEOUT_MS).then((result) => {
252
- if (cancelled) return;
253
- if (result === "timeout") {
254
- console.warn(
255
- `[@hook-sdk/template] PersistedKeysPrefetch: bulk-read n\xE3o completou em ${SAFETY_TIMEOUT_MS}ms. Liberando render mesmo assim \u2014 usePersistedState pode expor defaultValue at\xE9 o fetch terminar (G77).`
256
- );
257
- }
258
- setReady(true);
259
- });
260
- return () => {
261
- cancelled = true;
262
- };
263
- }, [appData, config.persistedKeys]);
264
- if (!ready) return null;
265
- return /* @__PURE__ */ jsx6(Fragment3, { children });
266
- }
267
-
268
- // src/internal/PushPrompt.tsx
269
- function PushPrompt() {
270
- return null;
271
- }
272
-
273
490
  // src/components/InstallGate/InstallGate.tsx
274
- import { useEffect as useEffect5, useRef as useRef2 } from "react";
491
+ import { useEffect as useEffect4, useRef } from "react";
275
492
 
276
493
  // src/hooks/useInstallPrompt.ts
277
- import { useCallback as useCallback2, useEffect as useEffect4, useState as useState4 } from "react";
494
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
278
495
  var IOS_RE = /iPad|iPhone|iPod/;
279
496
  var IOS_NON_SAFARI_RE = /CriOS|FxiOS|EdgiOS/;
280
497
  var ANDROID_RE = /Android/;
@@ -407,18 +624,18 @@ function useInstallPrompt(slug) {
407
624
  const iosBrowser = detectIOSBrowser(ua);
408
625
  const androidBrowser = detectAndroidBrowser(ua);
409
626
  const inAppApp = detectInAppApp(ua);
410
- const [isInstallable, setIsInstallable] = useState4(() => {
627
+ const [isInstallable, setIsInstallable] = useState2(() => {
411
628
  if (typeof window === "undefined") return false;
412
629
  return window.__pwaInstallPrompt != null;
413
630
  });
414
- const [isInstalled, setIsInstalled] = useState4(() => {
631
+ const [isInstalled, setIsInstalled] = useState2(() => {
415
632
  const { installed } = detectStandalone();
416
633
  return installed || readInstalledMarker(slug);
417
634
  });
418
- const [isDismissedSession, setIsDismissedSession] = useState4(() => readSessionSkip(slug));
419
- const [isDismissedPermanent, setIsDismissedPermanent] = useState4(() => readPermanentDismiss(slug).dismissed);
420
- const [skipCount, setSkipCount] = useState4(() => readSkipCount(slug));
421
- useEffect4(() => {
635
+ const [isDismissedSession, setIsDismissedSession] = useState2(() => readSessionSkip(slug));
636
+ const [isDismissedPermanent, setIsDismissedPermanent] = useState2(() => readPermanentDismiss(slug).dismissed);
637
+ const [skipCount, setSkipCount] = useState2(() => readSkipCount(slug));
638
+ useEffect3(() => {
422
639
  if (typeof window === "undefined") return;
423
640
  if (window.__pwaInstallPrompt) {
424
641
  setIsInstallable(true);
@@ -442,7 +659,7 @@ function useInstallPrompt(slug) {
442
659
  window.removeEventListener("appinstalled", onInstalled);
443
660
  };
444
661
  }, [slug]);
445
- useEffect4(() => {
662
+ useEffect3(() => {
446
663
  if (typeof window === "undefined") return;
447
664
  const mq = window.matchMedia?.("(display-mode: standalone)");
448
665
  if (!mq) return;
@@ -697,11 +914,11 @@ var INSTALL_COPY = {
697
914
  };
698
915
 
699
916
  // src/components/InstallGate/InstallSplash.tsx
700
- import { jsx as jsx7, jsxs } from "react/jsx-runtime";
917
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
701
918
  function InstallSplash({ children, title, subtitle }) {
702
919
  const { name, theme } = useTemplateConfig();
703
920
  const iconUrl = theme.icon_url || theme.logo_url || null;
704
- return /* @__PURE__ */ jsx7(
921
+ return /* @__PURE__ */ jsx6(
705
922
  "div",
706
923
  {
707
924
  role: "dialog",
@@ -710,14 +927,14 @@ function InstallSplash({ children, title, subtitle }) {
710
927
  "aria-describedby": subtitle ? "install-splash-subtitle" : void 0,
711
928
  style: overlayStyle,
712
929
  children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
713
- /* @__PURE__ */ jsx7("div", { style: { display: "flex", justifyContent: "center", marginBottom: 16 }, children: iconUrl ? /* @__PURE__ */ jsx7(
930
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", justifyContent: "center", marginBottom: 16 }, children: iconUrl ? /* @__PURE__ */ jsx6(
714
931
  "img",
715
932
  {
716
933
  src: iconUrl,
717
934
  alt: `\xCDcone de ${name}`,
718
935
  style: { width: 80, height: 80, borderRadius: 20, objectFit: "cover" }
719
936
  }
720
- ) : /* @__PURE__ */ jsx7(
937
+ ) : /* @__PURE__ */ jsx6(
721
938
  "div",
722
939
  {
723
940
  style: {
@@ -735,10 +952,10 @@ function InstallSplash({ children, title, subtitle }) {
735
952
  children: name.charAt(0).toUpperCase()
736
953
  }
737
954
  ) }),
738
- /* @__PURE__ */ jsx7("h1", { id: "install-splash-title", style: titleStyle, children: title }),
739
- subtitle && /* @__PURE__ */ jsx7("p", { id: "install-splash-subtitle", style: subtitleStyle, children: subtitle }),
740
- /* @__PURE__ */ jsx7("div", { style: { marginTop: 24 }, children }),
741
- /* @__PURE__ */ jsx7("p", { style: footerStyle, children: "por Hook" })
955
+ /* @__PURE__ */ jsx6("h1", { id: "install-splash-title", style: titleStyle, children: title }),
956
+ subtitle && /* @__PURE__ */ jsx6("p", { id: "install-splash-subtitle", style: subtitleStyle, children: subtitle }),
957
+ /* @__PURE__ */ jsx6("div", { style: { marginTop: 24 }, children }),
958
+ /* @__PURE__ */ jsx6("p", { style: footerStyle, children: "por Hook" })
742
959
  ] })
743
960
  }
744
961
  );
@@ -812,7 +1029,7 @@ var footerStyle = {
812
1029
  };
813
1030
 
814
1031
  // src/components/InstallGate/icons.tsx
815
- import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
1032
+ import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
816
1033
  var defaultSvgProps = (size) => ({
817
1034
  width: size,
818
1035
  height: size,
@@ -825,55 +1042,55 @@ var defaultSvgProps = (size) => ({
825
1042
  });
826
1043
  function ShareIconIOS({ size = 24, style, className }) {
827
1044
  return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
828
- /* @__PURE__ */ jsx8("path", { d: "M12 2L12 15" }),
829
- /* @__PURE__ */ jsx8("path", { d: "M8 6L12 2L16 6" }),
830
- /* @__PURE__ */ jsx8("path", { d: "M4 11v9a2 2 0 002 2h12a2 2 0 002-2v-9" })
1045
+ /* @__PURE__ */ jsx7("path", { d: "M12 2L12 15" }),
1046
+ /* @__PURE__ */ jsx7("path", { d: "M8 6L12 2L16 6" }),
1047
+ /* @__PURE__ */ jsx7("path", { d: "M4 11v9a2 2 0 002 2h12a2 2 0 002-2v-9" })
831
1048
  ] });
832
1049
  }
833
1050
  function MenuDotsVerticalIcon({ size = 24, style, className }) {
834
1051
  return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
835
- /* @__PURE__ */ jsx8("circle", { cx: "12", cy: "5", r: "1.5" }),
836
- /* @__PURE__ */ jsx8("circle", { cx: "12", cy: "12", r: "1.5" }),
837
- /* @__PURE__ */ jsx8("circle", { cx: "12", cy: "19", r: "1.5" })
1052
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "5", r: "1.5" }),
1053
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "1.5" }),
1054
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "19", r: "1.5" })
838
1055
  ] });
839
1056
  }
840
1057
  function MenuDotsHorizontalIcon({ size = 24, style, className }) {
841
1058
  return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
842
- /* @__PURE__ */ jsx8("circle", { cx: "5", cy: "12", r: "1.5" }),
843
- /* @__PURE__ */ jsx8("circle", { cx: "12", cy: "12", r: "1.5" }),
844
- /* @__PURE__ */ jsx8("circle", { cx: "19", cy: "12", r: "1.5" })
1059
+ /* @__PURE__ */ jsx7("circle", { cx: "5", cy: "12", r: "1.5" }),
1060
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "1.5" }),
1061
+ /* @__PURE__ */ jsx7("circle", { cx: "19", cy: "12", r: "1.5" })
845
1062
  ] });
846
1063
  }
847
1064
  function SquarePlusIcon({ size = 24, style, className }) {
848
1065
  return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
849
- /* @__PURE__ */ jsx8("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
850
- /* @__PURE__ */ jsx8("path", { d: "M12 8v8" }),
851
- /* @__PURE__ */ jsx8("path", { d: "M8 12h8" })
1066
+ /* @__PURE__ */ jsx7("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1067
+ /* @__PURE__ */ jsx7("path", { d: "M12 8v8" }),
1068
+ /* @__PURE__ */ jsx7("path", { d: "M8 12h8" })
852
1069
  ] });
853
1070
  }
854
1071
  function DownloadIcon({ size = 24, style, className }) {
855
1072
  return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
856
- /* @__PURE__ */ jsx8("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" }),
857
- /* @__PURE__ */ jsx8("polyline", { points: "7 10 12 15 17 10" }),
858
- /* @__PURE__ */ jsx8("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
1073
+ /* @__PURE__ */ jsx7("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" }),
1074
+ /* @__PURE__ */ jsx7("polyline", { points: "7 10 12 15 17 10" }),
1075
+ /* @__PURE__ */ jsx7("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
859
1076
  ] });
860
1077
  }
861
1078
  function ExternalLinkIcon({ size = 24, style, className }) {
862
1079
  return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
863
- /* @__PURE__ */ jsx8("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
864
- /* @__PURE__ */ jsx8("polyline", { points: "15 3 21 3 21 9" }),
865
- /* @__PURE__ */ jsx8("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
1080
+ /* @__PURE__ */ jsx7("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
1081
+ /* @__PURE__ */ jsx7("polyline", { points: "15 3 21 3 21 9" }),
1082
+ /* @__PURE__ */ jsx7("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
866
1083
  ] });
867
1084
  }
868
1085
  function XIcon({ size = 20, style, className }) {
869
1086
  return /* @__PURE__ */ jsxs2("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
870
- /* @__PURE__ */ jsx8("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
871
- /* @__PURE__ */ jsx8("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1087
+ /* @__PURE__ */ jsx7("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1088
+ /* @__PURE__ */ jsx7("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
872
1089
  ] });
873
1090
  }
874
1091
 
875
1092
  // src/components/InstallGate/variants/AndroidNativeVariant.tsx
876
- import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
1093
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
877
1094
  function AndroidNativeVariant({
878
1095
  state,
879
1096
  actions
@@ -889,12 +1106,12 @@ function AndroidNativeVariant({
889
1106
  onClick: () => void actions.promptInstall(),
890
1107
  style: { ...primaryButtonStyle, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8 },
891
1108
  children: [
892
- /* @__PURE__ */ jsx9(DownloadIcon, { size: 18 }),
1109
+ /* @__PURE__ */ jsx8(DownloadIcon, { size: 18 }),
893
1110
  copy.cta
894
1111
  ]
895
1112
  }
896
1113
  ),
897
- /* @__PURE__ */ jsx9(
1114
+ /* @__PURE__ */ jsx8(
898
1115
  "button",
899
1116
  {
900
1117
  "data-testid": "install-prompt-skip-session",
@@ -904,7 +1121,7 @@ function AndroidNativeVariant({
904
1121
  children: copy.skip
905
1122
  }
906
1123
  ),
907
- showPermanent && /* @__PURE__ */ jsx9(
1124
+ showPermanent && /* @__PURE__ */ jsx8(
908
1125
  "button",
909
1126
  {
910
1127
  "data-testid": "install-prompt-skip-permanent",
@@ -918,7 +1135,7 @@ function AndroidNativeVariant({
918
1135
  }
919
1136
 
920
1137
  // src/components/InstallGate/variants/AndroidManualVariant.tsx
921
- import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
1138
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
922
1139
  function AndroidManualVariant({
923
1140
  state,
924
1141
  actions
@@ -926,9 +1143,9 @@ function AndroidManualVariant({
926
1143
  const copy = INSTALL_COPY.android.manual;
927
1144
  const showPermanent = shouldShowPermanentOption(state);
928
1145
  return /* @__PURE__ */ jsxs4(InstallSplash, { title: copy.title, children: [
929
- /* @__PURE__ */ jsx10(Step, { n: 1, icon: /* @__PURE__ */ jsx10(MenuDotsVerticalIcon, { size: 20 }), children: copy.step1 }),
930
- /* @__PURE__ */ jsx10(Step, { n: 2, icon: /* @__PURE__ */ jsx10(DownloadIcon, { size: 18 }), children: copy.step2 }),
931
- /* @__PURE__ */ jsx10(
1146
+ /* @__PURE__ */ jsx9(Step, { n: 1, icon: /* @__PURE__ */ jsx9(MenuDotsVerticalIcon, { size: 20 }), children: copy.step1 }),
1147
+ /* @__PURE__ */ jsx9(Step, { n: 2, icon: /* @__PURE__ */ jsx9(DownloadIcon, { size: 18 }), children: copy.step2 }),
1148
+ /* @__PURE__ */ jsx9(
932
1149
  "button",
933
1150
  {
934
1151
  "data-testid": "install-prompt-cta-android-manual",
@@ -938,7 +1155,7 @@ function AndroidManualVariant({
938
1155
  children: copy.cta
939
1156
  }
940
1157
  ),
941
- /* @__PURE__ */ jsx10(
1158
+ /* @__PURE__ */ jsx9(
942
1159
  "button",
943
1160
  {
944
1161
  "data-testid": "install-prompt-skip-session",
@@ -948,7 +1165,7 @@ function AndroidManualVariant({
948
1165
  children: copy.skip
949
1166
  }
950
1167
  ),
951
- showPermanent && /* @__PURE__ */ jsx10(
1168
+ showPermanent && /* @__PURE__ */ jsx9(
952
1169
  "button",
953
1170
  {
954
1171
  "data-testid": "install-prompt-skip-permanent",
@@ -975,7 +1192,7 @@ function Step({ n, icon, children }) {
975
1192
  textAlign: "left"
976
1193
  },
977
1194
  children: [
978
- /* @__PURE__ */ jsx10(
1195
+ /* @__PURE__ */ jsx9(
979
1196
  "div",
980
1197
  {
981
1198
  style: {
@@ -994,15 +1211,15 @@ function Step({ n, icon, children }) {
994
1211
  children: n
995
1212
  }
996
1213
  ),
997
- /* @__PURE__ */ jsx10("div", { style: { flex: 1, fontSize: 15, color: "#333" }, children }),
998
- /* @__PURE__ */ jsx10("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1214
+ /* @__PURE__ */ jsx9("div", { style: { flex: 1, fontSize: 15, color: "#333" }, children }),
1215
+ /* @__PURE__ */ jsx9("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
999
1216
  ]
1000
1217
  }
1001
1218
  );
1002
1219
  }
1003
1220
 
1004
1221
  // src/components/InstallGate/Step.tsx
1005
- import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
1222
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1006
1223
  function Step2({
1007
1224
  n,
1008
1225
  title,
@@ -1020,7 +1237,7 @@ function Step2({
1020
1237
  textAlign: "left"
1021
1238
  },
1022
1239
  children: [
1023
- /* @__PURE__ */ jsx11(
1240
+ /* @__PURE__ */ jsx10(
1024
1241
  "div",
1025
1242
  {
1026
1243
  style: {
@@ -1040,8 +1257,8 @@ function Step2({
1040
1257
  }
1041
1258
  ),
1042
1259
  /* @__PURE__ */ jsxs5("div", { style: { flex: 1 }, children: [
1043
- /* @__PURE__ */ jsx11("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
1044
- subtitle && /* @__PURE__ */ jsx11("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
1260
+ /* @__PURE__ */ jsx10("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
1261
+ subtitle && /* @__PURE__ */ jsx10("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
1045
1262
  visual
1046
1263
  ] })
1047
1264
  ]
@@ -1050,7 +1267,7 @@ function Step2({
1050
1267
  }
1051
1268
 
1052
1269
  // src/components/InstallGate/variants/IOSafariVariant.tsx
1053
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1270
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1054
1271
  function IOSafariVariant({
1055
1272
  state,
1056
1273
  actions
@@ -1058,13 +1275,13 @@ function IOSafariVariant({
1058
1275
  const copy = INSTALL_COPY.iosSafari;
1059
1276
  const showPermanent = shouldShowPermanentOption(state);
1060
1277
  return /* @__PURE__ */ jsxs6(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1061
- /* @__PURE__ */ jsx12(
1278
+ /* @__PURE__ */ jsx11(
1062
1279
  Step2,
1063
1280
  {
1064
1281
  n: 1,
1065
1282
  title: copy.step1.title,
1066
1283
  subtitle: copy.step1.subtitle,
1067
- visual: /* @__PURE__ */ jsx12(
1284
+ visual: /* @__PURE__ */ jsx11(
1068
1285
  "div",
1069
1286
  {
1070
1287
  style: {
@@ -1076,12 +1293,12 @@ function IOSafariVariant({
1076
1293
  padding: "12px 0",
1077
1294
  marginTop: 8
1078
1295
  },
1079
- children: /* @__PURE__ */ jsx12(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1296
+ children: /* @__PURE__ */ jsx11(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1080
1297
  }
1081
1298
  )
1082
1299
  }
1083
1300
  ),
1084
- /* @__PURE__ */ jsx12(
1301
+ /* @__PURE__ */ jsx11(
1085
1302
  Step2,
1086
1303
  {
1087
1304
  n: 2,
@@ -1099,19 +1316,19 @@ function IOSafariVariant({
1099
1316
  marginTop: 8
1100
1317
  },
1101
1318
  children: [
1102
- /* @__PURE__ */ jsx12(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1103
- /* @__PURE__ */ jsx12("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1319
+ /* @__PURE__ */ jsx11(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1320
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1104
1321
  ]
1105
1322
  }
1106
1323
  )
1107
1324
  }
1108
1325
  ),
1109
- /* @__PURE__ */ jsx12(
1326
+ /* @__PURE__ */ jsx11(
1110
1327
  Step2,
1111
1328
  {
1112
1329
  n: 3,
1113
1330
  title: copy.step3.title,
1114
- visual: /* @__PURE__ */ jsx12(
1331
+ visual: /* @__PURE__ */ jsx11(
1115
1332
  "div",
1116
1333
  {
1117
1334
  style: {
@@ -1122,7 +1339,7 @@ function IOSafariVariant({
1122
1339
  padding: "10px 14px",
1123
1340
  marginTop: 8
1124
1341
  },
1125
- children: /* @__PURE__ */ jsx12(
1342
+ children: /* @__PURE__ */ jsx11(
1126
1343
  "span",
1127
1344
  {
1128
1345
  style: {
@@ -1137,7 +1354,7 @@ function IOSafariVariant({
1137
1354
  )
1138
1355
  }
1139
1356
  ),
1140
- /* @__PURE__ */ jsx12(
1357
+ /* @__PURE__ */ jsx11(
1141
1358
  "button",
1142
1359
  {
1143
1360
  "data-testid": "install-prompt-skip-session",
@@ -1147,7 +1364,7 @@ function IOSafariVariant({
1147
1364
  children: copy.skip
1148
1365
  }
1149
1366
  ),
1150
- showPermanent && /* @__PURE__ */ jsx12(
1367
+ showPermanent && /* @__PURE__ */ jsx11(
1151
1368
  "button",
1152
1369
  {
1153
1370
  "data-testid": "install-prompt-skip-permanent",
@@ -1161,7 +1378,7 @@ function IOSafariVariant({
1161
1378
  }
1162
1379
 
1163
1380
  // src/components/InstallGate/variants/IOSOtherVariant.tsx
1164
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1381
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1165
1382
  function IOSOtherVariant({
1166
1383
  state,
1167
1384
  actions
@@ -1169,13 +1386,13 @@ function IOSOtherVariant({
1169
1386
  const copy = INSTALL_COPY.iosOther;
1170
1387
  const showPermanent = shouldShowPermanentOption(state);
1171
1388
  return /* @__PURE__ */ jsxs7(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1172
- /* @__PURE__ */ jsx13(
1389
+ /* @__PURE__ */ jsx12(
1173
1390
  Step2,
1174
1391
  {
1175
1392
  n: 1,
1176
1393
  title: copy.step1.title,
1177
1394
  subtitle: copy.step1.subtitle,
1178
- visual: /* @__PURE__ */ jsx13(
1395
+ visual: /* @__PURE__ */ jsx12(
1179
1396
  "div",
1180
1397
  {
1181
1398
  style: {
@@ -1187,12 +1404,12 @@ function IOSOtherVariant({
1187
1404
  padding: "12px 0",
1188
1405
  marginTop: 8
1189
1406
  },
1190
- children: /* @__PURE__ */ jsx13(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1407
+ children: /* @__PURE__ */ jsx12(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1191
1408
  }
1192
1409
  )
1193
1410
  }
1194
1411
  ),
1195
- /* @__PURE__ */ jsx13(
1412
+ /* @__PURE__ */ jsx12(
1196
1413
  Step2,
1197
1414
  {
1198
1415
  n: 2,
@@ -1210,19 +1427,19 @@ function IOSOtherVariant({
1210
1427
  marginTop: 8
1211
1428
  },
1212
1429
  children: [
1213
- /* @__PURE__ */ jsx13(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1214
- /* @__PURE__ */ jsx13("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1430
+ /* @__PURE__ */ jsx12(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1431
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1215
1432
  ]
1216
1433
  }
1217
1434
  )
1218
1435
  }
1219
1436
  ),
1220
- /* @__PURE__ */ jsx13(
1437
+ /* @__PURE__ */ jsx12(
1221
1438
  Step2,
1222
1439
  {
1223
1440
  n: 3,
1224
1441
  title: copy.step3.title,
1225
- visual: /* @__PURE__ */ jsx13(
1442
+ visual: /* @__PURE__ */ jsx12(
1226
1443
  "div",
1227
1444
  {
1228
1445
  style: {
@@ -1233,7 +1450,7 @@ function IOSOtherVariant({
1233
1450
  padding: "10px 14px",
1234
1451
  marginTop: 8
1235
1452
  },
1236
- children: /* @__PURE__ */ jsx13(
1453
+ children: /* @__PURE__ */ jsx12(
1237
1454
  "span",
1238
1455
  {
1239
1456
  style: {
@@ -1248,7 +1465,7 @@ function IOSOtherVariant({
1248
1465
  )
1249
1466
  }
1250
1467
  ),
1251
- /* @__PURE__ */ jsx13(
1468
+ /* @__PURE__ */ jsx12(
1252
1469
  "button",
1253
1470
  {
1254
1471
  "data-testid": "install-prompt-skip-session",
@@ -1258,7 +1475,7 @@ function IOSOtherVariant({
1258
1475
  children: copy.skip
1259
1476
  }
1260
1477
  ),
1261
- showPermanent && /* @__PURE__ */ jsx13(
1478
+ showPermanent && /* @__PURE__ */ jsx12(
1262
1479
  "button",
1263
1480
  {
1264
1481
  "data-testid": "install-prompt-skip-permanent",
@@ -1272,8 +1489,8 @@ function IOSOtherVariant({
1272
1489
  }
1273
1490
 
1274
1491
  // src/components/InstallGate/variants/InAppBrowserVariant.tsx
1275
- import { useState as useState5 } from "react";
1276
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1492
+ import { useState as useState3 } from "react";
1493
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1277
1494
  function InAppBrowserVariant({
1278
1495
  state,
1279
1496
  actions
@@ -1282,7 +1499,7 @@ function InAppBrowserVariant({
1282
1499
  const appCopy = INSTALL_COPY.inApp[app] ?? INSTALL_COPY.inApp.other;
1283
1500
  const copy = INSTALL_COPY.inApp;
1284
1501
  const showPermanent = shouldShowPermanentOption(state);
1285
- const [copied, setCopied] = useState5(false);
1502
+ const [copied, setCopied] = useState3(false);
1286
1503
  const handleCopy = async () => {
1287
1504
  await actions.copyLink();
1288
1505
  setCopied(true);
@@ -1290,9 +1507,9 @@ function InAppBrowserVariant({
1290
1507
  };
1291
1508
  const DotsIcon = app === "facebook" || app === "telegram" ? MenuDotsVerticalIcon : MenuDotsHorizontalIcon;
1292
1509
  return /* @__PURE__ */ jsxs8(InstallSplash, { title: appCopy.title, children: [
1293
- /* @__PURE__ */ jsx14(Step3, { n: 1, icon: /* @__PURE__ */ jsx14(DotsIcon, { size: 20 }), children: appCopy.step1 }),
1294
- /* @__PURE__ */ jsx14(Step3, { n: 2, icon: /* @__PURE__ */ jsx14(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
1295
- /* @__PURE__ */ jsx14(
1510
+ /* @__PURE__ */ jsx13(Step3, { n: 1, icon: /* @__PURE__ */ jsx13(DotsIcon, { size: 20 }), children: appCopy.step1 }),
1511
+ /* @__PURE__ */ jsx13(Step3, { n: 2, icon: /* @__PURE__ */ jsx13(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
1512
+ /* @__PURE__ */ jsx13(
1296
1513
  "button",
1297
1514
  {
1298
1515
  "data-testid": "install-prompt-cta-inapp-copy",
@@ -1302,7 +1519,7 @@ function InAppBrowserVariant({
1302
1519
  children: copied ? copy.copiedToast : copy.copy
1303
1520
  }
1304
1521
  ),
1305
- /* @__PURE__ */ jsx14(
1522
+ /* @__PURE__ */ jsx13(
1306
1523
  "button",
1307
1524
  {
1308
1525
  "data-testid": "install-prompt-skip-session",
@@ -1312,7 +1529,7 @@ function InAppBrowserVariant({
1312
1529
  children: copy.skip
1313
1530
  }
1314
1531
  ),
1315
- showPermanent && /* @__PURE__ */ jsx14(
1532
+ showPermanent && /* @__PURE__ */ jsx13(
1316
1533
  "button",
1317
1534
  {
1318
1535
  "data-testid": "install-prompt-skip-permanent",
@@ -1343,7 +1560,7 @@ function Step3({
1343
1560
  textAlign: "left"
1344
1561
  },
1345
1562
  children: [
1346
- /* @__PURE__ */ jsx14(
1563
+ /* @__PURE__ */ jsx13(
1347
1564
  "div",
1348
1565
  {
1349
1566
  style: {
@@ -1362,15 +1579,15 @@ function Step3({
1362
1579
  children: n
1363
1580
  }
1364
1581
  ),
1365
- /* @__PURE__ */ jsx14("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1366
- /* @__PURE__ */ jsx14("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1582
+ /* @__PURE__ */ jsx13("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1583
+ /* @__PURE__ */ jsx13("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1367
1584
  ]
1368
1585
  }
1369
1586
  );
1370
1587
  }
1371
1588
 
1372
1589
  // src/components/InstallGate/variants/DesktopVariant.tsx
1373
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1590
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1374
1591
  function DesktopVariant({
1375
1592
  state,
1376
1593
  actions
@@ -1386,14 +1603,14 @@ function DesktopVariant({
1386
1603
  "aria-label": copy.title,
1387
1604
  style: bannerStyle,
1388
1605
  children: [
1389
- iconUrl ? /* @__PURE__ */ jsx15(
1606
+ iconUrl ? /* @__PURE__ */ jsx14(
1390
1607
  "img",
1391
1608
  {
1392
1609
  src: iconUrl,
1393
1610
  alt: "",
1394
1611
  style: { width: 40, height: 40, borderRadius: 10, objectFit: "cover", flexShrink: 0 }
1395
1612
  }
1396
- ) : /* @__PURE__ */ jsx15(
1613
+ ) : /* @__PURE__ */ jsx14(
1397
1614
  "div",
1398
1615
  {
1399
1616
  style: {
@@ -1413,8 +1630,8 @@ function DesktopVariant({
1413
1630
  }
1414
1631
  ),
1415
1632
  /* @__PURE__ */ jsxs9("div", { style: { flex: 1, minWidth: 0 }, children: [
1416
- /* @__PURE__ */ jsx15("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
1417
- /* @__PURE__ */ jsx15("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
1633
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
1634
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
1418
1635
  ] }),
1419
1636
  /* @__PURE__ */ jsxs9(
1420
1637
  "button",
@@ -1437,12 +1654,12 @@ function DesktopVariant({
1437
1654
  flexShrink: 0
1438
1655
  },
1439
1656
  children: [
1440
- /* @__PURE__ */ jsx15(DownloadIcon, { size: 14 }),
1657
+ /* @__PURE__ */ jsx14(DownloadIcon, { size: 14 }),
1441
1658
  copy.cta
1442
1659
  ]
1443
1660
  }
1444
1661
  ),
1445
- /* @__PURE__ */ jsx15(
1662
+ /* @__PURE__ */ jsx14(
1446
1663
  "button",
1447
1664
  {
1448
1665
  "data-testid": "install-prompt-desktop-close",
@@ -1457,7 +1674,7 @@ function DesktopVariant({
1457
1674
  padding: 4,
1458
1675
  flexShrink: 0
1459
1676
  },
1460
- children: /* @__PURE__ */ jsx15(XIcon, { size: 16 })
1677
+ children: /* @__PURE__ */ jsx14(XIcon, { size: 16 })
1461
1678
  }
1462
1679
  )
1463
1680
  ]
@@ -1481,14 +1698,14 @@ var bannerStyle = {
1481
1698
  };
1482
1699
 
1483
1700
  // src/components/InstallGate/InstallGate.tsx
1484
- import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1701
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1485
1702
  function InstallGate({ children }) {
1486
1703
  const { slug, features_enabled } = useTemplateConfig();
1487
1704
  const enabled = features_enabled.includes("install_prompt");
1488
1705
  const installState = useInstallPrompt(slug);
1489
1706
  const shouldBlock = enabled && shouldBlockInstall(installState);
1490
- const trackedRef = useRef2(null);
1491
- useEffect5(() => {
1707
+ const trackedRef = useRef(null);
1708
+ useEffect4(() => {
1492
1709
  if (!shouldBlock) return;
1493
1710
  if (typeof window === "undefined") return;
1494
1711
  const variantKey = `${slug}:${installState.variant}`;
@@ -1502,36 +1719,41 @@ function InstallGate({ children }) {
1502
1719
  variant: installState.variant
1503
1720
  });
1504
1721
  }, [shouldBlock, slug, installState.variant, installState.platform, installState.iosBrowser, installState.androidBrowser, installState.inAppApp]);
1505
- if (!enabled) return /* @__PURE__ */ jsx16(Fragment4, { children });
1506
- if (installState.isInstalled) return /* @__PURE__ */ jsx16(Fragment4, { children });
1722
+ if (!enabled) return /* @__PURE__ */ jsx15(Fragment3, { children });
1723
+ if (installState.isInstalled) return /* @__PURE__ */ jsx15(Fragment3, { children });
1507
1724
  if (installState.variant === "desktop") {
1508
1725
  const showBanner = !installState.isDismissedSession && !installState.isDismissedPermanent;
1509
- return /* @__PURE__ */ jsxs10(Fragment4, { children: [
1726
+ return /* @__PURE__ */ jsxs10(Fragment3, { children: [
1510
1727
  children,
1511
- showBanner && /* @__PURE__ */ jsx16(DesktopVariant, { state: installState, actions: installState })
1728
+ showBanner && /* @__PURE__ */ jsx15(DesktopVariant, { state: installState, actions: installState })
1512
1729
  ] });
1513
1730
  }
1514
- if (!shouldBlock) return /* @__PURE__ */ jsx16(Fragment4, { children });
1731
+ if (!shouldBlock) return /* @__PURE__ */ jsx15(Fragment3, { children });
1515
1732
  switch (installState.variant) {
1516
1733
  case "android-native":
1517
- return /* @__PURE__ */ jsx16(AndroidNativeVariant, { state: installState, actions: installState });
1734
+ return /* @__PURE__ */ jsx15(AndroidNativeVariant, { state: installState, actions: installState });
1518
1735
  case "android-manual":
1519
- return /* @__PURE__ */ jsx16(AndroidManualVariant, { state: installState, actions: installState });
1736
+ return /* @__PURE__ */ jsx15(AndroidManualVariant, { state: installState, actions: installState });
1520
1737
  case "ios-safari":
1521
- return /* @__PURE__ */ jsx16(IOSafariVariant, { state: installState, actions: installState });
1738
+ return /* @__PURE__ */ jsx15(IOSafariVariant, { state: installState, actions: installState });
1522
1739
  case "ios-other":
1523
- return /* @__PURE__ */ jsx16(IOSOtherVariant, { state: installState, actions: installState });
1740
+ return /* @__PURE__ */ jsx15(IOSOtherVariant, { state: installState, actions: installState });
1524
1741
  case "in-app":
1525
- return /* @__PURE__ */ jsx16(InAppBrowserVariant, { state: installState, actions: installState });
1742
+ return /* @__PURE__ */ jsx15(InAppBrowserVariant, { state: installState, actions: installState });
1526
1743
  case "none":
1527
1744
  default:
1528
- return /* @__PURE__ */ jsx16(Fragment4, { children });
1745
+ return /* @__PURE__ */ jsx15(Fragment3, { children });
1529
1746
  }
1530
1747
  }
1531
1748
 
1749
+ // src/internal/PushPrompt.tsx
1750
+ function PushPrompt() {
1751
+ return null;
1752
+ }
1753
+
1532
1754
  // src/defaults/ErrorBoundary.tsx
1533
1755
  import { Component } from "react";
1534
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1756
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1535
1757
  var ErrorBoundary = class extends Component {
1536
1758
  state = { error: null };
1537
1759
  static getDerivedStateFromError(error) {
@@ -1543,68 +1765,396 @@ var ErrorBoundary = class extends Component {
1543
1765
  render() {
1544
1766
  if (this.state.error) {
1545
1767
  return this.props.fallback ?? /* @__PURE__ */ jsxs11("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
1546
- /* @__PURE__ */ jsx17("h2", { children: "Algo deu errado" }),
1547
- /* @__PURE__ */ jsx17("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
1768
+ /* @__PURE__ */ jsx16("h2", { children: "Algo deu errado" }),
1769
+ /* @__PURE__ */ jsx16("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
1548
1770
  ] });
1549
1771
  }
1550
- return /* @__PURE__ */ jsx17(Fragment5, { children: this.props.children });
1772
+ return /* @__PURE__ */ jsx16(Fragment4, { children: this.props.children });
1551
1773
  }
1552
1774
  };
1553
1775
 
1554
- // src/hooks/useLoginForm.ts
1555
- import { useCallback as useCallback3, useMemo as useMemo3, useState as useState6 } from "react";
1556
- import { useHook as useHook4 } from "@hook-sdk/sdk";
1557
-
1558
- // src/errors.ts
1559
- import { SdkError, SdkAuthError, SdkRateLimitError } from "@hook-sdk/sdk";
1560
- function mapSdkError(err) {
1561
- if (err instanceof SdkRateLimitError) {
1562
- return {
1563
- code: "rate_limited",
1564
- message: `Aguarde ${err.retryAfter}s e tente novamente.`,
1565
- retryAfter: err.retryAfter
1776
+ // src/internal/PaymentReturnHandler.tsx
1777
+ import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef2, useState as useState4 } from "react";
1778
+ import { useHook as useHook3 } from "@hook-sdk/sdk";
1779
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1780
+ var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
1781
+ function PaymentReturnHandler({ children }) {
1782
+ const { subscription } = useHook3();
1783
+ const subRef = useRef2(subscription);
1784
+ subRef.current = subscription;
1785
+ const runIdRef = useRef2(0);
1786
+ const [state, setState] = useState4("idle");
1787
+ const runPoll = useCallback3(() => {
1788
+ const runId = ++runIdRef.current;
1789
+ setState("confirming");
1790
+ let attempts = 0;
1791
+ const tick = async () => {
1792
+ if (runIdRef.current !== runId) return;
1793
+ attempts++;
1794
+ try {
1795
+ await subRef.current.refresh();
1796
+ } catch {
1797
+ }
1798
+ if (runIdRef.current !== runId) return;
1799
+ const status = subRef.current.status();
1800
+ if (status === "active" || status === "trialing") {
1801
+ const cleanUrl = new URL(window.location.href);
1802
+ cleanUrl.searchParams.delete("paymentReturn");
1803
+ window.history.replaceState({}, "", cleanUrl.toString());
1804
+ setState("idle");
1805
+ return;
1806
+ }
1807
+ const delay = BACKOFF_MS[attempts - 1];
1808
+ if (delay === void 0) {
1809
+ setState("waiting");
1810
+ return;
1811
+ }
1812
+ setTimeout(tick, delay);
1566
1813
  };
1814
+ void tick();
1815
+ }, []);
1816
+ useEffect5(() => {
1817
+ if (typeof window === "undefined") return;
1818
+ const url = new URL(window.location.href);
1819
+ if (url.searchParams.get("paymentReturn") !== "1") return;
1820
+ runPoll();
1821
+ return () => {
1822
+ runIdRef.current++;
1823
+ };
1824
+ }, [runPoll]);
1825
+ if (state === "confirming") {
1826
+ return /* @__PURE__ */ jsx17("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
1567
1827
  }
1568
- if (err instanceof SdkAuthError) {
1569
- const detail = err.detail;
1570
- if (detail === "email_unverified") {
1571
- return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
1572
- }
1573
- if (detail === "account_locked") {
1574
- return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
1575
- }
1576
- return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
1577
- }
1578
- if (err instanceof SdkError && err.httpStatus === 0) {
1579
- return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
1580
- }
1581
- if (err instanceof TypeError) {
1582
- return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
1828
+ if (state === "waiting") {
1829
+ return /* @__PURE__ */ jsx17("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs12("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
1830
+ /* @__PURE__ */ jsx17("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
1831
+ /* @__PURE__ */ jsx17("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
1832
+ ] }) });
1583
1833
  }
1584
- return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
1834
+ return /* @__PURE__ */ jsx17(Fragment5, { children });
1585
1835
  }
1586
-
1587
- // src/hooks/useLoginForm.ts
1588
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1589
- var MIN_PASSWORD = 8;
1836
+ var overlayStyle2 = {
1837
+ position: "fixed",
1838
+ inset: 0,
1839
+ display: "flex",
1840
+ alignItems: "center",
1841
+ justifyContent: "center",
1842
+ background: "rgba(0,0,0,0.4)",
1843
+ zIndex: 9999,
1844
+ color: "white",
1845
+ fontSize: "1rem",
1846
+ padding: 24
1847
+ };
1848
+ var buttonStyle = {
1849
+ background: "white",
1850
+ color: "black",
1851
+ border: "none",
1852
+ borderRadius: 8,
1853
+ padding: "10px 24px",
1854
+ fontSize: "1rem",
1855
+ fontWeight: 600,
1856
+ cursor: "pointer"
1857
+ };
1858
+
1859
+ // src/AppRoot.tsx
1860
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
1861
+ function buildLegacyConfigShim(config) {
1862
+ const paywall = config.paywall;
1863
+ const isFree = paywall.mode === "free";
1864
+ const monthlyCents = isFree ? 0 : paywall.prices.monthlyCents;
1865
+ const trialDays = isFree ? 0 : paywall.trialDays ?? 0;
1866
+ return {
1867
+ slug: config.slug,
1868
+ name: config.name,
1869
+ email_alias: config.slug,
1870
+ theme: { primary_color: config.branding.primaryColor },
1871
+ features_enabled: config.features_enabled ?? [],
1872
+ dependencies_allowlist: ["react", "react-dom"],
1873
+ subscription: {
1874
+ mode: paywall.mode,
1875
+ price_cents: monthlyCents,
1876
+ currency: "brl",
1877
+ trial_days: trialDays,
1878
+ paywall_config: {
1879
+ title: config.name,
1880
+ benefits: ["Acesso completo"],
1881
+ cta: "Assinar"
1882
+ }
1883
+ },
1884
+ sdk_version_required: ">=0.16.0",
1885
+ max_bundle_size_kb: 500
1886
+ };
1887
+ }
1888
+ function AppRoot(props) {
1889
+ const {
1890
+ config: rawConfig,
1891
+ children,
1892
+ testRouter,
1893
+ testInitialEntries,
1894
+ Login,
1895
+ Signup,
1896
+ Forgot,
1897
+ Reset,
1898
+ EmailVerify,
1899
+ Paywall,
1900
+ Onboarding,
1901
+ PreAuthFlow
1902
+ } = props;
1903
+ if (!Login || !Signup || !Forgot || !Reset) {
1904
+ throw new Error(
1905
+ "[hook-template] <AppRoot>: Login, Signup, Forgot, Reset slot props are required."
1906
+ );
1907
+ }
1908
+ const config = parseAppConfig(rawConfig);
1909
+ if (config.paywall.mode !== "free" && !Paywall) {
1910
+ throw new Error(
1911
+ "[hook-template] <AppRoot>: Paywall slot prop is required when config.paywall.mode != 'free'."
1912
+ );
1913
+ }
1914
+ if (config.authFlow.requiresEmailVerify && !EmailVerify) {
1915
+ throw new Error(
1916
+ "[hook-template] <AppRoot>: EmailVerify slot prop is required when config.authFlow.requiresEmailVerify === true."
1917
+ );
1918
+ }
1919
+ if (config.onboarding?.trigger === "pre_signup_custom" && !PreAuthFlow) {
1920
+ throw new Error(
1921
+ "[hook-template] <AppRoot>: PreAuthFlow slot prop is required when config.onboarding.trigger === 'pre_signup_custom'."
1922
+ );
1923
+ }
1924
+ const legacyShim = useMemo3(() => buildLegacyConfigShim(config), [config]);
1925
+ const Router = testRouter === "memory" ? MemoryRouter : BrowserRouter;
1926
+ const basename = `/app/${config.slug}`;
1927
+ const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
1928
+ return /* @__PURE__ */ jsx18(ErrorBoundary, { children: /* @__PURE__ */ jsx18(AppConfigProvider, { config, children: /* @__PURE__ */ jsx18(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ jsx18(ThemeProvider, { children: /* @__PURE__ */ jsx18(PersistenceRegistry, { config: config.persistedKeys, children: /* @__PURE__ */ jsxs13(Router, { ...routerProps, children: [
1929
+ /* @__PURE__ */ jsx18(DeepLinkHandler, { deepLinks: config.deepLinks }),
1930
+ /* @__PURE__ */ jsx18(InstallGate, { children: /* @__PURE__ */ jsx18(
1931
+ AuthGated,
1932
+ {
1933
+ config,
1934
+ Login,
1935
+ Signup,
1936
+ Forgot,
1937
+ Reset,
1938
+ EmailVerify,
1939
+ Paywall,
1940
+ Onboarding,
1941
+ PreAuthFlow,
1942
+ children: /* @__PURE__ */ jsxs13(SubscriptionGate, { Paywall: Paywall ?? FallbackPaywall, children: [
1943
+ children,
1944
+ /* @__PURE__ */ jsx18(PushPrompt, {})
1945
+ ] })
1946
+ }
1947
+ ) })
1948
+ ] }) }) }) }) }) });
1949
+ }
1950
+ function AuthGated({
1951
+ children,
1952
+ config,
1953
+ Login,
1954
+ Signup,
1955
+ Forgot,
1956
+ Reset,
1957
+ EmailVerify,
1958
+ PreAuthFlow
1959
+ }) {
1960
+ const { authStatus } = useHook4();
1961
+ if (authStatus === "loading") return null;
1962
+ if (authStatus !== "authenticated") {
1963
+ if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
1964
+ return /* @__PURE__ */ jsxs13(Routes, { children: [
1965
+ /* @__PURE__ */ jsx18(Route, { path: "/signin", element: /* @__PURE__ */ jsx18(Login, {}) }),
1966
+ /* @__PURE__ */ jsx18(Route, { path: "/signup", element: /* @__PURE__ */ jsx18(Signup, {}) }),
1967
+ /* @__PURE__ */ jsx18(Route, { path: "/forgot", element: /* @__PURE__ */ jsx18(Forgot, {}) }),
1968
+ /* @__PURE__ */ jsx18(Route, { path: "/reset", element: /* @__PURE__ */ jsx18(Reset, {}) }),
1969
+ EmailVerify ? /* @__PURE__ */ jsx18(Route, { path: "/verify", element: /* @__PURE__ */ jsx18(EmailVerify, {}) }) : null,
1970
+ /* @__PURE__ */ jsx18(Route, { path: "/*", element: /* @__PURE__ */ jsx18(PreAuthFlow, {}) })
1971
+ ] });
1972
+ }
1973
+ return /* @__PURE__ */ jsxs13(Routes, { children: [
1974
+ /* @__PURE__ */ jsx18(Route, { path: "/", element: /* @__PURE__ */ jsx18(Login, {}) }),
1975
+ /* @__PURE__ */ jsx18(Route, { path: "/signup", element: /* @__PURE__ */ jsx18(Signup, {}) }),
1976
+ /* @__PURE__ */ jsx18(Route, { path: "/forgot", element: /* @__PURE__ */ jsx18(Forgot, {}) }),
1977
+ /* @__PURE__ */ jsx18(Route, { path: "/reset", element: /* @__PURE__ */ jsx18(Reset, {}) }),
1978
+ EmailVerify ? /* @__PURE__ */ jsx18(Route, { path: "/verify", element: /* @__PURE__ */ jsx18(EmailVerify, {}) }) : null,
1979
+ /* @__PURE__ */ jsx18(Route, { path: "*", element: /* @__PURE__ */ jsx18(Navigate, { to: "/", replace: true }) })
1980
+ ] });
1981
+ }
1982
+ return /* @__PURE__ */ jsx18(Fragment6, { children });
1983
+ }
1984
+ function FallbackPaywall() {
1985
+ return null;
1986
+ }
1987
+
1988
+ // src/hooks/usePush.ts
1989
+ import { useCallback as useCallback4, useEffect as useEffect6, useState as useState5 } from "react";
1990
+ import { useHook as useHook5 } from "@hook-sdk/sdk";
1991
+ function detectIosNeedsInstall() {
1992
+ if (typeof navigator === "undefined" || typeof window === "undefined") return false;
1993
+ const ua = navigator.userAgent || "";
1994
+ const isIos = /iPhone|iPad|iPod/.test(ua);
1995
+ if (!isIos) return false;
1996
+ const mm = window.matchMedia?.("(display-mode: standalone)");
1997
+ const standalone = mm?.matches === true;
1998
+ const legacyStandalone = typeof navigator.standalone === "boolean" ? navigator.standalone : false;
1999
+ return !(standalone || legacyStandalone);
2000
+ }
2001
+ function deriveState(push) {
2002
+ if (!push.isAvailable()) {
2003
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2004
+ return { kind: "unsupported" };
2005
+ }
2006
+ const status = push.status();
2007
+ if (status === "granted") return { kind: "subscribed" };
2008
+ if (status === "denied") return { kind: "denied" };
2009
+ if (status === "unsupported") {
2010
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2011
+ return { kind: "unsupported" };
2012
+ }
2013
+ return { kind: "prompt" };
2014
+ }
2015
+ function usePush() {
2016
+ const { push } = useHook5();
2017
+ const [state, setState] = useState5(() => deriveState(push));
2018
+ useEffect6(() => {
2019
+ setState(deriveState(push));
2020
+ }, [push]);
2021
+ const subscribe = useCallback4(async () => {
2022
+ try {
2023
+ await push.subscribe();
2024
+ setState({ kind: "subscribed" });
2025
+ } catch (e) {
2026
+ const code = e?.code ?? "push.unknown";
2027
+ const message = e?.message ?? "Push subscription failed";
2028
+ if (code === "push.permission_denied") setState({ kind: "denied" });
2029
+ else setState({ kind: "error", code, message });
2030
+ throw e;
2031
+ }
2032
+ }, [push]);
2033
+ const unsubscribe = useCallback4(async () => {
2034
+ try {
2035
+ await push.unsubscribe();
2036
+ setState({ kind: "prompt" });
2037
+ } catch (e) {
2038
+ setState({ kind: "error", code: e?.code ?? "push.unknown", message: e?.message ?? "failed" });
2039
+ throw e;
2040
+ }
2041
+ }, [push]);
2042
+ return { state, subscribe, unsubscribe };
2043
+ }
2044
+
2045
+ // src/components/PushPrompt.tsx
2046
+ import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
2047
+ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
2048
+ const { state, subscribe } = usePush();
2049
+ if (state.kind === "subscribed") return null;
2050
+ if (state.kind === "ios_needs_install") {
2051
+ return /* @__PURE__ */ jsxs14("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2052
+ /* @__PURE__ */ jsx19("h3", { children: texts.iosInstallTitle }),
2053
+ /* @__PURE__ */ jsx19("p", { children: texts.iosInstallBody }),
2054
+ onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx19("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2055
+ ] });
2056
+ }
2057
+ if (state.kind === "denied") {
2058
+ return /* @__PURE__ */ jsxs14("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
2059
+ /* @__PURE__ */ jsx19("h3", { children: texts.deniedTitle }),
2060
+ /* @__PURE__ */ jsx19("p", { children: texts.deniedBody })
2061
+ ] });
2062
+ }
2063
+ if (state.kind === "unsupported") {
2064
+ return /* @__PURE__ */ jsx19("div", { className, role: "region", children: /* @__PURE__ */ jsx19("p", { children: texts.unsupportedBody }) });
2065
+ }
2066
+ if (state.kind === "error") {
2067
+ return /* @__PURE__ */ jsx19("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx19("p", { children: state.message }) });
2068
+ }
2069
+ return /* @__PURE__ */ jsxs14("div", { className, role: "region", children: [
2070
+ /* @__PURE__ */ jsx19(
2071
+ "button",
2072
+ {
2073
+ type: "button",
2074
+ onClick: async () => {
2075
+ try {
2076
+ await subscribe();
2077
+ onSubscribed?.();
2078
+ } catch {
2079
+ }
2080
+ },
2081
+ children: texts.cta
2082
+ }
2083
+ ),
2084
+ onDeclined && /* @__PURE__ */ jsx19("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2085
+ ] });
2086
+ }
2087
+
2088
+ // src/defaults/LoadingState.tsx
2089
+ import { jsx as jsx20 } from "react/jsx-runtime";
2090
+ function LoadingState({ message }) {
2091
+ return /* @__PURE__ */ jsx20("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx20("span", { children: message ?? "Carregando..." }) });
2092
+ }
2093
+
2094
+ // src/defaults/EmptyState.tsx
2095
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
2096
+ function EmptyState({ title, description, action }) {
2097
+ return /* @__PURE__ */ jsxs15("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2098
+ /* @__PURE__ */ jsx21("h2", { style: { marginBottom: 8 }, children: title }),
2099
+ description && /* @__PURE__ */ jsx21("p", { style: { opacity: 0.7 }, children: description }),
2100
+ action && /* @__PURE__ */ jsx21("div", { style: { marginTop: 16 }, children: action })
2101
+ ] });
2102
+ }
2103
+
2104
+ // src/hooks/useLoginForm.ts
2105
+ import { useCallback as useCallback5, useMemo as useMemo4, useState as useState6 } from "react";
2106
+ import { useHook as useHook6 } from "@hook-sdk/sdk";
2107
+
2108
+ // src/errors.ts
2109
+ import { SdkError, SdkAuthError, SdkRateLimitError } from "@hook-sdk/sdk";
2110
+ function mapSdkError(err) {
2111
+ if (err instanceof SdkRateLimitError) {
2112
+ return {
2113
+ code: "rate_limited",
2114
+ message: `Aguarde ${err.retryAfter}s e tente novamente.`,
2115
+ retryAfter: err.retryAfter
2116
+ };
2117
+ }
2118
+ if (err instanceof SdkAuthError) {
2119
+ const detail = err.detail;
2120
+ if (detail === "email_unverified") {
2121
+ return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
2122
+ }
2123
+ if (detail === "account_locked") {
2124
+ return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
2125
+ }
2126
+ return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
2127
+ }
2128
+ if (err instanceof SdkError && err.httpStatus === 0) {
2129
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2130
+ }
2131
+ if (err instanceof TypeError) {
2132
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2133
+ }
2134
+ return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
2135
+ }
2136
+
2137
+ // src/hooks/useLoginForm.ts
2138
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2139
+ var MIN_PASSWORD = 8;
1590
2140
  function useLoginForm() {
1591
- const { auth } = useHook4();
2141
+ const { auth } = useHook6();
1592
2142
  const [email, setEmail] = useState6("");
1593
2143
  const [password, setPassword] = useState6("");
1594
2144
  const [submitting, setSubmitting] = useState6(false);
1595
2145
  const [error, setError] = useState6(null);
1596
- const emailError = useMemo3(() => {
2146
+ const emailError = useMemo4(() => {
1597
2147
  if (email.length === 0) return null;
1598
2148
  if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
1599
2149
  return null;
1600
2150
  }, [email]);
1601
- const passwordError = useMemo3(() => {
2151
+ const passwordError = useMemo4(() => {
1602
2152
  if (password.length === 0) return null;
1603
2153
  if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
1604
2154
  return null;
1605
2155
  }, [password]);
1606
2156
  const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
1607
- const submit = useCallback3(async () => {
2157
+ const submit = useCallback5(async () => {
1608
2158
  if (!canSubmit) return false;
1609
2159
  setSubmitting(true);
1610
2160
  setError(null);
@@ -1633,259 +2183,35 @@ function useLoginForm() {
1633
2183
  };
1634
2184
  }
1635
2185
 
1636
- // src/internal/GoogleSignInButton.tsx
1637
- import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1638
- function GoogleSignInButton({
1639
- onClick,
1640
- testId = "oauth-google",
1641
- label = "Continuar com Google"
1642
- }) {
1643
- return /* @__PURE__ */ jsxs12(
1644
- "button",
1645
- {
1646
- "data-testid": testId,
1647
- type: "button",
1648
- onClick,
1649
- style: {
1650
- width: "100%",
1651
- padding: "10px 12px",
1652
- display: "flex",
1653
- alignItems: "center",
1654
- justifyContent: "center",
1655
- gap: 10,
1656
- background: "#fff",
1657
- color: "#1f1f1f",
1658
- border: "1px solid #dadce0",
1659
- borderRadius: 8,
1660
- cursor: "pointer",
1661
- fontSize: 14,
1662
- fontWeight: 500,
1663
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
1664
- },
1665
- children: [
1666
- /* @__PURE__ */ jsx18(GoogleGlyph, {}),
1667
- label
1668
- ]
1669
- }
1670
- );
1671
- }
1672
- function GoogleGlyph() {
1673
- return /* @__PURE__ */ jsxs12("svg", { width: "18", height: "18", viewBox: "0 0 18 18", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [
1674
- /* @__PURE__ */ jsx18(
1675
- "path",
1676
- {
1677
- d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.874 2.684-6.615z",
1678
- fill: "#4285F4"
1679
- }
1680
- ),
1681
- /* @__PURE__ */ jsx18(
1682
- "path",
1683
- {
1684
- d: "M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z",
1685
- fill: "#34A853"
1686
- }
1687
- ),
1688
- /* @__PURE__ */ jsx18(
1689
- "path",
1690
- {
1691
- d: "M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z",
1692
- fill: "#FBBC05"
1693
- }
1694
- ),
1695
- /* @__PURE__ */ jsx18(
1696
- "path",
1697
- {
1698
- d: "M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z",
1699
- fill: "#EA4335"
1700
- }
1701
- )
1702
- ] });
1703
- }
1704
-
1705
- // src/internal/OAuthErrorBanner.tsx
1706
- import { useEffect as useEffect6, useState as useState7 } from "react";
1707
- import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1708
- var ERROR_MESSAGES = {
1709
- invalid_state: "Sess\xE3o expirou, tente de novo.",
1710
- access_denied: "Voc\xEA cancelou o login com Google.",
1711
- provider_error: "O Google recusou a autentica\xE7\xE3o. Tente de novo em alguns segundos.",
1712
- invalid_return_to: "Link inv\xE1lido. Tente entrar novamente."
1713
- };
1714
- function readErrorCode() {
1715
- if (typeof window === "undefined") return null;
1716
- const code = new URLSearchParams(window.location.search).get("oauth_error");
1717
- if (!code) return null;
1718
- return code;
1719
- }
1720
- function stripErrorFromUrl() {
1721
- if (typeof window === "undefined") return;
1722
- const url = new URL(window.location.href);
1723
- url.searchParams.delete("oauth_error");
1724
- window.history.replaceState({}, "", url.toString());
1725
- }
1726
- function OAuthErrorBanner() {
1727
- const [code, setCode] = useState7(() => readErrorCode());
1728
- useEffect6(() => {
1729
- if (code !== null) stripErrorFromUrl();
1730
- }, [code]);
1731
- if (!code) return null;
1732
- const message = ERROR_MESSAGES[code] ?? "N\xE3o conseguimos conectar ao Google. Tente de novo.";
1733
- return /* @__PURE__ */ jsxs13(
1734
- "div",
1735
- {
1736
- role: "alert",
1737
- "data-testid": "oauth-error-banner",
1738
- style: {
1739
- padding: "10px 12px",
1740
- marginBottom: 16,
1741
- background: "#fce8e6",
1742
- color: "#a50e0e",
1743
- borderRadius: 8,
1744
- fontSize: 14
1745
- },
1746
- children: [
1747
- message,
1748
- /* @__PURE__ */ jsx19(
1749
- "button",
1750
- {
1751
- type: "button",
1752
- onClick: () => setCode(null),
1753
- "aria-label": "Fechar",
1754
- style: {
1755
- float: "right",
1756
- background: "none",
1757
- border: "none",
1758
- color: "#a50e0e",
1759
- cursor: "pointer",
1760
- fontSize: 16,
1761
- lineHeight: 1,
1762
- padding: 0
1763
- },
1764
- children: "\xD7"
1765
- }
1766
- )
1767
- ]
1768
- }
1769
- );
1770
- }
1771
-
1772
- // src/defaults/DefaultLoginScreen.tsx
1773
- import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
1774
- function DefaultLoginScreen({ onNavigate }) {
1775
- const { name } = useTemplateConfig();
1776
- const f = useLoginForm();
1777
- return /* @__PURE__ */ jsxs14("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
1778
- /* @__PURE__ */ jsx20("h1", { style: { marginBottom: 8 }, children: name }),
1779
- /* @__PURE__ */ jsx20("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Entre na sua conta" }),
1780
- /* @__PURE__ */ jsx20(OAuthErrorBanner, {}),
1781
- /* @__PURE__ */ jsx20(GoogleSignInButton, { onClick: f.loginWithGoogle, testId: "login-oauth-google" }),
1782
- /* @__PURE__ */ jsxs14(
1783
- "div",
1784
- {
1785
- "aria-hidden": "true",
1786
- style: {
1787
- display: "flex",
1788
- alignItems: "center",
1789
- gap: 8,
1790
- margin: "16px 0",
1791
- color: "rgba(0,0,0,0.45)",
1792
- fontSize: 12
1793
- },
1794
- children: [
1795
- /* @__PURE__ */ jsx20("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } }),
1796
- "ou",
1797
- /* @__PURE__ */ jsx20("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } })
1798
- ]
1799
- }
1800
- ),
1801
- /* @__PURE__ */ jsxs14("form", { onSubmit: (e) => {
1802
- e.preventDefault();
1803
- void f.submit();
1804
- }, children: [
1805
- /* @__PURE__ */ jsxs14("label", { style: { display: "block", marginBottom: 12 }, children: [
1806
- "E-mail",
1807
- /* @__PURE__ */ jsx20(
1808
- "input",
1809
- {
1810
- "data-testid": "login-email",
1811
- type: "email",
1812
- value: f.email,
1813
- onChange: (e) => f.setEmail(e.target.value),
1814
- style: { display: "block", width: "100%" }
1815
- }
1816
- ),
1817
- f.emailError && /* @__PURE__ */ jsx20("small", { style: { color: "#c00" }, children: f.emailError })
1818
- ] }),
1819
- /* @__PURE__ */ jsxs14("label", { style: { display: "block", marginBottom: 12 }, children: [
1820
- "Senha",
1821
- /* @__PURE__ */ jsx20(
1822
- "input",
1823
- {
1824
- "data-testid": "login-password",
1825
- type: "password",
1826
- value: f.password,
1827
- onChange: (e) => f.setPassword(e.target.value),
1828
- style: { display: "block", width: "100%" }
1829
- }
1830
- ),
1831
- f.passwordError && /* @__PURE__ */ jsx20("small", { style: { color: "#c00" }, children: f.passwordError })
1832
- ] }),
1833
- f.error && /* @__PURE__ */ jsx20("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
1834
- /* @__PURE__ */ jsx20(
1835
- "button",
1836
- {
1837
- "data-testid": "login-submit",
1838
- type: "submit",
1839
- disabled: !f.canSubmit,
1840
- style: {
1841
- width: "100%",
1842
- padding: 12,
1843
- background: "var(--hook-color-primary)",
1844
- color: "#fff",
1845
- border: "none",
1846
- borderRadius: 8,
1847
- opacity: f.canSubmit ? 1 : 0.5
1848
- },
1849
- children: f.submitting ? "Entrando..." : "Entrar"
1850
- }
1851
- )
1852
- ] }),
1853
- /* @__PURE__ */ jsxs14("div", { style: { marginTop: 16, display: "flex", justifyContent: "space-between" }, children: [
1854
- /* @__PURE__ */ jsx20("button", { "data-testid": "login-goto-signup", type: "button", onClick: () => onNavigate("signup"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Criar conta" }),
1855
- /* @__PURE__ */ jsx20("button", { "data-testid": "login-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Esqueci senha" })
1856
- ] })
1857
- ] });
1858
- }
1859
-
1860
2186
  // src/hooks/useSignupForm.ts
1861
- import { useCallback as useCallback4, useMemo as useMemo4, useState as useState8 } from "react";
1862
- import { useHook as useHook5 } from "@hook-sdk/sdk";
2187
+ import { useCallback as useCallback6, useMemo as useMemo5, useState as useState7 } from "react";
2188
+ import { useHook as useHook7 } from "@hook-sdk/sdk";
1863
2189
  var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1864
2190
  var MIN_PASSWORD2 = 8;
1865
2191
  function useSignupForm() {
1866
- const { auth } = useHook5();
1867
- const [name, setName] = useState8("");
1868
- const [email, setEmail] = useState8("");
1869
- const [password, setPassword] = useState8("");
1870
- const [submitting, setSubmitting] = useState8(false);
1871
- const [error, setError] = useState8(null);
1872
- const nameError = useMemo4(() => {
2192
+ const { auth } = useHook7();
2193
+ const [name, setName] = useState7("");
2194
+ const [email, setEmail] = useState7("");
2195
+ const [password, setPassword] = useState7("");
2196
+ const [submitting, setSubmitting] = useState7(false);
2197
+ const [error, setError] = useState7(null);
2198
+ const nameError = useMemo5(() => {
1873
2199
  if (name.length === 0) return null;
1874
2200
  if (name.trim().length < 2) return "Nome muito curto.";
1875
2201
  return null;
1876
2202
  }, [name]);
1877
- const emailError = useMemo4(() => {
2203
+ const emailError = useMemo5(() => {
1878
2204
  if (email.length === 0) return null;
1879
2205
  if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
1880
2206
  return null;
1881
2207
  }, [email]);
1882
- const passwordError = useMemo4(() => {
2208
+ const passwordError = useMemo5(() => {
1883
2209
  if (password.length === 0) return null;
1884
2210
  if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
1885
2211
  return null;
1886
2212
  }, [password]);
1887
2213
  const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
1888
- const submit = useCallback4(async () => {
2214
+ const submit = useCallback6(async () => {
1889
2215
  if (!canSubmit) return false;
1890
2216
  setSubmitting(true);
1891
2217
  setError(null);
@@ -1899,1100 +2225,173 @@ function useSignupForm() {
1899
2225
  setSubmitting(false);
1900
2226
  }
1901
2227
  }, [auth, name, email, password, canSubmit]);
1902
- return {
1903
- name,
1904
- setName,
1905
- nameError,
1906
- email,
1907
- setEmail,
1908
- emailError,
1909
- password,
1910
- setPassword,
1911
- passwordError,
1912
- submit,
1913
- submitting,
1914
- canSubmit,
1915
- error,
1916
- loginWithGoogle: () => auth.loginWithGoogle()
1917
- };
1918
- }
1919
-
1920
- // src/defaults/DefaultSignupScreen.tsx
1921
- import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1922
- function DefaultSignupScreen({ onNavigate }) {
1923
- const { name } = useTemplateConfig();
1924
- const f = useSignupForm();
1925
- return /* @__PURE__ */ jsxs15("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
1926
- /* @__PURE__ */ jsx21("h1", { style: { marginBottom: 8 }, children: name }),
1927
- /* @__PURE__ */ jsx21("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Criar sua conta" }),
1928
- /* @__PURE__ */ jsx21(OAuthErrorBanner, {}),
1929
- /* @__PURE__ */ jsx21(GoogleSignInButton, { onClick: f.loginWithGoogle, testId: "signup-oauth-google" }),
1930
- /* @__PURE__ */ jsxs15(
1931
- "div",
1932
- {
1933
- "aria-hidden": "true",
1934
- style: {
1935
- display: "flex",
1936
- alignItems: "center",
1937
- gap: 8,
1938
- margin: "16px 0",
1939
- color: "rgba(0,0,0,0.45)",
1940
- fontSize: 12
1941
- },
1942
- children: [
1943
- /* @__PURE__ */ jsx21("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } }),
1944
- "ou",
1945
- /* @__PURE__ */ jsx21("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } })
1946
- ]
1947
- }
1948
- ),
1949
- /* @__PURE__ */ jsxs15("form", { onSubmit: (e) => {
1950
- e.preventDefault();
1951
- void f.submit();
1952
- }, children: [
1953
- /* @__PURE__ */ jsxs15("label", { style: { display: "block", marginBottom: 12 }, children: [
1954
- "Nome",
1955
- /* @__PURE__ */ jsx21("input", { "data-testid": "signup-name", value: f.name, onChange: (e) => f.setName(e.target.value), style: { display: "block", width: "100%" } }),
1956
- f.nameError && /* @__PURE__ */ jsx21("small", { style: { color: "#c00" }, children: f.nameError })
1957
- ] }),
1958
- /* @__PURE__ */ jsxs15("label", { style: { display: "block", marginBottom: 12 }, children: [
1959
- "E-mail",
1960
- /* @__PURE__ */ jsx21("input", { "data-testid": "signup-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
1961
- f.emailError && /* @__PURE__ */ jsx21("small", { style: { color: "#c00" }, children: f.emailError })
1962
- ] }),
1963
- /* @__PURE__ */ jsxs15("label", { style: { display: "block", marginBottom: 12 }, children: [
1964
- "Senha",
1965
- /* @__PURE__ */ jsx21("input", { "data-testid": "signup-password", type: "password", value: f.password, onChange: (e) => f.setPassword(e.target.value), style: { display: "block", width: "100%" } }),
1966
- f.passwordError && /* @__PURE__ */ jsx21("small", { style: { color: "#c00" }, children: f.passwordError })
1967
- ] }),
1968
- f.error && /* @__PURE__ */ jsx21("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
1969
- /* @__PURE__ */ jsx21("button", { "data-testid": "signup-submit", type: "submit", disabled: !f.canSubmit, style: { width: "100%", padding: 12, background: "var(--hook-color-primary)", color: "#fff", border: "none", borderRadius: 8, opacity: f.canSubmit ? 1 : 0.5 }, children: f.submitting ? "Criando..." : "Criar conta" })
1970
- ] }),
1971
- /* @__PURE__ */ jsx21("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ jsx21("button", { "data-testid": "signup-goto-login", type: "button", onClick: () => onNavigate("login"), style: { background: "none", border: "none", cursor: "pointer" }, children: "J\xE1 tem conta? Entre" }) })
1972
- ] });
1973
- }
1974
-
1975
- // src/hooks/useForgotForm.ts
1976
- import { useCallback as useCallback5, useMemo as useMemo5, useState as useState9 } from "react";
1977
- import { useHook as useHook6 } from "@hook-sdk/sdk";
1978
- var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1979
- function useForgotForm() {
1980
- const { auth } = useHook6();
1981
- const [email, setEmail] = useState9("");
1982
- const [submitting, setSubmitting] = useState9(false);
1983
- const [sent, setSent] = useState9(false);
1984
- const [error, setError] = useState9(null);
1985
- const emailError = useMemo5(() => {
1986
- if (email.length === 0) return null;
1987
- if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
1988
- return null;
1989
- }, [email]);
1990
- const canSubmit = email.length > 0 && emailError === null && !submitting;
1991
- const submit = useCallback5(async () => {
1992
- if (!canSubmit) return false;
1993
- setSubmitting(true);
1994
- setError(null);
1995
- try {
1996
- await auth.forgot({ email });
1997
- setSent(true);
1998
- return true;
1999
- } catch (err) {
2000
- setError(mapSdkError(err));
2001
- return false;
2002
- } finally {
2003
- setSubmitting(false);
2004
- }
2005
- }, [auth, email, canSubmit]);
2006
- return {
2007
- email,
2008
- setEmail,
2009
- emailError,
2010
- submit,
2011
- submitting,
2012
- canSubmit,
2013
- sent,
2014
- error
2015
- };
2016
- }
2017
-
2018
- // src/defaults/DefaultForgotScreen.tsx
2019
- import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
2020
- function DefaultForgotScreen({ onNavigate }) {
2021
- const { name } = useTemplateConfig();
2022
- const f = useForgotForm();
2023
- if (f.sent) {
2024
- return /* @__PURE__ */ jsxs16("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
2025
- /* @__PURE__ */ jsx22("h1", { children: "Verifique seu e-mail" }),
2026
- /* @__PURE__ */ jsx22("p", { style: { opacity: 0.7 }, children: "Enviamos um link pra redefinir sua senha." }),
2027
- /* @__PURE__ */ jsx22("button", { "data-testid": "forgot-back-login", type: "button", onClick: () => onNavigate("login"), children: "Voltar pro login" })
2028
- ] });
2029
- }
2030
- return /* @__PURE__ */ jsxs16("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
2031
- /* @__PURE__ */ jsx22("h1", { style: { marginBottom: 8 }, children: name }),
2032
- /* @__PURE__ */ jsx22("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Redefinir senha" }),
2033
- /* @__PURE__ */ jsxs16("form", { onSubmit: (e) => {
2034
- e.preventDefault();
2035
- void f.submit();
2036
- }, children: [
2037
- /* @__PURE__ */ jsxs16("label", { style: { display: "block", marginBottom: 12 }, children: [
2038
- "E-mail",
2039
- /* @__PURE__ */ jsx22("input", { "data-testid": "forgot-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
2040
- f.emailError && /* @__PURE__ */ jsx22("small", { style: { color: "#c00" }, children: f.emailError })
2041
- ] }),
2042
- f.error && /* @__PURE__ */ jsx22("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
2043
- /* @__PURE__ */ jsx22("button", { "data-testid": "forgot-submit", type: "submit", disabled: !f.canSubmit, style: { width: "100%", padding: 12, background: "var(--hook-color-primary)", color: "#fff", border: "none", borderRadius: 8, opacity: f.canSubmit ? 1 : 0.5 }, children: f.submitting ? "Enviando..." : "Enviar link" })
2044
- ] }),
2045
- /* @__PURE__ */ jsx22("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ jsx22("button", { "data-testid": "forgot-goto-login", type: "button", onClick: () => onNavigate("login"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Voltar pro login" }) })
2046
- ] });
2047
- }
2048
-
2049
- // src/hooks/useResetForm.ts
2050
- import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo6, useState as useState10 } from "react";
2051
- import { useHook as useHook7 } from "@hook-sdk/sdk";
2052
- var MIN_PASSWORD3 = 12;
2053
- function useResetForm() {
2054
- const { auth } = useHook7();
2055
- const [token, setToken] = useState10(null);
2056
- const [password, setPassword] = useState10("");
2057
- const [confirm, setConfirm] = useState10("");
2058
- const [submitting, setSubmitting] = useState10(false);
2059
- const [done, setDone] = useState10(false);
2060
- const [error, setError] = useState10(null);
2061
- useEffect7(() => {
2062
- if (typeof window === "undefined") return;
2063
- const params = new URLSearchParams(window.location.search);
2064
- const t = params.get("token");
2065
- setToken(t && t.length > 0 ? t : null);
2066
- }, []);
2067
- const passwordError = useMemo6(() => {
2068
- if (password.length === 0) return null;
2069
- if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2070
- return null;
2071
- }, [password]);
2072
- const confirmError = useMemo6(() => {
2073
- if (confirm.length === 0) return null;
2074
- if (confirm !== password) return "Senhas n\xE3o coincidem.";
2075
- return null;
2076
- }, [confirm, password]);
2077
- const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
2078
- const submit = useCallback6(async () => {
2079
- if (!canSubmit || token === null) return;
2080
- setSubmitting(true);
2081
- setError(null);
2082
- try {
2083
- await auth.reset({ token, newPassword: password });
2084
- setDone(true);
2085
- if (typeof window !== "undefined") {
2086
- const url = new URL(window.location.href);
2087
- url.searchParams.delete("token");
2088
- url.searchParams.delete("screen");
2089
- window.history.replaceState({}, "", url.toString());
2090
- }
2091
- } catch (err) {
2092
- setError(mapSdkError(err));
2093
- } finally {
2094
- setSubmitting(false);
2095
- }
2096
- }, [auth, token, password, canSubmit]);
2097
- return {
2098
- token,
2099
- password,
2100
- setPassword,
2101
- passwordError,
2102
- confirm,
2103
- setConfirm,
2104
- confirmError,
2105
- submit,
2106
- submitting,
2107
- canSubmit,
2108
- done,
2109
- error
2110
- };
2111
- }
2112
-
2113
- // src/defaults/DefaultResetScreen.tsx
2114
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
2115
- function DefaultResetScreen({ onNavigate }) {
2116
- const { name } = useTemplateConfig();
2117
- const f = useResetForm();
2118
- if (f.done) {
2119
- return /* @__PURE__ */ jsxs17("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
2120
- /* @__PURE__ */ jsx23("h1", { children: "Senha alterada" }),
2121
- /* @__PURE__ */ jsx23("p", { style: { opacity: 0.7 }, children: "Agora \xE9 s\xF3 fazer login com a nova senha." }),
2122
- /* @__PURE__ */ jsx23("button", { "data-testid": "reset-back-login", type: "button", onClick: () => onNavigate("login"), children: "Ir pro login" })
2123
- ] });
2124
- }
2125
- if (f.token === null) {
2126
- return /* @__PURE__ */ jsxs17("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
2127
- /* @__PURE__ */ jsx23("h1", { children: "Link inv\xE1lido" }),
2128
- /* @__PURE__ */ jsx23("p", { style: { opacity: 0.7 }, children: "Pe\xE7a um novo link de reset." }),
2129
- /* @__PURE__ */ jsx23("button", { "data-testid": "reset-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), children: "Pedir novo link" })
2130
- ] });
2131
- }
2132
- return /* @__PURE__ */ jsxs17("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
2133
- /* @__PURE__ */ jsx23("h1", { style: { marginBottom: 8 }, children: name }),
2134
- /* @__PURE__ */ jsx23("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Escolha uma nova senha" }),
2135
- /* @__PURE__ */ jsxs17("form", { onSubmit: (e) => {
2136
- e.preventDefault();
2137
- void f.submit();
2138
- }, children: [
2139
- /* @__PURE__ */ jsxs17("label", { style: { display: "block", marginBottom: 12 }, children: [
2140
- "Nova senha",
2141
- /* @__PURE__ */ jsx23("input", { "data-testid": "reset-password", type: "password", value: f.password, onChange: (e) => f.setPassword(e.target.value), style: { display: "block", width: "100%" }, autoComplete: "new-password" }),
2142
- f.passwordError && /* @__PURE__ */ jsx23("small", { style: { color: "#c00" }, children: f.passwordError })
2143
- ] }),
2144
- /* @__PURE__ */ jsxs17("label", { style: { display: "block", marginBottom: 12 }, children: [
2145
- "Confirmar senha",
2146
- /* @__PURE__ */ jsx23("input", { "data-testid": "reset-confirm", type: "password", value: f.confirm, onChange: (e) => f.setConfirm(e.target.value), style: { display: "block", width: "100%" }, autoComplete: "new-password" }),
2147
- f.confirmError && /* @__PURE__ */ jsx23("small", { style: { color: "#c00" }, children: f.confirmError })
2148
- ] }),
2149
- f.error && /* @__PURE__ */ jsx23("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
2150
- /* @__PURE__ */ jsx23("button", { "data-testid": "reset-submit", type: "submit", disabled: !f.canSubmit, style: { width: "100%", padding: 12, background: "var(--hook-color-primary)", color: "#fff", border: "none", borderRadius: 8, opacity: f.canSubmit ? 1 : 0.5 }, children: f.submitting ? "Alterando..." : "Alterar senha" })
2151
- ] })
2152
- ] });
2153
- }
2154
-
2155
- // src/defaults/DefaultPaywall.tsx
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";
2195
- function DefaultPaywall() {
2196
- const config = useTemplateConfig();
2197
- const plan = usePlan();
2198
- const {
2199
- checkout,
2200
- opening,
2201
- error,
2202
- availableMethods,
2203
- monthlyEquivalent,
2204
- pixPending,
2205
- dismissPix
2206
- } = usePaywallState();
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");
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 cycleValueCents = cycle === "YEARLY" && yearlyCents ? yearlyCents : monthlyCents;
2229
- const cycleLabel = cycle === "YEARLY" ? "por ano" : "por m\xEAs";
2230
- const pct = useMemo7(() => {
2231
- if (!yearlyCents || !monthlyCents) return 0;
2232
- const derived = Math.round((1 - yearlyCents / 12 / monthlyCents) * 100);
2233
- return Math.max(0, derived);
2234
- }, [monthlyCents, yearlyCents]);
2235
- const anchorBaseCents = cycle === "YEARLY" && yearlyCents ? monthlyFromYearly(yearlyCents) : monthlyCents;
2236
- const anchorCents = computeAnchorCents(anchorBaseCents, anchorMultiplier) ?? (anchorPriceCents && anchorPriceCents > anchorBaseCents ? anchorPriceCents : null);
2237
- const anchorDiscount = anchorCents ? discountPercent(anchorCents, activeCents) : 0;
2238
- const cpfDigits = cpf.replace(/\D/g, "");
2239
- 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;
2240
- const canCheckout = cpfDigits.length === 11 && !opening && (method === "pix-auto" || cardFieldsFilled);
2241
- const ctaLabel = useMemo7(() => {
2242
- if (opening) return "Abrindo\u2026";
2243
- if (trialDays > 0) return `Comece trial de ${trialDays} dias gr\xE1tis`;
2244
- return p.cta ?? "Assinar agora";
2245
- }, [opening, trialDays, p.cta]);
2246
- const footer = useMemo7(() => {
2247
- if (trialDays > 0) {
2248
- return `Sem cobran\xE7a agora. Cobran\xE7a autom\xE1tica em ${trialDays} dias. Cancele quando quiser.`;
2249
- }
2250
- return "Cobran\xE7a imediata. Cancele quando quiser.";
2251
- }, [trialDays]);
2252
- const yearSuffix = cardExpiryYear.length === 2 ? `20${cardExpiryYear}` : cardExpiryYear;
2253
- const phoneDigits = holderPhone.replace(/\D/g, "");
2254
- const postalDigits = holderPostalCode.replace(/\D/g, "");
2255
- const submit = () => {
2256
- if (method === "card") {
2257
- void checkout({
2258
- cpf: cpfDigits,
2259
- cycle,
2260
- method: "card",
2261
- card: {
2262
- number: cardNumber.replace(/\s/g, ""),
2263
- holderName: cardHolderName.trim(),
2264
- expiryMonth: cardExpiryMonth,
2265
- expiryYear: yearSuffix,
2266
- ccv: cardCcv
2267
- },
2268
- holderInfo: {
2269
- name: holderName.trim(),
2270
- email: holderEmail.trim(),
2271
- cpfCnpj: cpfDigits,
2272
- postalCode: postalDigits,
2273
- addressNumber: holderAddressNumber.trim(),
2274
- ...phoneDigits ? { phone: phoneDigits } : {}
2275
- }
2276
- });
2277
- return;
2278
- }
2279
- void checkout({ cpf: cpfDigits, cycle, method: "pix-auto" });
2280
- };
2281
- const inputStyle = {
2282
- width: "100%",
2283
- padding: 10,
2284
- fontSize: 14,
2285
- borderRadius: 8,
2286
- border: "1px solid #ccc",
2287
- boxSizing: "border-box"
2288
- };
2289
- const labelStyle = { display: "block", fontSize: 13, opacity: 0.75, marginBottom: 4 };
2290
- const fieldGroup = { marginBottom: 10, textAlign: "left" };
2291
- return /* @__PURE__ */ jsxs18("main", { style: { padding: 24, maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
2292
- /* @__PURE__ */ jsx24("h1", { style: { marginBottom: 8 }, children: p.title }),
2293
- p.subtitle && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.7, marginBottom: 24 }, children: p.subtitle }),
2294
- /* @__PURE__ */ jsxs18(
2295
- "div",
2296
- {
2297
- role: "group",
2298
- "aria-label": "Per\xEDodo de cobran\xE7a",
2299
- style: { display: "flex", gap: 8, marginBottom: 16 },
2300
- children: [
2301
- /* @__PURE__ */ jsx24(
2302
- "button",
2303
- {
2304
- type: "button",
2305
- "aria-pressed": cycle === "MONTHLY",
2306
- onClick: () => setCycle("MONTHLY"),
2307
- style: {
2308
- flex: 1,
2309
- padding: 12,
2310
- borderRadius: 8,
2311
- border: cycle === "MONTHLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2312
- background: cycle === "MONTHLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2313
- cursor: "pointer"
2314
- },
2315
- children: "Mensal"
2316
- }
2317
- ),
2318
- /* @__PURE__ */ jsxs18(
2319
- "button",
2320
- {
2321
- type: "button",
2322
- "aria-pressed": cycle === "YEARLY",
2323
- onClick: () => setCycle("YEARLY"),
2324
- disabled: !yearlyCents,
2325
- style: {
2326
- flex: 1,
2327
- padding: 12,
2328
- borderRadius: 8,
2329
- border: cycle === "YEARLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2330
- background: cycle === "YEARLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2331
- cursor: yearlyCents ? "pointer" : "not-allowed",
2332
- opacity: yearlyCents ? 1 : 0.5
2333
- },
2334
- children: [
2335
- "Anual",
2336
- pct > 0 ? ` \u2212${pct}%` : ""
2337
- ]
2338
- }
2339
- )
2340
- ]
2341
- }
2342
- ),
2343
- /* @__PURE__ */ jsxs18("div", { style: { marginBottom: 8 }, children: [
2344
- /* @__PURE__ */ jsxs18("div", { style: { fontSize: 32, fontWeight: 700, lineHeight: 1 }, children: [
2345
- formatBRL(activeCents),
2346
- /* @__PURE__ */ jsx24("span", { style: { fontSize: 16, fontWeight: 400, opacity: 0.7 }, children: "/m\xEAs" })
2347
- ] }),
2348
- anchorCents && /* @__PURE__ */ jsxs18("div", { style: { fontSize: 14, opacity: 0.6, marginTop: 4 }, children: [
2349
- /* @__PURE__ */ jsx24("span", { style: { textDecoration: "line-through" }, children: formatBRL(anchorCents) }),
2350
- anchorDiscount > 0 && /* @__PURE__ */ jsxs18("span", { style: { marginLeft: 6 }, children: [
2351
- "\u2212",
2352
- anchorDiscount,
2353
- "%"
2354
- ] })
2355
- ] }),
2356
- cycle === "YEARLY" && yearlyCents && /* @__PURE__ */ jsxs18("div", { style: { fontSize: 12, opacity: 0.6, marginTop: 4 }, children: [
2357
- "Cobrado ",
2358
- formatBRL(yearlyCents),
2359
- " por ano"
2360
- ] })
2361
- ] }),
2362
- /* @__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: [
2363
- /* @__PURE__ */ jsx24("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
2364
- /* @__PURE__ */ jsx24("span", { children: b })
2365
- ] }, b)) }),
2366
- /* @__PURE__ */ jsx24("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", style: { display: "flex", gap: 8, marginBottom: 16 }, children: availableMethods.map((m) => /* @__PURE__ */ jsx24(
2367
- "button",
2368
- {
2369
- type: "button",
2370
- role: "tab",
2371
- "aria-selected": method === m,
2372
- onClick: () => setMethod(m),
2373
- style: {
2374
- flex: 1,
2375
- padding: 10,
2376
- borderRadius: 8,
2377
- border: method === m ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2378
- background: method === m ? "var(--hook-color-primary-soft, #eef)" : "white",
2379
- cursor: "pointer"
2380
- },
2381
- children: m === "card" ? "\u{1F4B3} Cart\xE3o" : "\u{1F4F1} Pix Autom\xE1tico"
2382
- },
2383
- m
2384
- )) }),
2385
- method === "card" && /* @__PURE__ */ jsxs18(
2386
- "fieldset",
2387
- {
2388
- "data-testid": "paywall-card-form",
2389
- style: { border: "none", padding: 0, marginBottom: 16 },
2390
- children: [
2391
- /* @__PURE__ */ jsx24("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8 }, children: "Dados do cart\xE3o" }),
2392
- /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2393
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-number", style: labelStyle, children: "N\xFAmero do cart\xE3o" }),
2394
- /* @__PURE__ */ jsx24(
2395
- "input",
2396
- {
2397
- id: "pw-card-number",
2398
- "data-testid": "pw-card-number",
2399
- type: "text",
2400
- inputMode: "numeric",
2401
- autoComplete: "cc-number",
2402
- placeholder: "0000 0000 0000 0000",
2403
- value: cardNumber,
2404
- onChange: (e) => setCardNumber(e.target.value),
2405
- style: inputStyle
2406
- }
2407
- )
2408
- ] }),
2409
- /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2410
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-holder", style: labelStyle, children: "Nome impresso no cart\xE3o" }),
2411
- /* @__PURE__ */ jsx24(
2412
- "input",
2413
- {
2414
- id: "pw-card-holder",
2415
- "data-testid": "pw-card-holder",
2416
- type: "text",
2417
- autoComplete: "cc-name",
2418
- placeholder: "NOME SOBRENOME",
2419
- value: cardHolderName,
2420
- onChange: (e) => setCardHolderName(e.target.value),
2421
- style: inputStyle
2422
- }
2423
- )
2424
- ] }),
2425
- /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2426
- /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2427
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-exp-m", style: labelStyle, children: "M\xEAs" }),
2428
- /* @__PURE__ */ jsx24(
2429
- "input",
2430
- {
2431
- id: "pw-card-exp-m",
2432
- "data-testid": "pw-card-exp-m",
2433
- type: "text",
2434
- inputMode: "numeric",
2435
- autoComplete: "cc-exp-month",
2436
- placeholder: "MM",
2437
- maxLength: 2,
2438
- value: cardExpiryMonth,
2439
- onChange: (e) => setCardExpiryMonth(e.target.value.replace(/\D/g, "")),
2440
- style: inputStyle
2441
- }
2442
- )
2443
- ] }),
2444
- /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2445
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-exp-y", style: labelStyle, children: "Ano" }),
2446
- /* @__PURE__ */ jsx24(
2447
- "input",
2448
- {
2449
- id: "pw-card-exp-y",
2450
- "data-testid": "pw-card-exp-y",
2451
- type: "text",
2452
- inputMode: "numeric",
2453
- autoComplete: "cc-exp-year",
2454
- placeholder: "AA",
2455
- maxLength: 4,
2456
- value: cardExpiryYear,
2457
- onChange: (e) => setCardExpiryYear(e.target.value.replace(/\D/g, "")),
2458
- style: inputStyle
2459
- }
2460
- )
2461
- ] }),
2462
- /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2463
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-card-cvv", style: labelStyle, children: "CVV" }),
2464
- /* @__PURE__ */ jsx24(
2465
- "input",
2466
- {
2467
- id: "pw-card-cvv",
2468
- "data-testid": "pw-card-cvv",
2469
- type: "text",
2470
- inputMode: "numeric",
2471
- autoComplete: "cc-csc",
2472
- placeholder: "123",
2473
- maxLength: 4,
2474
- value: cardCcv,
2475
- onChange: (e) => setCardCcv(e.target.value.replace(/\D/g, "")),
2476
- style: inputStyle
2477
- }
2478
- )
2479
- ] })
2480
- ] }),
2481
- /* @__PURE__ */ jsx24("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8, marginTop: 8 }, children: "Dados do titular" }),
2482
- /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2483
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-name", style: labelStyle, children: "Nome completo" }),
2484
- /* @__PURE__ */ jsx24(
2485
- "input",
2486
- {
2487
- id: "pw-holder-name",
2488
- "data-testid": "pw-holder-name",
2489
- type: "text",
2490
- autoComplete: "name",
2491
- placeholder: "Nome Sobrenome",
2492
- value: holderName,
2493
- onChange: (e) => setHolderName(e.target.value),
2494
- style: inputStyle
2495
- }
2496
- )
2497
- ] }),
2498
- /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2499
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-email", style: labelStyle, children: "E-mail" }),
2500
- /* @__PURE__ */ jsx24(
2501
- "input",
2502
- {
2503
- id: "pw-holder-email",
2504
- "data-testid": "pw-holder-email",
2505
- type: "email",
2506
- autoComplete: "email",
2507
- placeholder: "voce@email.com",
2508
- value: holderEmail,
2509
- onChange: (e) => setHolderEmail(e.target.value),
2510
- style: inputStyle
2511
- }
2512
- )
2513
- ] }),
2514
- /* @__PURE__ */ jsxs18("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2515
- /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2516
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-cep", style: labelStyle, children: "CEP" }),
2517
- /* @__PURE__ */ jsx24(
2518
- "input",
2519
- {
2520
- id: "pw-holder-cep",
2521
- "data-testid": "pw-holder-cep",
2522
- type: "text",
2523
- inputMode: "numeric",
2524
- autoComplete: "postal-code",
2525
- placeholder: "00000-000",
2526
- value: holderPostalCode,
2527
- onChange: (e) => setHolderPostalCode(e.target.value),
2528
- style: inputStyle
2529
- }
2530
- )
2531
- ] }),
2532
- /* @__PURE__ */ jsxs18("div", { style: { flex: 1, textAlign: "left" }, children: [
2533
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-addr-n", style: labelStyle, children: "N\xFAmero" }),
2534
- /* @__PURE__ */ jsx24(
2535
- "input",
2536
- {
2537
- id: "pw-holder-addr-n",
2538
- "data-testid": "pw-holder-addr-n",
2539
- type: "text",
2540
- inputMode: "numeric",
2541
- placeholder: "123",
2542
- value: holderAddressNumber,
2543
- onChange: (e) => setHolderAddressNumber(e.target.value),
2544
- style: inputStyle
2545
- }
2546
- )
2547
- ] })
2548
- ] }),
2549
- /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2550
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-holder-phone", style: labelStyle, children: "Telefone (opcional)" }),
2551
- /* @__PURE__ */ jsx24(
2552
- "input",
2553
- {
2554
- id: "pw-holder-phone",
2555
- "data-testid": "pw-holder-phone",
2556
- type: "tel",
2557
- inputMode: "tel",
2558
- autoComplete: "tel",
2559
- placeholder: "(11) 99999-9999",
2560
- value: holderPhone,
2561
- onChange: (e) => setHolderPhone(e.target.value),
2562
- style: inputStyle
2563
- }
2564
- )
2565
- ] })
2566
- ]
2567
- }
2568
- ),
2569
- /* @__PURE__ */ jsxs18("div", { style: fieldGroup, children: [
2570
- /* @__PURE__ */ jsx24("label", { htmlFor: "pw-cpf", style: labelStyle, children: "Seu CPF" }),
2571
- /* @__PURE__ */ jsx24(
2572
- "input",
2573
- {
2574
- id: "pw-cpf",
2575
- "data-testid": "paywall-cpf",
2576
- type: "text",
2577
- inputMode: "numeric",
2578
- placeholder: "000.000.000-00",
2579
- value: cpf,
2580
- onChange: (e) => setCpf(e.target.value),
2581
- style: inputStyle
2582
- }
2583
- )
2584
- ] }),
2585
- error && /* @__PURE__ */ jsx24("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: error.message }),
2586
- method === "pix-auto" && /* @__PURE__ */ jsxs18(
2587
- "div",
2588
- {
2589
- "data-testid": "paywall-pix-callout",
2590
- style: {
2591
- background: "rgba(0,0,0,0.04)",
2592
- borderLeft: "3px solid var(--hook-color-primary)",
2593
- padding: "10px 12px",
2594
- marginBottom: 12,
2595
- borderRadius: 4,
2596
- fontSize: 12,
2597
- lineHeight: 1.45,
2598
- textAlign: "left"
2599
- },
2600
- children: [
2601
- /* @__PURE__ */ jsx24("strong", { children: "Como funciona:" }),
2602
- " no app do seu banco vai aparecer uma cobran\xE7a de",
2603
- " ",
2604
- /* @__PURE__ */ jsx24("strong", { children: "R$ 0,01" }),
2605
- " \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
2606
- trialDays > 0 ? /* @__PURE__ */ jsxs18(Fragment6, { children: [
2607
- " ",
2608
- "Depois, ",
2609
- /* @__PURE__ */ jsxs18("strong", { children: [
2610
- trialDays,
2611
- " dias gr\xE1tis"
2612
- ] }),
2613
- "; a cobran\xE7a de",
2614
- " ",
2615
- /* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
2616
- " ",
2617
- cycleLabel,
2618
- " s\xF3 come\xE7a depois."
2619
- ] }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
2620
- " ",
2621
- "A cobran\xE7a de ",
2622
- /* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
2623
- " ",
2624
- cycleLabel,
2625
- " vem em seguida."
2626
- ] })
2627
- ]
2628
- }
2629
- ),
2630
- /* @__PURE__ */ jsx24(
2631
- "button",
2632
- {
2633
- "data-testid": "paywall-cta",
2634
- type: "button",
2635
- onClick: submit,
2636
- disabled: !canCheckout,
2637
- style: {
2638
- width: "100%",
2639
- padding: 14,
2640
- background: "var(--hook-color-primary)",
2641
- color: "#fff",
2642
- border: "none",
2643
- borderRadius: 8,
2644
- opacity: canCheckout ? 1 : 0.5,
2645
- fontSize: 16,
2646
- fontWeight: 600,
2647
- cursor: canCheckout ? "pointer" : "not-allowed"
2648
- },
2649
- children: ctaLabel
2650
- }
2651
- ),
2652
- /* @__PURE__ */ jsx24("p", { style: { opacity: 0.55, marginTop: 16, fontSize: 12 }, children: footer }),
2653
- p.priceHint && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.6, marginTop: 8, fontSize: 12 }, children: p.priceHint }),
2654
- p.footerNote && /* @__PURE__ */ jsx24("p", { style: { opacity: 0.5, marginTop: 8, fontSize: 12 }, children: p.footerNote }),
2655
- pixPending && /* @__PURE__ */ jsx24(
2656
- "div",
2657
- {
2658
- "data-testid": "paywall-pix-modal",
2659
- role: "dialog",
2660
- "aria-label": "Pagamento Pix pendente",
2661
- style: {
2662
- position: "fixed",
2663
- inset: 0,
2664
- background: "rgba(0,0,0,0.6)",
2665
- display: "flex",
2666
- alignItems: "center",
2667
- justifyContent: "center",
2668
- padding: 24,
2669
- zIndex: 1e3
2670
- },
2671
- children: /* @__PURE__ */ jsxs18("div", { style: {
2672
- background: "#fff",
2673
- borderRadius: 12,
2674
- padding: 24,
2675
- maxWidth: 360,
2676
- width: "100%",
2677
- textAlign: "center"
2678
- }, children: [
2679
- pixPending.paid ? /* @__PURE__ */ jsxs18(Fragment6, { children: [
2680
- /* @__PURE__ */ jsx24("h2", { style: { marginTop: 0 }, children: "Pagamento confirmado!" }),
2681
- /* @__PURE__ */ jsx24("p", { style: { opacity: 0.7 }, children: "Liberando acesso\u2026" })
2682
- ] }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
2683
- /* @__PURE__ */ jsx24("h2", { style: { marginTop: 0, fontSize: 18 }, children: "Pague com Pix Autom\xE1tico" }),
2684
- /* @__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." }),
2685
- /* @__PURE__ */ jsxs18(
2686
- "p",
2687
- {
2688
- "data-testid": "pix-modal-explain",
2689
- style: { fontSize: 12, opacity: 0.65, marginTop: 8, lineHeight: 1.4 },
2690
- children: [
2691
- "Seu banco vai mostrar uma cobran\xE7a de ",
2692
- /* @__PURE__ */ jsx24("strong", { children: "R$ 0,01" }),
2693
- " \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
2694
- trialDays > 0 ? /* @__PURE__ */ jsxs18(Fragment6, { children: [
2695
- " ",
2696
- "A cobran\xE7a de ",
2697
- /* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
2698
- " ",
2699
- cycleLabel,
2700
- " come\xE7a depois dos ",
2701
- /* @__PURE__ */ jsxs18("strong", { children: [
2702
- trialDays,
2703
- " dias gr\xE1tis"
2704
- ] }),
2705
- ", automaticamente."
2706
- ] }) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
2707
- " ",
2708
- "A cobran\xE7a de ",
2709
- /* @__PURE__ */ jsx24("strong", { children: formatBRL(cycleValueCents) }),
2710
- " ",
2711
- cycleLabel,
2712
- " vem logo em seguida."
2713
- ] })
2714
- ]
2715
- }
2716
- ),
2717
- pixPending.qrCodeBase64 && /* @__PURE__ */ jsx24(
2718
- "img",
2719
- {
2720
- "data-testid": "pix-qr-image",
2721
- alt: "QR Code Pix",
2722
- src: `data:image/png;base64,${pixPending.qrCodeBase64}`,
2723
- style: { width: "100%", maxWidth: 240, margin: "12px auto", display: "block" }
2724
- }
2725
- ),
2726
- pixPending.qrCodePayload && /* @__PURE__ */ jsx24(
2727
- "textarea",
2728
- {
2729
- "data-testid": "pix-qr-payload",
2730
- readOnly: true,
2731
- value: pixPending.qrCodePayload,
2732
- style: {
2733
- width: "100%",
2734
- minHeight: 72,
2735
- padding: 8,
2736
- fontSize: 11,
2737
- fontFamily: "monospace",
2738
- borderRadius: 6,
2739
- border: "1px solid #ccc",
2740
- resize: "none"
2741
- }
2742
- }
2743
- ),
2744
- /* @__PURE__ */ jsx24("p", { style: { fontSize: 11, opacity: 0.55, marginTop: 12 }, children: "Aguardando confirma\xE7\xE3o do banco\u2026 Pode levar alguns segundos." })
2745
- ] }),
2746
- /* @__PURE__ */ jsx24(
2747
- "button",
2748
- {
2749
- type: "button",
2750
- onClick: dismissPix,
2751
- style: {
2752
- marginTop: 16,
2753
- padding: "8px 16px",
2754
- border: "1px solid #ccc",
2755
- borderRadius: 6,
2756
- background: "white",
2757
- cursor: "pointer"
2758
- },
2759
- children: "Fechar"
2760
- }
2761
- )
2762
- ] })
2763
- }
2764
- )
2765
- ] });
2766
- }
2767
-
2768
- // src/AppRoot.tsx
2769
- import { Fragment as Fragment7, jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
2770
- var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2771
- function PaymentReturnHandler({ children }) {
2772
- const { subscription } = useHook9();
2773
- const subRef = useRef3(subscription);
2774
- subRef.current = subscription;
2775
- const runIdRef = useRef3(0);
2776
- const [state, setState] = useState12("idle");
2777
- const runPoll = useCallback7(() => {
2778
- const runId = ++runIdRef.current;
2779
- setState("confirming");
2780
- let attempts = 0;
2781
- const tick = async () => {
2782
- if (runIdRef.current !== runId) return;
2783
- attempts++;
2784
- try {
2785
- await subRef.current.refresh();
2786
- } catch {
2787
- }
2788
- if (runIdRef.current !== runId) return;
2789
- const status = subRef.current.status();
2790
- if (status === "active" || status === "trialing") {
2791
- const cleanUrl = new URL(window.location.href);
2792
- cleanUrl.searchParams.delete("paymentReturn");
2793
- window.history.replaceState({}, "", cleanUrl.toString());
2794
- setState("idle");
2795
- return;
2796
- }
2797
- const delay = BACKOFF_MS[attempts - 1];
2798
- if (delay === void 0) {
2799
- setState("waiting");
2800
- return;
2801
- }
2802
- setTimeout(tick, delay);
2803
- };
2804
- void tick();
2805
- }, []);
2806
- useEffect8(() => {
2807
- if (typeof window === "undefined") return;
2808
- const url = new URL(window.location.href);
2809
- if (url.searchParams.get("paymentReturn") !== "1") return;
2810
- runPoll();
2811
- return () => {
2812
- runIdRef.current++;
2813
- };
2814
- }, [runPoll]);
2815
- if (state === "confirming") {
2816
- return /* @__PURE__ */ jsx25(
2817
- "div",
2818
- {
2819
- role: "status",
2820
- "aria-live": "polite",
2821
- style: overlayStyle2,
2822
- children: "Confirmando pagamento\u2026"
2823
- }
2824
- );
2825
- }
2826
- if (state === "waiting") {
2827
- return /* @__PURE__ */ jsx25("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs19("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
2828
- /* @__PURE__ */ jsx25("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
2829
- /* @__PURE__ */ jsx25(
2830
- "button",
2831
- {
2832
- type: "button",
2833
- onClick: runPoll,
2834
- style: buttonStyle,
2835
- children: "Atualizar"
2836
- }
2837
- )
2838
- ] }) });
2839
- }
2840
- return /* @__PURE__ */ jsx25(Fragment7, { children });
2841
- }
2842
- var overlayStyle2 = {
2843
- position: "fixed",
2844
- inset: 0,
2845
- display: "flex",
2846
- alignItems: "center",
2847
- justifyContent: "center",
2848
- background: "rgba(0,0,0,0.4)",
2849
- zIndex: 9999,
2850
- color: "white",
2851
- fontSize: "1rem",
2852
- padding: 24
2853
- };
2854
- var buttonStyle = {
2855
- background: "white",
2856
- color: "black",
2857
- border: "none",
2858
- borderRadius: 8,
2859
- padding: "10px 24px",
2860
- fontSize: "1rem",
2861
- fontWeight: 600,
2862
- cursor: "pointer"
2863
- };
2864
- function AppRoot({
2865
- config,
2866
- children,
2867
- Login = DefaultLoginScreen,
2868
- Signup = DefaultSignupScreen,
2869
- Forgot = DefaultForgotScreen,
2870
- Reset = DefaultResetScreen,
2871
- Paywall = DefaultPaywall
2872
- }) {
2873
- return /* @__PURE__ */ jsx25(PaymentReturnHandler, { children: /* @__PURE__ */ jsx25(TemplateConfigProvider, { config, children: /* @__PURE__ */ jsx25(ErrorBoundary, { children: /* @__PURE__ */ jsx25(ThemeProvider, { children: /* @__PURE__ */ jsx25(InstallGate, { children: /* @__PURE__ */ jsx25(AuthGate, { Login, Signup, Forgot, Reset, children: /* @__PURE__ */ jsx25(PersistedKeysPrefetch, { children: /* @__PURE__ */ jsxs19(SubscriptionGate, { Paywall, children: [
2874
- children,
2875
- /* @__PURE__ */ jsx25(PushPrompt, {})
2876
- ] }) }) }) }) }) }) }) });
2228
+ return {
2229
+ name,
2230
+ setName,
2231
+ nameError,
2232
+ email,
2233
+ setEmail,
2234
+ emailError,
2235
+ password,
2236
+ setPassword,
2237
+ passwordError,
2238
+ submit,
2239
+ submitting,
2240
+ canSubmit,
2241
+ error,
2242
+ loginWithGoogle: () => auth.loginWithGoogle()
2243
+ };
2877
2244
  }
2878
2245
 
2879
- // src/hooks/usePush.ts
2880
- import { useCallback as useCallback8, useEffect as useEffect9, useState as useState13 } from "react";
2881
- import { useHook as useHook10 } from "@hook-sdk/sdk";
2882
- function detectIosNeedsInstall() {
2883
- if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2884
- const ua = navigator.userAgent || "";
2885
- const isIos = /iPhone|iPad|iPod/.test(ua);
2886
- if (!isIos) return false;
2887
- const mm = window.matchMedia?.("(display-mode: standalone)");
2888
- const standalone = mm?.matches === true;
2889
- const legacyStandalone = typeof navigator.standalone === "boolean" ? navigator.standalone : false;
2890
- return !(standalone || legacyStandalone);
2891
- }
2892
- function deriveState(push) {
2893
- if (!push.isAvailable()) {
2894
- if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2895
- return { kind: "unsupported" };
2896
- }
2897
- const status = push.status();
2898
- if (status === "granted") return { kind: "subscribed" };
2899
- if (status === "denied") return { kind: "denied" };
2900
- if (status === "unsupported") {
2901
- if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2902
- return { kind: "unsupported" };
2903
- }
2904
- return { kind: "prompt" };
2905
- }
2906
- function usePush() {
2907
- const { push } = useHook10();
2908
- const [state, setState] = useState13(() => deriveState(push));
2909
- useEffect9(() => {
2910
- setState(deriveState(push));
2911
- }, [push]);
2912
- const subscribe = useCallback8(async () => {
2246
+ // src/hooks/useForgotForm.ts
2247
+ import { useCallback as useCallback7, useMemo as useMemo6, useState as useState8 } from "react";
2248
+ import { useHook as useHook8 } from "@hook-sdk/sdk";
2249
+ var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2250
+ function useForgotForm() {
2251
+ const { auth } = useHook8();
2252
+ const [email, setEmail] = useState8("");
2253
+ const [submitting, setSubmitting] = useState8(false);
2254
+ const [sent, setSent] = useState8(false);
2255
+ const [error, setError] = useState8(null);
2256
+ const emailError = useMemo6(() => {
2257
+ if (email.length === 0) return null;
2258
+ if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
2259
+ return null;
2260
+ }, [email]);
2261
+ const canSubmit = email.length > 0 && emailError === null && !submitting;
2262
+ const submit = useCallback7(async () => {
2263
+ if (!canSubmit) return false;
2264
+ setSubmitting(true);
2265
+ setError(null);
2913
2266
  try {
2914
- await push.subscribe();
2915
- setState({ kind: "subscribed" });
2916
- } catch (e) {
2917
- const code = e?.code ?? "push.unknown";
2918
- const message = e?.message ?? "Push subscription failed";
2919
- if (code === "push.permission_denied") setState({ kind: "denied" });
2920
- else setState({ kind: "error", code, message });
2921
- throw e;
2267
+ await auth.forgot({ email });
2268
+ setSent(true);
2269
+ return true;
2270
+ } catch (err) {
2271
+ setError(mapSdkError(err));
2272
+ return false;
2273
+ } finally {
2274
+ setSubmitting(false);
2922
2275
  }
2923
- }, [push]);
2924
- const unsubscribe = useCallback8(async () => {
2276
+ }, [auth, email, canSubmit]);
2277
+ return {
2278
+ email,
2279
+ setEmail,
2280
+ emailError,
2281
+ submit,
2282
+ submitting,
2283
+ canSubmit,
2284
+ sent,
2285
+ error
2286
+ };
2287
+ }
2288
+
2289
+ // src/hooks/useResetForm.ts
2290
+ import { useCallback as useCallback8, useEffect as useEffect7, useMemo as useMemo7, useState as useState9 } from "react";
2291
+ import { useHook as useHook9 } from "@hook-sdk/sdk";
2292
+ var MIN_PASSWORD3 = 12;
2293
+ function useResetForm() {
2294
+ const { auth } = useHook9();
2295
+ const [token, setToken] = useState9(null);
2296
+ const [password, setPassword] = useState9("");
2297
+ const [confirm, setConfirm] = useState9("");
2298
+ const [submitting, setSubmitting] = useState9(false);
2299
+ const [done, setDone] = useState9(false);
2300
+ const [error, setError] = useState9(null);
2301
+ useEffect7(() => {
2302
+ if (typeof window === "undefined") return;
2303
+ const params = new URLSearchParams(window.location.search);
2304
+ const t = params.get("token");
2305
+ setToken(t && t.length > 0 ? t : null);
2306
+ }, []);
2307
+ const passwordError = useMemo7(() => {
2308
+ if (password.length === 0) return null;
2309
+ if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2310
+ return null;
2311
+ }, [password]);
2312
+ const confirmError = useMemo7(() => {
2313
+ if (confirm.length === 0) return null;
2314
+ if (confirm !== password) return "Senhas n\xE3o coincidem.";
2315
+ return null;
2316
+ }, [confirm, password]);
2317
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
2318
+ const submit = useCallback8(async () => {
2319
+ if (!canSubmit || token === null) return;
2320
+ setSubmitting(true);
2321
+ setError(null);
2925
2322
  try {
2926
- await push.unsubscribe();
2927
- setState({ kind: "prompt" });
2928
- } catch (e) {
2929
- setState({ kind: "error", code: e?.code ?? "push.unknown", message: e?.message ?? "failed" });
2930
- throw e;
2323
+ await auth.reset({ token, newPassword: password });
2324
+ setDone(true);
2325
+ if (typeof window !== "undefined") {
2326
+ const url = new URL(window.location.href);
2327
+ url.searchParams.delete("token");
2328
+ url.searchParams.delete("screen");
2329
+ window.history.replaceState({}, "", url.toString());
2330
+ }
2331
+ } catch (err) {
2332
+ setError(mapSdkError(err));
2333
+ } finally {
2334
+ setSubmitting(false);
2931
2335
  }
2932
- }, [push]);
2933
- return { state, subscribe, unsubscribe };
2336
+ }, [auth, token, password, canSubmit]);
2337
+ return {
2338
+ token,
2339
+ password,
2340
+ setPassword,
2341
+ passwordError,
2342
+ confirm,
2343
+ setConfirm,
2344
+ confirmError,
2345
+ submit,
2346
+ submitting,
2347
+ canSubmit,
2348
+ done,
2349
+ error
2350
+ };
2934
2351
  }
2935
2352
 
2936
- // src/components/PushPrompt.tsx
2937
- import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
2938
- function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
2939
- const { state, subscribe } = usePush();
2940
- if (state.kind === "subscribed") return null;
2941
- if (state.kind === "ios_needs_install") {
2942
- return /* @__PURE__ */ jsxs20("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2943
- /* @__PURE__ */ jsx26("h3", { children: texts.iosInstallTitle }),
2944
- /* @__PURE__ */ jsx26("p", { children: texts.iosInstallBody }),
2945
- onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx26("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2946
- ] });
2947
- }
2948
- if (state.kind === "denied") {
2949
- return /* @__PURE__ */ jsxs20("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
2950
- /* @__PURE__ */ jsx26("h3", { children: texts.deniedTitle }),
2951
- /* @__PURE__ */ jsx26("p", { children: texts.deniedBody })
2952
- ] });
2953
- }
2954
- if (state.kind === "unsupported") {
2955
- return /* @__PURE__ */ jsx26("div", { className, role: "region", children: /* @__PURE__ */ jsx26("p", { children: texts.unsupportedBody }) });
2956
- }
2957
- if (state.kind === "error") {
2958
- return /* @__PURE__ */ jsx26("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx26("p", { children: state.message }) });
2959
- }
2960
- return /* @__PURE__ */ jsxs20("div", { className, role: "region", children: [
2961
- /* @__PURE__ */ jsx26(
2962
- "button",
2963
- {
2964
- type: "button",
2965
- onClick: async () => {
2966
- try {
2967
- await subscribe();
2968
- onSubscribed?.();
2969
- } catch {
2970
- }
2971
- },
2972
- children: texts.cta
2973
- }
2974
- ),
2975
- onDeclined && /* @__PURE__ */ jsx26("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2976
- ] });
2353
+ // src/hooks/usePlan.ts
2354
+ import { useHook as useHook10 } from "@hook-sdk/sdk";
2355
+ function usePlan() {
2356
+ const { plan } = useHook10();
2357
+ return plan;
2977
2358
  }
2978
2359
 
2979
- // src/defaults/EmptyState.tsx
2980
- import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
2981
- function EmptyState({ title, description, action }) {
2982
- return /* @__PURE__ */ jsxs21("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2983
- /* @__PURE__ */ jsx27("h2", { style: { marginBottom: 8 }, children: title }),
2984
- description && /* @__PURE__ */ jsx27("p", { style: { opacity: 0.7 }, children: description }),
2985
- action && /* @__PURE__ */ jsx27("div", { style: { marginTop: 16 }, children: action })
2986
- ] });
2360
+ // src/utils/price.ts
2361
+ function formatBRL(cents) {
2362
+ if (cents === null || cents === void 0) return "";
2363
+ const reais = cents / 100;
2364
+ return new Intl.NumberFormat("pt-BR", {
2365
+ style: "currency",
2366
+ currency: "BRL"
2367
+ }).format(reais);
2368
+ }
2369
+ function monthlyFromYearly(yearlyCents) {
2370
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2371
+ return Math.round(yearlyCents / 12);
2372
+ }
2373
+ function dailyFromYearly(yearlyCents) {
2374
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2375
+ return Math.round(yearlyCents / 365);
2376
+ }
2377
+ function computeAnchorCents(baseCents, multiplier) {
2378
+ if (multiplier === null || multiplier === void 0) return null;
2379
+ if (!Number.isFinite(multiplier)) return null;
2380
+ if (multiplier <= 1) return null;
2381
+ return Math.round(baseCents * multiplier);
2382
+ }
2383
+ function discountPercent(anchorCents, realCents) {
2384
+ if (anchorCents <= realCents) return 0;
2385
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
2987
2386
  }
2988
2387
 
2989
2388
  // src/hooks/useAuthPrimitives.ts
2990
- import { useEffect as useEffect10 } from "react";
2389
+ import { useEffect as useEffect8 } from "react";
2991
2390
  import { useHook as useHook11 } from "@hook-sdk/sdk";
2992
2391
  var warned = false;
2993
2392
  function useAuthPrimitives() {
2994
2393
  const { auth } = useHook11();
2995
- useEffect10(() => {
2394
+ useEffect8(() => {
2996
2395
  if (!warned && process.env.NODE_ENV !== "production") {
2997
2396
  warned = true;
2998
2397
  console.warn(
@@ -3013,23 +2412,34 @@ function useAuthPrimitives() {
3013
2412
  };
3014
2413
  }
3015
2414
 
3016
- // src/hooks/useSubscription.ts
2415
+ // src/hooks/useAuth.ts
3017
2416
  import { useHook as useHook12 } from "@hook-sdk/sdk";
2417
+ function useAuth() {
2418
+ const { user, authStatus, auth } = useHook12();
2419
+ return {
2420
+ user,
2421
+ authStatus,
2422
+ refresh: auth.refresh
2423
+ };
2424
+ }
2425
+
2426
+ // src/hooks/useSubscription.ts
2427
+ import { useHook as useHook13 } from "@hook-sdk/sdk";
3018
2428
  function useSubscription() {
3019
- const { subscription } = useHook12();
2429
+ const { subscription } = useHook13();
3020
2430
  return {
3021
2431
  status: subscription.status()
3022
2432
  };
3023
2433
  }
3024
2434
 
3025
2435
  // src/hooks/useReminders.ts
3026
- import { useCallback as useCallback9, useEffect as useEffect11, useState as useState14 } from "react";
3027
- import { useHook as useHook13 } from "@hook-sdk/sdk";
2436
+ import { useCallback as useCallback9, useEffect as useEffect9, useState as useState10 } from "react";
2437
+ import { useHook as useHook14 } from "@hook-sdk/sdk";
3028
2438
  function useReminders() {
3029
- const { push } = useHook13();
2439
+ const { push } = useHook14();
3030
2440
  const r = push.reminders;
3031
- const [reminders, setReminders] = useState14([]);
3032
- const [loading, setLoading] = useState14(true);
2441
+ const [reminders, setReminders] = useState10([]);
2442
+ const [loading, setLoading] = useState10(true);
3033
2443
  const reload = useCallback9(async () => {
3034
2444
  setLoading(true);
3035
2445
  try {
@@ -3039,7 +2449,7 @@ function useReminders() {
3039
2449
  setLoading(false);
3040
2450
  }
3041
2451
  }, [r]);
3042
- useEffect11(() => {
2452
+ useEffect9(() => {
3043
2453
  void reload();
3044
2454
  }, [reload]);
3045
2455
  const setReminder = useCallback9(async (input) => {
@@ -3060,9 +2470,9 @@ function useReminders() {
3060
2470
  }
3061
2471
 
3062
2472
  // src/hooks/useToast.ts
3063
- import { useCallback as useCallback10, useState as useState15 } from "react";
2473
+ import { useCallback as useCallback10, useState as useState11 } from "react";
3064
2474
  function useToast() {
3065
- const [items, setItems] = useState15([]);
2475
+ const [items, setItems] = useState11([]);
3066
2476
  const show = useCallback10((message, kind = "info") => {
3067
2477
  const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3068
2478
  setItems((prev) => [...prev, { id, message, kind }]);
@@ -3075,19 +2485,136 @@ function useToast() {
3075
2485
  }, []);
3076
2486
  return { items, show, dismiss };
3077
2487
  }
2488
+
2489
+ // src/RouteBoundary.tsx
2490
+ import { Routes as Routes2, Route as Route2 } from "react-router-dom";
2491
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
2492
+ function RouteBoundary({ children }) {
2493
+ return /* @__PURE__ */ jsxs16(Routes2, { children: [
2494
+ children,
2495
+ /* @__PURE__ */ jsx22(Route2, { path: "*", element: /* @__PURE__ */ jsx22(DefaultNotFound, {}) })
2496
+ ] });
2497
+ }
2498
+ function DefaultNotFound() {
2499
+ return /* @__PURE__ */ jsx22("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
2500
+ }
2501
+
2502
+ // src/PreAuthShell.tsx
2503
+ import { BrowserRouter as BrowserRouter2, MemoryRouter as MemoryRouter2, Routes as Routes3 } from "react-router-dom";
2504
+ import { jsx as jsx23 } from "react/jsx-runtime";
2505
+ function PreAuthShell({
2506
+ basename,
2507
+ testRouter,
2508
+ testInitialEntries,
2509
+ children
2510
+ }) {
2511
+ if (testRouter === "memory") {
2512
+ return /* @__PURE__ */ jsx23(MemoryRouter2, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ jsx23(Routes3, { children }) });
2513
+ }
2514
+ return /* @__PURE__ */ jsx23(BrowserRouter2, { basename, children: /* @__PURE__ */ jsx23(Routes3, { children }) });
2515
+ }
2516
+
2517
+ // src/OnboardingFlow.tsx
2518
+ import { useCallback as useCallback11, useMemo as useMemo8, useRef as useRef3, useState as useState12 } from "react";
2519
+ import { usePersistedState } from "@hook-sdk/sdk";
2520
+
2521
+ // src/hooks/useOnboardingStep.ts
2522
+ import { createContext as createContext3, useContext as useContext4 } from "react";
2523
+ var OnboardingStepContext = createContext3(null);
2524
+ function useOnboardingStep() {
2525
+ const ctx = useContext4(OnboardingStepContext);
2526
+ if (!ctx) {
2527
+ throw new Error(
2528
+ "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
2529
+ );
2530
+ }
2531
+ return ctx;
2532
+ }
2533
+
2534
+ // src/OnboardingFlow.tsx
2535
+ import { jsx as jsx24 } from "react/jsx-runtime";
2536
+ var isFilled = (v) => v != null && v !== "";
2537
+ function OnboardingFlow({
2538
+ steps,
2539
+ screens,
2540
+ onComplete,
2541
+ persistKey
2542
+ }) {
2543
+ const [draft, setDraft] = usePersistedState(persistKey, {});
2544
+ const [idx, setIdx] = useState12(0);
2545
+ const draftRef = useRef3(draft);
2546
+ draftRef.current = draft;
2547
+ const step = steps[idx];
2548
+ if (!step) {
2549
+ throw new Error(
2550
+ `[hook-template] OnboardingFlow: step index ${idx} out of range (steps.length=${steps.length})`
2551
+ );
2552
+ }
2553
+ const Screen = screens[step.screen];
2554
+ if (!Screen) {
2555
+ throw new Error(
2556
+ `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
2557
+ );
2558
+ }
2559
+ const valid = useMemo8(
2560
+ () => (step.validates ?? []).every((field) => isFilled(draft[field])),
2561
+ [draft, step]
2562
+ );
2563
+ const setValue = useCallback11(
2564
+ (patch) => {
2565
+ draftRef.current = { ...draftRef.current, ...patch };
2566
+ setDraft((prev2) => ({ ...prev2, ...patch }));
2567
+ },
2568
+ [setDraft]
2569
+ );
2570
+ const next = useCallback11(() => {
2571
+ const current = draftRef.current;
2572
+ const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
2573
+ if (!validNow) return;
2574
+ if (idx + 1 >= steps.length) {
2575
+ onComplete(current);
2576
+ } else {
2577
+ setIdx(idx + 1);
2578
+ }
2579
+ }, [idx, onComplete, step, steps.length]);
2580
+ const prev = useCallback11(() => setIdx((i) => Math.max(0, i - 1)), []);
2581
+ const ctx = useMemo8(
2582
+ () => ({
2583
+ stepIndex: idx,
2584
+ totalSteps: steps.length,
2585
+ value: draft,
2586
+ setValue,
2587
+ valid,
2588
+ next,
2589
+ prev
2590
+ }),
2591
+ [idx, steps.length, draft, setValue, valid, next, prev]
2592
+ );
2593
+ return /* @__PURE__ */ jsx24(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx24(Screen, {}) });
2594
+ }
2595
+
2596
+ // src/hooks/useFeature.ts
2597
+ function useFeature(name) {
2598
+ const config = useAppConfig();
2599
+ return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
2600
+ }
3078
2601
  export {
2602
+ AppConfigProvider,
2603
+ AppConfigSchema,
3079
2604
  AppRoot,
3080
- DefaultForgotScreen,
3081
- DefaultLoginScreen,
3082
- DefaultPaywall,
3083
- DefaultResetScreen,
3084
- DefaultSignupScreen,
2605
+ DeepLinkHandler,
3085
2606
  EmptyState,
3086
2607
  ErrorBoundary,
3087
2608
  InstallGate,
3088
2609
  InstallSplash,
3089
2610
  LoadingState,
2611
+ OnboardingFlow,
2612
+ PaymentReturnHandler,
2613
+ PersistenceRegistry,
2614
+ PreAuthShell,
3090
2615
  PushPrompt2 as PushPrompt,
2616
+ RouteBoundary,
2617
+ asaasErrorMessage,
3091
2618
  computeAnchorCents,
3092
2619
  dailyFromYearly,
3093
2620
  detectAndroidBrowser,
@@ -3098,13 +2625,17 @@ export {
3098
2625
  discountPercent,
3099
2626
  formatBRL,
3100
2627
  monthlyFromYearly,
2628
+ parseAppConfig,
3101
2629
  shouldBlockInstall,
3102
2630
  shouldShowPermanentOption,
2631
+ useAppConfig,
3103
2632
  useAuth,
3104
2633
  useAuthPrimitives,
2634
+ useFeature,
3105
2635
  useForgotForm,
3106
2636
  useInstallPrompt,
3107
2637
  useLoginForm,
2638
+ useOnboardingStep,
3108
2639
  usePaywallState,
3109
2640
  usePlan,
3110
2641
  usePush,