@hook-sdk/template 0.1.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 ADDED
@@ -0,0 +1,790 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AppRoot: () => AppRoot,
24
+ DefaultForgotScreen: () => DefaultForgotScreen,
25
+ DefaultLoginScreen: () => DefaultLoginScreen,
26
+ DefaultPaywall: () => DefaultPaywall,
27
+ DefaultResetScreen: () => DefaultResetScreen,
28
+ DefaultSignupScreen: () => DefaultSignupScreen,
29
+ EmptyState: () => EmptyState,
30
+ ErrorBoundary: () => ErrorBoundary,
31
+ LoadingState: () => LoadingState,
32
+ useAuth: () => useAuth,
33
+ useAuthPrimitives: () => useAuthPrimitives,
34
+ useForgotForm: () => useForgotForm,
35
+ useLoginForm: () => useLoginForm,
36
+ usePaywallState: () => usePaywallState,
37
+ usePush: () => usePush,
38
+ useResetForm: () => useResetForm,
39
+ useSignupForm: () => useSignupForm,
40
+ useSubscription: () => useSubscription,
41
+ useToast: () => useToast
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/internal/TemplateConfigContext.tsx
46
+ var import_react = require("react");
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ var TemplateConfigContext = (0, import_react.createContext)(null);
49
+ function TemplateConfigProvider({
50
+ config,
51
+ children
52
+ }) {
53
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TemplateConfigContext.Provider, { value: config, children });
54
+ }
55
+ function useTemplateConfig() {
56
+ const ctx = (0, import_react.useContext)(TemplateConfigContext);
57
+ if (ctx === null) {
58
+ throw new Error("useTemplateConfig must be used inside <TemplateConfigProvider>");
59
+ }
60
+ return ctx;
61
+ }
62
+
63
+ // src/internal/ThemeProvider.tsx
64
+ var import_jsx_runtime2 = require("react/jsx-runtime");
65
+ function ThemeProvider({ children }) {
66
+ const config = useTemplateConfig();
67
+ const style = {
68
+ "--hook-color-primary": config.theme.primary_color,
69
+ ...config.theme.background_color && {
70
+ "--hook-color-background": config.theme.background_color
71
+ }
72
+ };
73
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style, children });
74
+ }
75
+
76
+ // src/internal/AuthGate.tsx
77
+ var import_react2 = require("react");
78
+
79
+ // src/hooks/useAuth.ts
80
+ var import_sdk = require("@hook-sdk/sdk");
81
+ function useAuth() {
82
+ const { user, authStatus, auth } = (0, import_sdk.useHook)();
83
+ return {
84
+ user,
85
+ authStatus,
86
+ refresh: auth.refresh
87
+ };
88
+ }
89
+
90
+ // src/defaults/LoadingState.tsx
91
+ var import_jsx_runtime3 = require("react/jsx-runtime");
92
+ function LoadingState({ message }) {
93
+ 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..." }) });
94
+ }
95
+
96
+ // src/internal/AuthGate.tsx
97
+ var import_jsx_runtime4 = require("react/jsx-runtime");
98
+ function detectResetFromUrl() {
99
+ if (typeof window === "undefined") return false;
100
+ const params = new URLSearchParams(window.location.search);
101
+ return params.get("token") !== null && params.get("screen") === "reset";
102
+ }
103
+ function AuthGate({ Login, Signup, Forgot, Reset, children }) {
104
+ const { user, authStatus } = useAuth();
105
+ const [screen, setScreen] = (0, import_react2.useState)(
106
+ () => detectResetFromUrl() ? "reset" : "login"
107
+ );
108
+ (0, import_react2.useEffect)(() => {
109
+ if (user && screen !== "login") {
110
+ setScreen("login");
111
+ }
112
+ }, [user, screen]);
113
+ (0, import_react2.useEffect)(() => {
114
+ const onPop = () => {
115
+ if (detectResetFromUrl()) setScreen("reset");
116
+ };
117
+ window.addEventListener("popstate", onPop);
118
+ return () => window.removeEventListener("popstate", onPop);
119
+ }, []);
120
+ if (authStatus === "loading") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LoadingState, {});
121
+ if (!user) {
122
+ if (screen === "reset") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Reset, { onNavigate: setScreen });
123
+ if (screen === "signup") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Signup, { onNavigate: setScreen });
124
+ if (screen === "forgot") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Forgot, { onNavigate: setScreen });
125
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Login, { onNavigate: setScreen });
126
+ }
127
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
128
+ }
129
+
130
+ // src/hooks/usePaywallState.ts
131
+ var import_react3 = require("react");
132
+ var import_sdk2 = require("@hook-sdk/sdk");
133
+ function usePaywallState() {
134
+ const { subscription } = (0, import_sdk2.useHook)();
135
+ const [opening, setOpening] = (0, import_react3.useState)(false);
136
+ const [error, setError] = (0, import_react3.useState)(null);
137
+ const status = subscription.status();
138
+ const daysLeftInTrial = subscription.daysLeftInTrial();
139
+ const checkout = (0, import_react3.useCallback)(
140
+ async (args) => {
141
+ setOpening(true);
142
+ setError(null);
143
+ try {
144
+ const result = await subscription.checkoutCard({
145
+ cpf: args.cpf,
146
+ cycle: args.cycle ?? "MONTHLY"
147
+ });
148
+ window.location.href = result.invoiceUrl;
149
+ } catch (err) {
150
+ setError(err);
151
+ setOpening(false);
152
+ }
153
+ },
154
+ [subscription]
155
+ );
156
+ const cancel = (0, import_react3.useCallback)(async () => {
157
+ try {
158
+ await subscription.cancel();
159
+ await subscription.refresh();
160
+ } catch (err) {
161
+ setError(err);
162
+ }
163
+ }, [subscription]);
164
+ return { status, daysLeftInTrial, checkout, cancel, opening, error };
165
+ }
166
+
167
+ // src/internal/SubscriptionGate.tsx
168
+ var import_jsx_runtime5 = require("react/jsx-runtime");
169
+ function SubscriptionGate({ Paywall, children }) {
170
+ const { status } = usePaywallState();
171
+ if (status === "expired" || status === "canceled" || status === "past_due") {
172
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Paywall, {});
173
+ }
174
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
175
+ }
176
+
177
+ // src/internal/PushPrompt.tsx
178
+ function PushPrompt() {
179
+ return null;
180
+ }
181
+
182
+ // src/defaults/ErrorBoundary.tsx
183
+ var import_react4 = require("react");
184
+ var import_jsx_runtime6 = require("react/jsx-runtime");
185
+ var ErrorBoundary = class extends import_react4.Component {
186
+ state = { error: null };
187
+ static getDerivedStateFromError(error) {
188
+ return { error };
189
+ }
190
+ componentDidCatch(error) {
191
+ console.error("[ErrorBoundary] caught", error);
192
+ }
193
+ render() {
194
+ if (this.state.error) {
195
+ return this.props.fallback ?? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
196
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { children: "Algo deu errado" }),
197
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
198
+ ] });
199
+ }
200
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: this.props.children });
201
+ }
202
+ };
203
+
204
+ // src/hooks/useLoginForm.ts
205
+ var import_react5 = require("react");
206
+ var import_sdk4 = require("@hook-sdk/sdk");
207
+
208
+ // src/errors.ts
209
+ var import_sdk3 = require("@hook-sdk/sdk");
210
+ function mapSdkError(err) {
211
+ if (err instanceof import_sdk3.SdkRateLimitError) {
212
+ return {
213
+ code: "rate_limited",
214
+ message: `Aguarde ${err.retryAfter}s e tente novamente.`,
215
+ retryAfter: err.retryAfter
216
+ };
217
+ }
218
+ if (err instanceof import_sdk3.SdkAuthError) {
219
+ const detail = err.detail;
220
+ if (detail === "email_unverified") {
221
+ return { code: "email_unverified", message: "Confirme seu e-mail antes de entrar." };
222
+ }
223
+ if (detail === "account_locked") {
224
+ return { code: "account_locked", message: "Conta bloqueada. Contate o suporte." };
225
+ }
226
+ return { code: "invalid_credentials", message: "E-mail ou senha inv\xE1lidos." };
227
+ }
228
+ if (err instanceof import_sdk3.SdkError && err.httpStatus === 0) {
229
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
230
+ }
231
+ if (err instanceof TypeError) {
232
+ return { code: "network", message: "Sem conex\xE3o com o servidor. Verifique sua internet." };
233
+ }
234
+ return { code: "server", message: "Algo deu errado. Tente novamente em instantes." };
235
+ }
236
+
237
+ // src/hooks/useLoginForm.ts
238
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
239
+ var MIN_PASSWORD = 8;
240
+ function useLoginForm() {
241
+ const { auth } = (0, import_sdk4.useHook)();
242
+ const [email, setEmail] = (0, import_react5.useState)("");
243
+ const [password, setPassword] = (0, import_react5.useState)("");
244
+ const [submitting, setSubmitting] = (0, import_react5.useState)(false);
245
+ const [error, setError] = (0, import_react5.useState)(null);
246
+ const emailError = (0, import_react5.useMemo)(() => {
247
+ if (email.length === 0) return null;
248
+ if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
249
+ return null;
250
+ }, [email]);
251
+ const passwordError = (0, import_react5.useMemo)(() => {
252
+ if (password.length === 0) return null;
253
+ if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
254
+ return null;
255
+ }, [password]);
256
+ const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
257
+ const submit = (0, import_react5.useCallback)(async () => {
258
+ if (!canSubmit) return;
259
+ setSubmitting(true);
260
+ setError(null);
261
+ try {
262
+ await auth.login({ email, password });
263
+ } catch (err) {
264
+ setError(mapSdkError(err));
265
+ } finally {
266
+ setSubmitting(false);
267
+ }
268
+ }, [auth, email, password, canSubmit]);
269
+ return {
270
+ email,
271
+ setEmail,
272
+ emailError,
273
+ password,
274
+ setPassword,
275
+ passwordError,
276
+ submit,
277
+ submitting,
278
+ canSubmit,
279
+ error
280
+ };
281
+ }
282
+
283
+ // src/defaults/DefaultLoginScreen.tsx
284
+ var import_jsx_runtime7 = require("react/jsx-runtime");
285
+ function DefaultLoginScreen({ onNavigate }) {
286
+ const { name } = useTemplateConfig();
287
+ const f = useLoginForm();
288
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
289
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Entre na sua conta" }),
291
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { onSubmit: (e) => {
292
+ e.preventDefault();
293
+ void f.submit();
294
+ }, children: [
295
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
296
+ "E-mail",
297
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
298
+ "input",
299
+ {
300
+ "data-testid": "login-email",
301
+ type: "email",
302
+ value: f.email,
303
+ onChange: (e) => f.setEmail(e.target.value),
304
+ style: { display: "block", width: "100%" }
305
+ }
306
+ ),
307
+ f.emailError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("small", { style: { color: "#c00" }, children: f.emailError })
308
+ ] }),
309
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
310
+ "Senha",
311
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
312
+ "input",
313
+ {
314
+ "data-testid": "login-password",
315
+ type: "password",
316
+ value: f.password,
317
+ onChange: (e) => f.setPassword(e.target.value),
318
+ style: { display: "block", width: "100%" }
319
+ }
320
+ ),
321
+ f.passwordError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("small", { style: { color: "#c00" }, children: f.passwordError })
322
+ ] }),
323
+ f.error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
324
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
325
+ "button",
326
+ {
327
+ "data-testid": "login-submit",
328
+ type: "submit",
329
+ disabled: !f.canSubmit,
330
+ style: {
331
+ width: "100%",
332
+ padding: 12,
333
+ background: "var(--hook-color-primary)",
334
+ color: "#fff",
335
+ border: "none",
336
+ borderRadius: 8,
337
+ opacity: f.canSubmit ? 1 : 0.5
338
+ },
339
+ children: f.submitting ? "Entrando..." : "Entrar"
340
+ }
341
+ )
342
+ ] }),
343
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { marginTop: 16, display: "flex", justifyContent: "space-between" }, children: [
344
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { "data-testid": "login-goto-signup", type: "button", onClick: () => onNavigate("signup"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Criar conta" }),
345
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { "data-testid": "login-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Esqueci senha" })
346
+ ] })
347
+ ] });
348
+ }
349
+
350
+ // src/hooks/useSignupForm.ts
351
+ var import_react6 = require("react");
352
+ var import_sdk5 = require("@hook-sdk/sdk");
353
+ var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
354
+ var MIN_PASSWORD2 = 8;
355
+ function useSignupForm() {
356
+ const { auth } = (0, import_sdk5.useHook)();
357
+ const [name, setName] = (0, import_react6.useState)("");
358
+ const [email, setEmail] = (0, import_react6.useState)("");
359
+ const [password, setPassword] = (0, import_react6.useState)("");
360
+ const [submitting, setSubmitting] = (0, import_react6.useState)(false);
361
+ const [error, setError] = (0, import_react6.useState)(null);
362
+ const nameError = (0, import_react6.useMemo)(() => {
363
+ if (name.length === 0) return null;
364
+ if (name.trim().length < 2) return "Nome muito curto.";
365
+ return null;
366
+ }, [name]);
367
+ const emailError = (0, import_react6.useMemo)(() => {
368
+ if (email.length === 0) return null;
369
+ if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
370
+ return null;
371
+ }, [email]);
372
+ const passwordError = (0, import_react6.useMemo)(() => {
373
+ if (password.length === 0) return null;
374
+ if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
375
+ return null;
376
+ }, [password]);
377
+ const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
378
+ const submit = (0, import_react6.useCallback)(async () => {
379
+ if (!canSubmit) return;
380
+ setSubmitting(true);
381
+ setError(null);
382
+ try {
383
+ await auth.signup({ name, email, password });
384
+ } catch (err) {
385
+ setError(mapSdkError(err));
386
+ } finally {
387
+ setSubmitting(false);
388
+ }
389
+ }, [auth, name, email, password, canSubmit]);
390
+ return {
391
+ name,
392
+ setName,
393
+ nameError,
394
+ email,
395
+ setEmail,
396
+ emailError,
397
+ password,
398
+ setPassword,
399
+ passwordError,
400
+ submit,
401
+ submitting,
402
+ canSubmit,
403
+ error
404
+ };
405
+ }
406
+
407
+ // src/defaults/DefaultSignupScreen.tsx
408
+ var import_jsx_runtime8 = require("react/jsx-runtime");
409
+ function DefaultSignupScreen({ onNavigate }) {
410
+ const { name } = useTemplateConfig();
411
+ const f = useSignupForm();
412
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
413
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Criar sua conta" }),
415
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("form", { onSubmit: (e) => {
416
+ e.preventDefault();
417
+ void f.submit();
418
+ }, children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
420
+ "Nome",
421
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { "data-testid": "signup-name", value: f.name, onChange: (e) => f.setName(e.target.value), style: { display: "block", width: "100%" } }),
422
+ f.nameError && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("small", { style: { color: "#c00" }, children: f.nameError })
423
+ ] }),
424
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
425
+ "E-mail",
426
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { "data-testid": "signup-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
427
+ f.emailError && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("small", { style: { color: "#c00" }, children: f.emailError })
428
+ ] }),
429
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
430
+ "Senha",
431
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { "data-testid": "signup-password", type: "password", value: f.password, onChange: (e) => f.setPassword(e.target.value), style: { display: "block", width: "100%" } }),
432
+ f.passwordError && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("small", { style: { color: "#c00" }, children: f.passwordError })
433
+ ] }),
434
+ f.error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
435
+ /* @__PURE__ */ (0, import_jsx_runtime8.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" })
436
+ ] }),
437
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime8.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" }) })
438
+ ] });
439
+ }
440
+
441
+ // src/hooks/useForgotForm.ts
442
+ var import_react7 = require("react");
443
+ var import_sdk6 = require("@hook-sdk/sdk");
444
+ var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
445
+ function useForgotForm() {
446
+ const { auth } = (0, import_sdk6.useHook)();
447
+ const [email, setEmail] = (0, import_react7.useState)("");
448
+ const [submitting, setSubmitting] = (0, import_react7.useState)(false);
449
+ const [sent, setSent] = (0, import_react7.useState)(false);
450
+ const [error, setError] = (0, import_react7.useState)(null);
451
+ const emailError = (0, import_react7.useMemo)(() => {
452
+ if (email.length === 0) return null;
453
+ if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
454
+ return null;
455
+ }, [email]);
456
+ const canSubmit = email.length > 0 && emailError === null && !submitting;
457
+ const submit = (0, import_react7.useCallback)(async () => {
458
+ if (!canSubmit) return;
459
+ setSubmitting(true);
460
+ setError(null);
461
+ try {
462
+ await auth.forgot({ email });
463
+ setSent(true);
464
+ } catch (err) {
465
+ setError(mapSdkError(err));
466
+ } finally {
467
+ setSubmitting(false);
468
+ }
469
+ }, [auth, email, canSubmit]);
470
+ return {
471
+ email,
472
+ setEmail,
473
+ emailError,
474
+ submit,
475
+ submitting,
476
+ canSubmit,
477
+ sent,
478
+ error
479
+ };
480
+ }
481
+
482
+ // src/defaults/DefaultForgotScreen.tsx
483
+ var import_jsx_runtime9 = require("react/jsx-runtime");
484
+ function DefaultForgotScreen({ onNavigate }) {
485
+ const { name } = useTemplateConfig();
486
+ const f = useForgotForm();
487
+ if (f.sent) {
488
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
489
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h1", { children: "Verifique seu e-mail" }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { opacity: 0.7 }, children: "Enviamos um link pra redefinir sua senha." }),
491
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { "data-testid": "forgot-back-login", type: "button", onClick: () => onNavigate("login"), children: "Voltar pro login" })
492
+ ] });
493
+ }
494
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
495
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Redefinir senha" }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("form", { onSubmit: (e) => {
498
+ e.preventDefault();
499
+ void f.submit();
500
+ }, children: [
501
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
502
+ "E-mail",
503
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("input", { "data-testid": "forgot-email", type: "email", value: f.email, onChange: (e) => f.setEmail(e.target.value), style: { display: "block", width: "100%" } }),
504
+ f.emailError && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("small", { style: { color: "#c00" }, children: f.emailError })
505
+ ] }),
506
+ f.error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
507
+ /* @__PURE__ */ (0, import_jsx_runtime9.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" })
508
+ ] }),
509
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { marginTop: 16 }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { "data-testid": "forgot-goto-login", type: "button", onClick: () => onNavigate("login"), style: { background: "none", border: "none", cursor: "pointer" }, children: "Voltar pro login" }) })
510
+ ] });
511
+ }
512
+
513
+ // src/hooks/useResetForm.ts
514
+ var import_react8 = require("react");
515
+ var import_sdk7 = require("@hook-sdk/sdk");
516
+ var MIN_PASSWORD3 = 12;
517
+ function useResetForm() {
518
+ const { auth } = (0, import_sdk7.useHook)();
519
+ const [token, setToken] = (0, import_react8.useState)(null);
520
+ const [password, setPassword] = (0, import_react8.useState)("");
521
+ const [confirm, setConfirm] = (0, import_react8.useState)("");
522
+ const [submitting, setSubmitting] = (0, import_react8.useState)(false);
523
+ const [done, setDone] = (0, import_react8.useState)(false);
524
+ const [error, setError] = (0, import_react8.useState)(null);
525
+ (0, import_react8.useEffect)(() => {
526
+ if (typeof window === "undefined") return;
527
+ const params = new URLSearchParams(window.location.search);
528
+ const t = params.get("token");
529
+ setToken(t && t.length > 0 ? t : null);
530
+ }, []);
531
+ const passwordError = (0, import_react8.useMemo)(() => {
532
+ if (password.length === 0) return null;
533
+ if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
534
+ return null;
535
+ }, [password]);
536
+ const confirmError = (0, import_react8.useMemo)(() => {
537
+ if (confirm.length === 0) return null;
538
+ if (confirm !== password) return "Senhas n\xE3o coincidem.";
539
+ return null;
540
+ }, [confirm, password]);
541
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
542
+ const submit = (0, import_react8.useCallback)(async () => {
543
+ if (!canSubmit || token === null) return;
544
+ setSubmitting(true);
545
+ setError(null);
546
+ try {
547
+ await auth.reset({ token, newPassword: password });
548
+ setDone(true);
549
+ if (typeof window !== "undefined") {
550
+ const url = new URL(window.location.href);
551
+ url.searchParams.delete("token");
552
+ url.searchParams.delete("screen");
553
+ window.history.replaceState({}, "", url.toString());
554
+ }
555
+ } catch (err) {
556
+ setError(mapSdkError(err));
557
+ } finally {
558
+ setSubmitting(false);
559
+ }
560
+ }, [auth, token, password, canSubmit]);
561
+ return {
562
+ token,
563
+ password,
564
+ setPassword,
565
+ passwordError,
566
+ confirm,
567
+ setConfirm,
568
+ confirmError,
569
+ submit,
570
+ submitting,
571
+ canSubmit,
572
+ done,
573
+ error
574
+ };
575
+ }
576
+
577
+ // src/defaults/DefaultResetScreen.tsx
578
+ var import_jsx_runtime10 = require("react/jsx-runtime");
579
+ function DefaultResetScreen({ onNavigate }) {
580
+ const { name } = useTemplateConfig();
581
+ const f = useResetForm();
582
+ if (f.done) {
583
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
584
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { children: "Senha alterada" }),
585
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { opacity: 0.7 }, children: "Agora \xE9 s\xF3 fazer login com a nova senha." }),
586
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "data-testid": "reset-back-login", type: "button", onClick: () => onNavigate("login"), children: "Ir pro login" })
587
+ ] });
588
+ }
589
+ if (f.token === null) {
590
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto", textAlign: "center" }, children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { children: "Link inv\xE1lido" }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { opacity: 0.7 }, children: "Pe\xE7a um novo link de reset." }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "data-testid": "reset-goto-forgot", type: "button", onClick: () => onNavigate("forgot"), children: "Pedir novo link" })
594
+ ] });
595
+ }
596
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("main", { style: { padding: 24, maxWidth: 360, margin: "0 auto" }, children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { style: { marginBottom: 8 }, children: name }),
598
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: "Escolha uma nova senha" }),
599
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: (e) => {
600
+ e.preventDefault();
601
+ void f.submit();
602
+ }, children: [
603
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
604
+ "Nova senha",
605
+ /* @__PURE__ */ (0, import_jsx_runtime10.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" }),
606
+ f.passwordError && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { style: { color: "#c00" }, children: f.passwordError })
607
+ ] }),
608
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { style: { display: "block", marginBottom: 12 }, children: [
609
+ "Confirmar senha",
610
+ /* @__PURE__ */ (0, import_jsx_runtime10.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" }),
611
+ f.confirmError && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { style: { color: "#c00" }, children: f.confirmError })
612
+ ] }),
613
+ f.error && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: f.error.message }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime10.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" })
615
+ ] })
616
+ ] });
617
+ }
618
+
619
+ // src/defaults/DefaultPaywall.tsx
620
+ var import_react9 = require("react");
621
+ var import_jsx_runtime11 = require("react/jsx-runtime");
622
+ function DefaultPaywall() {
623
+ const config = useTemplateConfig();
624
+ const { checkout, opening, error } = usePaywallState();
625
+ const p = config.subscription.paywall_config;
626
+ const [cpf, setCpf] = (0, import_react9.useState)("");
627
+ const cpfDigits = cpf.replace(/\D/g, "");
628
+ const canCheckout = cpfDigits.length === 11 && !opening;
629
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("main", { style: { padding: 24, maxWidth: 440, margin: "0 auto", textAlign: "center" }, children: [
630
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { style: { marginBottom: 8 }, children: p.title }),
631
+ p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: p.subtitle }),
632
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", marginBottom: 24 }, children: p.benefits.map((b) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
633
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
634
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: b })
635
+ ] }, b)) }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { textAlign: "left", marginBottom: 16 }, children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { style: { display: "block", fontSize: 14, opacity: 0.7, marginBottom: 4 }, children: "Seu CPF (pra emiss\xE3o de recibo)" }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
639
+ "input",
640
+ {
641
+ "data-testid": "paywall-cpf",
642
+ type: "text",
643
+ inputMode: "numeric",
644
+ placeholder: "000.000.000-00",
645
+ value: cpf,
646
+ onChange: (e) => setCpf(e.target.value),
647
+ style: { width: "100%", padding: 10, fontSize: 14, borderRadius: 8, border: "1px solid #ccc" }
648
+ }
649
+ )
650
+ ] }),
651
+ error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: error.message }),
652
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
653
+ "button",
654
+ {
655
+ "data-testid": "paywall-cta",
656
+ type: "button",
657
+ onClick: () => void checkout({ cpf: cpfDigits }),
658
+ disabled: !canCheckout,
659
+ style: {
660
+ width: "100%",
661
+ padding: 14,
662
+ background: "var(--hook-color-primary)",
663
+ color: "#fff",
664
+ border: "none",
665
+ borderRadius: 8,
666
+ opacity: canCheckout ? 1 : 0.5,
667
+ fontSize: 16,
668
+ fontWeight: 600
669
+ },
670
+ children: opening ? "Abrindo..." : p.cta
671
+ }
672
+ ),
673
+ p.priceHint && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { opacity: 0.6, marginTop: 12 }, children: p.priceHint }),
674
+ p.footerNote && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { opacity: 0.5, marginTop: 16, fontSize: 12 }, children: p.footerNote })
675
+ ] });
676
+ }
677
+
678
+ // src/AppRoot.tsx
679
+ var import_jsx_runtime12 = require("react/jsx-runtime");
680
+ function AppRoot({
681
+ config,
682
+ children,
683
+ Login = DefaultLoginScreen,
684
+ Signup = DefaultSignupScreen,
685
+ Forgot = DefaultForgotScreen,
686
+ Reset = DefaultResetScreen,
687
+ Paywall = DefaultPaywall
688
+ }) {
689
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TemplateConfigProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AuthGate, { Login, Signup, Forgot, Reset, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(SubscriptionGate, { Paywall, children: [
690
+ children,
691
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PushPrompt, {})
692
+ ] }) }) }) }) });
693
+ }
694
+
695
+ // src/defaults/EmptyState.tsx
696
+ var import_jsx_runtime13 = require("react/jsx-runtime");
697
+ function EmptyState({ title, description, action }) {
698
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
699
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
700
+ description && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: { opacity: 0.7 }, children: description }),
701
+ action && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { marginTop: 16 }, children: action })
702
+ ] });
703
+ }
704
+
705
+ // src/hooks/useAuthPrimitives.ts
706
+ var import_react10 = require("react");
707
+ var import_sdk8 = require("@hook-sdk/sdk");
708
+ var warned = false;
709
+ function useAuthPrimitives() {
710
+ const { auth } = (0, import_sdk8.useHook)();
711
+ (0, import_react10.useEffect)(() => {
712
+ if (!warned && process.env.NODE_ENV !== "production") {
713
+ warned = true;
714
+ console.warn(
715
+ "[@hook-sdk/template] useAuthPrimitives() \xE9 escape hatch. Pra login/signup/forgot, use useLoginForm/useSignupForm/useForgotForm. Docs: docs/19-golden-template.md#escape-hatch"
716
+ );
717
+ }
718
+ }, []);
719
+ return {
720
+ login: auth.login,
721
+ signup: auth.signup,
722
+ logout: auth.logout,
723
+ logoutAll: auth.logoutAll,
724
+ forgot: auth.forgot,
725
+ resendVerify: auth.resendVerify,
726
+ changePassword: auth.changePassword,
727
+ changeEmail: auth.changeEmail,
728
+ refresh: auth.refresh
729
+ };
730
+ }
731
+
732
+ // src/hooks/useSubscription.ts
733
+ var import_sdk9 = require("@hook-sdk/sdk");
734
+ function useSubscription() {
735
+ const { subscription } = (0, import_sdk9.useHook)();
736
+ return {
737
+ status: subscription.status()
738
+ };
739
+ }
740
+
741
+ // src/hooks/usePush.ts
742
+ var import_sdk10 = require("@hook-sdk/sdk");
743
+ function usePush() {
744
+ const { push } = (0, import_sdk10.useHook)();
745
+ return {
746
+ status: push.status(),
747
+ subscribe: push.subscribe,
748
+ unsubscribe: push.unsubscribe
749
+ };
750
+ }
751
+
752
+ // src/hooks/useToast.ts
753
+ var import_react11 = require("react");
754
+ function useToast() {
755
+ const [items, setItems] = (0, import_react11.useState)([]);
756
+ const show = (0, import_react11.useCallback)((message, kind = "info") => {
757
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
758
+ setItems((prev) => [...prev, { id, message, kind }]);
759
+ setTimeout(() => {
760
+ setItems((prev) => prev.filter((t) => t.id !== id));
761
+ }, 4e3);
762
+ }, []);
763
+ const dismiss = (0, import_react11.useCallback)((id) => {
764
+ setItems((prev) => prev.filter((t) => t.id !== id));
765
+ }, []);
766
+ return { items, show, dismiss };
767
+ }
768
+ // Annotate the CommonJS export names for ESM import in node:
769
+ 0 && (module.exports = {
770
+ AppRoot,
771
+ DefaultForgotScreen,
772
+ DefaultLoginScreen,
773
+ DefaultPaywall,
774
+ DefaultResetScreen,
775
+ DefaultSignupScreen,
776
+ EmptyState,
777
+ ErrorBoundary,
778
+ LoadingState,
779
+ useAuth,
780
+ useAuthPrimitives,
781
+ useForgotForm,
782
+ useLoginForm,
783
+ usePaywallState,
784
+ usePush,
785
+ useResetForm,
786
+ useSignupForm,
787
+ useSubscription,
788
+ useToast
789
+ });
790
+ //# sourceMappingURL=index.cjs.map