@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.cjs CHANGED
@@ -20,18 +20,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AppConfigProvider: () => AppConfigProvider,
24
+ AppConfigSchema: () => AppConfigSchema,
23
25
  AppRoot: () => AppRoot,
24
- DefaultForgotScreen: () => DefaultForgotScreen,
25
- DefaultLoginScreen: () => DefaultLoginScreen,
26
- DefaultPaywall: () => DefaultPaywall,
27
- DefaultResetScreen: () => DefaultResetScreen,
28
- DefaultSignupScreen: () => DefaultSignupScreen,
26
+ DeepLinkHandler: () => DeepLinkHandler,
29
27
  EmptyState: () => EmptyState,
30
28
  ErrorBoundary: () => ErrorBoundary,
31
29
  InstallGate: () => InstallGate,
32
30
  InstallSplash: () => InstallSplash,
33
31
  LoadingState: () => LoadingState,
32
+ OnboardingFlow: () => OnboardingFlow,
33
+ PaymentReturnHandler: () => PaymentReturnHandler,
34
+ PersistenceRegistry: () => PersistenceRegistry,
35
+ PreAuthShell: () => PreAuthShell,
34
36
  PushPrompt: () => PushPrompt2,
37
+ RouteBoundary: () => RouteBoundary,
38
+ asaasErrorMessage: () => asaasErrorMessage,
35
39
  computeAnchorCents: () => computeAnchorCents,
36
40
  dailyFromYearly: () => dailyFromYearly,
37
41
  detectAndroidBrowser: () => detectAndroidBrowser,
@@ -42,13 +46,17 @@ __export(index_exports, {
42
46
  discountPercent: () => discountPercent,
43
47
  formatBRL: () => formatBRL,
44
48
  monthlyFromYearly: () => monthlyFromYearly,
49
+ parseAppConfig: () => parseAppConfig,
45
50
  shouldBlockInstall: () => shouldBlockInstall,
46
51
  shouldShowPermanentOption: () => shouldShowPermanentOption,
52
+ useAppConfig: () => useAppConfig,
47
53
  useAuth: () => useAuth,
48
54
  useAuthPrimitives: () => useAuthPrimitives,
55
+ useFeature: () => useFeature,
49
56
  useForgotForm: () => useForgotForm,
50
57
  useInstallPrompt: () => useInstallPrompt,
51
58
  useLoginForm: () => useLoginForm,
59
+ useOnboardingStep: () => useOnboardingStep,
52
60
  usePaywallState: () => usePaywallState,
53
61
  usePlan: () => usePlan,
54
62
  usePush: () => usePush,
@@ -61,25 +69,161 @@ __export(index_exports, {
61
69
  module.exports = __toCommonJS(index_exports);
62
70
 
63
71
  // src/AppRoot.tsx
64
- var import_react15 = require("react");
65
- var import_sdk10 = require("@hook-sdk/sdk");
72
+ var import_react11 = require("react");
73
+ var import_react_router_dom2 = require("react-router-dom");
74
+ var import_sdk4 = require("@hook-sdk/sdk");
66
75
 
67
- // src/internal/TemplateConfigContext.tsx
76
+ // src/config/AppConfigContext.tsx
68
77
  var import_react = require("react");
69
78
  var import_jsx_runtime = require("react/jsx-runtime");
70
- var TemplateConfigContext = (0, import_react.createContext)(null);
79
+ var AppConfigContext = (0, import_react.createContext)(null);
80
+ function AppConfigProvider({
81
+ config,
82
+ children
83
+ }) {
84
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppConfigContext.Provider, { value: config, children });
85
+ }
86
+ function useAppConfig() {
87
+ const v = (0, import_react.useContext)(AppConfigContext);
88
+ if (!v) {
89
+ throw new Error(
90
+ "[hook-template] useAppConfig: AppConfigProvider missing \u2014 wrap your app in <AppRoot>"
91
+ );
92
+ }
93
+ return v;
94
+ }
95
+
96
+ // src/config/schema.ts
97
+ var import_zod = require("zod");
98
+ var SnakeKeyRE = /^[a-z0-9][a-z0-9_.-]{0,127}$/;
99
+ var AuthFlowSchema = import_zod.z.object({
100
+ minPassword: import_zod.z.number().int().min(6).max(64),
101
+ requiresEmailVerify: import_zod.z.boolean(),
102
+ googleOAuth: import_zod.z.boolean(),
103
+ postAuthLanding: import_zod.z.string().startsWith("/"),
104
+ preAuthRoutes: import_zod.z.array(import_zod.z.string().startsWith("/"))
105
+ });
106
+ var PaywallNonFreeSchema = import_zod.z.object({
107
+ mode: import_zod.z.enum(["trial", "pay_first"]),
108
+ trialDays: import_zod.z.number().int().nonnegative().optional(),
109
+ cycles: import_zod.z.array(import_zod.z.enum(["MONTHLY", "YEARLY"])).min(1),
110
+ prices: import_zod.z.object({
111
+ monthlyCents: import_zod.z.number().int().nonnegative(),
112
+ yearlyCents: import_zod.z.number().int().nonnegative()
113
+ }),
114
+ anchorPrices: import_zod.z.object({
115
+ monthlyCents: import_zod.z.number().int().nonnegative(),
116
+ yearlyCents: import_zod.z.number().int().nonnegative()
117
+ }).optional(),
118
+ checkoutMethods: import_zod.z.array(import_zod.z.enum(["card", "pix-auto", "pix-once"])).min(1),
119
+ requiresCpf: import_zod.z.boolean(),
120
+ cancelWindowDays: import_zod.z.number().int().nonnegative().optional(),
121
+ errorMessages: import_zod.z.enum(["default", "custom"])
122
+ });
123
+ var PaywallFreeSchema = import_zod.z.object({ mode: import_zod.z.literal("free") });
124
+ var PaywallSchema = import_zod.z.discriminatedUnion("mode", [PaywallNonFreeSchema, PaywallFreeSchema]);
125
+ var PersistedKeySchema = import_zod.z.object({
126
+ key: import_zod.z.string().regex(SnakeKeyRE, "key must be snake_case (matches /^[a-z0-9][a-z0-9_.-]{0,127}$/)"),
127
+ default: import_zod.z.unknown(),
128
+ guardRegen: import_zod.z.boolean().optional(),
129
+ debounceMs: import_zod.z.number().int().positive().optional()
130
+ });
131
+ var OnboardingSchema = import_zod.z.object({
132
+ trigger: import_zod.z.enum(["pre_signup", "post_signup", "pre_signup_custom", "optional"]),
133
+ steps: import_zod.z.array(
134
+ import_zod.z.object({
135
+ id: import_zod.z.string().regex(SnakeKeyRE),
136
+ screen: import_zod.z.string(),
137
+ validates: import_zod.z.array(import_zod.z.string()).optional()
138
+ })
139
+ ).min(1),
140
+ persistTo: import_zod.z.literal("appData"),
141
+ persistKey: import_zod.z.string().regex(SnakeKeyRE)
142
+ });
143
+ var DeepLinksSchema = import_zod.z.object({
144
+ passwordReset: import_zod.z.string().startsWith("/").optional(),
145
+ emailVerify: import_zod.z.string().startsWith("/").optional()
146
+ }).strict();
147
+ var AppConfigSchema = import_zod.z.object({
148
+ slug: import_zod.z.string().regex(/^[a-z0-9-]+$/),
149
+ name: import_zod.z.string().min(1),
150
+ branding: import_zod.z.object({ primaryColor: import_zod.z.string(), logoUrl: import_zod.z.string().url() }),
151
+ authFlow: AuthFlowSchema,
152
+ paywall: PaywallSchema,
153
+ persistedKeys: import_zod.z.array(PersistedKeySchema),
154
+ onboarding: OnboardingSchema.optional(),
155
+ deepLinks: DeepLinksSchema.optional(),
156
+ features_enabled: import_zod.z.array(import_zod.z.string()).optional(),
157
+ // Build-time injected theme metadata (e.g. icon_url for InstallSplash).
158
+ // Apps don't author this directly; deploy workflows fill it from
159
+ // env-resolved bundle host. Permissive shape so apps/workflows can
160
+ // extend without re-bumping the template schema.
161
+ theme: import_zod.z.object({}).passthrough().optional()
162
+ }).strict();
163
+ function parseAppConfig(input) {
164
+ const r = AppConfigSchema.safeParse(input);
165
+ if (!r.success) {
166
+ const messages = r.error.issues.map((i) => `[${i.path.join(".")}] ${i.message}`).join("\n");
167
+ throw new Error(`Invalid app.config.json:
168
+ ${messages}`);
169
+ }
170
+ return r.data;
171
+ }
172
+
173
+ // src/PersistenceRegistry.tsx
174
+ var import_react2 = require("react");
175
+ var import_sdk = require("@hook-sdk/sdk");
176
+ var import_jsx_runtime2 = require("react/jsx-runtime");
177
+ function PersistenceRegistry({ config, children }) {
178
+ const { appData } = (0, import_sdk.useHook)();
179
+ (0, import_react2.useEffect)(() => {
180
+ if (config.length === 0) return;
181
+ const keys = config.map((c) => c.key);
182
+ const bulk = appData.bulkRead;
183
+ bulk?.(keys).catch(() => {
184
+ });
185
+ }, [config, appData]);
186
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
187
+ }
188
+
189
+ // src/DeepLinkHandler.tsx
190
+ var import_react3 = require("react");
191
+ var import_react_router_dom = require("react-router-dom");
192
+ function DeepLinkHandler({ deepLinks }) {
193
+ const nav = (0, import_react_router_dom.useNavigate)();
194
+ const loc = (0, import_react_router_dom.useLocation)();
195
+ (0, import_react3.useEffect)(() => {
196
+ if (!deepLinks) return;
197
+ const params = new URLSearchParams(loc.search);
198
+ const token = params.get("token");
199
+ if (!token) return;
200
+ if (deepLinks.passwordReset && deepLinks.passwordReset.includes(":token") && loc.pathname === "/") {
201
+ nav(deepLinks.passwordReset.replace(":token", token));
202
+ return;
203
+ }
204
+ if (deepLinks.emailVerify && deepLinks.emailVerify.includes(":token") && loc.pathname === "/") {
205
+ nav(deepLinks.emailVerify.replace(":token", token));
206
+ }
207
+ }, [deepLinks, loc, nav]);
208
+ return null;
209
+ }
210
+
211
+ // src/internal/TemplateConfigContext.tsx
212
+ var import_react4 = require("react");
213
+ var import_jsx_runtime3 = require("react/jsx-runtime");
214
+ var TemplateConfigContext = (0, import_react4.createContext)(null);
71
215
  function TemplateConfigProvider({
72
216
  config,
73
217
  children
74
218
  }) {
75
- const value = (0, import_react.useMemo)(() => ({
219
+ const value = (0, import_react4.useMemo)(() => ({
76
220
  ...config,
77
221
  mode: config.subscription?.mode ?? "trial"
78
222
  }), [config]);
79
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TemplateConfigContext.Provider, { value, children });
223
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TemplateConfigContext.Provider, { value, children });
80
224
  }
81
225
  function useTemplateConfig() {
82
- const ctx = (0, import_react.useContext)(TemplateConfigContext);
226
+ const ctx = (0, import_react4.useContext)(TemplateConfigContext);
83
227
  if (ctx === null) {
84
228
  throw new Error("useTemplateConfig must be used inside <TemplateConfigProvider>");
85
229
  }
@@ -87,7 +231,7 @@ function useTemplateConfig() {
87
231
  }
88
232
 
89
233
  // src/internal/ThemeProvider.tsx
90
- var import_jsx_runtime2 = require("react/jsx-runtime");
234
+ var import_jsx_runtime4 = require("react/jsx-runtime");
91
235
  function ThemeProvider({ children }) {
92
236
  const config = useTemplateConfig();
93
237
  const style = {
@@ -96,182 +240,308 @@ function ThemeProvider({ children }) {
96
240
  "--hook-color-background": config.theme.background_color
97
241
  }
98
242
  };
99
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style, children });
100
- }
101
-
102
- // src/internal/AuthGate.tsx
103
- var import_react2 = require("react");
104
-
105
- // src/hooks/useAuth.ts
106
- var import_sdk = require("@hook-sdk/sdk");
107
- function useAuth() {
108
- const { user, authStatus, auth } = (0, import_sdk.useHook)();
109
- return {
110
- user,
111
- authStatus,
112
- refresh: auth.refresh
113
- };
243
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style, children });
114
244
  }
115
245
 
116
- // src/defaults/LoadingState.tsx
117
- var import_jsx_runtime3 = require("react/jsx-runtime");
118
- function LoadingState({ message }) {
119
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: message ?? "Carregando..." }) });
120
- }
246
+ // src/hooks/usePaywallState.ts
247
+ var import_react5 = require("react");
248
+ var import_sdk2 = require("@hook-sdk/sdk");
121
249
 
122
- // src/internal/AuthGate.tsx
123
- var import_jsx_runtime4 = require("react/jsx-runtime");
124
- function detectResetFromUrl() {
125
- if (typeof window === "undefined") return false;
126
- const params = new URLSearchParams(window.location.search);
127
- return params.get("token") !== null && params.get("screen") === "reset";
128
- }
129
- function AuthGate({ Login, Signup, Forgot, Reset, children }) {
130
- const { user, authStatus } = useAuth();
131
- const [screen, setScreen] = (0, import_react2.useState)(
132
- () => detectResetFromUrl() ? "reset" : "login"
133
- );
134
- (0, import_react2.useEffect)(() => {
135
- if (user && screen !== "login") {
136
- setScreen("login");
137
- }
138
- }, [user, screen]);
139
- (0, import_react2.useEffect)(() => {
140
- const onPop = () => {
141
- if (detectResetFromUrl()) setScreen("reset");
142
- };
143
- window.addEventListener("popstate", onPop);
144
- return () => window.removeEventListener("popstate", onPop);
145
- }, []);
146
- if (authStatus === "loading") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LoadingState, {});
147
- if (!user) {
148
- if (screen === "reset") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Reset, { onNavigate: setScreen });
149
- if (screen === "signup") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Signup, { onNavigate: setScreen });
150
- if (screen === "forgot") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Forgot, { onNavigate: setScreen });
151
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Login, { onNavigate: setScreen });
152
- }
153
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
250
+ // src/errors/asaas-pt-br.ts
251
+ var MAP = {
252
+ invalid_cpf: "CPF inv\xE1lido. Confira os n\xFAmeros e tente novamente.",
253
+ cpf_required: "Por favor, informe seu CPF para continuar.",
254
+ card_declined: "Cart\xE3o recusado pela operadora. Tente outro cart\xE3o ou m\xE9todo.",
255
+ insufficient_funds: "Saldo insuficiente no cart\xE3o. Tente outro m\xE9todo.",
256
+ card_expired: "Cart\xE3o expirado. Use um cart\xE3o v\xE1lido.",
257
+ invalid_card_number: "N\xFAmero de cart\xE3o inv\xE1lido.",
258
+ invalid_cvv: "C\xF3digo de seguran\xE7a (CVV) inv\xE1lido.",
259
+ invalid_expiration: "Data de validade inv\xE1lida.",
260
+ generic_decline: "Pagamento recusado. Tente novamente em instantes ou use outro m\xE9todo.",
261
+ webhook_unverified: "N\xE3o conseguimos confirmar seu pagamento. Atualize a p\xE1gina em alguns segundos.",
262
+ pix_expired: "QR Code do PIX expirou. Gere um novo.",
263
+ pix_not_paid_yet: "PIX ainda n\xE3o foi pago. Aguardando confirma\xE7\xE3o."
264
+ };
265
+ function asaasErrorMessage(code) {
266
+ return MAP[code] ?? "Ocorreu um erro inesperado. Tente novamente em instantes.";
154
267
  }
155
268
 
156
269
  // src/hooks/usePaywallState.ts
