@hook-sdk/template 0.1.1 → 0.1.2

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.d.cts CHANGED
@@ -28,6 +28,11 @@ declare const AppConfigSchema: z.ZodObject<{
28
28
  price_cents: z.ZodNumber;
29
29
  currency: z.ZodLiteral<"brl">;
30
30
  trial_days: z.ZodNumber;
31
+ mode: z.ZodDefault<z.ZodEnum<{
32
+ trial: "trial";
33
+ pay_first: "pay_first";
34
+ free: "free";
35
+ }>>;
31
36
  paywall_config: z.ZodObject<{
32
37
  title: z.ZodString;
33
38
  subtitle: z.ZodOptional<z.ZodString>;
@@ -38,6 +43,7 @@ declare const AppConfigSchema: z.ZodObject<{
38
43
  }, z.core.$strict>;
39
44
  }, z.core.$strict>;
40
45
  sdk_version_required: z.ZodString;
46
+ template_version_required: z.ZodOptional<z.ZodString>;
41
47
  max_bundle_size_kb: z.ZodNumber;
42
48
  }, z.core.$strict>;
43
49
  type AppConfig = z.infer<typeof AppConfigSchema>;
@@ -118,7 +124,12 @@ interface UseLoginFormResult {
118
124
  password: string;
119
125
  setPassword: (v: string) => void;
120
126
  passwordError: string | null;
121
- submit: () => Promise<void>;
127
+ /**
128
+ * Submete o form. Retorna true se o login deu OK (cookies setados), false
129
+ * se validação falhou, credenciais inválidas, rate-limit ou erro de rede.
130
+ * Em caso de false, `error` state é populado com detalhes.
131
+ */
132
+ submit: () => Promise<boolean>;
122
133
  submitting: boolean;
123
134
  canSubmit: boolean;
124
135
  error: AuthFormError | null;
@@ -135,7 +146,12 @@ interface UseSignupFormResult {
135
146
  password: string;
136
147
  setPassword: (v: string) => void;
137
148
  passwordError: string | null;
138
- submit: () => Promise<void>;
149
+ /**
150
+ * Submete o form. Retorna true se o signup deu OK (backend respondeu 2xx),
151
+ * false se validação falhou, houve erro de rede/servidor, ou email já em uso.
152
+ * Em caso de false, `error` state é populado com detalhes.
153
+ */
154
+ submit: () => Promise<boolean>;
139
155
  submitting: boolean;
140
156
  canSubmit: boolean;
141
157
  error: AuthFormError | null;
@@ -146,7 +162,13 @@ interface UseForgotFormResult {
146
162
  email: string;
147
163
  setEmail: (v: string) => void;
148
164
  emailError: string | null;
149
- submit: () => Promise<void>;
165
+ /**
166
+ * Submete o form. Retorna true se o backend aceitou a requisição (email
167
+ * de reset foi enfileirado — sem leak de "existe esse email" por design),
168
+ * false se validação falhou, rate-limit ou erro de rede.
169
+ * Em caso de sucesso, `sent` state também é setado para true.
170
+ */
171
+ submit: () => Promise<boolean>;
150
172
  submitting: boolean;
151
173
  canSubmit: boolean;
152
174
  sent: boolean;
@@ -182,7 +204,7 @@ interface UseResetFormResult {
182
204
  */
183
205
  declare function useResetForm(): UseResetFormResult;
184
206
 
185
- type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'none';
207
+ type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
186
208
  /**
187
209
  * Hook headless pro Paywall. Expõe status atual da subscription + ação
188
210
  * `checkout(cpf)` que chama `POST /payments/checkout/card` e redireciona
package/dist/index.d.ts CHANGED
@@ -28,6 +28,11 @@ declare const AppConfigSchema: z.ZodObject<{
28
28
  price_cents: z.ZodNumber;
29
29
  currency: z.ZodLiteral<"brl">;
30
30
  trial_days: z.ZodNumber;
31
+ mode: z.ZodDefault<z.ZodEnum<{
32
+ trial: "trial";
33
+ pay_first: "pay_first";
34
+ free: "free";
35
+ }>>;
31
36
  paywall_config: z.ZodObject<{
32
37
  title: z.ZodString;
33
38
  subtitle: z.ZodOptional<z.ZodString>;
@@ -38,6 +43,7 @@ declare const AppConfigSchema: z.ZodObject<{
38
43
  }, z.core.$strict>;
39
44
  }, z.core.$strict>;
40
45
  sdk_version_required: z.ZodString;
46
+ template_version_required: z.ZodOptional<z.ZodString>;
41
47
  max_bundle_size_kb: z.ZodNumber;
42
48
  }, z.core.$strict>;
43
49
  type AppConfig = z.infer<typeof AppConfigSchema>;
@@ -118,7 +124,12 @@ interface UseLoginFormResult {
118
124
  password: string;
119
125
  setPassword: (v: string) => void;
120
126
  passwordError: string | null;
121
- submit: () => Promise<void>;
127
+ /**
128
+ * Submete o form. Retorna true se o login deu OK (cookies setados), false
129
+ * se validação falhou, credenciais inválidas, rate-limit ou erro de rede.
130
+ * Em caso de false, `error` state é populado com detalhes.
131
+ */
132
+ submit: () => Promise<boolean>;
122
133
  submitting: boolean;
123
134
  canSubmit: boolean;
124
135
  error: AuthFormError | null;
@@ -135,7 +146,12 @@ interface UseSignupFormResult {
135
146
  password: string;
136
147
  setPassword: (v: string) => void;
137
148
  passwordError: string | null;
138
- submit: () => Promise<void>;
149
+ /**
150
+ * Submete o form. Retorna true se o signup deu OK (backend respondeu 2xx),
151
+ * false se validação falhou, houve erro de rede/servidor, ou email já em uso.
152
+ * Em caso de false, `error` state é populado com detalhes.
153
+ */
154
+ submit: () => Promise<boolean>;
139
155
  submitting: boolean;
140
156
  canSubmit: boolean;
141
157
  error: AuthFormError | null;
@@ -146,7 +162,13 @@ interface UseForgotFormResult {
146
162
  email: string;
147
163
  setEmail: (v: string) => void;
148
164
  emailError: string | null;
149
- submit: () => Promise<void>;
165
+ /**
166
+ * Submete o form. Retorna true se o backend aceitou a requisição (email
167
+ * de reset foi enfileirado — sem leak de "existe esse email" por design),
168
+ * false se validação falhou, rate-limit ou erro de rede.
169
+ * Em caso de sucesso, `sent` state também é setado para true.
170
+ */
171
+ submit: () => Promise<boolean>;
150
172
  submitting: boolean;
151
173
  canSubmit: boolean;
152
174
  sent: boolean;
@@ -182,7 +204,7 @@ interface UseResetFormResult {
182
204
  */
183
205
  declare function useResetForm(): UseResetFormResult;
184
206
 
185
- type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'none';
207
+ type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
186
208
  /**
187
209
  * Hook headless pro Paywall. Expõe status atual da subscription + ação
188
210
  * `checkout(cpf)` que chama `POST /payments/checkout/card` e redireciona
package/dist/index.js CHANGED
@@ -1,12 +1,20 @@
1
+ // src/AppRoot.tsx
2
+ import { useEffect as useEffect3, useState as useState8 } from "react";
3
+ import { useHook as useHook7 } from "@hook-sdk/sdk";
4
+
1
5
  // src/internal/TemplateConfigContext.tsx
2
- import { createContext, useContext } from "react";
6
+ import { createContext, useContext, useMemo } from "react";
3
7
  import { jsx } from "react/jsx-runtime";
4
8
  var TemplateConfigContext = createContext(null);
5
9
  function TemplateConfigProvider({
6
10
  config,
7
11
  children
8
12
  }) {
9
- return /* @__PURE__ */ jsx(TemplateConfigContext.Provider, { value: config, children });
13
+ const value = useMemo(() => ({
14
+ ...config,
15
+ mode: config.subscription?.mode ?? "trial"
16
+ }), [config]);
17
+ return /* @__PURE__ */ jsx(TemplateConfigContext.Provider, { value, children });
10
18
  }
11
19
  function useTemplateConfig() {
12
20
  const ctx = useContext(TemplateConfigContext);
@@ -122,11 +130,17 @@ function usePaywallState() {
122
130
 
123
131
  // src/internal/SubscriptionGate.tsx
124
132
  import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
133
+ var BLOCKING = /* @__PURE__ */ new Set([
134
+ "pending",
135
+ "expired",
136
+ "canceled",
137
+ "none"
138
+ ]);
125
139
  function SubscriptionGate({ Paywall, children }) {
140
+ const { mode } = useTemplateConfig();
126
141
  const { status } = usePaywallState();
127
- if (status === "expired" || status === "canceled" || status === "past_due") {
128
- return /* @__PURE__ */ jsx5(Paywall, {});
129
- }
142
+ if (mode === "free") return /* @__PURE__ */ jsx5(Fragment2, { children });
143
+ if (BLOCKING.has(status)) return /* @__PURE__ */ jsx5(Paywall, {});
130
144
  return /* @__PURE__ */ jsx5(Fragment2, { children });
131
145
  }
132
146
 
@@ -158,7 +172,7 @@ var ErrorBoundary = class extends Component {
158
172
  };
159
173
 
160
174
  // src/hooks/useLoginForm.ts
161
- import { useCallback as useCallback2, useMemo, useState as useState3 } from "react";
175
+ import { useCallback as useCallback2, useMemo as useMemo2, useState as useState3 } from "react";
162
176
  import { useHook as useHook3 } from "@hook-sdk/sdk";
163
177
 
164
178
  // src/errors.ts
@@ -199,25 +213,27 @@ function useLoginForm() {
199
213
  const [password, setPassword] = useState3("");
200
214
  const [submitting, setSubmitting] = useState3(false);
201
215
  const [error, setError] = useState3(null);
202
- const emailError = useMemo(() => {
216
+ const emailError = useMemo2(() => {
203
217
  if (email.length === 0) return null;
204
218
  if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
205
219
  return null;
206
220
  }, [email]);
207
- const passwordError = useMemo(() => {
221
+ const passwordError = useMemo2(() => {
208
222
  if (password.length === 0) return null;
209
223
  if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
210
224
  return null;
211
225
  }, [password]);
212
226
  const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
213
227
  const submit = useCallback2(async () => {
214
- if (!canSubmit) return;
228
+ if (!canSubmit) return false;
215
229
  setSubmitting(true);
216
230
  setError(null);
217
231
  try {
218
232
  await auth.login({ email, password });
233
+ return true;
219
234
  } catch (err) {
220
235
  setError(mapSdkError(err));
236
+ return false;
221
237
  } finally {
222
238
  setSubmitting(false);
223
239
  }
@@ -304,7 +320,7 @@ function DefaultLoginScreen({ onNavigate }) {
304
320
  }
305
321
 
306
322
  // src/hooks/useSignupForm.ts
307
- import { useCallback as useCallback3, useMemo as useMemo2, useState as useState4 } from "react";
323
+ import { useCallback as useCallback3, useMemo as useMemo3, useState as useState4 } from "react";
308
324
  import { useHook as useHook4 } from "@hook-sdk/sdk";
309
325
  var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
310
326
  var MIN_PASSWORD2 = 8;
@@ -315,30 +331,32 @@ function useSignupForm() {
315
331
  const [password, setPassword] = useState4("");
316
332
  const [submitting, setSubmitting] = useState4(false);
317
333
  const [error, setError] = useState4(null);
318
- const nameError = useMemo2(() => {
334
+ const nameError = useMemo3(() => {
319
335
  if (name.length === 0) return null;
320
336
  if (name.trim().length < 2) return "Nome muito curto.";
321
337
  return null;
322
338
  }, [name]);
323
- const emailError = useMemo2(() => {
339
+ const emailError = useMemo3(() => {
324
340
  if (email.length === 0) return null;
325
341
  if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
326
342
  return null;
327
343
  }, [email]);
328
- const passwordError = useMemo2(() => {
344
+ const passwordError = useMemo3(() => {
329
345
  if (password.length === 0) return null;
330
346
  if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
331
347
  return null;
332
348
  }, [password]);
333
349
  const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
334
350
  const submit = useCallback3(async () => {
335
- if (!canSubmit) return;
351
+ if (!canSubmit) return false;
336
352
  setSubmitting(true);
337
353
  setError(null);
338
354
  try {
339
355
  await auth.signup({ name, email, password });
356
+ return true;
340
357
  } catch (err) {
341
358
  setError(mapSdkError(err));
359
+ return false;
342
360
  } finally {
343
361
  setSubmitting(false);
344
362
  }
@@ -395,7 +413,7 @@ function DefaultSignupScreen({ onNavigate }) {
395
413
  }
396
414
 
397
415
  // src/hooks/useForgotForm.ts
398
- import { useCallback as useCallback4, useMemo as useMemo3, useState as useState5 } from "react";
416
+ import { useCallback as useCallback4, useMemo as useMemo4, useState as useState5 } from "react";
399
417
  import { useHook as useHook5 } from "@hook-sdk/sdk";
400
418
  var EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
401
419
  function useForgotForm() {
@@ -404,21 +422,23 @@ function useForgotForm() {
404
422
  const [submitting, setSubmitting] = useState5(false);
405
423
  const [sent, setSent] = useState5(false);
406
424
  const [error, setError] = useState5(null);
407
- const emailError = useMemo3(() => {
425
+ const emailError = useMemo4(() => {
408
426
  if (email.length === 0) return null;
409
427
  if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
410
428
  return null;
411
429
  }, [email]);
412
430
  const canSubmit = email.length > 0 && emailError === null && !submitting;
413
431
  const submit = useCallback4(async () => {
414
- if (!canSubmit) return;
432
+ if (!canSubmit) return false;
415
433
  setSubmitting(true);
416
434
  setError(null);
417
435
  try {
418
436
  await auth.forgot({ email });
419
437
  setSent(true);
438
+ return true;
420
439
  } catch (err) {
421
440
  setError(mapSdkError(err));
441
+ return false;
422
442
  } finally {
423
443
  setSubmitting(false);
424
444
  }
@@ -467,7 +487,7 @@ function DefaultForgotScreen({ onNavigate }) {
467
487
  }
468
488
 
469
489
  // src/hooks/useResetForm.ts
470
- import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo4, useState as useState6 } from "react";
490
+ import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo5, useState as useState6 } from "react";
471
491
  import { useHook as useHook6 } from "@hook-sdk/sdk";
472
492
  var MIN_PASSWORD3 = 12;
473
493
  function useResetForm() {
@@ -484,12 +504,12 @@ function useResetForm() {
484
504
  const t = params.get("token");
485
505
  setToken(t && t.length > 0 ? t : null);
486
506
  }, []);
487
- const passwordError = useMemo4(() => {
507
+ const passwordError = useMemo5(() => {
488
508
  if (password.length === 0) return null;
489
509
  if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
490
510
  return null;
491
511
  }, [password]);
492
- const confirmError = useMemo4(() => {
512
+ const confirmError = useMemo5(() => {
493
513
  if (confirm.length === 0) return null;
494
514
  if (confirm !== password) return "Senhas n\xE3o coincidem.";
495
515
  return null;
@@ -632,7 +652,66 @@ function DefaultPaywall() {
632
652
  }
633
653
 
634
654
  // src/AppRoot.tsx
635
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
655
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
656
+ function PaymentReturnHandler({ children }) {
657
+ const { subscription } = useHook7();
658
+ const [confirming, setConfirming] = useState8(false);
659
+ useEffect3(() => {
660
+ if (typeof window === "undefined") return;
661
+ const url = new URL(window.location.href);
662
+ if (url.searchParams.get("paymentReturn") !== "1") return;
663
+ let cancelled = false;
664
+ let attempts = 0;
665
+ setConfirming(true);
666
+ const tick = async () => {
667
+ if (cancelled) return;
668
+ if (attempts >= 10) {
669
+ setConfirming(false);
670
+ return;
671
+ }
672
+ attempts++;
673
+ try {
674
+ await subscription.refresh();
675
+ } catch {
676
+ }
677
+ if (cancelled) return;
678
+ if (subscription.status() === "active") {
679
+ const cleanUrl = new URL(window.location.href);
680
+ cleanUrl.searchParams.delete("paymentReturn");
681
+ window.history.replaceState({}, "", cleanUrl.toString());
682
+ setConfirming(false);
683
+ return;
684
+ }
685
+ setTimeout(tick, 1e3);
686
+ };
687
+ void tick();
688
+ return () => {
689
+ cancelled = true;
690
+ };
691
+ }, [subscription]);
692
+ if (confirming) {
693
+ return /* @__PURE__ */ jsx12(
694
+ "div",
695
+ {
696
+ role: "status",
697
+ "aria-live": "polite",
698
+ style: {
699
+ position: "fixed",
700
+ inset: 0,
701
+ display: "flex",
702
+ alignItems: "center",
703
+ justifyContent: "center",
704
+ background: "rgba(0,0,0,0.4)",
705
+ zIndex: 9999,
706
+ color: "white",
707
+ fontSize: "1rem"
708
+ },
709
+ children: "Confirmando pagamento\u2026"
710
+ }
711
+ );
712
+ }
713
+ return /* @__PURE__ */ jsx12(Fragment4, { children });
714
+ }
636
715
  function AppRoot({
637
716
  config,
638
717
  children,
@@ -642,10 +721,10 @@ function AppRoot({
642
721
  Reset = DefaultResetScreen,
643
722
  Paywall = DefaultPaywall
644
723
  }) {
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: [
724
+ return /* @__PURE__ */ jsx12(PaymentReturnHandler, { children: /* @__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
725
  children,
647
726
  /* @__PURE__ */ jsx12(PushPrompt, {})
648
- ] }) }) }) }) });
727
+ ] }) }) }) }) }) });
649
728
  }
650
729
 
651
730
  // src/defaults/EmptyState.tsx
@@ -659,12 +738,12 @@ function EmptyState({ title, description, action }) {
659
738
  }
660
739
 
661
740
  // src/hooks/useAuthPrimitives.ts
662
- import { useEffect as useEffect3 } from "react";
663
- import { useHook as useHook7 } from "@hook-sdk/sdk";
741
+ import { useEffect as useEffect4 } from "react";
742
+ import { useHook as useHook8 } from "@hook-sdk/sdk";
664
743
  var warned = false;
665
744
  function useAuthPrimitives() {
666
- const { auth } = useHook7();
667
- useEffect3(() => {
745
+ const { auth } = useHook8();
746
+ useEffect4(() => {
668
747
  if (!warned && process.env.NODE_ENV !== "production") {
669
748
  warned = true;
670
749
  console.warn(
@@ -686,18 +765,18 @@ function useAuthPrimitives() {
686
765
  }
687
766
 
688
767
  // src/hooks/useSubscription.ts
689
- import { useHook as useHook8 } from "@hook-sdk/sdk";
768
+ import { useHook as useHook9 } from "@hook-sdk/sdk";
690
769
  function useSubscription() {
691
- const { subscription } = useHook8();
770
+ const { subscription } = useHook9();
692
771
  return {
693
772
  status: subscription.status()
694
773
  };
695
774
  }
696
775
 
697
776
  // src/hooks/usePush.ts
698
- import { useHook as useHook9 } from "@hook-sdk/sdk";
777
+ import { useHook as useHook10 } from "@hook-sdk/sdk";
699
778
  function usePush() {
700
- const { push } = useHook9();
779
+ const { push } = useHook10();
701
780
  return {
702
781
  status: push.status(),
703
782
  subscribe: push.subscribe,
@@ -706,9 +785,9 @@ function usePush() {
706
785
  }
707
786
 
708
787
  // src/hooks/useToast.ts
709
- import { useCallback as useCallback6, useState as useState8 } from "react";
788
+ import { useCallback as useCallback6, useState as useState9 } from "react";
710
789
  function useToast() {
711
- const [items, setItems] = useState8([]);
790
+ const [items, setItems] = useState9([]);
712
791
  const show = useCallback6((message, kind = "info") => {
713
792
  const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
714
793
  setItems((prev) => [...prev, { id, message, kind }]);