@hook-sdk/template 0.9.1 → 0.11.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,147 @@
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
+ // Build-time injected theme metadata (e.g. icon_url for InstallSplash).
88
+ // Apps don't author this directly; deploy workflows fill it from
89
+ // env-resolved bundle host. Permissive shape so apps/workflows can
90
+ // extend without re-bumping the template schema.
91
+ theme: z.object({}).passthrough().optional()
92
+ }).strict();
93
+ function parseAppConfig(input) {
94
+ const r = AppConfigSchema.safeParse(input);
95
+ if (!r.success) {
96
+ const messages = r.error.issues.map((i) => `[${i.path.join(".")}] ${i.message}`).join("\n");
97
+ throw new Error(`Invalid app.config.json:
98
+ ${messages}`);
99
+ }
100
+ return r.data;
101
+ }
102
+
103
+ // src/PersistenceRegistry.tsx
104
+ import { useEffect } from "react";
105
+ import { useHook } from "@hook-sdk/sdk";
106
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
107
+ function PersistenceRegistry({ config, children }) {
108
+ const { appData } = useHook();
109
+ useEffect(() => {
110
+ if (config.length === 0) return;
111
+ const keys = config.map((c) => c.key);
112
+ const bulk = appData.bulkRead;
113
+ bulk?.(keys).catch(() => {
114
+ });
115
+ }, [config, appData]);
116
+ return /* @__PURE__ */ jsx2(Fragment, { children });
117
+ }
118
+
119
+ // src/DeepLinkHandler.tsx
120
+ import { useEffect as useEffect2 } from "react";
121
+ import { useLocation, useNavigate } from "react-router-dom";
122
+ function DeepLinkHandler({ deepLinks }) {
123
+ const nav = useNavigate();
124
+ const loc = useLocation();
125
+ useEffect2(() => {
126
+ if (!deepLinks) return;
127
+ const params = new URLSearchParams(loc.search);
128
+ const token = params.get("token");
129
+ if (!token) return;
130
+ if (deepLinks.passwordReset && deepLinks.passwordReset.includes(":token") && loc.pathname === "/") {
131
+ nav(deepLinks.passwordReset.replace(":token", token));
132
+ return;
133
+ }
134
+ if (deepLinks.emailVerify && deepLinks.emailVerify.includes(":token") && loc.pathname === "/") {
135
+ nav(deepLinks.emailVerify.replace(":token", token));
136
+ }
137
+ }, [deepLinks, loc, nav]);
138
+ return null;
139
+ }
140
+
141
+ // src/internal/TemplateConfigContext.tsx
142
+ import { createContext as createContext2, useContext as useContext2, useMemo } from "react";
143
+ import { jsx as jsx3 } from "react/jsx-runtime";
144
+ var TemplateConfigContext = createContext2(null);
9
145
  function TemplateConfigProvider({
10
146
  config,
11
147
  children
@@ -14,10 +150,10 @@ function TemplateConfigProvider({
14
150
  ...config,
15
151
  mode: config.subscription?.mode ?? "trial"
16
152
  }), [config]);
17
- return /* @__PURE__ */ jsx(TemplateConfigContext.Provider, { value, children });
153
+ return /* @__PURE__ */ jsx3(TemplateConfigContext.Provider, { value, children });
18
154
  }
19
155
  function useTemplateConfig() {
20
- const ctx = useContext(TemplateConfigContext);
156
+ const ctx = useContext2(TemplateConfigContext);
21
157
  if (ctx === null) {
22
158
  throw new Error("useTemplateConfig must be used inside <TemplateConfigProvider>");
23
159
  }
@@ -25,7 +161,7 @@ function useTemplateConfig() {
25
161
  }
26
162
 
27
163
  // src/internal/ThemeProvider.tsx
28
- import { jsx as jsx2 } from "react/jsx-runtime";
164
+ import { jsx as jsx4 } from "react/jsx-runtime";
29
165
  function ThemeProvider({ children }) {
30
166
  const config = useTemplateConfig();
31
167
  const style = {
@@ -34,182 +170,308 @@ function ThemeProvider({ children }) {
34
170
  "--hook-color-background": config.theme.background_color
35
171
  }
36
172
  };
37
- return /* @__PURE__ */ jsx2("div", { style, children });
173
+ return /* @__PURE__ */ jsx4("div", { style, children });
38
174
  }
39
175
 
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
- }
176
+ // src/hooks/usePaywallState.ts
177
+ import { useCallback, useContext as useContext3, useMemo as useMemo2, useState } from "react";
178
+ import { useHook as useHook2 } from "@hook-sdk/sdk";
59
179
 
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 });
180
+ // src/errors/asaas-pt-br.ts
181
+ var MAP = {
182
+ invalid_cpf: "CPF inv\xE1lido. Confira os n\xFAmeros e tente novamente.",
183
+ cpf_required: "Por favor, informe seu CPF para continuar.",
184
+ card_declined: "Cart\xE3o recusado pela operadora. Tente outro cart\xE3o ou m\xE9todo.",
185
+ insufficient_funds: "Saldo insuficiente no cart\xE3o. Tente outro m\xE9todo.",
186
+ card_expired: "Cart\xE3o expirado. Use um cart\xE3o v\xE1lido.",
187
+ invalid_card_number: "N\xFAmero de cart\xE3o inv\xE1lido.",
188
+ invalid_cvv: "C\xF3digo de seguran\xE7a (CVV) inv\xE1lido.",
189
+ invalid_expiration: "Data de validade inv\xE1lida.",
190
+ generic_decline: "Pagamento recusado. Tente novamente em instantes ou use outro m\xE9todo.",
191
+ webhook_unverified: "N\xE3o conseguimos confirmar seu pagamento. Atualize a p\xE1gina em alguns segundos.",
192
+ pix_expired: "QR Code do PIX expirou. Gere um novo.",
193
+ pix_not_paid_yet: "PIX ainda n\xE3o foi pago. Aguardando confirma\xE7\xE3o."
194
+ };
195
+ function asaasErrorMessage(code) {
196
+ return MAP[code] ?? "Ocorreu um erro inesperado. Tente novamente em instantes.";
92
197
  }
93
198
 
94
199
  // 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";