157
- var import_react3 = require("react");
158
- var import_sdk2 = require("@hook-sdk/sdk");
270
+ function isCheckoutFailure(r) {
271
+ return "ok" in r && r.ok === false;
272
+ }
273
+ var FALLBACK_PAYWALL = {
274
+ mode: "pay_first",
275
+ cycles: ["MONTHLY"],
276
+ prices: { monthlyCents: 0, yearlyCents: 0 },
277
+ checkoutMethods: ["card", "pix-auto"],
278
+ requiresCpf: true,
279
+ errorMessages: "default"
280
+ };
281
+ var isMethodAvailable = (availability, method) => availability[method] !== false;
159
282
  function usePaywallState() {
160
283
  const { subscription, plan } = (0, import_sdk2.useHook)();
161
- const [opening, setOpening] = (0, import_react3.useState)(false);
162
- const [error, setError] = (0, import_react3.useState)(null);
163
- const [pixPending, setPixPending] = (0, import_react3.useState)(null);
284
+ const configFromCtx = (0, import_react5.useContext)(AppConfigContext);
285
+ const paywall = configFromCtx?.paywall ?? FALLBACK_PAYWALL;
286
+ const isFree = paywall.mode === "free";
287
+ const declaredMethods = (0, import_react5.useMemo)(
288
+ () => isFree ? [] : paywall.checkoutMethods,
289
+ [isFree, paywall]
290
+ );
291
+ const availability = subscription.methodAvailability ?? {
292
+ card: null,
293
+ "pix-auto": null,
294
+ "pix-once": null
295
+ };
296
+ const methods = (0, import_react5.useMemo)(
297
+ () => declaredMethods.filter((m) => isMethodAvailable(availability, m)),
298
+ [declaredMethods, availability]
299
+ );
300
+ const defaultMethod = methods[0] ?? declaredMethods[0] ?? "card";
301
+ const [selectedMethodRaw, setSelectedMethod] = (0, import_react5.useState)(defaultMethod);
302
+ const selectedMethod = methods.includes(selectedMethodRaw) ? selectedMethodRaw : methods[0] ?? selectedMethodRaw;
303
+ const initialCycle = isFree ? "MONTHLY" : paywall.cycles[0] ?? "MONTHLY";
304
+ const [cycle, setCycle] = (0, import_react5.useState)(initialCycle);
305
+ const cpfRequired = !isFree && paywall.requiresCpf;
306
+ const [cpf, setCpf] = (0, import_react5.useState)("");
307
+ const cpfValid = (0, import_react5.useMemo)(() => /^[0-9]{11}$/.test(cpf), [cpf]);
308
+ const [card, setCardState] = (0, import_react5.useState)({
309
+ number: "",
310
+ cvv: "",
311
+ expiry: "",
312
+ holder: ""
313
+ });
314
+ const setCard = (0, import_react5.useCallback)((patch) => {
315
+ setCardState((prev) => ({ ...prev, ...patch }));
316
+ }, []);
317
+ const [error, setError] = (0, import_react5.useState)(null);
318
+ const [submitting, setSubmitting] = (0, import_react5.useState)(false);
164
319
  const status = subscription.status();
165
320
  const daysLeftInTrial = subscription.daysLeftInTrial();
166
321
  const initialLoadComplete = subscription.initialLoadComplete;
167
- const availableMethods = (0, import_react3.useMemo)(
168
- () => ["card", "pix-auto"],
169
- []
170
- );
171
- const priceCents = plan.data?.priceCents ?? 0;
172
- const yearlyPriceCents = plan.data?.yearlyPriceCents ?? null;
173
- const monthlyEquivalent = (0, import_react3.useCallback)(
174
- (cycle) => {
175
- if (cycle === "YEARLY" && yearlyPriceCents) {
176
- return Math.round(yearlyPriceCents / 12);
177
- }
178
- return priceCents;
322
+ const pixPending = (0, import_react5.useMemo)(() => {
323
+ const sdkPix = subscription.pixPending;
324
+ if (!sdkPix) return null;
325
+ const liveStatus = subscription.current?.status;
326
+ const paid = liveStatus === "ACTIVE" || liveStatus === "TRIAL";
327
+ return {
328
+ method: sdkPix.method,
329
+ qrCodePayload: sdkPix.qrCodePayload,
330
+ qrCodeBase64: sdkPix.qrCodeBase64,
331
+ expiresAt: null,
332
+ paid
333
+ };
334
+ }, [subscription.pixPending, subscription.current]);
335
+ const monthlyEquivalent = (0, import_react5.useCallback)(
336
+ (c) => {
337
+ const monthlyCents = plan.data?.priceCents ?? (isFree ? 0 : paywall.prices.monthlyCents);
338
+ const yearlyCents = plan.data?.yearlyPriceCents ?? (isFree ? null : paywall.prices.yearlyCents);
339
+ if (c === "YEARLY" && yearlyCents) return Math.round(yearlyCents / 12);
340
+ return monthlyCents;
179
341
  },
180
- [priceCents, yearlyPriceCents]
342
+ [plan, paywall, isFree]
181
343
  );
182
- const checkout = (0, import_react3.useCallback)(
344
+ const planDerived = (0, import_react5.useMemo)(() => {
345
+ if (isFree) return null;
346
+ const monthlyCents = paywall.prices.monthlyCents;
347
+ const yearlyCents = paywall.prices.yearlyCents;
348
+ const anchor = paywall.anchorPrices;
349
+ const discount = anchor && anchor.yearlyCents > 0 ? Math.round((1 - paywall.prices.yearlyCents / anchor.yearlyCents) * 100) : 0;
350
+ return {
351
+ monthlyCents,
352
+ yearlyCents,
353
+ anchorMonthlyCents: anchor?.monthlyCents,
354
+ anchorYearlyCents: anchor?.yearlyCents,
355
+ monthlyEquivalent: cycle === "YEARLY" ? Math.round(yearlyCents / 12) : monthlyCents,
356
+ discountPercent: discount
357
+ };
358
+ }, [paywall, cycle, isFree]);
359
+ const useDefaultMessages = paywall.mode !== "free" && paywall.errorMessages === "default";
360
+ const buildError = (0, import_react5.useCallback)(
361
+ (code, fallbackMessage) => ({
362
+ code,
363
+ message: fallbackMessage,
364
+ userMessage: useDefaultMessages ? asaasErrorMessage(code) : fallbackMessage
365
+ }),
366
+ [useDefaultMessages]
367
+ );
368
+ const submit = (0, import_react5.useCallback)(async () => {
369
+ setSubmitting(true);
370
+ setError(null);
371
+ const methodToUse = selectedMethod;
372
+ if (!isMethodAvailable(availability, methodToUse)) {
373
+ const code = "method_unavailable";
374
+ setError(buildError(code, `method ${methodToUse} unavailable`));
375
+ setSubmitting(false);
376
+ return {
377
+ ok: false,
378
+ code: "method_unavailable",
379
+ method: methodToUse,
380
+ reason: "pre_flight_unavailable"
381
+ };
382
+ }
383
+ try {
384
+ let result;
385
+ if (methodToUse === "card") {
386
+ const [expMonthRaw = "", expYearRaw = ""] = card.expiry.split("/");
387
+ const expYearTrimmed = expYearRaw.trim();
388
+ const cardData = {
389
+ number: card.number,
390
+ holderName: card.holder,
391
+ expiryMonth: expMonthRaw.trim(),
392
+ expiryYear: expYearTrimmed.length === 2 ? `20${expYearTrimmed}` : expYearTrimmed,
393
+ ccv: card.cvv
394
+ };
395
+ const holderInfo = {
396
+ name: card.holder,
397
+ email: "",
398
+ cpfCnpj: cpf,
399
+ postalCode: "",
400
+ addressNumber: ""
401
+ };
402
+ result = await subscription.checkout({
403
+ method: "card",
404
+ cycle,
405
+ cpf,
406
+ card: cardData,
407
+ holderInfo
408
+ });
409
+ } else if (methodToUse === "pix-auto") {
410
+ result = await subscription.checkout({ method: "pix-auto", cycle, cpf });
411
+ } else {
412
+ result = await subscription.checkout({ method: "pix-once", cycle, cpf });
413
+ }
414
+ if (isCheckoutFailure(result)) {
415
+ setError(buildError(result.code, `${result.code}: ${result.reason}`));
416
+ setSubmitting(false);
417
+ return result;
418
+ }
419
+ await subscription.refresh();
420
+ setSubmitting(false);
421
+ return result;
422
+ } catch (err) {
423
+ const code = err?.code ?? "generic_decline";
424
+ const message = err instanceof Error ? err.message : String(err);
425
+ setError(buildError(code, message));
426
+ setSubmitting(false);
427
+ return void 0;
428
+ }
429
+ }, [selectedMethod, availability, subscription, cycle, cpf, card, buildError]);
430
+ const checkout = (0, import_react5.useCallback)(
183
431
  async (args) => {
184
- setOpening(true);
432
+ setSubmitting(true);
185
433
  setError(null);
186
- setPixPending(null);
187
434
  try {
188
435
  if (args.method === "card") {
189
436
  if (!args.card || !args.holderInfo) {
190
- throw new Error('card and holderInfo are required when method is "card"');
437
+ throw Object.assign(
438
+ new Error('card and holderInfo are required when method is "card"'),
439
+ { code: "validation" }
440
+ );
191
441
  }
192
- await subscription.checkout({
442
+ const sdkArgs = {
193
443
  method: "card",
194
444
  cycle: args.cycle,
195
445
  cpf: args.cpf,
196
446
  card: args.card,
197
447
  holderInfo: args.holderInfo,
198
448
  ...args.remoteIp ? { remoteIp: args.remoteIp } : {}
199
- });
449
+ };
450
+ const result2 = await subscription.checkout(sdkArgs);
451
+ if (isCheckoutFailure(result2)) {
452
+ setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
453
+ setSubmitting(false);
454
+ return;
455
+ }
200
456
  await subscription.refresh();
201
- setOpening(false);
457
+ setSubmitting(false);
458
+ return;
459
+ }
460
+ if (args.method === "pix-auto") {
461
+ const result2 = await subscription.checkout({
462
+ method: "pix-auto",
463
+ cycle: args.cycle,
464
+ cpf: args.cpf
465
+ });
466
+ if (isCheckoutFailure(result2)) {
467
+ setError(buildError(result2.code, `${result2.code}: ${result2.reason}`));
468
+ setSubmitting(false);
469
+ return;
470
+ }
471
+ setSubmitting(false);
202
472
  return;
203
473
  }
204
474
  const result = await subscription.checkout({
205
- method: "pix-auto",
475
+ method: "pix-once",
206
476
  cycle: args.cycle,
207
477
  cpf: args.cpf
208
478
  });
209
- if (result.method !== "pix-auto") {
210
- throw new Error(`unexpected checkout result method: ${result.method}`);
479
+ if (isCheckoutFailure(result)) {
480
+ setError(buildError(result.code, `${result.code}: ${result.reason}`));
481
+ setSubmitting(false);
482
+ return;
211
483
  }
212
- setPixPending({
213
- method: "pix-auto",
214
- qrCodePayload: result.qrCodePayload,
215
- qrCodeBase64: result.qrCodeBase64,
216
- expiresAt: null,
217
- paid: false
218
- });
219
- setOpening(false);
484
+ setSubmitting(false);
220
485
  } catch (err) {
221
- setError(err);
222
- setOpening(false);
486
+ const code = err?.code ?? "generic_decline";
487
+ const message = err instanceof Error ? err.message : String(err);
488
+ setError(buildError(code, message));
489
+ setSubmitting(false);
223
490
  }
224
491
  },
225
- [subscription]
492
+ [subscription, buildError]
226
493
  );
227
- const cancel = (0, import_react3.useCallback)(async () => {
494
+ const cancel = (0, import_react5.useCallback)(async () => {
228
495
  try {
229
496
  await subscription.cancel();
230
497
  await subscription.refresh();
231
498
  } catch (err) {
232
- setError(err);
499
+ const code = err?.code ?? "generic_decline";
500
+ const message = err instanceof Error ? err.message : String(err);
501
+ setError(buildError(code, message));
233
502
  }
234
- }, [subscription]);
235
- const dismissPix = (0, import_react3.useCallback)(() => setPixPending(null), []);
236
- const subRef = (0, import_react3.useRef)(subscription);
237
- subRef.current = subscription;
238
- (0, import_react3.useEffect)(() => {
239
- if (!pixPending || pixPending.paid) return;
240
- let attempts = 0;
241
- const MAX_ATTEMPTS = 60;
242
- let cancelled = false;
243
- const tick = async () => {
244
- if (cancelled || attempts >= MAX_ATTEMPTS) return;
245
- attempts++;
246
- try {
247
- await subRef.current.refresh();
248
- if (cancelled) return;
249
- const s = subRef.current.status();
250
- if (s === "active" || s === "trialing") {
251
- setPixPending((prev) => prev ? { ...prev, paid: true } : prev);
252
- return;
253
- }
254
- } catch {
255
- }
256
- if (!cancelled) setTimeout(tick, 3e3);
257
- };
258
- setTimeout(tick, 3e3);
259
- return () => {
260
- cancelled = true;
261
- };
262
- }, [pixPending]);
503
+ }, [subscription, buildError]);
504
+ const cardState = (0, import_react5.useMemo)(
505
+ () => ({ ...card, set: setCard }),
506
+ [card, setCard]
507
+ );
508
+ const cpfState = (0, import_react5.useMemo)(
509
+ () => ({ required: cpfRequired, value: cpf, set: setCpf, valid: cpfValid }),
510
+ [cpfRequired, cpf, cpfValid]
511
+ );
263
512
  return {
513
+ // Subscription status (reactive, proxied from SDK)
264
514
  status,
265
515
  daysLeftInTrial,
266
516
  initialLoadComplete,
517
+ // Plan derivation from config (sync, no fetch)
518
+ plan: planDerived,
519
+ // Cycle + method selection
520
+ cycle,
521
+ setCycle,
522
+ methods,
523
+ selectedMethod,
524
+ setSelectedMethod,
525
+ // Form state
526
+ cpfState,
527
+ cardState,
528
+ // High-level + legacy actions
529
+ submit,
267
530
  checkout,
268
531
  cancel,
269
- opening,
270
- error,
532
+ // PIX state (proxied from SDK; legacy `paid`/`expiresAt` derived)
271
533
  pixPending,
272
- dismissPix,
273
- availableMethods,
274
- monthlyEquivalent
534
+ // Error + submit progress
535
+ error,
536
+ submitting,
537
+ // Backward-compat aliases (deprecated; remove in template 0.11)
538
+ opening: submitting,
539
+ availableMethods: methods,
540
+ monthlyEquivalent,
541
+ dismissPix: () => {
542
+ },
543
+ refreshPlan: () => {
544
+ }
275
545
  };
276
546
  }
277
547
 
@@ -292,51 +562,11 @@ function SubscriptionGate({ Paywall, children }) {
292
562
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
293
563
  }
294
564
 
295
- // src/internal/PersistedKeysPrefetch.tsx
296
- var import_react4 = require("react");
297
- var import_sdk3 = require("@hook-sdk/sdk");
298
- var import_jsx_runtime6 = require("react/jsx-runtime");
299
- var SAFETY_TIMEOUT_MS = 3e3;
300
- function PersistedKeysPrefetch({ children }) {
301
- const { appData } = (0, import_sdk3.useHook)();
302
- const config = useTemplateConfig();
303
- const hasKeys = !!config.persistedKeys && config.persistedKeys.length > 0;
304
- const [ready, setReady] = (0, import_react4.useState)(!hasKeys);
305
- (0, import_react4.useEffect)(() => {
306
- const keys = config.persistedKeys;
307
- if (!keys || keys.length === 0) {
308
- setReady(true);
309
- return;
310
- }
311
- let cancelled = false;
312
- appData.cache.startPrefetch(keys, (ks) => appData.bulkRead(ks));
313
- void appData.cache.waitForPrimed(SAFETY_TIMEOUT_MS).then((result) => {
314
- if (cancelled) return;
315
- if (result === "timeout") {
316
- console.warn(
317
- `[@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).`
318
- );
319
- }
320
- setReady(true);
321
- });
322
- return () => {
323
- cancelled = true;
324
- };
325
- }, [appData, config.persistedKeys]);
326
- if (!ready) return null;
327
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children });
328
- }
329
-
330
- // src/internal/PushPrompt.tsx
331
- function PushPrompt() {
332
- return null;
333
- }
334
-
335
565
  // src/components/InstallGate/InstallGate.tsx
336
- var import_react7 = require("react");
566
+ var import_react8 = require("react");
337
567
 
338
568
  // src/hooks/useInstallPrompt.ts
339
- var import_react5 = require("react");
569
+ var import_react6 = require("react");
340
570
  var IOS_RE = /iPad|iPhone|iPod/;
341
571
  var IOS_NON_SAFARI_RE = /CriOS|FxiOS|EdgiOS/;
342
572
  var ANDROID_RE = /Android/;
@@ -469,18 +699,18 @@ function useInstallPrompt(slug) {
469
699
  const iosBrowser = detectIOSBrowser(ua);
470
700
  const androidBrowser = detectAndroidBrowser(ua);
471
701
  const inAppApp = detectInAppApp(ua);
472
- const [isInstallable, setIsInstallable] = (0, import_react5.useState)(() => {
702
+ const [isInstallable, setIsInstallable] = (0, import_react6.useState)(() => {
473
703
  if (typeof window === "undefined") return false;
474
704
  return window.__pwaInstallPrompt != null;
475
705
  });
476
- const [isInstalled, setIsInstalled] = (0, import_react5.useState)(() => {
706
+ const [isInstalled, setIsInstalled] = (0, import_react6.useState)(() => {
477
707
  const { installed } = detectStandalone();
478
708
  return installed || readInstalledMarker(slug);
479
709
  });
480
- const [isDismissedSession, setIsDismissedSession] = (0, import_react5.useState)(() => readSessionSkip(slug));
481
- const [isDismissedPermanent, setIsDismissedPermanent] = (0, import_react5.useState)(() => readPermanentDismiss(slug).dismissed);
482
- const [skipCount, setSkipCount] = (0, import_react5.useState)(() => readSkipCount(slug));
483
- (0, import_react5.useEffect)(() => {
710
+ const [isDismissedSession, setIsDismissedSession] = (0, import_react6.useState)(() => readSessionSkip(slug));
711
+ const [isDismissedPermanent, setIsDismissedPermanent] = (0, import_react6.useState)(() => readPermanentDismiss(slug).dismissed);
712
+ const [skipCount, setSkipCount] = (0, import_react6.useState)(() => readSkipCount(slug));
713
+ (0, import_react6.useEffect)(() => {
484
714
  if (typeof window === "undefined") return;
485
715
  if (window.__pwaInstallPrompt) {
486
716
  setIsInstallable(true);
@@ -504,7 +734,7 @@ function useInstallPrompt(slug) {
504
734
  window.removeEventListener("appinstalled", onInstalled);
505
735
  };
506
736
  }, [slug]);
507
- (0, import_react5.useEffect)(() => {
737
+ (0, import_react6.useEffect)(() => {
508
738
  if (typeof window === "undefined") return;
509
739
  const mq = window.matchMedia?.("(display-mode: standalone)");
510
740
  if (!mq) return;
@@ -528,7 +758,7 @@ function useInstallPrompt(slug) {
528
758
  isDismissedPermanent,
529
759
  skipCount
530
760
  });
531
- const promptInstall = (0, import_react5.useCallback)(async () => {
761
+ const promptInstall = (0, import_react6.useCallback)(async () => {
532
762
  if (typeof window === "undefined") return false;
533
763
  const prompt = window.__pwaInstallPrompt;
534
764
  if (!prompt) return false;
@@ -550,7 +780,7 @@ function useInstallPrompt(slug) {
550
780
  return false;
551
781
  }
552
782
  }, [slug]);
553
- const dismissSession = (0, import_react5.useCallback)(() => {
783
+ const dismissSession = (0, import_react6.useCallback)(() => {
554
784
  if (typeof sessionStorage !== "undefined") {
555
785
  try {
556
786
  sessionStorage.setItem(storageKey.sessionSkip(slug), "true");
@@ -564,20 +794,20 @@ function useInstallPrompt(slug) {
564
794
  setIsDismissedSession(true);
565
795
  track("pwa_install_session_skip", { slug, platform, skip_count: newCount });
566
796
  }, [slug, skipCount, platform]);
567
- const dismissPermanent = (0, import_react5.useCallback)(() => {
797
+ const dismissPermanent = (0, import_react6.useCallback)(() => {
568
798
  const storage = safeStorage();
569
799
  if (storage) storage.setItem(storageKey.dismissedAt(slug), (/* @__PURE__ */ new Date()).toISOString());
570
800
  setIsDismissedPermanent(true);
571
801
  track("pwa_install_permanent_dismiss", { slug, platform, prior_skip_count: skipCount });
572
802
  }, [slug, platform, skipCount]);
573
- const copyLink = (0, import_react5.useCallback)(async () => {
803
+ const copyLink = (0, import_react6.useCallback)(async () => {
574
804
  if (typeof navigator === "undefined" || typeof location === "undefined") return;
575
805
  try {
576
806
  await navigator.clipboard?.writeText?.(location.href);
577
807
  } catch {
578
808
  }
579
809
  }, []);
580
- const reset = (0, import_react5.useCallback)(() => {
810
+ const reset = (0, import_react6.useCallback)(() => {
581
811
  const storage = safeStorage();
582
812
  if (storage) {
583
813
  storage.removeItem(storageKey.dismissedAt(slug));
@@ -759,11 +989,11 @@ var INSTALL_COPY = {
759
989
  };
760
990
 
761
991
  // src/components/InstallGate/InstallSplash.tsx
762
- var import_jsx_runtime7 = require("react/jsx-runtime");
992
+ var import_jsx_runtime6 = require("react/jsx-runtime");
763
993
  function InstallSplash({ children, title, subtitle }) {
764
994
  const { name, theme } = useTemplateConfig();
765
995
  const iconUrl = theme.icon_url || theme.logo_url || null;
766
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
996
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
767
997
  "div",
768
998
  {
769
999
  role: "dialog",
@@ -771,15 +1001,15 @@ function InstallSplash({ children, title, subtitle }) {
771
1001
  "aria-labelledby": "install-splash-title",
772
1002
  "aria-describedby": subtitle ? "install-splash-subtitle" : void 0,
773
1003
  style: overlayStyle,
774
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: cardStyle, children: [
775
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { display: "flex", justifyContent: "center", marginBottom: 16 }, children: iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1004
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: cardStyle, children: [
1005
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { display: "flex", justifyContent: "center", marginBottom: 16 }, children: iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
776
1006
  "img",
777
1007
  {
778
1008
  src: iconUrl,
779
1009
  alt: `\xCDcone de ${name}`,
780
1010
  style: { width: 80, height: 80, borderRadius: 20, objectFit: "cover" }
781
1011
  }
782
- ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1012
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
783
1013
  "div",
784
1014
  {
785
1015
  style: {
@@ -797,10 +1027,10 @@ function InstallSplash({ children, title, subtitle }) {
797
1027
  children: name.charAt(0).toUpperCase()
798
1028
  }
799
1029
  ) }),
800
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { id: "install-splash-title", style: titleStyle, children: title }),
801
- subtitle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { id: "install-splash-subtitle", style: subtitleStyle, children: subtitle }),
802
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { marginTop: 24 }, children }),
803
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: footerStyle, children: "por Hook" })
1030
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { id: "install-splash-title", style: titleStyle, children: title }),
1031
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { id: "install-splash-subtitle", style: subtitleStyle, children: subtitle }),
1032
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { marginTop: 24 }, children }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: footerStyle, children: "por Hook" })
804
1034
  ] })
805
1035
  }
806
1036
  );
@@ -874,7 +1104,7 @@ var footerStyle = {
874
1104
  };
875
1105
 
876
1106
  // src/components/InstallGate/icons.tsx
877
- var import_jsx_runtime8 = require("react/jsx-runtime");
1107
+ var import_jsx_runtime7 = require("react/jsx-runtime");
878
1108
  var defaultSvgProps = (size) => ({
879
1109
  width: size,
880
1110
  height: size,
@@ -886,64 +1116,64 @@ var defaultSvgProps = (size) => ({
886
1116
  strokeLinejoin: "round"
887
1117
  });
888
1118
  function ShareIconIOS({ size = 24, style, className }) {
889
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
890
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 2L12 15" }),
891
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 6L12 2L16 6" }),
892
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M4 11v9a2 2 0 002 2h12a2 2 0 002-2v-9" })
1119
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
1120
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 2L12 15" }),
1121
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 6L12 2L16 6" }),
1122
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 11v9a2 2 0 002 2h12a2 2 0 002-2v-9" })
893
1123
  ] });
894
1124
  }
895
1125
  function MenuDotsVerticalIcon({ size = 24, style, className }) {
896
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
897
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "5", r: "1.5" }),
898
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
899
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "19", r: "1.5" })
1126
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
1127
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "5", r: "1.5" }),
1128
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "19", r: "1.5" })
900
1130
  ] });
901
1131
  }
902
1132
  function MenuDotsHorizontalIcon({ size = 24, style, className }) {
903
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
904
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "5", cy: "12", r: "1.5" }),
905
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
906
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "19", cy: "12", r: "1.5" })
1133
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
1134
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "5", cy: "12", r: "1.5" }),
1135
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "12", cy: "12", r: "1.5" }),
1136
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("circle", { cx: "19", cy: "12", r: "1.5" })
907
1137
  ] });
