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