200
+ function isCheckoutFailure(r) {
201
+ return "ok" in r && r.ok === false;
202
+ }
203
+ var FALLBACK_PAYWALL = {
204
+ mode: "pay_first",
205
+ cycles: ["MONTHLY"],
206
+ prices: { monthlyCents: 0, yearlyCents: 0 },
207
+ checkoutMethods: ["card", "pix-auto"],
208
+ requiresCpf: true,
209
+ errorMessages: "default"
210
+ };
211
+ var isMethodAvailable = (availability, method) => availability[method] !== false;
97
212
  function usePaywallState() {
98
213
  const { subscription, plan } = useHook2();
99
- const [opening, setOpening] = useState2(false);
100
- const [error, setError] = useState2(null);
101
- const [pixPending, setPixPending] = useState2(null);
214
+ const configFromCtx = useContext3(AppConfigContext);
215
+ const paywall = configFromCtx?.paywall ?? FALLBACK_PAYWALL;
216
+ const isFree = paywall.mode === "free";
217
+ const declaredMethods = useMemo2(
218
+ () => isFree ? [] : paywall.checkoutMethods,
219
+ [isFree, paywall]
220
+ );
221
+ const availability = subscription.methodAvailability ?? {
222
+ card: null,
223
+ "pix-auto": null,
224
+ "pix-once": null
225
+ };
226
+ const methods = useMemo2(
227
+ () => declaredMethods.filter((m) => isMethodAvailable(availability, m)),
228
+ [declaredMethods, availability]
229
+ );
230
+ const defaultMethod = methods[0] ?? declaredMethods[0] ?? "card";
231
+ const [selectedMethodRaw, setSelectedMethod] = useState(defaultMethod);
232
+ const selectedMethod = methods.includes(selectedMethodRaw) ? selectedMethodRaw : methods[0] ?? selectedMethodRaw;
233
+ const initialCycle = isFree ? "MONTHLY" : paywall.cycles[0] ?? "MONTHLY";
234
+ const [cycle, setCycle] = useState(initialCycle);
235
+ const cpfRequired = !isFree && paywall.requiresCpf;
236
+ const [cpf, setCpf] = useState("");
237
+ const cpfValid = useMemo2(() => /^[0-9]{11}$/.test(cpf), [cpf]);
238
+ const [card, setCardState] = useState({
239
+ number: "",
240
+ cvv: "",
241
+ expiry: "",
242
+ holder: ""
243
+ });
244
+ const setCard = useCallback((patch) => {
245
+ setCardState((prev) => ({ ...prev, ...patch }));
246
+ }, []);
247
+ const [error, setError] = useState(null);
248
+ const [submitting, setSubmitting] = useState(false);
102
249
  const status = subscription.status();
103
250
  const daysLeftInTrial = subscription.daysLeftInTrial();
104
251
  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;
252
+ const pixPending = useMemo2(() => {
253
+ const sdkPix = subscription.pixPending;
254
+ if (!sdkPix) return null;
255
+ const liveStatus = subscription.current?.status;
256
+ const paid = liveStatus === "ACTIVE" || liveStatus === "TRIAL";
257
+ return {
258
+ method: sdkPix.method,
259
+ qrCodePayload: sdkPix.qrCodePayload,
260
+ qrCodeBase64: sdkPix.qrCodeBase64,
261
+ expiresAt: null,
262
+ paid
263
+ };
264
+ }, [subscription.pixPending, subscription.current]);
111
265
  const monthlyEquivalent = useCallback(
112
- (cycle) => {
113
- if (cycle === "YEARLY" && yearlyPriceCents) {
114
- return Math.round(yearlyPriceCents / 12);
115
- }
116
- return priceCents;
266
+ (c) => {
267
+ const monthlyCents = plan.data?.priceCents ?? (isFree ? 0 : paywall.prices.monthlyCents);
268
+ const yearlyCents = plan.data?.yearlyPriceCents ?? (isFree ? null : paywall.prices.yearlyCents);
269
+ if (c === "YEARLY" && yearlyCents) return Math.round(yearlyCents / 12);
270
+ return monthlyCents;
117
271
  },
118
- [priceCents, yearlyPriceCents]
272
+ [plan, paywall, isFree]
119
273
  );
274
+ const planDerived = useMemo2(() => {
275
+ if (isFree) return null;
276
+ const monthlyCents = paywall.prices.monthlyCents;
277
+ const yearlyCents = paywall.prices.yearlyCents;
278
+ const anchor = paywall.anchorPrices;
279
+ const discount = anchor && anchor.yearlyCents > 0 ? Math.round((1 - paywall.prices.yearlyCents / anchor.yearlyCents) * 100) : 0;
280
+ return {
281
+ monthlyCents,
282
+ yearlyCents,
283
+ anchorMonthlyCents: anchor?.monthlyCents,
284
+ anchorYearlyCents: anchor?.yearlyCents,
285
+ monthlyEquivalent: cycle === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents,
286
+ discountPercent: discount
287
+ };
288
+ }, [paywall, cycle, isFree]);
289
+ const useDefaultMessages = paywall.mode !== "free" && paywall.errorMessages === "default";
290
+ const buildError = useCallback(
291
+ (code, fallbackMessage) => ({
292
+ code,
293
+ message: fallbackMessage,
294
+ userMessage: useDefaultMessages ? asaasErrorMessage(code) : fallbackMessage
295
+ }),
296
+ [useDefaultMessages]
297
+ );
298
+ const submit = useCallback(async () => {
299
+ setSubmitting(true);
300
+ setError(null);
301
+ const methodToUse = selectedMethod;
302
+ if (!isMethodAvailable(availability, methodToUse)) {
303
+ const code = "method_unavailable";
304
+ setError(buildError(code, `method ${methodToUse} unavailable`));
305
+ setSubmitting(false);
306
+ return {
307
+ ok: false,
308
+ code: "method_unavailable",
309
+ method: methodToUse,
310
+ reason: "pre_flight_unavailable"
311
+ };
312
+ }
313
+ try {
314
+ let result;
315
+ if (methodToUse === "card") {
316
+ const [expMonthRaw = "", expYearRaw = ""] = card.expiry.split("/");
317
+ const expYearTrimmed = expYearRaw.trim();
318
+ const cardData = {
319
+ number: card.number,
320
+ holderName: card.holder,
321
+ expiryMonth: expMonthRaw.trim(),
322
+ expiryYear: expYearTrimmed.length === 2 ? `20${expYearTrimmed}` : expYearTrimmed,
323
+ ccv: card.cvv
324
+ };
325
+ const holderInfo = {
326
+ name: card.holder,
327
+ email: "",
328
+ cpfCnpj: cpf,
329
+ postalCode: "",
330
+ addressNumber: ""
331
+ };
332
+ result = await subscription.checkout({
333
+ method: "card",
334
+ cycle,
335
+ cpf,
336
+ card: cardData,
337
+ holderInfo
338
+ });
339
+ } else if (methodToUse === "pix-auto") {
340
+ result = await subscription.checkout({ method: "pix-auto", cycle, cpf });
341
+ } else {
342
+ result = await subscription.checkout({ method: "pix-once", cycle, cpf });
343
+ }
344
+ if (isCheckoutFailure(result)) {
345
+ setError(buildError(result.code, `${result.code}: ${result.reason}`));
346
+ setSubmitting(false);
347
+ return result;
348
+ }
349
+ await subscription.refresh();
350
+ setSubmitting(false);
351
+ return result;
352
+ } catch (err) {
353
+ const code = err?.code ?? "generic_decline";
354
+ const message = err instanceof Error ? err.message : String(err);
355
+ setError(buildError(code, message));
356
+ setSubmitting(false);
357
+ return void 0;
358
+ }
359
+ }, [selectedMethod, availability, subscription, cycle, cpf, card, buildError]);
120
360
  const checkout = useCallback(
121
361
  async (args) => {
122
- setOpening(true);
362
+ setSubmitting(true);
123
363
  setError(null);
124
- setPixPending(null);
125
364
  try {
126
365
  if (args.method === "card") {
127
366
  if (!args.card || !args.holderInfo) {
128
- throw new Error('card and holderInfo are required when method is "card"');
367
+ throw Object.assign(
368
+ new Error('card and holderInfo are required when method is "card"'),
369
+ { code: "validation" }
370
+ );
129
371
  }
130
- await subscription.checkout({
372
+ const sdkArgs = {
131
373
  method: "card",
132
374
  cycle: args.cycle,
133
375
  cpf: args.cpf,
134
376
  card: args.card,
135
377
  holderInfo: args.holderInfo,
136
378
  ...args.remoteIp ? { remoteIp: args.remoteIp } : {}
137
- });
379
+ };
380
+ const result2 = await subscription.checkout(sdkArgs);
381
+ if (isCheckoutFailure(result2)) {
382
+ setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
383
+ setSubmitting(false);
384
+ return;
385
+ }
138
386
  await subscription.refresh();
139
- setOpening(false);
387
+ setSubmitting(false);
388
+ return;
389
+ }
390
+ if (args.method === "pix-auto") {
391
+ const result2 = await subscription.checkout({
392
+ method: "pix-auto",
393
+ cycle: args.cycle,
394
+ cpf: args.cpf
395
+ });
396
+ if (isCheckoutFailure(result2)) {
397
+ setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
398
+ setSubmitting(false);
399
+ return;
400
+ }
401
+ setSubmitting(false);
140
402
  return;
141
403
  }
142
404
  const result = await subscription.checkout({
143
- method: "pix-auto",
405
+ method: "pix-once",
144
406
  cycle: args.cycle,
145
407
  cpf: args.cpf
146
408
  });
147
- if (result.method !== "pix-auto") {
148
- throw new Error(`unexpected checkout result method: ${result.method}`);
409
+ if (isCheckoutFailure(result)) {
410
+ setError(buildError(result.code, `${result.code}: ${result.reason}`));
411
+ setSubmitting(false);
412
+ return;
149
413
  }
150
- setPixPending({
151
- method: "pix-auto",
152
- qrCodePayload: result.qrCodePayload,
153
- qrCodeBase64: result.qrCodeBase64,
154
- expiresAt: null,
155
- paid: false
156
- });
157
- setOpening(false);
414
+ setSubmitting(false);
158
415
  } catch (err) {
159
- setError(err);
160
- setOpening(false);
416
+ const code = err?.code ?? "generic_decline";
417
+ const message = err instanceof Error ? err.message : String(err);
418
+ setError(buildError(code, message));
419
+ setSubmitting(false);
161
420
  }
162
421
  },
163
- [subscription]
422
+ [subscription, buildError]
164
423
  );
165
424
  const cancel = useCallback(async () => {
166
425
  try {
167
426
  await subscription.cancel();
168
427
  await subscription.refresh();
169
428
  } catch (err) {
170
- setError(err);
429
+ const code = err?.code ?? "generic_decline";
430
+ const message = err instanceof Error ? err.message : String(err);
431
+ setError(buildError(code, message));
171
432
  }
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]);
433
+ }, [subscription, buildError]);
434
+ const cardState = useMemo2(
435
+ () => ({ ...card, set: setCard }),
436
+ [card, setCard]
437
+ );
438
+ const cpfState = useMemo2(
439
+ () => ({ required: cpfRequired, value: cpf, set: setCpf, valid: cpfValid }),
440
+ [cpfRequired, cpf, cpfValid]
441
+ );
201
442
  return {
443
+ // Subscription status (reactive, proxied from SDK)
202
444
  status,
203
445
  daysLeftInTrial,
204
446
  initialLoadComplete,
447
+ // Plan derivation from config (sync, no fetch)
448
+ plan: planDerived,
449
+ // Cycle + method selection
450
+ cycle,
451
+ setCycle,
452
+ methods,
453
+ selectedMethod,
454
+ setSelectedMethod,
455
+ // Form state
456
+ cpfState,
457
+ cardState,
458
+ // High-level + legacy actions
459
+ submit,
205
460
  checkout,
206
461
  cancel,
207
- opening,
208
- error,
462
+ // PIX state (proxied from SDK; legacy `paid`/`expiresAt` derived)
209
463
  pixPending,
210
- dismissPix,
211
- availableMethods,
212
- monthlyEquivalent
464
+ // Error + submit progress
465
+ error,
466
+ submitting,
467
+ // Backward-compat aliases (deprecated; remove in template 0.11)
468
+ opening: submitting,
469
+ availableMethods: methods,
470
+ monthlyEquivalent,
471
+ dismissPix: () => {
472
+ },
473
+ refreshPlan: () => {
474
+ }
213
475
  };
214
476
  }
215
477
 
@@ -230,51 +492,11 @@ function SubscriptionGate({ Paywall, children }) {
230
492
  return /* @__PURE__ */ jsx5(Fragment2, { children });
231
493
  }
232
494
 
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
495
  // src/components/InstallGate/InstallGate.tsx
274
- import { useEffect as useEffect5, useRef as useRef2 } from "react";
496
+ import { useEffect as useEffect4, useRef } from "react";
275
497
 
276
498
  // src/hooks/useInstallPrompt.ts