908
1138
  }
909
1139
  function SquarePlusIcon({ size = 24, style, className }) {
910
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
911
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
912
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M12 8v8" }),
913
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 12h8" })
1140
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
1141
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1142
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M12 8v8" }),
1143
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 12h8" })
914
1144
  ] });
915
1145
  }
916
1146
  function DownloadIcon({ size = 24, style, className }) {
917
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
918
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" }),
919
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polyline", { points: "7 10 12 15 17 10" }),
920
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
1147
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
1148
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" }),
1149
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polyline", { points: "7 10 12 15 17 10" }),
1150
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
921
1151
  ] });
922
1152
  }
923
1153
  function ExternalLinkIcon({ size = 24, style, className }) {
924
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
925
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
926
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polyline", { points: "15 3 21 3 21 9" }),
927
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
1154
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
1155
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }),
1156
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("polyline", { points: "15 3 21 3 21 9" }),
1157
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
928
1158
  ] });
929
1159
  }
930
1160
  function XIcon({ size = 20, style, className }) {
931
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
932
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
933
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1161
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { ...defaultSvgProps(size), style, className, "aria-hidden": "true", children: [
1162
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1163
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
934
1164
  ] });
935
1165
  }
936
1166
 
937
1167
  // src/components/InstallGate/variants/AndroidNativeVariant.tsx
938
- var import_jsx_runtime9 = require("react/jsx-runtime");
1168
+ var import_jsx_runtime8 = require("react/jsx-runtime");
939
1169
  function AndroidNativeVariant({
940
1170
  state,
941
1171
  actions
942
1172
  }) {
943
1173
  const copy = INSTALL_COPY.android.native;
944
1174
  const showPermanent = shouldShowPermanentOption(state);
945
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
946
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1175
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1176
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
947
1177
  "button",
948
1178
  {
949
1179
  "data-testid": "install-prompt-cta-android-native",
@@ -951,12 +1181,12 @@ function AndroidNativeVariant({
951
1181
  onClick: () => void actions.promptInstall(),
952
1182
  style: { ...primaryButtonStyle, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8 },
953
1183
  children: [
954
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DownloadIcon, { size: 18 }),
1184
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DownloadIcon, { size: 18 }),
955
1185
  copy.cta
956
1186
  ]
957
1187
  }
958
1188
  ),
959
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1189
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
960
1190
  "button",
961
1191
  {
962
1192
  "data-testid": "install-prompt-skip-session",
@@ -966,7 +1196,7 @@ function AndroidNativeVariant({
966
1196
  children: copy.skip
967
1197
  }
968
1198
  ),
969
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1199
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
970
1200
  "button",
971
1201
  {
972
1202
  "data-testid": "install-prompt-skip-permanent",
@@ -980,17 +1210,17 @@ function AndroidNativeVariant({
980
1210
  }
981
1211
 
982
1212
  // src/components/InstallGate/variants/AndroidManualVariant.tsx
983
- var import_jsx_runtime10 = require("react/jsx-runtime");
1213
+ var import_jsx_runtime9 = require("react/jsx-runtime");
984
1214
  function AndroidManualVariant({
985
1215
  state,
986
1216
  actions
987
1217
  }) {
988
1218
  const copy = INSTALL_COPY.android.manual;
989
1219
  const showPermanent = shouldShowPermanentOption(state);
990
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(InstallSplash, { title: copy.title, children: [
991
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Step, { n: 1, icon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MenuDotsVerticalIcon, { size: 20 }), children: copy.step1 }),
992
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Step, { n: 2, icon: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DownloadIcon, { size: 18 }), children: copy.step2 }),
993
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1220
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(InstallSplash, { title: copy.title, children: [
1221
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Step, { n: 1, icon: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MenuDotsVerticalIcon, { size: 20 }), children: copy.step1 }),
1222
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Step, { n: 2, icon: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DownloadIcon, { size: 18 }), children: copy.step2 }),
1223
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
994
1224
  "button",
995
1225
  {
996
1226
  "data-testid": "install-prompt-cta-android-manual",
@@ -1000,7 +1230,7 @@ function AndroidManualVariant({
1000
1230
  children: copy.cta
1001
1231
  }
1002
1232
  ),
1003
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1233
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1004
1234
  "button",
1005
1235
  {
1006
1236
  "data-testid": "install-prompt-skip-session",
@@ -1010,7 +1240,7 @@ function AndroidManualVariant({
1010
1240
  children: copy.skip
1011
1241
  }
1012
1242
  ),
1013
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1243
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1014
1244
  "button",
1015
1245
  {
1016
1246
  "data-testid": "install-prompt-skip-permanent",
@@ -1023,7 +1253,7 @@ function AndroidManualVariant({
1023
1253
  ] });
1024
1254
  }
1025
1255
  function Step({ n, icon, children }) {
1026
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1256
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1027
1257
  "div",
1028
1258
  {
1029
1259
  style: {
@@ -1037,7 +1267,7 @@ function Step({ n, icon, children }) {
1037
1267
  textAlign: "left"
1038
1268
  },
1039
1269
  children: [
1040
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1270
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1041
1271
  "div",
1042
1272
  {
1043
1273
  style: {
@@ -1056,22 +1286,22 @@ function Step({ n, icon, children }) {
1056
1286
  children: n
1057
1287
  }
1058
1288
  ),
1059
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { flex: 1, fontSize: 15, color: "#333" }, children }),
1060
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { flex: 1, fontSize: 15, color: "#333" }, children }),
1290
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1061
1291
  ]
1062
1292
  }
1063
1293
  );
1064
1294
  }
1065
1295
 
1066
1296
  // src/components/InstallGate/Step.tsx