277
- import { useCallback as useCallback2, useEffect as useEffect4, useState as useState4 } from "react";
499
+ import { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
278
500
  var IOS_RE = /iPad|iPhone|iPod/;
279
501
  var IOS_NON_SAFARI_RE = /CriOS|FxiOS|EdgiOS/;
280
502
  var ANDROID_RE = /Android/;
@@ -407,18 +629,18 @@ function useInstallPrompt(slug) {
407
629
  const iosBrowser = detectIOSBrowser(ua);
408
630
  const androidBrowser = detectAndroidBrowser(ua);
409
631
  const inAppApp = detectInAppApp(ua);
410
- const [isInstallable, setIsInstallable] = useState4(() => {
632
+ const [isInstallable, setIsInstallable] = useState2(() => {
411
633
  if (typeof window === "undefined") return false;
412
634
  return window.__pwaInstallPrompt != null;
413
635
  });
414
- const [isInstalled, setIsInstalled] = useState4(() => {
636
+ const [isInstalled, setIsInstalled] = useState2(() => {
415
637
  const { installed } = detectStandalone();
416
638
  return installed || readInstalledMarker(slug);
417
639
  });
418
- const [isDismissedSession, setIsDismissedSession] = useState4(() => readSessionSkip(slug));
419
- const [isDismissedPermanent, setIsDismissedPermanent] = useState4(() => readPermanentDismiss(slug).dismissed);
420
- const [skipCount, setSkipCount] = useState4(() => readSkipCount(slug));
421
- useEffect4(() => {
640
+ const [isDismissedSession, setIsDismissedSession] = useState2(() => readSessionSkip(slug));
641
+ const [isDismissedPermanent, setIsDismissedPermanent] = useState2(() => readPermanentDismiss(slug).dismissed);
642
+ const [skipCount, setSkipCount] = useState2(() => readSkipCount(slug));
643
+ useEffect3(() => {
422
644
  if (typeof window === "undefined") return;
423
645
  if (window.__pwaInstallPrompt) {
424
646
  setIsInstallable(true);
@@ -442,7 +664,7 @@ function useInstallPrompt(slug) {
442
664
  window.removeEventListener("appinstalled", onInstalled);
443
665
  };
444
666
  }, [slug]);
445
- useEffect4(() => {
667
+ useEffect3(() => {
446
668
  if (typeof window === "undefined") return;
447
669
  const mq = window.matchMedia?.("(display-mode: standalone)");
448
670
  if (!mq) return;
@@ -697,11 +919,11 @@ var INSTALL_COPY = {
697
919
  };
698
920
 
699
921
  // src/components/InstallGate/InstallSplash.tsx
700
- import { jsx as jsx7, jsxs } from "react/jsx-runtime";
922
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
701
923
  function InstallSplash({ children, title, subtitle }) {
702
924
  const { name, theme } = useTemplateConfig();
703
925
  const iconUrl = theme.icon_url || theme.logo_url || null;
704
- return /* @__PURE__ */ jsx7(
926
+ return /* @__PURE__ */ jsx6(
705
927
  "div",
706
928
  {
707
929
  role: "dialog",
@@ -710,14 +932,14 @@ function InstallSplash({ children, title, subtitle }) {
710
932
  "aria-describedby": subtitle ? "install-splash-subtitle" : void 0,
711
933
  style: overlayStyle,
712
934
  children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
713
- /* @__PURE__ */ jsx7("div", { style: { display: "flex", justifyContent: "center", marginBottom: 16 }, children: iconUrl ? /* @__PURE__ */ jsx7(
935
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", justifyContent: "center", marginBottom: 16 }, children: iconUrl ? /* @__PURE__ */ jsx6(
714
936
  "img",
715
937
  {
716
938
  src: iconUrl,
717
939
  alt: `\xCDcone de ${name}`,
718
940
  style: { width: 80, height: 80, borderRadius: 20, objectFit: "cover" }
719
941
  }
720
- ) : /* @__PURE__ */ jsx7(
942
+ ) : /* @__PURE__ */ jsx6(
721
943
  "div",
722
944
  {
723
945
  style: {
@@ -735,10 +957,10 @@ function InstallSplash({ children, title, subtitle }) {
735
957
  children: name.charAt(0).toUpperCase()
736
958
  }
737
959
  ) }),
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" })
960
+ /* @__PURE__ */ jsx6("h1", { id: "install-splash-title", style: titleStyle, children: title }),
961
+ subtitle && /* @__PURE__ */ jsx6("p", { id: "install-splash-subtitle", style: subtitleStyle, children: subtitle }),
962
+ /* @__PURE__ */ jsx6("div", { style: { marginTop: 24 }, children }),
963
+ /* @__PURE__ */ jsx6("p", { style: footerStyle, children: "por Hook" })
742
964
  ] })
743
965
  }
744
966
  );
@@ -812,7 +1034,7 @@ var footerStyle = {
812
1034
  };
813
1035
 
814
1036
  // src/components/InstallGate/icons.tsx
815
- import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
1037
+ import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
816
1038
  var defaultSvgProps = (size) => ({
817
1039
  width: size,
818
1040
  height: size,
@@ -825,55 +1047,55 @@ var defaultSvgProps = (size) => ({
825
1047
  });
826
1048
  function ShareIconIOS({ size = 24, style, className }) {
827
1049
  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" })
1050
+ /* @__PURE__ */ jsx7("path", { d: "M12 2L12 15" }),
1051
+ /* @__PURE__ */ jsx7("path", { d: "M8 6L12 2L16 6" }),
1052
+ /* @__PURE__ */ jsx7("path", { d: "M4 11v9a2 2 0 002 2h12a2 2 0 002-2v-9" })
831
1053
  ] });
832
1054
  }
833
1055
  function MenuDotsVerticalIcon({ size = 24, style, className }) {
834
1056
  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" })
1057
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "5", r: "1.5" }),
1058
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "1.5" }),
1059
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "19", r: "1.5" })
838
1060
  ] });
839
1061
  }
840
1062
  function MenuDotsHorizontalIcon({ size = 24, style, className }) {
841
1063
  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" })
1064
+ /* @__PURE__ */ jsx7("circle", { cx: "5", cy: "12", r: "1.5" }),
1065
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "1.5" }),
1066
+ /* @__PURE__ */ jsx7("circle", { cx: "19", cy: "12", r: "1.5" })
845
1067
  ] });
846
1068
  }
847
1069
  function SquarePlusIcon({ size = 24, style, className }) {
848
1070
  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" })
1071
+ /* @__PURE__ */ jsx7("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1072
+ /* @__PURE__ */ jsx7("path", { d: "M12 8v8" }),
1073
+ /* @__PURE__ */ jsx7("path", { d: "M8 12h8" })
852
1074
  ] });
853
1075
  }
854
1076
  function DownloadIcon({ size = 24, style, className }) {
855
1077
  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" })
1078
+ /* @__PURE__ */ jsx7("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" }),
1079
+ /* @__PURE__ */ jsx7("polyline", { points: "7 10 12 15 17 10" }),
1080
+ /* @__PURE__ */ jsx7("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
859
1081
  ] });
860
1082
  }
861
1083
  function ExternalLinkIcon({ size = 24, style, className }) {
862
1084
  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" })
1085
+ /* @__PURE__ */ jsx7("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
1086
+ /* @__PURE__ */ jsx7("polyline", { points: "15 3 21 3 21 9" }),
1087
+ /* @__PURE__ */ jsx7("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
866
1088
  ] });
867
1089
  }
868
1090
  function XIcon({ size = 20, style, className }) {
869
1091
  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" })
1092
+ /* @__PURE__ */ jsx7("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1093
+ /* @__PURE__ */ jsx7("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
872
1094
  ] });
873
1095
  }
874
1096
 
875
1097
  // src/components/InstallGate/variants/AndroidNativeVariant.tsx