1067
- var import_jsx_runtime11 = require("react/jsx-runtime");
1297
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1068
1298
  function Step2({
1069
1299
  n,
1070
1300
  title,
1071
1301
  subtitle,
1072
1302
  visual
1073
1303
  }) {
1074
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1304
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1075
1305
  "div",
1076
1306
  {
1077
1307
  style: {
@@ -1082,7 +1312,7 @@ function Step2({
1082
1312
  textAlign: "left"
1083
1313
  },
1084
1314
  children: [
1085
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1315
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1086
1316
  "div",
1087
1317
  {
1088
1318
  style: {
@@ -1101,9 +1331,9 @@ function Step2({
1101
1331
  children: n
1102
1332
  }
1103
1333
  ),
1104
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1 }, children: [
1105
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
1106
- subtitle && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
1334
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1 }, children: [
1335
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
1336
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
1107
1337
  visual
1108
1338
  ] })
1109
1339
  ]
@@ -1112,21 +1342,21 @@ function Step2({
1112
1342
  }
1113
1343
 
1114
1344
  // src/components/InstallGate/variants/IOSafariVariant.tsx
1115
- var import_jsx_runtime12 = require("react/jsx-runtime");
1345
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1116
1346
  function IOSafariVariant({
1117
1347
  state,
1118
1348
  actions
1119
1349
  }) {
1120
1350
  const copy = INSTALL_COPY.iosSafari;
1121
1351
  const showPermanent = shouldShowPermanentOption(state);
1122
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1123
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1352
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1353
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1124
1354
  Step2,
1125
1355
  {
1126
1356
  n: 1,
1127
1357
  title: copy.step1.title,
1128
1358
  subtitle: copy.step1.subtitle,
1129
- visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1359
+ visual: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1130
1360
  "div",
1131
1361
  {
1132
1362
  style: {
@@ -1138,17 +1368,17 @@ function IOSafariVariant({
1138
1368
  padding: "12px 0",
1139
1369
  marginTop: 8
1140
1370
  },
1141
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1371
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1142
1372
  }
1143
1373
  )
1144
1374
  }
1145
1375
  ),
1146
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1376
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1147
1377
  Step2,
1148
1378
  {
1149
1379
  n: 2,
1150
1380
  title: copy.step2.title,
1151
- visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1381
+ visual: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1152
1382
  "div",
1153
1383
  {
1154
1384
  style: {
@@ -1161,19 +1391,19 @@ function IOSafariVariant({
1161
1391
  marginTop: 8
1162
1392
  },
1163
1393
  children: [
1164
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1165
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1394
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1395
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1166
1396
  ]
1167
1397
  }
1168
1398
  )
1169
1399
  }
1170
1400
  ),
1171
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1401
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1172
1402
  Step2,
1173
1403
  {
1174
1404
  n: 3,
1175
1405
  title: copy.step3.title,
1176
- visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1406
+ visual: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1177
1407
  "div",
1178
1408
  {
1179
1409
  style: {
@@ -1184,7 +1414,7 @@ function IOSafariVariant({
1184
1414
  padding: "10px 14px",
1185
1415
  marginTop: 8
1186
1416
  },
1187
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1417
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1188
1418
  "span",
1189
1419
  {
1190
1420
  style: {
@@ -1199,7 +1429,7 @@ function IOSafariVariant({
1199
1429
  )
1200
1430
  }
1201
1431
  ),
1202
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1432
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1203
1433
  "button",
1204
1434
  {
1205
1435
  "data-testid": "install-prompt-skip-session",
@@ -1209,7 +1439,7 @@ function IOSafariVariant({
1209
1439
  children: copy.skip
1210
1440
  }
1211
1441
  ),
1212
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1442
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1213
1443
  "button",
1214
1444
  {
1215
1445
  "data-testid": "install-prompt-skip-permanent",
@@ -1223,21 +1453,21 @@ function IOSafariVariant({
1223
1453
  }
1224
1454
 
1225
1455
  // src/components/InstallGate/variants/IOSOtherVariant.tsx
1226
- var import_jsx_runtime13 = require("react/jsx-runtime");
1456
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1227
1457
  function IOSOtherVariant({
1228
1458
  state,
1229
1459
  actions
1230
1460
  }) {
1231
1461
  const copy = INSTALL_COPY.iosOther;
1232
1462
  const showPermanent = shouldShowPermanentOption(state);
1233
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1234
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1463
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1464
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1235
1465
  Step2,
1236
1466
  {
1237
1467
  n: 1,
1238
1468
  title: copy.step1.title,
1239
1469
  subtitle: copy.step1.subtitle,
1240
- visual: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1470
+ visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1241
1471
  "div",
1242
1472
  {
1243
1473
  style: {
@@ -1249,17 +1479,17 @@ function IOSOtherVariant({
1249
1479
  padding: "12px 0",
1250
1480
  marginTop: 8
1251
1481
  },
1252
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1482
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1253
1483
  }
1254
1484
  )
1255
1485
  }
1256
1486
  ),
1257
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1487
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1258
1488
  Step2,
1259
1489
  {
1260
1490
  n: 2,
1261
1491
  title: copy.step2.title,
1262
- visual: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1492
+ visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1263
1493
  "div",
1264
1494
  {
1265
1495
  style: {
@@ -1272,19 +1502,19 @@ function IOSOtherVariant({
1272
1502
  marginTop: 8
1273
1503
  },
1274
1504
  children: [
1275
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1276
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1505
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1506
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1277
1507
  ]
1278
1508
  }
1279
1509
  )
1280
1510
  }
1281
1511
  ),
1282
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1512
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1283
1513
  Step2,
1284
1514
  {
1285
1515
  n: 3,
1286
1516
  title: copy.step3.title,
1287
- visual: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1517
+ visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1288
1518
  "div",
1289
1519
  {
1290
1520
  style: {
@@ -1295,7 +1525,7 @@ function IOSOtherVariant({
1295
1525
  padding: "10px 14px",
1296
1526
  marginTop: 8
1297
1527
  },
1298
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1528
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1299
1529
  "span",
1300
1530
  {
1301
1531
  style: {
@@ -1310,7 +1540,7 @@ function IOSOtherVariant({
1310
1540
  )
1311
1541
  }
1312
1542
  ),
1313
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1543
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1314
1544
  "button",
1315
1545
  {
1316
1546
  "data-testid": "install-prompt-skip-session",
@@ -1320,7 +1550,7 @@ function IOSOtherVariant({
1320
1550
  children: copy.skip
1321
1551
  }
1322
1552
  ),
1323
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1553
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1324
1554
  "button",
1325
1555
  {
1326
1556
  "data-testid": "install-prompt-skip-permanent",
@@ -1334,8 +1564,8 @@ function IOSOtherVariant({
1334
1564
  }
1335
1565
 
1336
1566
  // src/components/InstallGate/variants/InAppBrowserVariant.tsx
1337
- var import_react6 = require("react");
1338
- var import_jsx_runtime14 = require("react/jsx-runtime");
1567
+ var import_react7 = require("react");
1568
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1339
1569
  function InAppBrowserVariant({
1340
1570
  state,
1341
1571
  actions
@@ -1344,17 +1574,17 @@ function InAppBrowserVariant({
1344
1574
  const appCopy = INSTALL_COPY.inApp[app] ?? INSTALL_COPY.inApp.other;
1345
1575
  const copy = INSTALL_COPY.inApp;
1346
1576
  const showPermanent = shouldShowPermanentOption(state);
1347
- const [copied, setCopied] = (0, import_react6.useState)(false);
1577
+ const [copied, setCopied] = (0, import_react7.useState)(false);
1348
1578
  const handleCopy = async () => {
1349
1579
  await actions.copyLink();
1350
1580
  setCopied(true);
1351
1581
  setTimeout(() => setCopied(false), 2e3);
1352
1582
  };
1353
1583
  const DotsIcon = app === "facebook" || app === "telegram" ? MenuDotsVerticalIcon : MenuDotsHorizontalIcon;
1354
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(InstallSplash, { title: appCopy.title, children: [
1355
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Step3, { n: 1, icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DotsIcon, { size: 20 }), children: appCopy.step1 }),
1356
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Step3, { n: 2, icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
1357
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1584
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(InstallSplash, { title: appCopy.title, children: [
1585
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Step3, { n: 1, icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DotsIcon, { size: 20 }), children: appCopy.step1 }),
1586
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Step3, { n: 2, icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
1587
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1358
1588
  "button",
1359
1589
  {
1360
1590
  "data-testid": "install-prompt-cta-inapp-copy",
@@ -1364,7 +1594,7 @@ function InAppBrowserVariant({
1364
1594
  children: copied ? copy.copiedToast : copy.copy
1365
1595
  }
1366
1596
  ),
1367
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1597
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1368
1598
  "button",
1369
1599
  {
1370
1600
  "data-testid": "install-prompt-skip-session",
@@ -1374,7 +1604,7 @@ function InAppBrowserVariant({
1374
1604
  children: copy.skip
1375
1605
  }
1376
1606
  ),
1377
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1607
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1378
1608
  "button",
1379
1609
  {
1380
1610
  "data-testid": "install-prompt-skip-permanent",
@@ -1391,7 +1621,7 @@ function Step3({
1391
1621
  icon,
1392
1622
  children
1393
1623
  }) {
1394
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1624
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1395
1625
  "div",
1396
1626
  {
1397
1627
  style: {
@@ -1405,7 +1635,7 @@ function Step3({
1405
1635
  textAlign: "left"
1406
1636
  },
1407
1637
  children: [
1408
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1638
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1409
1639
  "div",
1410
1640
  {
1411
1641
  style: {
@@ -1424,15 +1654,15 @@ function Step3({
1424
1654
  children: n
1425
1655
  }
1426
1656
  ),
1427
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1428
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1657
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1658
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1429
1659
  ]
1430
1660
  }
1431
1661
  );
1432
1662
  }
1433
1663
 
1434
1664
  // src/components/InstallGate/variants/DesktopVariant.tsx
1435
- var import_jsx_runtime15 = require("react/jsx-runtime");
1665
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1436
1666
  function DesktopVariant({
1437
1667
  state,
1438
1668
  actions
@@ -1441,21 +1671,21 @@ function DesktopVariant({
1441
1671
  const copy = INSTALL_COPY.desktop;
1442
1672
  const iconUrl = theme.icon_url || theme.logo_url || null;
1443
1673
  if (!state.isInstallable) return null;
1444
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1674
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1445
1675
  "div",
1446
1676
  {
1447
1677
  role: "complementary",
1448
1678
  "aria-label": copy.title,
1449
1679
  style: bannerStyle,
1450
1680
  children: [
1451
- iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1681
+ iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1452
1682
  "img",
1453
1683
  {
1454
1684
  src: iconUrl,
1455
1685
  alt: "",
1456
1686
  style: { width: 40, height: 40, borderRadius: 10, objectFit: "cover", flexShrink: 0 }
1457
1687
  }
1458
- ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1688
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1459
1689
  "div",
1460
1690
  {
1461
1691
  style: {
@@ -1474,11 +1704,11 @@ function DesktopVariant({
1474
1704
  children: name.charAt(0).toUpperCase()
1475
1705
  }
1476
1706
  ),
1477
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
1478
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
1479
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
1707
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
1708
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
1709
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
1480
1710
  ] }),
1481
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1711
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1482
1712
  "button",
1483
1713
  {
1484
1714
  "data-testid": "install-prompt-cta-desktop",
@@ -1499,12 +1729,12 @@ function DesktopVariant({
1499
1729
  flexShrink: 0
1500
1730
  },
1501
1731
  children: [
1502
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DownloadIcon, { size: 14 }),
1732
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DownloadIcon, { size: 14 }),
1503
1733
  copy.cta
1504
1734
  ]
1505
1735
  }
1506
1736
  ),
1507
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1737
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1508
1738
  "button",
1509
1739
  {
1510
1740
  "data-testid": "install-prompt-desktop-close",
@@ -1519,7 +1749,7 @@ function DesktopVariant({
1519
1749
  padding: 4,
1520
1750
  flexShrink: 0
1521
1751
  },
1522
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(XIcon, { size: 16 })
1752
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(XIcon, { size: 16 })
1523
1753
  }
1524
1754
  )
1525
1755
  ]
@@ -1543,14 +1773,14 @@ var bannerStyle = {
1543
1773
  };
1544
1774
 
1545
1775
  // src/components/InstallGate/InstallGate.tsx
1546
- var import_jsx_runtime16 = require("react/jsx-runtime");
1776
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1547
1777
  function InstallGate({ children }) {
1548
1778
  const { slug, features_enabled } = useTemplateConfig();
1549
1779
  const enabled = features_enabled.includes("install_prompt");
1550
1780
  const installState = useInstallPrompt(slug);
1551
1781
  const shouldBlock = enabled && shouldBlockInstall(installState);
1552
- const trackedRef = (0, import_react7.useRef)(null);
1553
- (0, import_react7.useEffect)(() => {
1782
+ const trackedRef = (0, import_react8.useRef)(null);
1783
+ (0, import_react8.useEffect)(() => {
1554
1784
  if (!shouldBlock) return;
1555
1785
  if (typeof window === "undefined") return;
1556
1786
  const variantKey = `${slug}:${installState.variant}`;
@@ -1564,1279 +1794,79 @@ function InstallGate({ children }) {
1564
1794
  variant: installState.variant
1565
1795
  });
1566
1796
  }, [shouldBlock, slug, installState.variant, installState.platform, installState.iosBrowser, installState.androidBrowser, installState.inAppApp]);
1567
- if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1568
- if (installState.isInstalled) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1797
+ if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1798
+ if (installState.isInstalled) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1569
1799
  if (installState.variant === "desktop") {
1570
1800
  const showBanner = !installState.isDismissedSession && !installState.isDismissedPermanent;
1571
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1801
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1572
1802
  children,
1573
- showBanner && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DesktopVariant, { state: installState, actions: installState })
1803
+ showBanner && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DesktopVariant, { state: installState, actions: installState })
1574
1804
  ] });
1575
1805
  }
1576
- if (!shouldBlock) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1806
+ if (!shouldBlock) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1577
1807
  switch (installState.variant) {
1578
1808
  case "android-native":
1579
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AndroidNativeVariant, { state: installState, actions: installState });
1809
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AndroidNativeVariant, { state: installState, actions: installState });
1580
1810
  case "android-manual":
1581
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AndroidManualVariant, { state: installState, actions: installState });
1811
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AndroidManualVariant, { state: installState, actions: installState });
1582
1812
  case "ios-safari":
1583
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IOSafariVariant, { state: installState, actions: installState });
1813
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IOSafariVariant, { state: installState, actions: installState });
1584
1814
  case "ios-other":
1585
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IOSOtherVariant, { state: installState, actions: installState });
1815
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IOSOtherVariant, { state: installState, actions: installState });
1586
1816
  case "in-app":
1587
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(InAppBrowserVariant, { state: installState, actions: installState });
1817
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(InAppBrowserVariant, { state: installState, actions: installState });
1588
1818
  case "none":
1589
1819
  default:
1590
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1820
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1591
1821
  }
1592
1822
  }
1593
1823
 
1824
+ // src/internal/PushPrompt.tsx
1825
+ function PushPrompt() {
1826
+ return null;
1827
+ }
1828
+
1594
1829
  // src/defaults/ErrorBoundary.tsx
1595
- var import_react8 = require("react");
1596
- var import_jsx_runtime17 = require("react/jsx-runtime");
1597
- var ErrorBoundary = class extends import_react8.Component {
1830
+ var import_react9 = require("react");
1831
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1832
+ var ErrorBoundary = class extends import_react9.Component {
1598
1833
  state = { error: null };
1599
1834
  static getDerivedStateFromError(error) {
1600
1835
  return { error };
1601
1836
  }
1602
- componentDidCatch(error) {
1603
- console.error("[ErrorBoundary] caught", error);
1837
+ componentDidCatch(error, info) {
1838
+ console.error(
1839
+ "[ErrorBoundary] caught:",
1840
+ error?.message || "(no message)",
1841
+ "\nstack:",
1842
+ error?.stack || "(no stack)",
1843
+ "\ncomponentStack:",
1844
+ info?.componentStack || "(no componentStack)"
1845
+ );
1604
1846
  }
1605
1847
  render() {
1606
1848
  if (this.state.error) {
1607
- return this.props.fallback ?? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
1608
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h2", { children: "Algo deu errado" }),
1609
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
1849
+ return this.props.fallback ?? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
1850
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h2", { children: "Algo deu errado" }),
1851
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
1610
1852
  ] });
1611
1853
  }
1612
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children: this.props.children });
1854
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children: this.props.children });
1613
1855
  }
1614
1856
  };
1615
1857
 
1616
- // src/hooks/useLoginForm.ts
1617
- var import_react9 = require("react");
1618
- var import_sdk5 = require("@hook-sdk/sdk");
1619
-
1620
- // src/errors.ts
1621
- var import_sdk4 = require("@hook-sdk/sdk");
1622
- function mapSdkError(err) {
1623
- if (err instanceof import_sdk4.SdkRateLimitError) {
1624
- return {
1625
- code: "rate_limited",
1626
- message: `Aguarde ${err.retryAfter}s e tente novamente.`,
1627
- retryAfter: err.retryAfter
1628
- };
1629
- }
1630
- if (err instanceof import_sdk4.SdkAuthError) {
1631
- const detail = err.detail;
1632
- if (detail === "email_unverified") {
1633
- return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
1634
- }
1635
- if (detail === "account_locked") {
1636
- return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
1637
- }
1638
- return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
1639
- }
1640
- if (err instanceof import_sdk4.SdkError && err.httpStatus === 0) {
1641
- return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
1642
- }
1643
- if (err instanceof TypeError) {
1644
- return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
1645
- }
1646
- return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
1647
- }
1648
-
1649
- // src/hooks/useLoginForm.ts
1650
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1651
- var MIN_PASSWORD = 8;
1652
- function useLoginForm() {
1653
- const { auth } = (0, import_sdk5.useHook)();
1654
- const [email, setEmail] = (0, import_react9.useState)("");
1655
- const [password, setPassword] = (0, import_react9.useState)("");
1656
- const [submitting, setSubmitting] = (0, import_react9.useState)(false);
1657
- const [error, setError] = (0, import_react9.useState)(null);
1658
- const emailError = (0, import_react9.useMemo)(() => {
1659
- if (email.length === 0) return null;
1660
- if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
1661
- return null;
1662
- }, [email]);
1663
- const passwordError = (0, import_react9.useMemo)(() => {
1664
- if (password.length === 0) return null;
1665
- if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
1666
- return null;
1667
- }, [password]);
1668
- const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
1669
- const submit = (0, import_react9.useCallback)(async () => {
1670
- if (!canSubmit) return false;
1671
- setSubmitting(true);
1672
- setError(null);
1673
- try {
1674
- await auth.login({ email, password });
1675
- return true;
1676
- } catch (err) {
1677
- setError(mapSdkError(err));
1678
- return false;
1679
- } finally {
1680
- setSubmitting(false);
1681
- }
1682
- }, [auth, email, password, canSubmit]);
1683
- return {
1684
- email,
1685
- setEmail,
1686
- emailError,
1687
- password,
1688
- setPassword,
1689
- passwordError,
1690
- submit,
1691
- submitting,
1692
- canSubmit,
1693
- error,
1694
- loginWithGoogle: () => auth.loginWithGoogle()
1695
- };
1696
- }
1697
-
1698
- // src/internal/GoogleSignInButton.tsx
1699
- var import_jsx_runtime18 = require("react/jsx-runtime");
1700
- function GoogleSignInButton({
1701
- onClick,
1702
- testId = "oauth-google",
1703
- label = "Continuar com Google"
1704
- }) {
1705
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1706
- "button",
1707
- {
1708
- "data-testid": testId,
1709
- type: "button",
1710
- onClick,
1711
- style: {
1712
- width: "100%",
1713
- padding: "10px 12px",
1714
- display: "flex",
1715
- alignItems: "center",
1716
- justifyContent: "center",
1717
- gap: 10,
1718
- background: "#fff",
1719
- color: "#1f1f1f",
1720
- border: "1px solid #dadce0",
1721
- borderRadius: 8,
1722
- cursor: "pointer",
1723
- fontSize: 14,
1724
- fontWeight: 500,
1725
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
1726
- },
1727
- children: [
1728
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(GoogleGlyph, {}),
1729
- label
1730
- ]
1731
- }
1732
- );
1733
- }
1734
- function GoogleGlyph() {
1735
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [
1736
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1737
- "path",
1738
- {
1739
- 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",
1740
- fill: "#4285F4"
1741
- }
1742
- ),
1743
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1744
- "path",
1745
- {
1746
- 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",
1747
- fill: "#34A853"
1748
- }
1749
- ),
1750
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1751
- "path",
1752
- {
1753
- 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",
1754
- fill: "#FBBC05"
1755
- }
1756
- ),
1757
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1758
- "path",
1759
- {
1760
- 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",
1761
- fill: "#EA4335"
1762
- }
1763
- )
1764
- ] });
1765
- }
1766
-
1767
- // src/internal/OAuthErrorBanner.tsx
1768
- var import_react10 = require("react");
1769
- var import_jsx_runtime19 = require("react/jsx-runtime");
1770
- var ERROR_MESSAGES = {
1771
- invalid_state: "Sess\xE3o expirou, tente de novo.",
1772
- access_denied: "Voc\xEA cancelou o login com Google.",
1773
- provider_error: "O Google recusou a autentica\xE7\xE3o. Tente de novo em alguns segundos.",
1774
- invalid_return_to: "Link inv\xE1lido. Tente entrar novamente."
1775
- };
1776
- function readErrorCode() {
1777
- if (typeof window === "undefined") return null;
1778
- const code = new URLSearchParams(window.location.search).get("oauth_error");
1779
- if (!code) return null;
1780
- return code;
1781
- }
1782
- function stripErrorFromUrl() {
1783
- if (typeof window === "undefined") return;
1784
- const url = new URL(window.location.href);
1785
- url.searchParams.delete("oauth_error");
1786
- window.history.replaceState({}, "", url.toString());
1787
- }
1788
- function OAuthErrorBanner() {
1789
- const [code, setCode] = (0, import_react10.useState)(() => readErrorCode());
1790
- (0, import_react10.useEffect)(() => {
1791
- if (code !== null) stripErrorFromUrl();
1792
- }, [code]);
1793
- if (!code) return null;
1794
- const message = ERROR_MESSAGES[code] ?? "N\xE3o conseguimos conectar ao Google. Tente de novo.";
1795
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1796
- "div",
1797
- {
1798
- role: "alert",
1799
- "data-testid": "oauth-error-banner",
1800
- style: {
1801
- padding: "10px 12px",
1802
- marginBottom: 16,
1803
- background: "#fce8e6",
1804
- color: "#a50e0e",
1805
- borderRadius: 8,
1806
- fontSize: 14
1807
- },
1808
- children: [
1809
- message,
1810
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1811
- "button",
1812
- {
1813
- type: "button",
1814
- onClick: () => setCode(null),
1815
- "aria-label": "Fechar",
1816
- style: {
1817
- float: "right",
1818
- background: "none",
1819
- border: "none",
1820
- color: "#a50e0e",
1821
- cursor: "pointer",
1822
- fontSize: 16,
1823
- lineHeight: 1,
1824
- padding: 0
1825
- },
1826
- children: "\xD7"
1827
- }
1828
- )
1829
- ]
1830
- }
1831
- );
1832
- }
1833
-
1834
- // src/defaults/DefaultLoginScreen.tsx
1835
- var import_jsx_runtime20 = require("react/jsx-runtime");
1836
- function DefaultLoginScreen({ onNavigate }) {
1837
- const { name } = useTemplateConfig();
1838
- const f = useLoginForm();
1839
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
1840
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
1841
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Entre na sua conta" }),
1842
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(OAuthErrorBanner, {}),
1843
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(GoogleSignInButton, { onClick: f.loginWithGoogle, testId: "login-oauth-google" }),
1844
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1845
- "div",
1846
- {
1847
- "aria-hidden": "true",
1848
- style: {
1849
- display: "flex",
1850
- alignItems: "center",
1851
- gap: 8,
1852
- margin: "16px 0",
1853
- color: "rgba(0,0,0,0.45)",
1854
- fontSize: 12
1855
- },
1856
- children: [
1857
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } }),
1858
- "ou",
1859
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } })
1860
- ]
1861
- }
1862
- ),
1863
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("form", { onSubmit: (e) => {
1864
- e.preventDefault();
1865
- void f.submit();
1866
- }, children: [
1867
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
1868
- "E-mail",
1869
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1870
- "input",
1871
- {
1872
- "data-testid": "login-email",
1873
- type: "email",
1874
- value: f.email,
1875
- onChange: (e) => f.setEmail(e.target.value),
1876
- style: { display: "block", width: "100%" }
1877
- }
1878
- ),
1879
- f.emailError && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("small", { style: { color: "#c00" }, children: f.emailError })
1880
- ] }),
1881
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
1882
- "Senha",
1883
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1884
- "input",
1885
- {
1886
- "data-testid": "login-password",
1887
- type: "password",
1888
- value: f.password,
1889
- onChange: (e) => f.setPassword(e.target.value),
1890
- style: { display: "block", width: "100%" }
1891
- }
1892
- ),
1893
- f.passwordError && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("small", { style: { color: "#c00" }, children: f.passwordError })
1894
- ] }),
1895
- f.error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
1896
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1897
- "button",
1898
- {
1899
- "data-testid": "login-submit",
1900
- type: "submit",
1901
- disabled: !f.canSubmit,
1902
- style: {
1903
- width: "100%",
1904
- padding: 12,
1905
- background: "var(--hook-color-primary)",
1906
- color: "#fff",
1907
- border: "none",
1908
- borderRadius: 8,
1909
- opacity: f.canSubmit ? 1 : 0.5
1910
- },
1911
- children: f.submitting ? "Entrando..." : "Entrar"
1912
- }
1913
- )
1914
- ] }),
1915
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { marginTop: 16, display: "flex", justifyContent: "space-between" }, children: [
1916
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { "data-testid": "login-goto-signup", type: "button", onClick: () => onNavigate("signup"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Criar conta" }),
1917
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { "data-testid": "login-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Esqueci senha" })
1918
- ] })
1919
- ] });
1920
- }
1921
-
1922
- // src/hooks/useSignupForm.ts
1923
- var import_react11 = require("react");
1924
- var import_sdk6 = require("@hook-sdk/sdk");
1925
- var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1926
- var MIN_PASSWORD2 = 8;
1927
- function useSignupForm() {
1928
- const { auth } = (0, import_sdk6.useHook)();
1929
- const [name, setName] = (0, import_react11.useState)("");
1930
- const [email, setEmail] = (0, import_react11.useState)("");
1931
- const [password, setPassword] = (0, import_react11.useState)("");
1932
- const [submitting, setSubmitting] = (0, import_react11.useState)(false);
1933
- const [error, setError] = (0, import_react11.useState)(null);
1934
- const nameError = (0, import_react11.useMemo)(() => {
1935
- if (name.length === 0) return null;
1936
- if (name.trim().length < 2) return "Nome muito curto.";
1937
- return null;
1938
- }, [name]);
1939
- const emailError = (0, import_react11.useMemo)(() => {
1940
- if (email.length === 0) return null;
1941
- if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
1942
- return null;
1943
- }, [email]);
1944
- const passwordError = (0, import_react11.useMemo)(() => {
1945
- if (password.length === 0) return null;
1946
- if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
1947
- return null;
1948
- }, [password]);
1949
- const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
1950
- const submit = (0, import_react11.useCallback)(async () => {
1951
- if (!canSubmit) return false;
1952
- setSubmitting(true);
1953
- setError(null);
1954
- try {
1955
- await auth.signup({ name, email, password });
1956
- return true;
1957
- } catch (err) {
1958
- setError(mapSdkError(err));
1959
- return false;
1960
- } finally {
1961
- setSubmitting(false);
1962
- }
1963
- }, [auth, name, email, password, canSubmit]);
1964
- return {
1965
- name,
1966
- setName,
1967
- nameError,
1968
- email,
1969
- setEmail,
1970
- emailError,
1971
- password,
1972
- setPassword,
1973
- passwordError,
1974
- submit,
1975
- submitting,
1976
- canSubmit,
1977
- error,
1978
- loginWithGoogle: () => auth.loginWithGoogle()
1979
- };
1980
- }
1981
-
1982
- // src/defaults/DefaultSignupScreen.tsx
1983
- var import_jsx_runtime21 = require("react/jsx-runtime");
1984
- function DefaultSignupScreen({ onNavigate }) {
1985
- const { name } = useTemplateConfig();
1986
- const f = useSignupForm();
1987
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
1988
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
1989
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Criar sua conta" }),
1990
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(OAuthErrorBanner, {}),
1991
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(GoogleSignInButton, { onClick: f.loginWithGoogle, testId: "signup-oauth-google" }),
1992
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1993
- "div",
1994
- {
1995
- "aria-hidden": "true",
1996
- style: {
1997
- display: "flex",
1998
- alignItems: "center",
1999
- gap: 8,
2000
- margin: "16px 0",
2001
- color: "rgba(0,0,0,0.45)",
2002
- fontSize: 12
2003
- },
2004
- children: [
2005
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } }),
2006
- "ou",
2007
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { flex: 1, height: 1, background: "rgba(0,0,0,0.1)" } })
2008
- ]
2009
- }
2010
- ),
2011
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("form", { onSubmit: (e) => {
2012
- e.preventDefault();
2013
- void f.submit();
2014
- }, children: [
2015
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
2016
- "Nome",
2017
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { "data-testid": "signup-name", value: f.name, onChange: (e) => f.setName(e.target.value), style: { display: "block", width: "100%" } }),
2018
- f.nameError && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("small", { style: { color: "#c00" }, children: f.nameError })
2019
- ] }),
2020
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
2021
- "E-mail",
2022
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { "data-testid": "signup-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
2023
- f.emailError && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("small", { style: { color: "#c00" }, children: f.emailError })
2024
- ] }),
2025
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
2026
- "Senha",
2027
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { "data-testid": "signup-password", type: "password", value: f.password, onChange: (e) => f.setPassword(e.target.value), style: { display: "block", width: "100%" } }),
2028
- f.passwordError && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("small", { style: { color: "#c00" }, children: f.passwordError })
2029
- ] }),
2030
- f.error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
2031
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("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" })
2032
- ] }),
2033
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { "data-testid": "signup-goto-login", type: "button", onClick: () => onNavigate("login"), style: { background: "none", border: "none", cursor: "pointer" }, children: "J\xE1 tem conta? Entre" }) })
2034
- ] });
2035
- }
2036
-
2037
- // src/hooks/useForgotForm.ts
2038
- var import_react12 = require("react");
2039
- var import_sdk7 = require("@hook-sdk/sdk");
2040
- var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2041
- function useForgotForm() {
2042
- const { auth } = (0, import_sdk7.useHook)();
2043
- const [email, setEmail] = (0, import_react12.useState)("");
2044
- const [submitting, setSubmitting] = (0, import_react12.useState)(false);
2045
- const [sent, setSent] = (0, import_react12.useState)(false);
2046
- const [error, setError] = (0, import_react12.useState)(null);
2047
- const emailError = (0, import_react12.useMemo)(() => {
2048
- if (email.length === 0) return null;
2049
- if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
2050
- return null;
2051
- }, [email]);
2052
- const canSubmit = email.length > 0 && emailError === null && !submitting;
2053
- const submit = (0, import_react12.useCallback)(async () => {
2054
- if (!canSubmit) return false;
2055
- setSubmitting(true);
2056
- setError(null);
2057
- try {
2058
- await auth.forgot({ email });
2059
- setSent(true);
2060
- return true;
2061
- } catch (err) {
2062
- setError(mapSdkError(err));
2063
- return false;
2064
- } finally {
2065
- setSubmitting(false);
2066
- }
2067
- }, [auth, email, canSubmit]);
2068
- return {
2069
- email,
2070
- setEmail,
2071
- emailError,
2072
- submit,
2073
- submitting,
2074
- canSubmit,
2075
- sent,
2076
- error
2077
- };
2078
- }
2079
-
2080
- // src/defaults/DefaultForgotScreen.tsx
2081
- var import_jsx_runtime22 = require("react/jsx-runtime");
2082
- function DefaultForgotScreen({ onNavigate }) {
2083
- const { name } = useTemplateConfig();
2084
- const f = useForgotForm();
2085
- if (f.sent) {
2086
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
2087
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h1", { children: "Verifique seu e-mail" }),
2088
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { style: { opacity: 0.7 }, children: "Enviamos um link pra redefinir sua senha." }),
2089
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { "data-testid": "forgot-back-login", type: "button", onClick: () => onNavigate("login"), children: "Voltar pro login" })
2090
- ] });
2091
- }
2092
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
2093
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
2094
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Redefinir senha" }),
2095
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("form", { onSubmit: (e) => {
2096
- e.preventDefault();
2097
- void f.submit();
2098
- }, children: [
2099
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
2100
- "E-mail",
2101
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("input", { "data-testid": "forgot-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
2102
- f.emailError && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("small", { style: { color: "#c00" }, children: f.emailError })
2103
- ] }),
2104
- f.error && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
2105
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("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" })
2106
- ] }),
2107
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { "data-testid": "forgot-goto-login", type: "button", onClick: () => onNavigate("login"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Voltar pro login" }) })
2108
- ] });
2109
- }
2110
-
2111
- // src/hooks/useResetForm.ts
2112
- var import_react13 = require("react");
2113
- var import_sdk8 = require("@hook-sdk/sdk");
2114
- var MIN_PASSWORD3 = 12;
2115
- function useResetForm() {
2116
- const { auth } = (0, import_sdk8.useHook)();
2117
- const [token, setToken] = (0, import_react13.useState)(null);
2118
- const [password, setPassword] = (0, import_react13.useState)("");
2119
- const [confirm, setConfirm] = (0, import_react13.useState)("");
2120
- const [submitting, setSubmitting] = (0, import_react13.useState)(false);
2121
- const [done, setDone] = (0, import_react13.useState)(false);
2122
- const [error, setError] = (0, import_react13.useState)(null);
2123
- (0, import_react13.useEffect)(() => {
2124
- if (typeof window === "undefined") return;
2125
- const params = new URLSearchParams(window.location.search);
2126
- const t = params.get("token");
2127
- setToken(t && t.length > 0 ? t : null);
2128
- }, []);
2129
- const passwordError = (0, import_react13.useMemo)(() => {
2130
- if (password.length === 0) return null;
2131
- if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2132
- return null;
2133
- }, [password]);
2134
- const confirmError = (0, import_react13.useMemo)(() => {
2135
- if (confirm.length === 0) return null;
2136
- if (confirm !== password) return "Senhas n\xE3o coincidem.";
2137
- return null;
2138
- }, [confirm, password]);
2139
- const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
2140
- const submit = (0, import_react13.useCallback)(async () => {
2141
- if (!canSubmit || token === null) return;
2142
- setSubmitting(true);
2143
- setError(null);
2144
- try {
2145
- await auth.reset({ token, newPassword: password });
2146
- setDone(true);
2147
- if (typeof window !== "undefined") {
2148
- const url = new URL(window.location.href);
2149
- url.searchParams.delete("token");
2150
- url.searchParams.delete("screen");
2151
- window.history.replaceState({}, "", url.toString());
2152
- }
2153
- } catch (err) {
2154
- setError(mapSdkError(err));
2155
- } finally {
2156
- setSubmitting(false);
2157
- }
2158
- }, [auth, token, password, canSubmit]);
2159
- return {
2160
- token,
2161
- password,
2162
- setPassword,
2163
- passwordError,
2164
- confirm,
2165
- setConfirm,
2166
- confirmError,
2167
- submit,
2168
- submitting,
2169
- canSubmit,
2170
- done,
2171
- error
2172
- };
2173
- }
2174
-
2175
- // src/defaults/DefaultResetScreen.tsx
2176
- var import_jsx_runtime23 = require("react/jsx-runtime");
2177
- function DefaultResetScreen({ onNavigate }) {
2178
- const { name } = useTemplateConfig();
2179
- const f = useResetForm();
2180
- if (f.done) {
2181
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
2182
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h1", { children: "Senha alterada" }),
2183
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { style: { opacity: 0.7 }, children: "Agora \xE9 s\xF3 fazer login com a nova senha." }),
2184
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { "data-testid": "reset-back-login", type: "button", onClick: () => onNavigate("login"), children: "Ir pro login" })
2185
- ] });
2186
- }
2187
- if (f.token === null) {
2188
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
2189
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h1", { children: "Link inv\xE1lido" }),
2190
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { style: { opacity: 0.7 }, children: "Pe\xE7a um novo link de reset." }),
2191
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { "data-testid": "reset-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), children: "Pedir novo link" })
2192
- ] });
2193
- }
2194
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
2195
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
2196
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Escolha uma nova senha" }),
2197
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("form", { onSubmit: (e) => {
2198
- e.preventDefault();
2199
- void f.submit();
2200
- }, children: [
2201
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
2202
- "Nova senha",
2203
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("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" }),
2204
- f.passwordError && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("small", { style: { color: "#c00" }, children: f.passwordError })
2205
- ] }),
2206
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
2207
- "Confirmar senha",
2208
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("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" }),
2209
- f.confirmError && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("small", { style: { color: "#c00" }, children: f.confirmError })
2210
- ] }),
2211
- f.error && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
2212
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("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" })
2213
- ] })
2214
- ] });
2215
- }
2216
-
2217
- // src/defaults/DefaultPaywall.tsx
2218
- var import_react14 = require("react");
2219
-
2220
- // src/hooks/usePlan.ts
2221
- var import_sdk9 = require("@hook-sdk/sdk");
2222
- function usePlan() {
2223
- const { plan } = (0, import_sdk9.useHook)();
2224
- return plan;
2225
- }
2226
-
2227
- // src/utils/price.ts
2228
- function formatBRL(cents) {
2229
- if (cents === null || cents === void 0) return "";
2230
- const reais = cents / 100;
2231
- return new Intl.NumberFormat("pt-BR", {
2232
- style: "currency",
2233
- currency: "BRL"
2234
- }).format(reais);
2235
- }
2236
- function monthlyFromYearly(yearlyCents) {
2237
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2238
- return Math.round(yearlyCents / 12);
2239
- }
2240
- function dailyFromYearly(yearlyCents) {
2241
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2242
- return Math.round(yearlyCents / 365);
2243
- }
2244
- function computeAnchorCents(baseCents, multiplier) {
2245
- if (multiplier === null || multiplier === void 0) return null;
2246
- if (!Number.isFinite(multiplier)) return null;
2247
- if (multiplier <= 1) return null;
2248
- return Math.round(baseCents * multiplier);
2249
- }
2250
- function discountPercent(anchorCents, realCents) {
2251
- if (anchorCents <= realCents) return 0;
2252
- return Math.floor((anchorCents - realCents) / anchorCents * 100);
2253
- }
2254
-
2255
- // src/defaults/DefaultPaywall.tsx
2256
- var import_jsx_runtime24 = require("react/jsx-runtime");
2257
- function DefaultPaywall() {
2258
- const config = useTemplateConfig();
2259
- const plan = usePlan();
2260
- const {
2261
- checkout,
2262
- opening,
2263
- error,
2264
- availableMethods,
2265
- monthlyEquivalent,
2266
- pixPending,
2267
- dismissPix
2268
- } = usePaywallState();
2269
- const p = config.subscription.paywall_config;
2270
- const paywallCfg = plan.data?.paywallConfig ?? {};
2271
- const anchorMultiplier = paywallCfg.anchorMultiplier ?? p.anchorMultiplier;
2272
- const anchorPriceCents = paywallCfg.anchorPriceCents ?? p.anchorPriceCents;
2273
- const [cycle, setCycle] = (0, import_react14.useState)("MONTHLY");
2274
- const [method, setMethod] = (0, import_react14.useState)("card");
2275
- const [cpf, setCpf] = (0, import_react14.useState)("");
2276
- const [cardNumber, setCardNumber] = (0, import_react14.useState)("");
2277
- const [cardHolderName, setCardHolderName] = (0, import_react14.useState)("");
2278
- const [cardExpiryMonth, setCardExpiryMonth] = (0, import_react14.useState)("");
2279
- const [cardExpiryYear, setCardExpiryYear] = (0, import_react14.useState)("");
2280
- const [cardCcv, setCardCcv] = (0, import_react14.useState)("");
2281
- const [holderName, setHolderName] = (0, import_react14.useState)("");
2282
- const [holderEmail, setHolderEmail] = (0, import_react14.useState)("");
2283
- const [holderPostalCode, setHolderPostalCode] = (0, import_react14.useState)("");
2284
- const [holderAddressNumber, setHolderAddressNumber] = (0, import_react14.useState)("");
2285
- const [holderPhone, setHolderPhone] = (0, import_react14.useState)("");
2286
- const trialDays = plan.data?.trialDays ?? 0;
2287
- const monthlyCents = plan.data?.priceCents ?? 0;
2288
- const yearlyCents = plan.data?.yearlyPriceCents ?? null;
2289
- const activeCents = cycle === "YEARLY" && yearlyCents ? monthlyEquivalent("YEARLY") : monthlyCents;
2290
- const cycleValueCents = cycle === "YEARLY" && yearlyCents ? yearlyCents : monthlyCents;
2291
- const cycleLabel = cycle === "YEARLY" ? "por ano" : "por m\xEAs";
2292
- const pct = (0, import_react14.useMemo)(() => {
2293
- if (!yearlyCents || !monthlyCents) return 0;
2294
- const derived = Math.round((1 - yearlyCents / 12 / monthlyCents) * 100);
2295
- return Math.max(0, derived);
2296
- }, [monthlyCents, yearlyCents]);
2297
- const anchorBaseCents = cycle === "YEARLY" && yearlyCents ? monthlyFromYearly(yearlyCents) : monthlyCents;
2298
- const anchorCents = computeAnchorCents(anchorBaseCents, anchorMultiplier) ?? (anchorPriceCents && anchorPriceCents > anchorBaseCents ? anchorPriceCents : null);
2299
- const anchorDiscount = anchorCents ? discountPercent(anchorCents, activeCents) : 0;
2300
- const cpfDigits = cpf.replace(/\D/g, "");
2301
- 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;
2302
- const canCheckout = cpfDigits.length === 11 && !opening && (method === "pix-auto" || cardFieldsFilled);
2303
- const ctaLabel = (0, import_react14.useMemo)(() => {
2304
- if (opening) return "Abrindo\u2026";
2305
- if (trialDays > 0) return `Comece trial de ${trialDays} dias gr\xE1tis`;
2306
- return p.cta ?? "Assinar agora";
2307
- }, [opening, trialDays, p.cta]);
2308
- const footer = (0, import_react14.useMemo)(() => {
2309
- if (trialDays > 0) {
2310
- return `Sem cobran\xE7a agora. Cobran\xE7a autom\xE1tica em ${trialDays} dias. Cancele quando quiser.`;
2311
- }
2312
- return "Cobran\xE7a imediata. Cancele quando quiser.";
2313
- }, [trialDays]);
2314
- const yearSuffix = cardExpiryYear.length === 2 ? `20${cardExpiryYear}` : cardExpiryYear;
2315
- const phoneDigits = holderPhone.replace(/\D/g, "");
2316
- const postalDigits = holderPostalCode.replace(/\D/g, "");
2317
- const submit = () => {
2318
- if (method === "card") {
2319
- void checkout({
2320
- cpf: cpfDigits,
2321
- cycle,
2322
- method: "card",
2323
- card: {
2324
- number: cardNumber.replace(/\s/g, ""),
2325
- holderName: cardHolderName.trim(),
2326
- expiryMonth: cardExpiryMonth,
2327
- expiryYear: yearSuffix,
2328
- ccv: cardCcv
2329
- },
2330
- holderInfo: {
2331
- name: holderName.trim(),
2332
- email: holderEmail.trim(),
2333
- cpfCnpj: cpfDigits,
2334
- postalCode: postalDigits,
2335
- addressNumber: holderAddressNumber.trim(),
2336
- ...phoneDigits ? { phone: phoneDigits } : {}
2337
- }
2338
- });
2339
- return;
2340
- }
2341
- void checkout({ cpf: cpfDigits, cycle, method: "pix-auto" });
2342
- };
2343
- const inputStyle = {
2344
- width: "100%",
2345
- padding: 10,
2346
- fontSize: 14,
2347
- borderRadius: 8,
2348
- border: "1px solid #ccc",
2349
- boxSizing: "border-box"
2350
- };
2351
- const labelStyle = { display: "block", fontSize: 13, opacity: 0.75, marginBottom: 4 };
2352
- const fieldGroup = { marginBottom: 10, textAlign: "left" };
2353
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("main", { style: { padding: 24, maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
2354
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h1", { style: { marginBottom: 8 }, children: p.title }),
2355
- p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: p.subtitle }),
2356
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2357
- "div",
2358
- {
2359
- role: "group",
2360
- "aria-label": "Per\xEDodo de cobran\xE7a",
2361
- style: { display: "flex", gap: 8, marginBottom: 16 },
2362
- children: [
2363
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2364
- "button",
2365
- {
2366
- type: "button",
2367
- "aria-pressed": cycle === "MONTHLY",
2368
- onClick: () => setCycle("MONTHLY"),
2369
- style: {
2370
- flex: 1,
2371
- padding: 12,
2372
- borderRadius: 8,
2373
- border: cycle === "MONTHLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2374
- background: cycle === "MONTHLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2375
- cursor: "pointer"
2376
- },
2377
- children: "Mensal"
2378
- }
2379
- ),
2380
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2381
- "button",
2382
- {
2383
- type: "button",
2384
- "aria-pressed": cycle === "YEARLY",
2385
- onClick: () => setCycle("YEARLY"),
2386
- disabled: !yearlyCents,
2387
- style: {
2388
- flex: 1,
2389
- padding: 12,
2390
- borderRadius: 8,
2391
- border: cycle === "YEARLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2392
- background: cycle === "YEARLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2393
- cursor: yearlyCents ? "pointer" : "not-allowed",
2394
- opacity: yearlyCents ? 1 : 0.5
2395
- },
2396
- children: [
2397
- "Anual",
2398
- pct > 0 ? ` \u2212${pct}%` : ""
2399
- ]
2400
- }
2401
- )
2402
- ]
2403
- }
2404
- ),
2405
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { marginBottom: 8 }, children: [
2406
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 32, fontWeight: 700, lineHeight: 1 }, children: [
2407
- formatBRL(activeCents),
2408
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { fontSize: 16, fontWeight: 400, opacity: 0.7 }, children: "/m\xEAs" })
2409
- ] }),
2410
- anchorCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 14, opacity: 0.6, marginTop: 4 }, children: [
2411
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { textDecoration: "line-through" }, children: formatBRL(anchorCents) }),
2412
- anchorDiscount > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { style: { marginLeft: 6 }, children: [
2413
- "\u2212",
2414
- anchorDiscount,
2415
- "%"
2416
- ] })
2417
- ] }),
2418
- cycle === "YEARLY" && yearlyCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 12, opacity: 0.6, marginTop: 4 }, children: [
2419
- "Cobrado ",
2420
- formatBRL(yearlyCents),
2421
- " por ano"
2422
- ] })
2423
- ] }),
2424
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", margin: "24px 0" }, children: p.benefits.map((b) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
2425
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
2426
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { children: b })
2427
- ] }, b)) }),
2428
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", style: { display: "flex", gap: 8, marginBottom: 16 }, children: availableMethods.map((m) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2429
- "button",
2430
- {
2431
- type: "button",
2432
- role: "tab",
2433
- "aria-selected": method === m,
2434
- onClick: () => setMethod(m),
2435
- style: {
2436
- flex: 1,
2437
- padding: 10,
2438
- borderRadius: 8,
2439
- border: method === m ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2440
- background: method === m ? "var(--hook-color-primary-soft, #eef)" : "white",
2441
- cursor: "pointer"
2442
- },
2443
- children: m === "card" ? "\u{1F4B3} Cart\xE3o" : "\u{1F4F1} Pix Autom\xE1tico"
2444
- },
2445
- m
2446
- )) }),
2447
- method === "card" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2448
- "fieldset",
2449
- {
2450
- "data-testid": "paywall-card-form",
2451
- style: { border: "none", padding: 0, marginBottom: 16 },
2452
- children: [
2453
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8 }, children: "Dados do cart\xE3o" }),
2454
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2455
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-number", style: labelStyle, children: "N\xFAmero do cart\xE3o" }),
2456
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2457
- "input",
2458
- {
2459
- id: "pw-card-number",
2460
- "data-testid": "pw-card-number",
2461
- type: "text",
2462
- inputMode: "numeric",
2463
- autoComplete: "cc-number",
2464
- placeholder: "0000 0000 0000 0000",
2465
- value: cardNumber,
2466
- onChange: (e) => setCardNumber(e.target.value),
2467
- style: inputStyle
2468
- }
2469
- )
2470
- ] }),
2471
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2472
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-holder", style: labelStyle, children: "Nome impresso no cart\xE3o" }),
2473
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2474
- "input",
2475
- {
2476
- id: "pw-card-holder",
2477
- "data-testid": "pw-card-holder",
2478
- type: "text",
2479
- autoComplete: "cc-name",
2480
- placeholder: "NOME SOBRENOME",
2481
- value: cardHolderName,
2482
- onChange: (e) => setCardHolderName(e.target.value),
2483
- style: inputStyle
2484
- }
2485
- )
2486
- ] }),
2487
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2488
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2489
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-m", style: labelStyle, children: "M\xEAs" }),
2490
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2491
- "input",
2492
- {
2493
- id: "pw-card-exp-m",
2494
- "data-testid": "pw-card-exp-m",
2495
- type: "text",
2496
- inputMode: "numeric",
2497
- autoComplete: "cc-exp-month",
2498
- placeholder: "MM",
2499
- maxLength: 2,
2500
- value: cardExpiryMonth,
2501
- onChange: (e) => setCardExpiryMonth(e.target.value.replace(/\D/g, "")),
2502
- style: inputStyle
2503
- }
2504
- )
2505
- ] }),
2506
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2507
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-y", style: labelStyle, children: "Ano" }),
2508
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2509
- "input",
2510
- {
2511
- id: "pw-card-exp-y",
2512
- "data-testid": "pw-card-exp-y",
2513
- type: "text",
2514
- inputMode: "numeric",
2515
- autoComplete: "cc-exp-year",
2516
- placeholder: "AA",
2517
- maxLength: 4,
2518
- value: cardExpiryYear,
2519
- onChange: (e) => setCardExpiryYear(e.target.value.replace(/\D/g, "")),
2520
- style: inputStyle
2521
- }
2522
- )
2523
- ] }),
2524
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2525
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-cvv", style: labelStyle, children: "CVV" }),
2526
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2527
- "input",
2528
- {
2529
- id: "pw-card-cvv",
2530
- "data-testid": "pw-card-cvv",
2531
- type: "text",
2532
- inputMode: "numeric",
2533
- autoComplete: "cc-csc",
2534
- placeholder: "123",
2535
- maxLength: 4,
2536
- value: cardCcv,
2537
- onChange: (e) => setCardCcv(e.target.value.replace(/\D/g, "")),
2538
- style: inputStyle
2539
- }
2540
- )
2541
- ] })
2542
- ] }),
2543
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8, marginTop: 8 }, children: "Dados do titular" }),
2544
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2545
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-name", style: labelStyle, children: "Nome completo" }),
2546
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2547
- "input",
2548
- {
2549
- id: "pw-holder-name",
2550
- "data-testid": "pw-holder-name",
2551
- type: "text",
2552
- autoComplete: "name",
2553
- placeholder: "Nome Sobrenome",
2554
- value: holderName,
2555
- onChange: (e) => setHolderName(e.target.value),
2556
- style: inputStyle
2557
- }
2558
- )
2559
- ] }),
2560
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2561
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-email", style: labelStyle, children: "E-mail" }),
2562
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2563
- "input",
2564
- {
2565
- id: "pw-holder-email",
2566
- "data-testid": "pw-holder-email",
2567
- type: "email",
2568
- autoComplete: "email",
2569
- placeholder: "voce@email.com",
2570
- value: holderEmail,
2571
- onChange: (e) => setHolderEmail(e.target.value),
2572
- style: inputStyle
2573
- }
2574
- )
2575
- ] }),
2576
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2577
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2578
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-cep", style: labelStyle, children: "CEP" }),
2579
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2580
- "input",
2581
- {
2582
- id: "pw-holder-cep",
2583
- "data-testid": "pw-holder-cep",
2584
- type: "text",
2585
- inputMode: "numeric",
2586
- autoComplete: "postal-code",
2587
- placeholder: "00000-000",
2588
- value: holderPostalCode,
2589
- onChange: (e) => setHolderPostalCode(e.target.value),
2590
- style: inputStyle
2591
- }
2592
- )
2593
- ] }),
2594
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2595
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-addr-n", style: labelStyle, children: "N\xFAmero" }),
2596
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2597
- "input",
2598
- {
2599
- id: "pw-holder-addr-n",
2600
- "data-testid": "pw-holder-addr-n",
2601
- type: "text",
2602
- inputMode: "numeric",
2603
- placeholder: "123",
2604
- value: holderAddressNumber,
2605
- onChange: (e) => setHolderAddressNumber(e.target.value),
2606
- style: inputStyle
2607
- }
2608
- )
2609
- ] })
2610
- ] }),
2611
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2612
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-phone", style: labelStyle, children: "Telefone (opcional)" }),
2613
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2614
- "input",
2615
- {
2616
- id: "pw-holder-phone",
2617
- "data-testid": "pw-holder-phone",
2618
- type: "tel",
2619
- inputMode: "tel",
2620
- autoComplete: "tel",
2621
- placeholder: "(11) 99999-9999",
2622
- value: holderPhone,
2623
- onChange: (e) => setHolderPhone(e.target.value),
2624
- style: inputStyle
2625
- }
2626
- )
2627
- ] })
2628
- ]
2629
- }
2630
- ),
2631
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2632
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-cpf", style: labelStyle, children: "Seu CPF" }),
2633
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2634
- "input",
2635
- {
2636
- id: "pw-cpf",
2637
- "data-testid": "paywall-cpf",
2638
- type: "text",
2639
- inputMode: "numeric",
2640
- placeholder: "000.000.000-00",
2641
- value: cpf,
2642
- onChange: (e) => setCpf(e.target.value),
2643
- style: inputStyle
2644
- }
2645
- )
2646
- ] }),
2647
- error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: error.message }),
2648
- method === "pix-auto" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2649
- "div",
2650
- {
2651
- "data-testid": "paywall-pix-callout",
2652
- style: {
2653
- background: "rgba(0,0,0,0.04)",
2654
- borderLeft: "3px solid var(--hook-color-primary)",
2655
- padding: "10px 12px",
2656
- marginBottom: 12,
2657
- borderRadius: 4,
2658
- fontSize: 12,
2659
- lineHeight: 1.45,
2660
- textAlign: "left"
2661
- },
2662
- children: [
2663
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: "Como funciona:" }),
2664
- " no app do seu banco vai aparecer uma cobran\xE7a de",
2665
- " ",
2666
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: "R$ 0,01" }),
2667
- " \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
2668
- trialDays > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2669
- " ",
2670
- "Depois, ",
2671
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("strong", { children: [
2672
- trialDays,
2673
- " dias gr\xE1tis"
2674
- ] }),
2675
- "; a cobran\xE7a de",
2676
- " ",
2677
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2678
- " ",
2679
- cycleLabel,
2680
- " s\xF3 come\xE7a depois."
2681
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2682
- " ",
2683
- "A cobran\xE7a de ",
2684
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2685
- " ",
2686
- cycleLabel,
2687
- " vem em seguida."
2688
- ] })
2689
- ]
2690
- }
2691
- ),
2692
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2693
- "button",
2694
- {
2695
- "data-testid": "paywall-cta",
2696
- type: "button",
2697
- onClick: submit,
2698
- disabled: !canCheckout,
2699
- style: {
2700
- width: "100%",
2701
- padding: 14,
2702
- background: "var(--hook-color-primary)",
2703
- color: "#fff",
2704
- border: "none",
2705
- borderRadius: 8,
2706
- opacity: canCheckout ? 1 : 0.5,
2707
- fontSize: 16,
2708
- fontWeight: 600,
2709
- cursor: canCheckout ? "pointer" : "not-allowed"
2710
- },
2711
- children: ctaLabel
2712
- }
2713
- ),
2714
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.55, marginTop: 16, fontSize: 12 }, children: footer }),
2715
- p.priceHint && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.6, marginTop: 8, fontSize: 12 }, children: p.priceHint }),
2716
- p.footerNote && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.5, marginTop: 8, fontSize: 12 }, children: p.footerNote }),
2717
- pixPending && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2718
- "div",
2719
- {
2720
- "data-testid": "paywall-pix-modal",
2721
- role: "dialog",
2722
- "aria-label": "Pagamento Pix pendente",
2723
- style: {
2724
- position: "fixed",
2725
- inset: 0,
2726
- background: "rgba(0,0,0,0.6)",
2727
- display: "flex",
2728
- alignItems: "center",
2729
- justifyContent: "center",
2730
- padding: 24,
2731
- zIndex: 1e3
2732
- },
2733
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: {
2734
- background: "#fff",
2735
- borderRadius: 12,
2736
- padding: 24,
2737
- maxWidth: 360,
2738
- width: "100%",
2739
- textAlign: "center"
2740
- }, children: [
2741
- pixPending.paid ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2742
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0 }, children: "Pagamento confirmado!" }),
2743
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.7 }, children: "Liberando acesso\u2026" })
2744
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2745
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0, fontSize: 18 }, children: "Pague com Pix Autom\xE1tico" }),
2746
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { fontSize: 13, opacity: 0.75 }, children: "Escaneie o QR Code no app do seu banco pra autorizar o d\xE9bito recorrente." }),
2747
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2748
- "p",
2749
- {
2750
- "data-testid": "pix-modal-explain",
2751
- style: { fontSize: 12, opacity: 0.65, marginTop: 8, lineHeight: 1.4 },
2752
- children: [
2753
- "Seu banco vai mostrar uma cobran\xE7a de ",
2754
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: "R$ 0,01" }),
2755
- " \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
2756
- trialDays > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2757
- " ",
2758
- "A cobran\xE7a de ",
2759
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2760
- " ",
2761
- cycleLabel,
2762
- " come\xE7a depois dos ",
2763
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("strong", { children: [
2764
- trialDays,
2765
- " dias gr\xE1tis"
2766
- ] }),
2767
- ", automaticamente."
2768
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2769
- " ",
2770
- "A cobran\xE7a de ",
2771
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2772
- " ",
2773
- cycleLabel,
2774
- " vem logo em seguida."
2775
- ] })
2776
- ]
2777
- }
2778
- ),
2779
- pixPending.qrCodeBase64 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2780
- "img",
2781
- {
2782
- "data-testid": "pix-qr-image",
2783
- alt: "QR Code Pix",
2784
- src: `data:image/png;base64,${pixPending.qrCodeBase64}`,
2785
- style: { width: "100%", maxWidth: 240, margin: "12px auto", display: "block" }
2786
- }
2787
- ),
2788
- pixPending.qrCodePayload && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2789
- "textarea",
2790
- {
2791
- "data-testid": "pix-qr-payload",
2792
- readOnly: true,
2793
- value: pixPending.qrCodePayload,
2794
- style: {
2795
- width: "100%",
2796
- minHeight: 72,
2797
- padding: 8,
2798
- fontSize: 11,
2799
- fontFamily: "monospace",
2800
- borderRadius: 6,
2801
- border: "1px solid #ccc",
2802
- resize: "none"
2803
- }
2804
- }
2805
- ),
2806
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { fontSize: 11, opacity: 0.55, marginTop: 12 }, children: "Aguardando confirma\xE7\xE3o do banco\u2026 Pode levar alguns segundos." })
2807
- ] }),
2808
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2809
- "button",
2810
- {
2811
- type: "button",
2812
- onClick: dismissPix,
2813
- style: {
2814
- marginTop: 16,
2815
- padding: "8px 16px",
2816
- border: "1px solid #ccc",
2817
- borderRadius: 6,
2818
- background: "white",
2819
- cursor: "pointer"
2820
- },
2821
- children: "Fechar"
2822
- }
2823
- )
2824
- ] })
2825
- }
2826
- )
2827
- ] });
2828
- }
2829
-
2830
- // src/AppRoot.tsx
2831
- var import_jsx_runtime25 = require("react/jsx-runtime");
1858
+ // src/internal/PaymentReturnHandler.tsx
1859
+ var import_react10 = require("react");
1860
+ var import_sdk3 = require("@hook-sdk/sdk");
1861
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2832
1862
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2833
1863
  function PaymentReturnHandler({ children }) {
2834
- const { subscription } = (0, import_sdk10.useHook)();
2835
- const subRef = (0, import_react15.useRef)(subscription);
1864
+ const { subscription } = (0, import_sdk3.useHook)();
1865
+ const subRef = (0, import_react10.useRef)(subscription);
2836
1866
  subRef.current = subscription;
2837
- const runIdRef = (0, import_react15.useRef)(0);
2838
- const [state, setState] = (0, import_react15.useState)("idle");
2839
- const runPoll = (0, import_react15.useCallback)(() => {
1867
+ const runIdRef = (0, import_react10.useRef)(0);
1868
+ const [state, setState] = (0, import_react10.useState)("idle");
1869
+ const runPoll = (0, import_react10.useCallback)(() => {
2840
1870
  const runId = ++runIdRef.current;
2841
1871
  setState("confirming");
2842
1872
  let attempts = 0;
@@ -2865,7 +1895,7 @@ function PaymentReturnHandler({ children }) {
2865
1895
  };
2866
1896
  void tick();
2867
1897
  }, []);
2868
- (0, import_react15.useEffect)(() => {
1898
+ (0, import_react10.useEffect)(() => {
2869
1899
  if (typeof window === "undefined") return;
2870
1900
  const url = new URL(window.location.href);
2871
1901
  if (url.searchParams.get("paymentReturn") !== "1") return;
@@ -2875,31 +1905,15 @@ function PaymentReturnHandler({ children }) {
2875
1905
  };
2876
1906
  }, [runPoll]);
2877
1907
  if (state === "confirming") {
2878
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2879
- "div",
2880
- {
2881
- role: "status",
2882
- "aria-live": "polite",
2883
- style: overlayStyle2,
2884
- children: "Confirmando pagamento\u2026"
2885
- }
2886
- );
1908
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
2887
1909
  }
2888
1910
  if (state === "waiting") {
2889
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
2890
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
2891
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2892
- "button",
2893
- {
2894
- type: "button",
2895
- onClick: runPoll,
2896
- style: buttonStyle,
2897
- children: "Atualizar"
2898
- }
2899
- )
1911
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
1912
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
2900
1914
  ] }) });