876
- import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
1098
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
877
1099
  function AndroidNativeVariant({
878
1100
  state,
879
1101
  actions
@@ -889,12 +1111,12 @@ function AndroidNativeVariant({
889
1111
  onClick: () => void actions.promptInstall(),
890
1112
  style: { ...primaryButtonStyle, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8 },
891
1113
  children: [
892
- /* @__PURE__ */ jsx9(DownloadIcon, { size: 18 }),
1114
+ /* @__PURE__ */ jsx8(DownloadIcon, { size: 18 }),
893
1115
  copy.cta
894
1116
  ]
895
1117
  }
896
1118
  ),
897
- /* @__PURE__ */ jsx9(
1119
+ /* @__PURE__ */ jsx8(
898
1120
  "button",
899
1121
  {
900
1122
  "data-testid": "install-prompt-skip-session",
@@ -904,7 +1126,7 @@ function AndroidNativeVariant({
904
1126
  children: copy.skip
905
1127
  }
906
1128
  ),
907
- showPermanent && /* @__PURE__ */ jsx9(
1129
+ showPermanent && /* @__PURE__ */ jsx8(
908
1130
  "button",
909
1131
  {
910
1132
  "data-testid": "install-prompt-skip-permanent",
@@ -918,7 +1140,7 @@ function AndroidNativeVariant({
918
1140
  }
919
1141
 
920
1142
  // src/components/InstallGate/variants/AndroidManualVariant.tsx
921
- import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
1143
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
922
1144
  function AndroidManualVariant({
923
1145
  state,
924
1146
  actions
@@ -926,9 +1148,9 @@ function AndroidManualVariant({
926
1148
  const copy = INSTALL_COPY.android.manual;
927
1149
  const showPermanent = shouldShowPermanentOption(state);
928
1150
  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(
1151
+ /* @__PURE__ */ jsx9(Step, { n: 1, icon: /* @__PURE__ */ jsx9(MenuDotsVerticalIcon, { size: 20 }), children: copy.step1 }),
1152
+ /* @__PURE__ */ jsx9(Step, { n: 2, icon: /* @__PURE__ */ jsx9(DownloadIcon, { size: 18 }), children: copy.step2 }),
1153
+ /* @__PURE__ */ jsx9(
932
1154
  "button",
933
1155
  {
934
1156
  "data-testid": "install-prompt-cta-android-manual",
@@ -938,7 +1160,7 @@ function AndroidManualVariant({
938
1160
  children: copy.cta
939
1161
  }
940
1162
  ),
941
- /* @__PURE__ */ jsx10(
1163
+ /* @__PURE__ */ jsx9(
942
1164
  "button",
943
1165
  {
944
1166
  "data-testid": "install-prompt-skip-session",
@@ -948,7 +1170,7 @@ function AndroidManualVariant({
948
1170
  children: copy.skip
949
1171
  }
950
1172
  ),
951
- showPermanent && /* @__PURE__ */ jsx10(
1173
+ showPermanent && /* @__PURE__ */ jsx9(
952
1174
  "button",
953
1175
  {
954
1176
  "data-testid": "install-prompt-skip-permanent",
@@ -975,7 +1197,7 @@ function Step({ n, icon, children }) {
975
1197
  textAlign: "left"
976
1198
  },
977
1199
  children: [
978
- /* @__PURE__ */ jsx10(
1200
+ /* @__PURE__ */ jsx9(
979
1201
  "div",
980
1202
  {
981
1203
  style: {
@@ -994,15 +1216,15 @@ function Step({ n, icon, children }) {
994
1216
  children: n
995
1217
  }
996
1218
  ),
997
- /* @__PURE__ */ jsx10("div", { style: { flex: 1, fontSize: 15, color: "#333" }, children }),
998
- /* @__PURE__ */ jsx10("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1219
+ /* @__PURE__ */ jsx9("div", { style: { flex: 1, fontSize: 15, color: "#333" }, children }),
1220
+ /* @__PURE__ */ jsx9("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
999
1221
  ]
1000
1222
  }
1001
1223
  );
1002
1224
  }
1003
1225
 
1004
1226
  // src/components/InstallGate/Step.tsx
1005
- import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
1227
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1006
1228
  function Step2({
1007
1229
  n,
1008
1230
  title,
@@ -1020,7 +1242,7 @@ function Step2({
1020
1242
  textAlign: "left"
1021
1243
  },
1022
1244
  children: [
1023
- /* @__PURE__ */ jsx11(
1245
+ /* @__PURE__ */ jsx10(
1024
1246
  "div",
1025
1247
  {
1026
1248
  style: {
@@ -1040,8 +1262,8 @@ function Step2({
1040
1262
  }
1041
1263
  ),
1042
1264
  /* @__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 }),
1265
+ /* @__PURE__ */ jsx10("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
1266
+ subtitle && /* @__PURE__ */ jsx10("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
1045
1267
  visual
1046
1268
  ] })
1047
1269
  ]
@@ -1050,7 +1272,7 @@ function Step2({
1050
1272
  }
1051
1273
 
1052
1274
  // src/components/InstallGate/variants/IOSafariVariant.tsx
1053
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1275
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1054
1276
  function IOSafariVariant({
1055
1277
  state,
1056
1278
  actions
@@ -1058,13 +1280,13 @@ function IOSafariVariant({
1058
1280
  const copy = INSTALL_COPY.iosSafari;
1059
1281
  const showPermanent = shouldShowPermanentOption(state);
1060
1282
  return /* @__PURE__ */ jsxs6(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1061
- /* @__PURE__ */ jsx12(
1283
+ /* @__PURE__ */ jsx11(
1062
1284
  Step2,
1063
1285
  {
1064
1286
  n: 1,
1065
1287
  title: copy.step1.title,
1066
1288
  subtitle: copy.step1.subtitle,
1067
- visual: /* @__PURE__ */ jsx12(
1289
+ visual: /* @__PURE__ */ jsx11(
1068
1290
  "div",
1069
1291
  {
1070
1292
  style: {
@@ -1076,12 +1298,12 @@ function IOSafariVariant({
1076
1298
  padding: "12px 0",
1077
1299
  marginTop: 8
1078
1300
  },
1079
- children: /* @__PURE__ */ jsx12(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1301
+ children: /* @__PURE__ */ jsx11(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1080
1302
  }
1081
1303
  )
1082
1304
  }
1083
1305
  ),
1084
- /* @__PURE__ */ jsx12(
1306
+ /* @__PURE__ */ jsx11(
1085
1307
  Step2,
1086
1308
  {
1087
1309
  n: 2,
@@ -1099,19 +1321,19 @@ function IOSafariVariant({
1099
1321
  marginTop: 8
1100
1322
  },
1101
1323
  children: [
1102
- /* @__PURE__ */ jsx12(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1103
- /* @__PURE__ */ jsx12("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1324
+ /* @__PURE__ */ jsx11(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1325
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1104
1326
  ]
1105
1327
  }
1106
1328
  )
1107
1329
  }
1108
1330
  ),
1109
- /* @__PURE__ */ jsx12(
1331
+ /* @__PURE__ */ jsx11(
1110
1332
  Step2,
1111
1333
  {
1112
1334
  n: 3,
1113
1335
  title: copy.step3.title,
1114
- visual: /* @__PURE__ */ jsx12(
1336
+ visual: /* @__PURE__ */ jsx11(
1115
1337
  "div",
1116
1338
  {
1117
1339
  style: {
@@ -1122,7 +1344,7 @@ function IOSafariVariant({
1122
1344
  padding: "10px 14px",
1123
1345
  marginTop: 8
1124
1346
  },
1125
- children: /* @__PURE__ */ jsx12(
1347
+ children: /* @__PURE__ */ jsx11(
1126
1348
  "span",
1127
1349
  {
1128
1350
  style: {
@@ -1137,7 +1359,7 @@ function IOSafariVariant({
1137
1359
  )
1138
1360
  }
1139
1361
  ),
1140
- /* @__PURE__ */ jsx12(
1362
+ /* @__PURE__ */ jsx11(
1141
1363
  "button",
1142
1364
  {
1143
1365
  "data-testid": "install-prompt-skip-session",
@@ -1147,7 +1369,7 @@ function IOSafariVariant({
1147
1369
  children: copy.skip
1148
1370
  }
1149
1371
  ),
1150
- showPermanent && /* @__PURE__ */ jsx12(
1372
+ showPermanent && /* @__PURE__ */ jsx11(
1151
1373
  "button",
1152
1374
  {
1153
1375
  "data-testid": "install-prompt-skip-permanent",
@@ -1161,7 +1383,7 @@ function IOSafariVariant({
1161
1383
  }
1162
1384
 
1163
1385
  // src/components/InstallGate/variants/IOSOtherVariant.tsx
1164
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1386
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1165
1387
  function IOSOtherVariant({
1166
1388
  state,
1167
1389
  actions
@@ -1169,13 +1391,13 @@ function IOSOtherVariant({
1169
1391
  const copy = INSTALL_COPY.iosOther;
1170
1392
  const showPermanent = shouldShowPermanentOption(state);
1171
1393
  return /* @__PURE__ */ jsxs7(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1172
- /* @__PURE__ */ jsx13(
1394
+ /* @__PURE__ */ jsx12(
1173
1395
  Step2,
1174
1396
  {
1175
1397
  n: 1,
1176
1398
  title: copy.step1.title,
1177
1399
  subtitle: copy.step1.subtitle,
1178
- visual: /* @__PURE__ */ jsx13(
1400
+ visual: /* @__PURE__ */ jsx12(
1179
1401
  "div",
1180
1402
  {
1181
1403
  style: {
@@ -1187,12 +1409,12 @@ function IOSOtherVariant({
1187
1409
  padding: "12px 0",
1188
1410
  marginTop: 8
1189
1411
  },
1190
- children: /* @__PURE__ */ jsx13(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1412
+ children: /* @__PURE__ */ jsx12(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1191
1413
  }
1192
1414
  )
1193
1415
  }
1194
1416
  ),
1195
- /* @__PURE__ */ jsx13(
1417
+ /* @__PURE__ */ jsx12(
1196
1418
  Step2,
1197
1419
  {
1198
1420
  n: 2,
@@ -1210,19 +1432,19 @@ function IOSOtherVariant({
1210
1432
  marginTop: 8
1211
1433
  },
1212
1434
  children: [
1213
- /* @__PURE__ */ jsx13(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1214
- /* @__PURE__ */ jsx13("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1435
+ /* @__PURE__ */ jsx12(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1436
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1215
1437
  ]
1216
1438
  }
1217
1439
  )
1218
1440
  }
1219
1441
  ),
1220
- /* @__PURE__ */ jsx13(
1442
+ /* @__PURE__ */ jsx12(
1221
1443
  Step2,
1222
1444
  {
1223
1445
  n: 3,
1224
1446
  title: copy.step3.title,
1225
- visual: /* @__PURE__ */ jsx13(
1447
+ visual: /* @__PURE__ */ jsx12(
1226
1448
  "div",
1227
1449
  {
1228
1450
  style: {
@@ -1233,7 +1455,7 @@ function IOSOtherVariant({
1233
1455
  padding: "10px 14px",
1234
1456
  marginTop: 8
1235
1457
  },
1236
- children: /* @__PURE__ */ jsx13(
1458
+ children: /* @__PURE__ */ jsx12(
1237
1459
  "span",
1238
1460
  {
1239
1461
  style: {
@@ -1248,7 +1470,7 @@ function IOSOtherVariant({
1248
1470
  )
1249
1471
  }
1250
1472
  ),
1251
- /* @__PURE__ */ jsx13(
1473
+ /* @__PURE__ */ jsx12(
1252
1474
  "button",
1253
1475
  {
1254
1476
  "data-testid": "install-prompt-skip-session",
@@ -1258,7 +1480,7 @@ function IOSOtherVariant({
1258
1480
  children: copy.skip
1259
1481
  }
1260
1482
  ),
1261
- showPermanent && /* @__PURE__ */ jsx13(
1483
+ showPermanent && /* @__PURE__ */ jsx12(
1262
1484
  "button",
1263
1485
  {
1264
1486
  "data-testid": "install-prompt-skip-permanent",
@@ -1272,8 +1494,8 @@ function IOSOtherVariant({
1272
1494
  }
1273
1495
 
1274
1496
  // 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";
1497
+ import { useState as useState3 } from "react";
1498
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1277
1499
  function InAppBrowserVariant({
1278
1500
  state,
1279
1501
  actions
@@ -1282,7 +1504,7 @@ function InAppBrowserVariant({
1282
1504
  const appCopy = INSTALL_COPY.inApp[app] ?? INSTALL_COPY.inApp.other;
1283
1505
  const copy = INSTALL_COPY.inApp;
1284
1506
  const showPermanent = shouldShowPermanentOption(state);
1285
- const [copied, setCopied] = useState5(false);
1507
+ const [copied, setCopied] = useState3(false);
1286
1508
  const handleCopy = async () => {
1287
1509
  await actions.copyLink();
1288
1510
  setCopied(true);
@@ -1290,9 +1512,9 @@ function InAppBrowserVariant({
1290
1512
  };
1291
1513
  const DotsIcon = app === "facebook" || app === "telegram" ? MenuDotsVerticalIcon : MenuDotsHorizontalIcon;
1292
1514
  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(
1515
+ /* @__PURE__ */ jsx13(Step3, { n: 1, icon: /* @__PURE__ */ jsx13(DotsIcon, { size: 20 }), children: appCopy.step1 }),
1516
+ /* @__PURE__ */ jsx13(Step3, { n: 2, icon: /* @__PURE__ */ jsx13(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
1517
+ /* @__PURE__ */ jsx13(
1296
1518
  "button",
1297
1519
  {
1298
1520
  "data-testid": "install-prompt-cta-inapp-copy",
@@ -1302,7 +1524,7 @@ function InAppBrowserVariant({
1302
1524
  children: copied ? copy.copiedToast : copy.copy
1303
1525
  }
1304
1526
  ),
1305
- /* @__PURE__ */ jsx14(
1527
+ /* @__PURE__ */ jsx13(
1306
1528
  "button",
1307
1529
  {
1308
1530
  "data-testid": "install-prompt-skip-session",
@@ -1312,7 +1534,7 @@ function InAppBrowserVariant({
1312
1534
  children: copy.skip
1313
1535
  }
1314
1536
  ),
1315
- showPermanent && /* @__PURE__ */ jsx14(
1537
+ showPermanent && /* @__PURE__ */ jsx13(
1316
1538
  "button",
1317
1539
  {
1318
1540
  "data-testid": "install-prompt-skip-permanent",
@@ -1343,7 +1565,7 @@ function Step3({
1343
1565
  textAlign: "left"
1344
1566
  },
1345
1567
  children: [
1346
- /* @__PURE__ */ jsx14(
1568
+ /* @__PURE__ */ jsx13(
1347
1569
  "div",
1348
1570
  {
1349
1571
  style: {
@@ -1362,15 +1584,15 @@ function Step3({
1362
1584
  children: n
1363
1585
  }
1364
1586
  ),
1365
- /* @__PURE__ */ jsx14("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1366
- /* @__PURE__ */ jsx14("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1587
+ /* @__PURE__ */ jsx13("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1588
+ /* @__PURE__ */ jsx13("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1367
1589
  ]
1368
1590
  }
1369
1591
  );
1370
1592
  }
1371
1593
 
1372
1594
  // src/components/InstallGate/variants/DesktopVariant.tsx
1373
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1595
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1374
1596
  function DesktopVariant({
1375
1597
  state,
1376
1598
  actions
@@ -1386,14 +1608,14 @@ function DesktopVariant({
1386
1608
  "aria-label": copy.title,
1387
1609
  style: bannerStyle,
1388
1610
  children: [
1389
- iconUrl ? /* @__PURE__ */ jsx15(
1611
+ iconUrl ? /* @__PURE__ */ jsx14(
1390
1612
  "img",
1391
1613
  {
1392
1614
  src: iconUrl,
1393
1615
  alt: "",
1394
1616
  style: { width: 40, height: 40, borderRadius: 10, objectFit: "cover", flexShrink: 0 }
1395
1617
  }
1396
- ) : /* @__PURE__ */ jsx15(
1618
+ ) : /* @__PURE__ */ jsx14(
1397
1619
  "div",
1398
1620
  {
1399
1621
  style: {
@@ -1413,8 +1635,8 @@ function DesktopVariant({
1413
1635
  }
1414
1636
  ),
1415
1637
  /* @__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 })
1638
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
1639
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
1418
1640
  ] }),
1419
1641
  /* @__PURE__ */ jsxs9(
1420
1642
  "button",
@@ -1437,12 +1659,12 @@ function DesktopVariant({
1437
1659
  flexShrink: 0
1438
1660
  },
1439
1661
  children: [
1440
- /* @__PURE__ */ jsx15(DownloadIcon, { size: 14 }),
1662
+ /* @__PURE__ */ jsx14(DownloadIcon, { size: 14 }),
1441
1663
  copy.cta
1442
1664
  ]
1443
1665
  }
1444
1666
  ),
1445
- /* @__PURE__ */ jsx15(
1667
+ /* @__PURE__ */ jsx14(
1446
1668
  "button",
1447
1669
  {
1448
1670
  "data-testid": "install-prompt-desktop-close",
@@ -1457,7 +1679,7 @@ function DesktopVariant({
1457
1679
  padding: 4,
1458
1680
  flexShrink: 0
1459
1681
  },
1460
- children: /* @__PURE__ */ jsx15(XIcon, { size: 16 })
1682
+ children: /* @__PURE__ */ jsx14(XIcon, { size: 16 })
1461
1683
  }
1462
1684
  )
1463
1685
  ]
@@ -1481,14 +1703,14 @@ var bannerStyle = {
1481
1703
  };
1482
1704
 
1483
1705
  // src/components/InstallGate/InstallGate.tsx
1484
- import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1706
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1485
1707
  function InstallGate({ children }) {
1486
1708
  const { slug, features_enabled } = useTemplateConfig();
1487
1709
  const enabled = features_enabled.includes("install_prompt");
1488
1710
  const installState = useInstallPrompt(slug);
1489
1711
  const shouldBlock = enabled && shouldBlockInstall(installState);
1490
- const trackedRef = useRef2(null);
1491
- useEffect5(() => {
1712
+ const trackedRef = useRef(null);
1713
+ useEffect4(() => {
1492
1714
  if (!shouldBlock) return;
1493
1715
  if (typeof window === "undefined") return;
1494
1716
  const variantKey = `${slug}:${installState.variant}`;
@@ -1502,109 +1724,449 @@ function InstallGate({ children }) {
1502
1724
  variant: installState.variant
1503
1725
  });
1504
1726
  }, [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 });
1727
+ if (!enabled) return /* @__PURE__ */ jsx15(Fragment3, { children });
1728
+ if (installState.isInstalled) return /* @__PURE__ */ jsx15(Fragment3, { children });
1507
1729
  if (installState.variant === "desktop") {
1508
1730
  const showBanner = !installState.isDismissedSession && !installState.isDismissedPermanent;
1509
- return /* @__PURE__ */ jsxs10(Fragment4, { children: [
1731
+ return /* @__PURE__ */ jsxs10(Fragment3, { children: [
1510
1732
  children,
1511
- showBanner && /* @__PURE__ */ jsx16(DesktopVariant, { state: installState, actions: installState })
1733
+ showBanner && /* @__PURE__ */ jsx15(DesktopVariant, { state: installState, actions: installState })
1512
1734
  ] });
1513
1735
  }
1514
- if (!shouldBlock) return /* @__PURE__ */ jsx16(Fragment4, { children });
1736
+ if (!shouldBlock) return /* @__PURE__ */ jsx15(Fragment3, { children });
1515
1737
  switch (installState.variant) {
1516
1738
  case "android-native":
1517
- return /* @__PURE__ */ jsx16(AndroidNativeVariant, { state: installState, actions: installState });
1739
+ return /* @__PURE__ */ jsx15(AndroidNativeVariant, { state: installState, actions: installState });
1518
1740
  case "android-manual":
1519
- return /* @__PURE__ */ jsx16(AndroidManualVariant, { state: installState, actions: installState });
1741
+ return /* @__PURE__ */ jsx15(AndroidManualVariant, { state: installState, actions: installState });
1520
1742
  case "ios-safari":
1521
- return /* @__PURE__ */ jsx16(IOSafariVariant, { state: installState, actions: installState });
1743
+ return /* @__PURE__ */ jsx15(IOSafariVariant, { state: installState, actions: installState });
1522
1744
  case "ios-other":
1523
- return /* @__PURE__ */ jsx16(IOSOtherVariant, { state: installState, actions: installState });
1745
+ return /* @__PURE__ */ jsx15(IOSOtherVariant, { state: installState, actions: installState });
1524
1746
  case "in-app":
1525
- return /* @__PURE__ */ jsx16(InAppBrowserVariant, { state: installState, actions: installState });
1747
+ return /* @__PURE__ */ jsx15(InAppBrowserVariant, { state: installState, actions: installState });
1526
1748
  case "none":
1527
1749
  default:
1528
- return /* @__PURE__ */ jsx16(Fragment4, { children });
1750
+ return /* @__PURE__ */ jsx15(Fragment3, { children });
1529
1751
  }
1530
1752
  }
1531
1753
 
1754
+ // src/internal/PushPrompt.tsx
1755
+ function PushPrompt() {
1756
+ return null;
1757
+ }
1758
+
1532
1759
  // src/defaults/ErrorBoundary.tsx
1533
1760
  import { Component } from "react";
1534
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1761
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1535
1762
  var ErrorBoundary = class extends Component {
1536
1763
  state = { error: null };
1537
1764
  static getDerivedStateFromError(error) {
1538
1765
  return { error };
1539
1766
  }
1540
- componentDidCatch(error) {
1541
- console.error("[ErrorBoundary] caught", error);
1767
+ componentDidCatch(error, info) {
1768
+ console.error(
1769
+ "[ErrorBoundary] caught:",
1770
+ error?.message || "(no message)",
1771
+ "\nstack:",
1772
+ error?.stack || "(no stack)",
1773
+ "\ncomponentStack:",
1774
+ info?.componentStack || "(no componentStack)"
1775
+ );
1542
1776
  }
1543
1777
  render() {
1544
1778
  if (this.state.error) {
1545
1779
  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." })
1780
+ /* @__PURE__ */ jsx16("h2", { children: "Algo deu errado" }),
1781
+ /* @__PURE__ */ jsx16("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
1548
1782
  ] });
1549
1783
  }
1550
- return /* @__PURE__ */ jsx17(Fragment5, { children: this.props.children });
1784
+ return /* @__PURE__ */ jsx16(Fragment4, { children: this.props.children });
1551
1785
  }
1552
1786
  };
1553
1787
 
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
1788
+ // src/internal/PaymentReturnHandler.tsx
1789
+ import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef2, useState as useState4 } from "react";
1790
+ import { useHook as useHook3 } from "@hook-sdk/sdk";
1791
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1792
+ var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
1793
+ function PaymentReturnHandler({ children }) {
1794
+ const { subscription } = useHook3();
1795
+ const subRef = useRef2(subscription);
1796
+ subRef.current = subscription;
1797
+ const runIdRef = useRef2(0);
1798
+ const [state, setState] = useState4("idle");
1799
+ const runPoll = useCallback3(() => {
1800
+ const runId = ++runIdRef.current;
1801
+ setState("confirming");
1802
+ let attempts = 0;
1803
+ const tick = async () => {
1804
+ if (runIdRef.current !== runId) return;
1805
+ attempts++;
1806
+ try {
1807
+ await subRef.current.refresh();
1808
+ } catch {
1809
+ }
1810
+ if (runIdRef.current !== runId) return;
1811
+ const status = subRef.current.status();
1812
+ if (status === "active" || status === "trialing") {
1813
+ const cleanUrl = new URL(window.location.href);
1814
+ cleanUrl.searchParams.delete("paymentReturn");
1815
+ window.history.replaceState({}, "", cleanUrl.toString());
1816
+ setState("idle");
1817
+ return;
1818
+ }
1819
+ const delay = BACKOFF_MS[attempts - 1];
1820
+ if (delay === void 0) {
1821
+ setState("waiting");
1822
+ return;
1823
+ }
1824
+ setTimeout(tick, delay);
1566
1825
  };
1826
+ void tick();
1827
+ }, []);
1828
+ useEffect5(() => {
1829
+ if (typeof window === "undefined") return;
1830
+ const url = new URL(window.location.href);
1831
+ if (url.searchParams.get("paymentReturn") !== "1") return;
1832
+ runPoll();
1833
+ return () => {
1834
+ runIdRef.current++;
1835
+ };
1836
+ }, [runPoll]);
1837
+ if (state === "confirming") {
1838
+ return /* @__PURE__ */ jsx17("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
1567
1839
  }
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." };
1840
+ if (state === "waiting") {
1841
+ return /* @__PURE__ */ jsx17("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ jsxs12("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
1842
+ /* @__PURE__ */ jsx17("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
1843
+ /* @__PURE__ */ jsx17("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
1844
+ ] }) });
1583
1845
  }
1584
- return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
1846
+ return /* @__PURE__ */ jsx17(Fragment5, { children });
1585
1847
  }
1586
-
1587
- // src/hooks/useLoginForm.ts
1588
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1848
+ var overlayStyle2 = {
1849
+ position: "fixed",
1850
+ inset: 0,
1851
+ display: "flex",
1852
+ alignItems: "center",
1853
+ justifyContent: "center",
1854
+ background: "rgba(0,0,0,0.4)",
1855
+ zIndex: 9999,
1856
+ color: "white",
1857
+ fontSize: "1rem",
1858
+ padding: 24
1859
+ };
1860
+ var buttonStyle = {
1861
+ background: "white",
1862
+ color: "black",
1863
+ border: "none",
1864
+ borderRadius: 8,
1865
+ padding: "10px 24px",
1866
+ fontSize: "1rem",
1867
+ fontWeight: 600,
1868
+ cursor: "pointer"
1869
+ };
1870
+
1871
+ // src/AppRoot.tsx
1872
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
1873
+ function buildLegacyConfigShim(config) {
1874
+ const paywall = config.paywall;
1875
+ const isFree = paywall.mode === "free";
1876
+ const monthlyCents = isFree ? 0 : paywall.prices.monthlyCents;
1877
+ const trialDays = isFree ? 0 : paywall.trialDays ?? 0;
1878
+ return {
1879
+ slug: config.slug,
1880
+ name: config.name,
1881
+ email_alias: config.slug,
1882
+ theme: { primary_color: config.branding.primaryColor },
1883
+ features_enabled: config.features_enabled ?? [],
1884
+ dependencies_allowlist: ["react", "react-dom"],
1885
+ subscription: {
1886
+ mode: paywall.mode,
1887
+ price_cents: monthlyCents,
1888
+ currency: "brl",
1889
+ trial_days: trialDays,
1890
+ paywall_config: {
1891
+ title: config.name,
1892
+ benefits: ["Acesso completo"],
1893
+ cta: "Assinar"
1894
+ }
1895
+ },
1896
+ sdk_version_required: ">=0.16.0",
1897
+ max_bundle_size_kb: 500
1898
+ };
1899
+ }
1900
+ function AppRoot(props) {
1901
+ const {
1902
+ config: rawConfig,
1903
+ children,
1904
+ testRouter,
1905
+ testInitialEntries,
1906
+ Login,
1907
+ Signup,
1908
+ Forgot,
1909
+ Reset,
1910
+ EmailVerify,
1911
+ Paywall,
1912
+ Onboarding,
1913
+ PreAuthFlow
1914
+ } = props;
1915
+ if (!Login || !Signup || !Forgot || !Reset) {
1916
+ throw new Error(
1917
+ "[hook-template] <AppRoot>: Login, Signup, Forgot, Reset slot props are required."
1918
+ );
1919
+ }
1920
+ const config = parseAppConfig(rawConfig);
1921
+ if (config.paywall.mode !== "free" && !Paywall) {
1922
+ throw new Error(
1923
+ "[hook-template] <AppRoot>: Paywall slot prop is required when config.paywall.mode != 'free'."
1924
+ );
1925
+ }
1926
+ if (config.authFlow.requiresEmailVerify && !EmailVerify) {
1927
+ throw new Error(
1928
+ "[hook-template] <AppRoot>: EmailVerify slot prop is required when config.authFlow.requiresEmailVerify === true."
1929
+ );
1930
+ }
1931
+ if (config.onboarding?.trigger === "pre_signup_custom" && !PreAuthFlow) {
1932
+ throw new Error(
1933
+ "[hook-template] <AppRoot>: PreAuthFlow slot prop is required when config.onboarding.trigger === 'pre_signup_custom'."
1934
+ );
1935
+ }
1936
+ const legacyShim = useMemo3(() => buildLegacyConfigShim(config), [config]);
1937
+ const Router = testRouter === "memory" ? MemoryRouter : BrowserRouter;
1938
+ const basename = `/app/${config.slug}`;
1939
+ const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
1940
+ 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: [
1941
+ /* @__PURE__ */ jsx18(DeepLinkHandler, { deepLinks: config.deepLinks }),
1942
+ /* @__PURE__ */ jsx18(InstallGate, { children: /* @__PURE__ */ jsx18(
1943
+ AuthGated,
1944
+ {
1945
+ config,
1946
+ Login,
1947
+ Signup,
1948
+ Forgot,
1949
+ Reset,
1950
+ EmailVerify,
1951
+ Paywall,
1952
+ Onboarding,
1953
+ PreAuthFlow,
1954
+ children: /* @__PURE__ */ jsxs13(SubscriptionGate, { Paywall: Paywall ?? FallbackPaywall, children: [
1955
+ children,
1956
+ /* @__PURE__ */ jsx18(PushPrompt, {})
1957
+ ] })
1958
+ }
1959
+ ) })
1960
+ ] }) }) }) }) }) });
1961
+ }
1962
+ function AuthGated({
1963
+ children,
1964
+ config,
1965
+ Login,
1966
+ Signup,
1967
+ Forgot,
1968
+ Reset,
1969
+ EmailVerify,
1970
+ PreAuthFlow
1971
+ }) {
1972
+ const { authStatus } = useHook4();
1973
+ if (authStatus === "loading") return null;
1974
+ if (authStatus !== "authenticated") {
1975
+ if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
1976
+ return /* @__PURE__ */ jsxs13(Routes, { children: [
1977
+ /* @__PURE__ */ jsx18(Route, { path: "/signin", element: /* @__PURE__ */ jsx18(Login, {}) }),
1978
+ /* @__PURE__ */ jsx18(Route, { path: "/signup", element: /* @__PURE__ */ jsx18(Signup, {}) }),
1979
+ /* @__PURE__ */ jsx18(Route, { path: "/forgot", element: /* @__PURE__ */ jsx18(Forgot, {}) }),
1980
+ /* @__PURE__ */ jsx18(Route, { path: "/reset", element: /* @__PURE__ */ jsx18(Reset, {}) }),
1981
+ EmailVerify ? /* @__PURE__ */ jsx18(Route, { path: "/verify", element: /* @__PURE__ */ jsx18(EmailVerify, {}) }) : null,
1982
+ /* @__PURE__ */ jsx18(Route, { path: "/*", element: /* @__PURE__ */ jsx18(PreAuthFlow, {}) })
1983
+ ] });
1984
+ }
1985
+ return /* @__PURE__ */ jsxs13(Routes, { children: [
1986
+ /* @__PURE__ */ jsx18(Route, { path: "/", element: /* @__PURE__ */ jsx18(Login, {}) }),
1987
+ /* @__PURE__ */ jsx18(Route, { path: "/signup", element: /* @__PURE__ */ jsx18(Signup, {}) }),
1988
+ /* @__PURE__ */ jsx18(Route, { path: "/forgot", element: /* @__PURE__ */ jsx18(Forgot, {}) }),
1989
+ /* @__PURE__ */ jsx18(Route, { path: "/reset", element: /* @__PURE__ */ jsx18(Reset, {}) }),
1990
+ EmailVerify ? /* @__PURE__ */ jsx18(Route, { path: "/verify", element: /* @__PURE__ */ jsx18(EmailVerify, {}) }) : null,
1991
+ /* @__PURE__ */ jsx18(Route, { path: "*", element: /* @__PURE__ */ jsx18(Navigate, { to: "/", replace: true }) })
1992
+ ] });
1993
+ }
1994
+ return /* @__PURE__ */ jsx18(Fragment6, { children });
1995
+ }
1996
+ function FallbackPaywall() {
1997
+ return null;
1998
+ }
1999
+
2000
+ // src/hooks/usePush.ts
2001
+ import { useCallback as useCallback4, useEffect as useEffect6, useState as useState5 } from "react";
2002
+ import { useHook as useHook5 } from "@hook-sdk/sdk";
2003
+ function detectIosNeedsInstall() {
2004
+ if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2005
+ const ua = navigator.userAgent || "";
2006
+ const isIos = /iPhone|iPad|iPod/.test(ua);
2007
+ if (!isIos) return false;
2008
+ const mm = window.matchMedia?.("(display-mode: standalone)");
2009
+ const standalone = mm?.matches === true;
2010
+ const legacyStandalone = typeof navigator.standalone === "boolean" ? navigator.standalone : false;
2011
+ return !(standalone || legacyStandalone);
2012
+ }
2013
+ function deriveState(push) {
2014
+ if (!push.isAvailable()) {
2015
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2016
+ return { kind: "unsupported" };
2017
+ }
2018
+ const status = push.status();
2019
+ if (status === "granted") return { kind: "subscribed" };
2020
+ if (status === "denied") return { kind: "denied" };
2021
+ if (status === "unsupported") {
2022
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2023
+ return { kind: "unsupported" };
2024
+ }
2025
+ return { kind: "prompt" };
2026
+ }
2027
+ function usePush() {
2028
+ const { push } = useHook5();
2029
+ const [state, setState] = useState5(() => deriveState(push));
2030
+ useEffect6(() => {
2031
+ setState(deriveState(push));
2032
+ }, [push]);
2033
+ const subscribe = useCallback4(async () => {
2034
+ try {
2035
+ await push.subscribe();
2036
+ setState({ kind: "subscribed" });
2037
+ } catch (e) {
2038
+ const code = e?.code ?? "push.unknown";
2039
+ const message = e?.message ?? "Push subscription failed";
2040
+ if (code === "push.permission_denied") setState({ kind: "denied" });
2041
+ else setState({ kind: "error", code, message });
2042
+ throw e;
2043
+ }
2044
+ }, [push]);
2045
+ const unsubscribe = useCallback4(async () => {
2046
+ try {
2047
+ await push.unsubscribe();
2048
+ setState({ kind: "prompt" });
2049
+ } catch (e) {
2050
+ setState({ kind: "error", code: e?.code ?? "push.unknown", message: e?.message ?? "failed" });
2051
+ throw e;
2052
+ }
2053
+ }, [push]);
2054
+ return { state, subscribe, unsubscribe };
2055
+ }
2056
+
2057
+ // src/components/PushPrompt.tsx
2058
+ import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
2059
+ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
2060
+ const { state, subscribe } = usePush();
2061
+ if (state.kind === "subscribed") return null;
2062
+ if (state.kind === "ios_needs_install") {
2063
+ return /* @__PURE__ */ jsxs14("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2064
+ /* @__PURE__ */ jsx19("h3", { children: texts.iosInstallTitle }),
2065
+ /* @__PURE__ */ jsx19("p", { children: texts.iosInstallBody }),
2066
+ onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ jsx19("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2067
+ ] });
2068
+ }
2069
+ if (state.kind === "denied") {
2070
+ return /* @__PURE__ */ jsxs14("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
2071
+ /* @__PURE__ */ jsx19("h3", { children: texts.deniedTitle }),
2072
+ /* @__PURE__ */ jsx19("p", { children: texts.deniedBody })
2073
+ ] });
2074
+ }
2075
+ if (state.kind === "unsupported") {
2076
+ return /* @__PURE__ */ jsx19("div", { className, role: "region", children: /* @__PURE__ */ jsx19("p", { children: texts.unsupportedBody }) });
2077
+ }
2078
+ if (state.kind === "error") {
2079
+ return /* @__PURE__ */ jsx19("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ jsx19("p", { children: state.message }) });
2080
+ }
2081
+ return /* @__PURE__ */ jsxs14("div", { className, role: "region", children: [
2082
+ /* @__PURE__ */ jsx19(
2083
+ "button",
2084
+ {
2085
+ type: "button",
2086
+ onClick: async () => {
2087
+ try {
2088
+ await subscribe();
2089
+ onSubscribed?.();
2090
+ } catch {
2091
+ }
2092
+ },
2093
+ children: texts.cta
2094
+ }
2095
+ ),
2096
+ onDeclined && /* @__PURE__ */ jsx19("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2097
+ ] });
2098
+ }
2099
+
2100
+ // src/defaults/LoadingState.tsx
2101
+ import { jsx as jsx20 } from "react/jsx-runtime";
2102
+ function LoadingState({ message }) {
2103
+ return /* @__PURE__ */ jsx20("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ jsx20("span", { children: message ?? "Carregando..." }) });
2104
+ }
2105
+
2106
+ // src/defaults/EmptyState.tsx
2107
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
2108
+ function EmptyState({ title, description, action }) {
2109
+ return /* @__PURE__ */ jsxs15("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2110
+ /* @__PURE__ */ jsx21("h2", { style: { marginBottom: 8 }, children: title }),
2111
+ description && /* @__PURE__ */ jsx21("p", { style: { opacity: 0.7 }, children: description }),
2112
+ action && /* @__PURE__ */ jsx21("div", { style: { marginTop: 16 }, children: action })
2113
+ ] });
2114
+ }
2115
+
2116
+ // src/hooks/useLoginForm.ts
2117
+ import { useCallback as useCallback5, useMemo as useMemo4, useState as useState6 } from "react";
2118
+ import { useHook as useHook6 } from "@hook-sdk/sdk";
2119
+
2120
+ // src/errors.ts
2121
+ import { SdkError, SdkAuthError, SdkRateLimitError } from "@hook-sdk/sdk";
2122
+ function mapSdkError(err) {
2123
+ if (err instanceof SdkRateLimitError) {
2124
+ return {
2125
+ code: "rate_limited",
2126
+ message: `Aguarde ${err.retryAfter}s e tente novamente.`,
2127
+ retryAfter: err.retryAfter
2128
+ };
2129
+ }
2130
+ if (err instanceof SdkAuthError) {
2131
+ const detail = err.detail;
2132
+ if (detail === "email_unverified") {
2133
+ return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
2134
+ }
2135
+ if (detail === "account_locked") {
2136
+ return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
2137
+ }
2138
+ return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
2139
+ }
2140
+ if (err instanceof SdkError && err.httpStatus === 0) {
2141
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2142
+ }
2143
+ if (err instanceof TypeError) {
2144
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2145
+ }
2146
+ return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
2147
+ }
2148
+
2149
+ // src/hooks/useLoginForm.ts
2150
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1589
2151
  var MIN_PASSWORD = 8;
1590
2152
  function useLoginForm() {
1591
- const { auth } = useHook4();
2153
+ const { auth } = useHook6();
1592
2154
  const [email, setEmail] = useState6("");
1593
2155
  const [password, setPassword] = useState6("");
1594
2156
  const [submitting, setSubmitting] = useState6(false);
1595
2157
  const [error, setError] = useState6(null);
1596
- const emailError = useMemo3(() => {
2158
+ const emailError = useMemo4(() => {
1597
2159
  if (email.length === 0) return null;
1598
2160
  if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
1599
2161
  return null;
1600
2162
  }, [email]);
1601
- const passwordError = useMemo3(() => {
2163
+ const passwordError = useMemo4(() => {
1602
2164
  if (password.length === 0) return null;
1603
2165
  if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
1604
2166
  return null;
1605
2167
  }, [password]);
1606
2168
  const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
1607
- const submit = useCallback3(async () => {
2169
+ const submit = useCallback5(async () => {
1608
2170
  if (!canSubmit) return false;
1609
2171
  setSubmitting(true);
1610
2172
  setError(null);
@@ -1633,259 +2195,35 @@ function useLoginForm() {
1633
2195
  };
1634
2196
  }
1635
2197
 
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
2198
  // 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";
2199
+ import { useCallback as useCallback6, useMemo as useMemo5, useState as useState7 } from "react";
2200
+ import { useHook as useHook7 } from "@hook-sdk/sdk";
1863
2201
  var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1864
2202
  var MIN_PASSWORD2 = 8;
1865
2203
  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(() => {
2204
+ const { auth } = useHook7();
2205
+ const [name, setName] = useState7("");
2206
+ const [email, setEmail] = useState7("");
2207
+ const [password, setPassword] = useState7("");
2208
+ const [submitting, setSubmitting] = useState7(false);
2209
+ const [error, setError] = useState7(null);
2210
+ const nameError = useMemo5(() => {
1873
2211
  if (name.length === 0) return null;
1874
2212
  if (name.trim().length < 2) return "Nome muito curto.";
1875
2213
  return null;
1876
2214
  }, [name]);
1877
- const emailError = useMemo4(() => {
2215
+ const emailError = useMemo5(() => {
1878
2216
  if (email.length === 0) return null;
1879
2217
  if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
1880
2218
  return null;
1881
2219
  }, [email]);
1882
- const passwordError = useMemo4(() => {
2220
+ const passwordError = useMemo5(() => {
1883
2221
  if (password.length === 0) return null;
1884
2222
  if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
1885
2223
  return null;
1886
2224
  }, [password]);
1887
2225
  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 () => {
2226
+ const submit = useCallback6(async () => {
1889
2227
  if (!canSubmit) return false;
1890
2228
  setSubmitting(true);
1891
2229
  setError(null);
@@ -1904,1095 +2242,168 @@ function useSignupForm() {
1904
2242
  setName,
1905
2243
  nameError,
1906
2244
  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
- ] }) }) }) }) }) }) }) });
2245
+ setEmail,
2246
+ emailError,
2247
+ password,
2248
+ setPassword,
2249
+ passwordError,
2250
+ submit,
2251
+ submitting,
2252
+ canSubmit,
2253
+ error,
2254
+ loginWithGoogle: () => auth.loginWithGoogle()
2255
+ };
2877
2256
  }
2878
2257
 
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 () => {
2258
+ // src/hooks/useForgotForm.ts
2259
+ import { useCallback as useCallback7, useMemo as useMemo6, useState as useState8 } from "react";
2260
+ import { useHook as useHook8 } from "@hook-sdk/sdk";
2261
+ var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2262
+ function useForgotForm() {
2263
+ const { auth } = useHook8();
2264
+ const [email, setEmail] = useState8("");
2265
+ const [submitting, setSubmitting] = useState8(false);
2266
+ const [sent, setSent] = useState8(false);
2267
+ const [error, setError] = useState8(null);
2268
+ const emailError = useMemo6(() => {
2269
+ if (email.length === 0) return null;
2270
+ if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
2271
+ return null;
2272
+ }, [email]);
2273
+ const canSubmit = email.length > 0 && emailError === null && !submitting;
2274
+ const submit = useCallback7(async () => {
2275
+ if (!canSubmit) return false;
2276
+ setSubmitting(true);
2277
+ setError(null);
2913
2278
  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;
2279
+ await auth.forgot({ email });
2280
+ setSent(true);
2281
+ return true;
2282
+ } catch (err) {
2283
+ setError(mapSdkError(err));
2284
+ return false;
2285
+ } finally {
2286
+ setSubmitting(false);
2922
2287
  }
2923
- }, [push]);
2924
- const unsubscribe = useCallback8(async () => {
2288
+ }, [auth, email, canSubmit]);
2289
+ return {
2290
+ email,
2291
+ setEmail,
2292
+ emailError,
2293
+ submit,
2294
+ submitting,
2295
+ canSubmit,
2296
+ sent,
2297
+ error
2298
+ };
2299
+ }
2300
+
2301
+ // src/hooks/useResetForm.ts
2302
+ import { useCallback as useCallback8, useEffect as useEffect7, useMemo as useMemo7, useState as useState9 } from "react";
2303
+ import { useHook as useHook9 } from "@hook-sdk/sdk";
2304
+ var MIN_PASSWORD3 = 12;
2305
+ function useResetForm() {
2306
+ const { auth } = useHook9();
2307
+ const [token, setToken] = useState9(null);
2308
+ const [password, setPassword] = useState9("");
2309
+ const [confirm, setConfirm] = useState9("");
2310
+ const [submitting, setSubmitting] = useState9(false);
2311
+ const [done, setDone] = useState9(false);
2312
+ const [error, setError] = useState9(null);
2313
+ useEffect7(() => {
2314
+ if (typeof window === "undefined") return;
2315
+ const params = new URLSearchParams(window.location.search);
2316
+ const t = params.get("token");
2317
+ setToken(t && t.length > 0 ? t : null);
2318
+ }, []);
2319
+ const passwordError = useMemo7(() => {
2320
+ if (password.length === 0) return null;
2321
+ if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2322
+ return null;
2323
+ }, [password]);
2324
+ const confirmError = useMemo7(() => {
2325
+ if (confirm.length === 0) return null;
2326
+ if (confirm !== password) return "Senhas n\xE3o coincidem.";
2327
+ return null;
2328
+ }, [confirm, password]);
2329
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
2330
+ const submit = useCallback8(async () => {
2331
+ if (!canSubmit || token === null) return;
2332
+ setSubmitting(true);
2333
+ setError(null);
2925
2334
  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;
2335
+ await auth.reset({ token, newPassword: password });
2336
+ setDone(true);
2337
+ if (typeof window !== "undefined") {
2338
+ const url = new URL(window.location.href);
2339
+ url.searchParams.delete("token");
2340
+ url.searchParams.delete("screen");
2341
+ window.history.replaceState({}, "", url.toString());
2342
+ }
2343
+ } catch (err) {
2344
+ setError(mapSdkError(err));
2345
+ } finally {
2346
+ setSubmitting(false);
2931
2347
  }
2932
- }, [push]);
2933
- return { state, subscribe, unsubscribe };
2348
+ }, [auth, token, password, canSubmit]);
2349
+ return {
2350
+ token,
2351
+ password,
2352
+ setPassword,
2353
+ passwordError,
2354
+ confirm,
2355
+ setConfirm,
2356
+ confirmError,
2357
+ submit,
2358
+ submitting,
2359
+ canSubmit,
2360
+ done,
2361
+ error
2362
+ };
2934
2363
  }
2935
2364
 
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
- ] });
2365
+ // src/hooks/usePlan.ts
2366
+ import { useHook as useHook10 } from "@hook-sdk/sdk";
2367
+ function usePlan() {
2368
+ const { plan } = useHook10();
2369
+ return plan;
2977
2370
  }
2978
2371
 
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
- ] });
2372
+ // src/utils/price.ts
2373
+ function formatBRL(cents) {
2374
+ if (cents === null || cents === void 0) return "";
2375
+ const reais = cents / 100;
2376
+ return new Intl.NumberFormat("pt-BR", {
2377
+ style: "currency",
2378
+ currency: "BRL"
2379
+ }).format(reais);
2380
+ }
2381
+ function monthlyFromYearly(yearlyCents) {
2382
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2383
+ return Math.round(yearlyCents / 12);
2384
+ }
2385
+ function dailyFromYearly(yearlyCents) {
2386
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2387
+ return Math.round(yearlyCents / 365);
2388
+ }
2389
+ function computeAnchorCents(baseCents, multiplier) {
2390
+ if (multiplier === null || multiplier === void 0) return null;
2391
+ if (!Number.isFinite(multiplier)) return null;
2392
+ if (multiplier <= 1) return null;
2393
+ return Math.round(baseCents * multiplier);
2394
+ }
2395
+ function discountPercent(anchorCents, realCents) {
2396
+ if (anchorCents <= realCents) return 0;
2397
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
2987
2398
  }
2988
2399
 
2989
2400
  // src/hooks/useAuthPrimitives.ts
2990
- import { useEffect as useEffect10 } from "react";
2401
+ import { useEffect as useEffect8 } from "react";
2991
2402
  import { useHook as useHook11 } from "@hook-sdk/sdk";
2992
2403
  var warned = false;
2993
2404
  function useAuthPrimitives() {
2994
2405
  const { auth } = useHook11();
2995
- useEffect10(() => {
2406
+ useEffect8(() => {
2996
2407
  if (!warned && process.env.NODE_ENV !== "production") {
2997
2408
  warned = true;
2998
2409
  console.warn(
@@ -3013,23 +2424,34 @@ function useAuthPrimitives() {
3013
2424
  };
3014
2425
  }
3015
2426
 
3016
- // src/hooks/useSubscription.ts
2427
+ // src/hooks/useAuth.ts
3017
2428
  import { useHook as useHook12 } from "@hook-sdk/sdk";
2429
+ function useAuth() {
2430
+ const { user, authStatus, auth } = useHook12();
2431
+ return {
2432
+ user,
2433
+ authStatus,
2434
+ refresh: auth.refresh
2435
+ };
2436
+ }
2437
+
2438
+ // src/hooks/useSubscription.ts
2439
+ import { useHook as useHook13 } from "@hook-sdk/sdk";
3018
2440
  function useSubscription() {
3019
- const { subscription } = useHook12();
2441
+ const { subscription } = useHook13();
3020
2442
  return {
3021
2443
  status: subscription.status()
3022
2444
  };
3023
2445
  }
3024
2446
 
3025
2447
  // 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";
2448
+ import { useCallback as useCallback9, useEffect as useEffect9, useState as useState10 } from "react";
2449
+ import { useHook as useHook14 } from "@hook-sdk/sdk";
3028
2450
  function useReminders() {
3029
- const { push } = useHook13();
2451
+ const { push } = useHook14();
3030
2452
  const r = push.reminders;
3031
- const [reminders, setReminders] = useState14([]);
3032
- const [loading, setLoading] = useState14(true);
2453
+ const [reminders, setReminders] = useState10([]);
2454
+ const [loading, setLoading] = useState10(true);
3033
2455
  const reload = useCallback9(async () => {
3034
2456
  setLoading(true);
3035
2457
  try {
@@ -3039,7 +2461,7 @@ function useReminders() {
3039
2461
  setLoading(false);
3040
2462
  }
3041
2463
  }, [r]);
3042
- useEffect11(() => {
2464
+ useEffect9(() => {
3043
2465
  void reload();
3044
2466
  }, [reload]);
3045
2467
  const setReminder = useCallback9(async (input) => {
@@ -3060,9 +2482,9 @@ function useReminders() {
3060
2482
  }
3061
2483
 
3062
2484
  // src/hooks/useToast.ts
3063
- import { useCallback as useCallback10, useState as useState15 } from "react";
2485
+ import { useCallback as useCallback10, useState as useState11 } from "react";
3064
2486
  function useToast() {
3065
- const [items, setItems] = useState15([]);
2487
+ const [items, setItems] = useState11([]);
3066
2488
  const show = useCallback10((message, kind = "info") => {
3067
2489
  const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3068
2490
  setItems((prev) => [...prev, { id, message, kind }]);
@@ -3075,19 +2497,156 @@ function useToast() {
3075
2497
  }, []);
3076
2498
  return { items, show, dismiss };
3077
2499
  }
2500
+
2501
+ // src/RouteBoundary.tsx
2502
+ import { Routes as Routes2, Route as Route2 } from "react-router-dom";
2503
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
2504
+ function RouteBoundary({ children }) {
2505
+ return /* @__PURE__ */ jsxs16(Routes2, { children: [
2506
+ children,
2507
+ /* @__PURE__ */ jsx22(Route2, { path: "*", element: /* @__PURE__ */ jsx22(DefaultNotFound, {}) })
2508
+ ] });
2509
+ }
2510
+ function DefaultNotFound() {
2511
+ return /* @__PURE__ */ jsx22("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
2512
+ }
2513
+
2514
+ // src/PreAuthShell.tsx
2515
+ import { BrowserRouter as BrowserRouter2, MemoryRouter as MemoryRouter2, Routes as Routes3 } from "react-router-dom";
2516
+ import { jsx as jsx23 } from "react/jsx-runtime";
2517
+ function PreAuthShell({
2518
+ basename,
2519
+ testRouter,
2520
+ testInitialEntries,
2521
+ children
2522
+ }) {
2523
+ if (testRouter === "memory") {
2524
+ return /* @__PURE__ */ jsx23(MemoryRouter2, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ jsx23(Routes3, { children }) });
2525
+ }
2526
+ return /* @__PURE__ */ jsx23(BrowserRouter2, { basename, children: /* @__PURE__ */ jsx23(Routes3, { children }) });
2527
+ }
2528
+
2529
+ // src/OnboardingFlow.tsx
2530
+ import { useCallback as useCallback11, useMemo as useMemo8, useRef as useRef3 } from "react";
2531
+ import { usePersistedState } from "@hook-sdk/sdk";
2532
+
2533
+ // src/hooks/useOnboardingStep.ts
2534
+ import { createContext as createContext3, useContext as useContext4 } from "react";
2535
+ var OnboardingStepContext = createContext3(null);
2536
+ function useOnboardingStep() {
2537
+ const ctx = useContext4(OnboardingStepContext);
2538
+ if (!ctx) {
2539
+ throw new Error(
2540
+ "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
2541
+ );
2542
+ }
2543
+ return ctx;
2544
+ }
2545
+
2546
+ // src/OnboardingFlow.tsx
2547
+ import { jsx as jsx24 } from "react/jsx-runtime";
2548
+ var isFilled = (v) => v != null && v !== "";
2549
+ var CURRENT_STEP_FIELD = "currentStep";
2550
+ function readPersistedStepIdx(draft) {
2551
+ const raw = draft[CURRENT_STEP_FIELD];
2552
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
2553
+ }
2554
+ function OnboardingFlow({
2555
+ steps,
2556
+ screens,
2557
+ onComplete,
2558
+ persistKey
2559
+ }) {
2560
+ const [draft, setDraft, status] = usePersistedState(persistKey, {});
2561
+ const draftRef = useRef3(draft);
2562
+ draftRef.current = draft;
2563
+ const idx = readPersistedStepIdx(draft);
2564
+ const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
2565
+ const setIdx = useCallback11(
2566
+ (n) => {
2567
+ setDraft((prev) => {
2568
+ const prevIdx = readPersistedStepIdx(prev);
2569
+ const nextIdx = typeof n === "function" ? n(prevIdx) : n;
2570
+ return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
2571
+ });
2572
+ },
2573
+ [setDraft]
2574
+ );
2575
+ const setValue = useCallback11(
2576
+ (patch) => {
2577
+ draftRef.current = { ...draftRef.current, ...patch };
2578
+ setDraft((prev) => ({ ...prev, ...patch }));
2579
+ },
2580
+ [setDraft]
2581
+ );
2582
+ const step = steps[clampedIdx];
2583
+ const valid = useMemo8(
2584
+ () => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
2585
+ [draft, step]
2586
+ );
2587
+ const next = useCallback11(() => {
2588
+ if (!step) return;
2589
+ const current = draftRef.current;
2590
+ const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
2591
+ if (!validNow) return;
2592
+ if (clampedIdx + 1 >= steps.length) {
2593
+ onComplete(current);
2594
+ } else {
2595
+ setIdx(clampedIdx + 1);
2596
+ }
2597
+ }, [clampedIdx, onComplete, step, steps.length, setIdx]);
2598
+ const prevStep = useCallback11(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
2599
+ const ctx = useMemo8(
2600
+ () => ({
2601
+ stepIndex: clampedIdx,
2602
+ totalSteps: steps.length,
2603
+ value: draft,
2604
+ setValue,
2605
+ valid,
2606
+ next,
2607
+ prev: prevStep
2608
+ }),
2609
+ [clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
2610
+ );
2611
+ if (status.loading) {
2612
+ return null;
2613
+ }
2614
+ if (!step) {
2615
+ throw new Error(
2616
+ `[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
2617
+ );
2618
+ }
2619
+ const Screen = screens[step.screen];
2620
+ if (!Screen) {
2621
+ throw new Error(
2622
+ `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
2623
+ );
2624
+ }
2625
+ return /* @__PURE__ */ jsx24(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx24(Screen, {}) });
2626
+ }
2627
+
2628
+ // src/hooks/useFeature.ts
2629
+ function useFeature(name) {
2630
+ const config = useAppConfig();
2631
+ return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
2632
+ }
3078
2633
  export {
2634
+ AppConfigProvider,
2635
+ AppConfigSchema,
3079
2636
  AppRoot,
3080
- DefaultForgotScreen,
3081
- DefaultLoginScreen,
3082
- DefaultPaywall,
3083
- DefaultResetScreen,
3084
- DefaultSignupScreen,
2637
+ DeepLinkHandler,
3085
2638
  EmptyState,
3086
2639
  ErrorBoundary,
3087
2640
  InstallGate,
3088
2641
  InstallSplash,
3089
2642
  LoadingState,
2643
+ OnboardingFlow,
2644
+ PaymentReturnHandler,
2645
+ PersistenceRegistry,
2646
+ PreAuthShell,
3090
2647
  PushPrompt2 as PushPrompt,
2648
+ RouteBoundary,
2649
+ asaasErrorMessage,
3091
2650
  computeAnchorCents,
3092
2651
  dailyFromYearly,
3093
2652
  detectAndroidBrowser,
@@ -3098,13 +2657,17 @@ export {
3098
2657
  discountPercent,
3099
2658
  formatBRL,
3100
2659
  monthlyFromYearly,
2660
+ parseAppConfig,
3101
2661
  shouldBlockInstall,
3102
2662
  shouldShowPermanentOption,
2663
+ useAppConfig,
3103
2664
  useAuth,
3104
2665
  useAuthPrimitives,
2666
+ useFeature,
3105
2667
  useForgotForm,
3106
2668
  useInstallPrompt,
3107
2669
  useLoginForm,
2670
+ useOnboardingStep,
3108
2671
  usePaywallState,
3109
2672
  usePlan,
3110
2673
  usePush,