2901
1915
  }
2902
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_jsx_runtime25.Fragment, { children });
1916
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children });
2903
1917
  }
2904
1918
  var overlayStyle2 = {
2905
1919
  position: "fixed",
@@ -2923,129 +1937,534 @@ var buttonStyle = {
2923
1937
  fontWeight: 600,
2924
1938
  cursor: "pointer"
2925
1939
  };
2926
- function AppRoot({
2927
- config,
1940
+
1941
+ // src/AppRoot.tsx
1942
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1943
+ function buildLegacyConfigShim(config) {
1944
+ const paywall = config.paywall;
1945
+ const isFree = paywall.mode === "free";
1946
+ const monthlyCents = isFree ? 0 : paywall.prices.monthlyCents;
1947
+ const trialDays = isFree ? 0 : paywall.trialDays ?? 0;
1948
+ return {
1949
+ slug: config.slug,
1950
+ name: config.name,
1951
+ email_alias: config.slug,
1952
+ theme: { primary_color: config.branding.primaryColor },
1953
+ features_enabled: config.features_enabled ?? [],
1954
+ dependencies_allowlist: ["react", "react-dom"],
1955
+ subscription: {
1956
+ mode: paywall.mode,
1957
+ price_cents: monthlyCents,
1958
+ currency: "brl",
1959
+ trial_days: trialDays,
1960
+ paywall_config: {
1961
+ title: config.name,
1962
+ benefits: ["Acesso completo"],
1963
+ cta: "Assinar"
1964
+ }
1965
+ },
1966
+ sdk_version_required: ">=0.16.0",
1967
+ max_bundle_size_kb: 500
1968
+ };
1969
+ }
1970
+ function AppRoot(props) {
1971
+ const {
1972
+ config: rawConfig,
1973
+ children,
1974
+ testRouter,
1975
+ testInitialEntries,
1976
+ Login,
1977
+ Signup,
1978
+ Forgot,
1979
+ Reset,
1980
+ EmailVerify,
1981
+ Paywall,
1982
+ Onboarding,
1983
+ PreAuthFlow
1984
+ } = props;
1985
+ if (!Login || !Signup || !Forgot || !Reset) {
1986
+ throw new Error(
1987
+ "[hook-template] <AppRoot>: Login, Signup, Forgot, Reset slot props are required."
1988
+ );
1989
+ }
1990
+ const config = parseAppConfig(rawConfig);
1991
+ if (config.paywall.mode !== "free" && !Paywall) {
1992
+ throw new Error(
1993
+ "[hook-template] <AppRoot>: Paywall slot prop is required when config.paywall.mode != 'free'."
1994
+ );
1995
+ }
1996
+ if (config.authFlow.requiresEmailVerify && !EmailVerify) {
1997
+ throw new Error(
1998
+ "[hook-template] <AppRoot>: EmailVerify slot prop is required when config.authFlow.requiresEmailVerify === true."
1999
+ );
2000
+ }
2001
+ if (config.onboarding?.trigger === "pre_signup_custom" && !PreAuthFlow) {
2002
+ throw new Error(
2003
+ "[hook-template] <AppRoot>: PreAuthFlow slot prop is required when config.onboarding.trigger === 'pre_signup_custom'."
2004
+ );
2005
+ }
2006
+ const legacyShim = (0, import_react11.useMemo)(() => buildLegacyConfigShim(config), [config]);
2007
+ const Router = testRouter === "memory" ? import_react_router_dom2.MemoryRouter : import_react_router_dom2.BrowserRouter;
2008
+ const basename = `/app/${config.slug}`;
2009
+ const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
2010
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(AppConfigProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PersistenceRegistry, { config: config.persistedKeys, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Router, { ...routerProps, children: [
2011
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DeepLinkHandler, { deepLinks: config.deepLinks }),
2012
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(InstallGate, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2013
+ AuthGated,
2014
+ {
2015
+ config,
2016
+ Login,
2017
+ Signup,
2018
+ Forgot,
2019
+ Reset,
2020
+ EmailVerify,
2021
+ Paywall,
2022
+ Onboarding,
2023
+ PreAuthFlow,
2024
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(SubscriptionGate, { Paywall: Paywall ?? FallbackPaywall, children: [
2025
+ children,
2026
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PushPrompt, {})
2027
+ ] })
2028
+ }
2029
+ ) })
2030
+ ] }) }) }) }) }) });
2031
+ }
2032
+ function AuthGated({
2928
2033
  children,
2929
- Login = DefaultLoginScreen,
2930
- Signup = DefaultSignupScreen,
2931
- Forgot = DefaultForgotScreen,
2932
- Reset = DefaultResetScreen,
2933
- Paywall = DefaultPaywall
2034
+ config,
2035
+ Login,
2036
+ Signup,
2037
+ Forgot,
2038
+ Reset,
2039
+ EmailVerify,
2040
+ PreAuthFlow
2934
2041
  }) {
2935
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PaymentReturnHandler, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TemplateConfigProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(InstallGate, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(AuthGate, { Login, Signup, Forgot, Reset, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PersistedKeysPrefetch, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(SubscriptionGate, { Paywall, children: [
2936
- children,
2937
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PushPrompt, {})
2938
- ] }) }) }) }) }) }) }) });
2042
+ const { authStatus } = (0, import_sdk4.useHook)();
2043
+ if (authStatus === "loading") return null;
2044
+ if (authStatus !== "authenticated") {
2045
+ if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
2046
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_router_dom2.Routes, { children: [
2047
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/signin", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Login, {}) }),
2048
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Signup, {}) }),
2049
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Forgot, {}) }),
2050
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Reset, {}) }),
2051
+ EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(EmailVerify, {}) }) : null,
2052
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/*", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PreAuthFlow, {}) })
2053
+ ] });
2054
+ }
2055
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_react_router_dom2.Routes, { children: [
2056
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Login, {}) }),
2057
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Signup, {}) }),
2058
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Forgot, {}) }),
2059
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Reset, {}) }),
2060
+ EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(EmailVerify, {}) }) : null,
2061
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react_router_dom2.Navigate, { to: "/", replace: true }) })
2062
+ ] });
2063
+ }
2064
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children });
2065
+ }
2066
+ function FallbackPaywall() {
2067
+ return null;
2068
+ }
2069
+
2070
+ // src/hooks/usePush.ts
2071
+ var import_react12 = require("react");
2072
+ var import_sdk5 = require("@hook-sdk/sdk");
2073
+ function detectIosNeedsInstall() {
2074
+ if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2075
+ const ua = navigator.userAgent || "";
2076
+ const isIos = /iPhone|iPad|iPod/.test(ua);
2077
+ if (!isIos) return false;
2078
+ const mm = window.matchMedia?.("(display-mode: standalone)");
2079
+ const standalone = mm?.matches === true;
2080
+ const legacyStandalone = typeof navigator.standalone === "boolean" ? navigator.standalone : false;
2081
+ return !(standalone || legacyStandalone);
2082
+ }
2083
+ function deriveState(push) {
2084
+ if (!push.isAvailable()) {
2085
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2086
+ return { kind: "unsupported" };
2087
+ }
2088
+ const status = push.status();
2089
+ if (status === "granted") return { kind: "subscribed" };
2090
+ if (status === "denied") return { kind: "denied" };
2091
+ if (status === "unsupported") {
2092
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2093
+ return { kind: "unsupported" };
2094
+ }
2095
+ return { kind: "prompt" };
2096
+ }
2097
+ function usePush() {
2098
+ const { push } = (0, import_sdk5.useHook)();
2099
+ const [state, setState] = (0, import_react12.useState)(() => deriveState(push));
2100
+ (0, import_react12.useEffect)(() => {
2101
+ setState(deriveState(push));
2102
+ }, [push]);
2103
+ const subscribe = (0, import_react12.useCallback)(async () => {
2104
+ try {
2105
+ await push.subscribe();
2106
+ setState({ kind: "subscribed" });
2107
+ } catch (e) {
2108
+ const code = e?.code ?? "push.unknown";
2109
+ const message = e?.message ?? "Push subscription failed";
2110
+ if (code === "push.permission_denied") setState({ kind: "denied" });
2111
+ else setState({ kind: "error", code, message });
2112
+ throw e;
2113
+ }
2114
+ }, [push]);
2115
+ const unsubscribe = (0, import_react12.useCallback)(async () => {
2116
+ try {
2117
+ await push.unsubscribe();
2118
+ setState({ kind: "prompt" });
2119
+ } catch (e) {
2120
+ setState({ kind: "error", code: e?.code ?? "push.unknown", message: e?.message ?? "failed" });
2121
+ throw e;
2122
+ }
2123
+ }, [push]);
2124
+ return { state, subscribe, unsubscribe };
2125
+ }
2126
+
2127
+ // src/components/PushPrompt.tsx
2128
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2129
+ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
2130
+ const { state, subscribe } = usePush();
2131
+ if (state.kind === "subscribed") return null;
2132
+ if (state.kind === "ios_needs_install") {
2133
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2134
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { children: texts.iosInstallTitle }),
2135
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { children: texts.iosInstallBody }),
2136
+ onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2137
+ ] });
2138
+ }
2139
+ if (state.kind === "denied") {
2140
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
2141
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { children: texts.deniedTitle }),
2142
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { children: texts.deniedBody })
2143
+ ] });
2144
+ }
2145
+ if (state.kind === "unsupported") {
2146
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className, role: "region", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { children: texts.unsupportedBody }) });
2147
+ }
2148
+ if (state.kind === "error") {
2149
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { children: state.message }) });
2150
+ }
2151
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className, role: "region", children: [
2152
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2153
+ "button",
2154
+ {
2155
+ type: "button",
2156
+ onClick: async () => {
2157
+ try {
2158
+ await subscribe();
2159
+ onSubscribed?.();
2160
+ } catch {
2161
+ }
2162
+ },
2163
+ children: texts.cta
2164
+ }
2165
+ ),
2166
+ onDeclined && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2167
+ ] });
2168
+ }
2169
+
2170
+ // src/defaults/LoadingState.tsx
2171
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2172
+ function LoadingState({ message }) {
2173
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: message ?? "Carregando..." }) });
2174
+ }
2175
+
2176
+ // src/defaults/EmptyState.tsx
2177
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2178
+ function EmptyState({ title, description, action }) {
2179
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2180
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
2181
+ description && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { style: { opacity: 0.7 }, children: description }),
2182
+ action && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { marginTop: 16 }, children: action })
2183
+ ] });
2184
+ }
2185
+
2186
+ // src/hooks/useLoginForm.ts
2187
+ var import_react13 = require("react");
2188
+ var import_sdk7 = require("@hook-sdk/sdk");
2189
+
2190
+ // src/errors.ts
2191
+ var import_sdk6 = require("@hook-sdk/sdk");
2192
+ function mapSdkError(err) {
2193
+ if (err instanceof import_sdk6.SdkRateLimitError) {
2194
+ return {
2195
+ code: "rate_limited",
2196
+ message: `Aguarde ${err.retryAfter}s e tente novamente.`,
2197
+ retryAfter: err.retryAfter
2198
+ };
2199
+ }
2200
+ if (err instanceof import_sdk6.SdkAuthError) {
2201
+ const detail = err.detail;
2202
+ if (detail === "email_unverified") {
2203
+ return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
2204
+ }
2205
+ if (detail === "account_locked") {
2206
+ return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
2207
+ }
2208
+ return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
2209
+ }
2210
+ if (err instanceof import_sdk6.SdkError && err.httpStatus === 0) {
2211
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2212
+ }
2213
+ if (err instanceof TypeError) {
2214
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
2215
+ }
2216
+ return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
2217
+ }
2218
+
2219
+ // src/hooks/useLoginForm.ts
2220
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2221
+ var MIN_PASSWORD = 8;
2222
+ function useLoginForm() {
2223
+ const { auth } = (0, import_sdk7.useHook)();
2224
+ const [email, setEmail] = (0, import_react13.useState)("");
2225
+ const [password, setPassword] = (0, import_react13.useState)("");
2226
+ const [submitting, setSubmitting] = (0, import_react13.useState)(false);
2227
+ const [error, setError] = (0, import_react13.useState)(null);
2228
+ const emailError = (0, import_react13.useMemo)(() => {
2229
+ if (email.length === 0) return null;
2230
+ if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
2231
+ return null;
2232
+ }, [email]);
2233
+ const passwordError = (0, import_react13.useMemo)(() => {
2234
+ if (password.length === 0) return null;
2235
+ if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
2236
+ return null;
2237
+ }, [password]);
2238
+ const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
2239
+ const submit = (0, import_react13.useCallback)(async () => {
2240
+ if (!canSubmit) return false;
2241
+ setSubmitting(true);
2242
+ setError(null);
2243
+ try {
2244
+ await auth.login({ email, password });
2245
+ return true;
2246
+ } catch (err) {
2247
+ setError(mapSdkError(err));
2248
+ return false;
2249
+ } finally {
2250
+ setSubmitting(false);
2251
+ }
2252
+ }, [auth, email, password, canSubmit]);
2253
+ return {
2254
+ email,
2255
+ setEmail,
2256
+ emailError,
2257
+ password,
2258
+ setPassword,
2259
+ passwordError,
2260
+ submit,
2261
+ submitting,
2262
+ canSubmit,
2263
+ error,
2264
+ loginWithGoogle: () => auth.loginWithGoogle()
2265
+ };
2266
+ }
2267
+
2268
+ // src/hooks/useSignupForm.ts
2269
+ var import_react14 = require("react");
2270
+ var import_sdk8 = require("@hook-sdk/sdk");
2271
+ var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2272
+ var MIN_PASSWORD2 = 8;
2273
+ function useSignupForm() {
2274
+ const { auth } = (0, import_sdk8.useHook)();
2275
+ const [name, setName] = (0, import_react14.useState)("");
2276
+ const [email, setEmail] = (0, import_react14.useState)("");
2277
+ const [password, setPassword] = (0, import_react14.useState)("");
2278
+ const [submitting, setSubmitting] = (0, import_react14.useState)(false);
2279
+ const [error, setError] = (0, import_react14.useState)(null);
2280
+ const nameError = (0, import_react14.useMemo)(() => {
2281
+ if (name.length === 0) return null;
2282
+ if (name.trim().length < 2) return "Nome muito curto.";
2283
+ return null;
2284
+ }, [name]);
2285
+ const emailError = (0, import_react14.useMemo)(() => {
2286
+ if (email.length === 0) return null;
2287
+ if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
2288
+ return null;
2289
+ }, [email]);
2290
+ const passwordError = (0, import_react14.useMemo)(() => {
2291
+ if (password.length === 0) return null;
2292
+ if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
2293
+ return null;
2294
+ }, [password]);
2295
+ const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
2296
+ const submit = (0, import_react14.useCallback)(async () => {
2297
+ if (!canSubmit) return false;
2298
+ setSubmitting(true);
2299
+ setError(null);
2300
+ try {
2301
+ await auth.signup({ name, email, password });
2302
+ return true;
2303
+ } catch (err) {
2304
+ setError(mapSdkError(err));
2305
+ return false;
2306
+ } finally {
2307
+ setSubmitting(false);
2308
+ }
2309
+ }, [auth, name, email, password, canSubmit]);
2310
+ return {
2311
+ name,
2312
+ setName,
2313
+ nameError,
2314
+ email,
2315
+ setEmail,
2316
+ emailError,
2317
+ password,
2318
+ setPassword,
2319
+ passwordError,
2320
+ submit,
2321
+ submitting,
2322
+ canSubmit,
2323
+ error,
2324
+ loginWithGoogle: () => auth.loginWithGoogle()
2325
+ };
2939
2326
  }
2940
2327
 
2941
- // src/hooks/usePush.ts
2942
- var import_react16 = require("react");
2943
- var import_sdk11 = require("@hook-sdk/sdk");
2944
- function detectIosNeedsInstall() {
2945
- if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2946
- const ua = navigator.userAgent || "";
2947
- const isIos = /iPhone|iPad|iPod/.test(ua);
2948
- if (!isIos) return false;
2949
- const mm = window.matchMedia?.("(display-mode: standalone)");
2950
- const standalone = mm?.matches === true;
2951
- const legacyStandalone = typeof navigator.standalone === "boolean" ? navigator.standalone : false;
2952
- return !(standalone || legacyStandalone);
2953
- }
2954
- function deriveState(push) {
2955
- if (!push.isAvailable()) {
2956
- if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2957
- return { kind: "unsupported" };
2958
- }
2959
- const status = push.status();
2960
- if (status === "granted") return { kind: "subscribed" };
2961
- if (status === "denied") return { kind: "denied" };
2962
- if (status === "unsupported") {
2963
- if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
2964
- return { kind: "unsupported" };
2965
- }
2966
- return { kind: "prompt" };
2967
- }
2968
- function usePush() {
2969
- const { push } = (0, import_sdk11.useHook)();
2970
- const [state, setState] = (0, import_react16.useState)(() => deriveState(push));
2971
- (0, import_react16.useEffect)(() => {
2972
- setState(deriveState(push));
2973
- }, [push]);
2974
- const subscribe = (0, import_react16.useCallback)(async () => {
2328
+ // src/hooks/useForgotForm.ts
2329
+ var import_react15 = require("react");
2330
+ var import_sdk9 = require("@hook-sdk/sdk");
2331
+ var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2332
+ function useForgotForm() {
2333
+ const { auth } = (0, import_sdk9.useHook)();
2334
+ const [email, setEmail] = (0, import_react15.useState)("");
2335
+ const [submitting, setSubmitting] = (0, import_react15.useState)(false);
2336
+ const [sent, setSent] = (0, import_react15.useState)(false);
2337
+ const [error, setError] = (0, import_react15.useState)(null);
2338
+ const emailError = (0, import_react15.useMemo)(() => {
2339
+ if (email.length === 0) return null;
2340
+ if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
2341
+ return null;
2342
+ }, [email]);
2343
+ const canSubmit = email.length > 0 && emailError === null && !submitting;
2344
+ const submit = (0, import_react15.useCallback)(async () => {
2345
+ if (!canSubmit) return false;
2346
+ setSubmitting(true);
2347
+ setError(null);
2975
2348
  try {
2976
- await push.subscribe();
2977
- setState({ kind: "subscribed" });
2978
- } catch (e) {
2979
- const code = e?.code ?? "push.unknown";
2980
- const message = e?.message ?? "Push subscription failed";
2981
- if (code === "push.permission_denied") setState({ kind: "denied" });
2982
- else setState({ kind: "error", code, message });
2983
- throw e;
2349
+ await auth.forgot({ email });
2350
+ setSent(true);
2351
+ return true;
2352
+ } catch (err) {
2353
+ setError(mapSdkError(err));
2354
+ return false;
2355
+ } finally {
2356
+ setSubmitting(false);
2984
2357
  }
2985
- }, [push]);
2986
- const unsubscribe = (0, import_react16.useCallback)(async () => {
2358
+ }, [auth, email, canSubmit]);
2359
+ return {
2360
+ email,
2361
+ setEmail,
2362
+ emailError,
2363
+ submit,
2364
+ submitting,
2365
+ canSubmit,
2366
+ sent,
2367
+ error
2368
+ };
2369
+ }
2370
+
2371
+ // src/hooks/useResetForm.ts
2372
+ var import_react16 = require("react");
2373
+ var import_sdk10 = require("@hook-sdk/sdk");
2374
+ var MIN_PASSWORD3 = 12;
2375
+ function useResetForm() {
2376
+ const { auth } = (0, import_sdk10.useHook)();
2377
+ const [token, setToken] = (0, import_react16.useState)(null);
2378
+ const [password, setPassword] = (0, import_react16.useState)("");
2379
+ const [confirm, setConfirm] = (0, import_react16.useState)("");
2380
+ const [submitting, setSubmitting] = (0, import_react16.useState)(false);
2381
+ const [done, setDone] = (0, import_react16.useState)(false);
2382
+ const [error, setError] = (0, import_react16.useState)(null);
2383
+ (0, import_react16.useEffect)(() => {
2384
+ if (typeof window === "undefined") return;
2385
+ const params = new URLSearchParams(window.location.search);
2386
+ const t = params.get("token");
2387
+ setToken(t && t.length > 0 ? t : null);
2388
+ }, []);
2389
+ const passwordError = (0, import_react16.useMemo)(() => {
2390
+ if (password.length === 0) return null;
2391
+ if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2392
+ return null;
2393
+ }, [password]);
2394
+ const confirmError = (0, import_react16.useMemo)(() => {
2395
+ if (confirm.length === 0) return null;
2396
+ if (confirm !== password) return "Senhas n\xE3o coincidem.";
2397
+ return null;
2398
+ }, [confirm, password]);
2399
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
2400
+ const submit = (0, import_react16.useCallback)(async () => {
2401
+ if (!canSubmit || token === null) return;
2402
+ setSubmitting(true);
2403
+ setError(null);
2987
2404
  try {
2988
- await push.unsubscribe();
2989
- setState({ kind: "prompt" });
2990
- } catch (e) {
2991
- setState({ kind: "error", code: e?.code ?? "push.unknown", message: e?.message ?? "failed" });
2992
- throw e;
2405
+ await auth.reset({ token, newPassword: password });
2406
+ setDone(true);
2407
+ if (typeof window !== "undefined") {
2408
+ const url = new URL(window.location.href);
2409
+ url.searchParams.delete("token");
2410
+ url.searchParams.delete("screen");
2411
+ window.history.replaceState({}, "", url.toString());
2412
+ }
2413
+ } catch (err) {
2414
+ setError(mapSdkError(err));
2415
+ } finally {
2416
+ setSubmitting(false);
2993
2417
  }
2994
- }, [push]);
2995
- return { state, subscribe, unsubscribe };
2418
+ }, [auth, token, password, canSubmit]);
2419
+ return {
2420
+ token,
2421
+ password,
2422
+ setPassword,
2423
+ passwordError,
2424
+ confirm,
2425
+ setConfirm,
2426
+ confirmError,
2427
+ submit,
2428
+ submitting,
2429
+ canSubmit,
2430
+ done,
2431
+ error
2432
+ };
2996
2433
  }
2997
2434
 
2998
- // src/components/PushPrompt.tsx
2999
- var import_jsx_runtime26 = require("react/jsx-runtime");
3000
- function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
3001
- const { state, subscribe } = usePush();
3002
- if (state.kind === "subscribed") return null;
3003
- if (state.kind === "ios_needs_install") {
3004
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
3005
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { children: texts.iosInstallTitle }),
3006
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { children: texts.iosInstallBody }),
3007
- onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
3008
- ] });
3009
- }
3010
- if (state.kind === "denied") {
3011
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
3012
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("h3", { children: texts.deniedTitle }),
3013
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { children: texts.deniedBody })
3014
- ] });
3015
- }
3016
- if (state.kind === "unsupported") {
3017
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className, role: "region", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { children: texts.unsupportedBody }) });
3018
- }
3019
- if (state.kind === "error") {
3020
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { children: state.message }) });
3021
- }
3022
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className, role: "region", children: [
3023
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
3024
- "button",
3025
- {
3026
- type: "button",
3027
- onClick: async () => {
3028
- try {
3029
- await subscribe();
3030
- onSubscribed?.();
3031
- } catch {
3032
- }
3033
- },
3034
- children: texts.cta
3035
- }
3036
- ),
3037
- onDeclined && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
3038
- ] });
2435
+ // src/hooks/usePlan.ts
2436
+ var import_sdk11 = require("@hook-sdk/sdk");
2437
+ function usePlan() {
2438
+ const { plan } = (0, import_sdk11.useHook)();
2439
+ return plan;
3039
2440
  }
3040
2441
 
3041
- // src/defaults/EmptyState.tsx
3042
- var import_jsx_runtime27 = require("react/jsx-runtime");
3043
- function EmptyState({ title, description, action }) {
3044
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
3045
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
3046
- description && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { style: { opacity: 0.7 }, children: description }),
3047
- action && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { style: { marginTop: 16 }, children: action })
3048
- ] });
2442
+ // src/utils/price.ts
2443
+ function formatBRL(cents) {
2444
+ if (cents === null || cents === void 0) return "";
2445
+ const reais = cents / 100;
2446
+ return new Intl.NumberFormat("pt-BR", {
2447
+ style: "currency",
2448
+ currency: "BRL"
2449
+ }).format(reais);
2450
+ }
2451
+ function monthlyFromYearly(yearlyCents) {
2452
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2453
+ return Math.round(yearlyCents / 12);
2454
+ }
2455
+ function dailyFromYearly(yearlyCents) {
2456
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2457
+ return Math.round(yearlyCents / 365);
2458
+ }
2459
+ function computeAnchorCents(baseCents, multiplier) {
2460
+ if (multiplier === null || multiplier === void 0) return null;
2461
+ if (!Number.isFinite(multiplier)) return null;
2462
+ if (multiplier <= 1) return null;
2463
+ return Math.round(baseCents * multiplier);
2464
+ }
2465
+ function discountPercent(anchorCents, realCents) {
2466
+ if (anchorCents <= realCents) return 0;
2467
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
3049
2468
  }
3050
2469
 
3051
2470
  // src/hooks/useAuthPrimitives.ts
@@ -3075,10 +2494,21 @@ function useAuthPrimitives() {
3075
2494
  };
3076
2495
  }
3077
2496
 
3078
- // src/hooks/useSubscription.ts
2497
+ // src/hooks/useAuth.ts
3079
2498
  var import_sdk13 = require("@hook-sdk/sdk");
2499
+ function useAuth() {
2500
+ const { user, authStatus, auth } = (0, import_sdk13.useHook)();
2501
+ return {
2502
+ user,
2503
+ authStatus,
2504
+ refresh: auth.refresh
2505
+ };
2506
+ }
2507
+
2508
+ // src/hooks/useSubscription.ts
2509
+ var import_sdk14 = require("@hook-sdk/sdk");
3080
2510
  function useSubscription() {
3081
- const { subscription } = (0, import_sdk13.useHook)();
2511
+ const { subscription } = (0, import_sdk14.useHook)();
3082
2512
  return {
3083
2513
  status: subscription.status()
3084
2514
  };
@@ -3086,9 +2516,9 @@ function useSubscription() {
3086
2516
 
3087
2517
  // src/hooks/useReminders.ts
3088
2518
  var import_react18 = require("react");
3089
- var import_sdk14 = require("@hook-sdk/sdk");
2519
+ var import_sdk15 = require("@hook-sdk/sdk");
3090
2520
  function useReminders() {
3091
- const { push } = (0, import_sdk14.useHook)();
2521
+ const { push } = (0, import_sdk15.useHook)();
3092
2522
  const r = push.reminders;
3093
2523
  const [reminders, setReminders] = (0, import_react18.useState)([]);
3094
2524
  const [loading, setLoading] = (0, import_react18.useState)(true);
@@ -3137,20 +2567,157 @@ function useToast() {
3137
2567
  }, []);
3138
2568
  return { items, show, dismiss };
3139
2569
  }
2570
+
2571
+ // src/RouteBoundary.tsx
2572
+ var import_react_router_dom3 = require("react-router-dom");
2573
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2574
+ function RouteBoundary({ children }) {
2575
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_router_dom3.Routes, { children: [
2576
+ children,
2577
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_router_dom3.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(DefaultNotFound, {}) })
2578
+ ] });
2579
+ }
2580
+ function DefaultNotFound() {
2581
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
2582
+ }
2583
+
2584
+ // src/PreAuthShell.tsx
2585
+ var import_react_router_dom4 = require("react-router-dom");
2586
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2587
+ function PreAuthShell({
2588
+ basename,
2589
+ testRouter,
2590
+ testInitialEntries,
2591
+ children
2592
+ }) {
2593
+ if (testRouter === "memory") {
2594
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom4.MemoryRouter, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom4.Routes, { children }) });
2595
+ }
2596
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom4.BrowserRouter, { basename, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom4.Routes, { children }) });
2597
+ }
2598
+
2599
+ // src/OnboardingFlow.tsx
2600
+ var import_react21 = require("react");
2601
+ var import_sdk16 = require("@hook-sdk/sdk");
2602
+
2603
+ // src/hooks/useOnboardingStep.ts
2604
+ var import_react20 = require("react");
2605
+ var OnboardingStepContext = (0, import_react20.createContext)(null);
2606
+ function useOnboardingStep() {
2607
+ const ctx = (0, import_react20.useContext)(OnboardingStepContext);
2608
+ if (!ctx) {
2609
+ throw new Error(
2610
+ "[hook-template] useOnboardingStep must be used inside <OnboardingFlow>. (G75)"
2611
+ );
2612
+ }
2613
+ return ctx;
2614
+ }
2615
+
2616
+ // src/OnboardingFlow.tsx
2617
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2618
+ var isFilled = (v) => v != null && v !== "";
2619
+ var CURRENT_STEP_FIELD = "currentStep";
2620
+ function readPersistedStepIdx(draft) {
2621
+ const raw = draft[CURRENT_STEP_FIELD];
2622
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 0;
2623
+ }
2624
+ function OnboardingFlow({
2625
+ steps,
2626
+ screens,
2627
+ onComplete,
2628
+ persistKey
2629
+ }) {
2630
+ const [draft, setDraft, status] = (0, import_sdk16.usePersistedState)(persistKey, {});
2631
+ const draftRef = (0, import_react21.useRef)(draft);
2632
+ draftRef.current = draft;
2633
+ const idx = readPersistedStepIdx(draft);
2634
+ const clampedIdx = Math.min(Math.max(idx, 0), Math.max(steps.length - 1, 0));
2635
+ const setIdx = (0, import_react21.useCallback)(
2636
+ (n) => {
2637
+ setDraft((prev) => {
2638
+ const prevIdx = readPersistedStepIdx(prev);
2639
+ const nextIdx = typeof n === "function" ? n(prevIdx) : n;
2640
+ return { ...prev, [CURRENT_STEP_FIELD]: nextIdx };
2641
+ });
2642
+ },
2643
+ [setDraft]
2644
+ );
2645
+ const setValue = (0, import_react21.useCallback)(
2646
+ (patch) => {
2647
+ draftRef.current = { ...draftRef.current, ...patch };
2648
+ setDraft((prev) => ({ ...prev, ...patch }));
2649
+ },
2650
+ [setDraft]
2651
+ );
2652
+ const step = steps[clampedIdx];
2653
+ const valid = (0, import_react21.useMemo)(
2654
+ () => step ? (step.validates ?? []).every((field) => isFilled(draft[field])) : false,
2655
+ [draft, step]
2656
+ );
2657
+ const next = (0, import_react21.useCallback)(() => {
2658
+ if (!step) return;
2659
+ const current = draftRef.current;
2660
+ const validNow = (step.validates ?? []).every((field) => isFilled(current[field]));
2661
+ if (!validNow) return;
2662
+ if (clampedIdx + 1 >= steps.length) {
2663
+ onComplete(current);
2664
+ } else {
2665
+ setIdx(clampedIdx + 1);
2666
+ }
2667
+ }, [clampedIdx, onComplete, step, steps.length, setIdx]);
2668
+ const prevStep = (0, import_react21.useCallback)(() => setIdx((i) => Math.max(0, i - 1)), [setIdx]);
2669
+ const ctx = (0, import_react21.useMemo)(
2670
+ () => ({
2671
+ stepIndex: clampedIdx,
2672
+ totalSteps: steps.length,
2673
+ value: draft,
2674
+ setValue,
2675
+ valid,
2676
+ next,
2677
+ prev: prevStep
2678
+ }),
2679
+ [clampedIdx, steps.length, draft, setValue, valid, next, prevStep]
2680
+ );
2681
+ if (status.loading) {
2682
+ return null;
2683
+ }
2684
+ if (!step) {
2685
+ throw new Error(
2686
+ `[hook-template] OnboardingFlow: step index ${clampedIdx} out of range (steps.length=${steps.length})`
2687
+ );
2688
+ }
2689
+ const Screen = screens[step.screen];
2690
+ if (!Screen) {
2691
+ throw new Error(
2692
+ `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
2693
+ );
2694
+ }
2695
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Screen, {}) });
2696
+ }
2697
+
2698
+ // src/hooks/useFeature.ts
2699
+ function useFeature(name) {
2700
+ const config = useAppConfig();
2701
+ return Array.isArray(config.features_enabled) && config.features_enabled.includes(name);
2702
+ }
3140
2703
  // Annotate the CommonJS export names for ESM import in node:
3141
2704
  0 && (module.exports = {
2705
+ AppConfigProvider,
2706
+ AppConfigSchema,
3142
2707
  AppRoot,
3143
- DefaultForgotScreen,
3144
- DefaultLoginScreen,
3145
- DefaultPaywall,
3146
- DefaultResetScreen,
3147
- DefaultSignupScreen,
2708
+ DeepLinkHandler,
3148
2709
  EmptyState,
3149
2710
  ErrorBoundary,
3150
2711
  InstallGate,
3151
2712
  InstallSplash,
3152
2713
  LoadingState,
2714
+ OnboardingFlow,
2715
+ PaymentReturnHandler,
2716
+ PersistenceRegistry,
2717
+ PreAuthShell,
3153
2718
  PushPrompt,
2719
+ RouteBoundary,
2720
+ asaasErrorMessage,
3154
2721
  computeAnchorCents,
3155
2722
  dailyFromYearly,
3156
2723
  detectAndroidBrowser,
@@ -3161,13 +2728,17 @@ function useToast() {
3161
2728
  discountPercent,
3162
2729
  formatBRL,
3163
2730
  monthlyFromYearly,
2731
+ parseAppConfig,
3164
2732
  shouldBlockInstall,
3165
2733
  shouldShowPermanentOption,
2734
+ useAppConfig,
3166
2735
  useAuth,
3167
2736
  useAuthPrimitives,
2737
+ useFeature,
3168
2738
  useForgotForm,
3169
2739
  useInstallPrompt,
3170
2740
  useLoginForm,
2741
+ useOnboardingStep,
3171
2742
  usePaywallState,
3172
2743
  usePlan,
3173
2744
  usePush,