@hook-sdk/template 0.13.0 → 0.14.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
@@ -1919,14 +1919,18 @@ var import_react11 = require("react");
1919
1919
  var import_sdk4 = require("@hook-sdk/sdk");
1920
1920
  var import_jsx_runtime18 = require("react/jsx-runtime");
1921
1921
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
1922
+ var MAX_CYCLES = 3;
1923
+ var SUPPORT_MAILTO = "mailto:suporte@usehook.net?subject=Pagamento%20pendente";
1922
1924
  function PaymentReturnHandler({ children }) {
1923
1925
  const { subscription } = (0, import_sdk4.useHook)();
1924
1926
  const subRef = (0, import_react11.useRef)(subscription);
1925
1927
  subRef.current = subscription;
1926
1928
  const runIdRef = (0, import_react11.useRef)(0);
1929
+ const cyclesRef = (0, import_react11.useRef)(0);
1927
1930
  const [state, setState] = (0, import_react11.useState)("idle");
1928
1931
  const runPoll = (0, import_react11.useCallback)(() => {
1929
1932
  const runId = ++runIdRef.current;
1933
+ cyclesRef.current += 1;
1930
1934
  setState("confirming");
1931
1935
  let attempts = 0;
1932
1936
  const tick = async () => {
@@ -1942,12 +1946,17 @@ function PaymentReturnHandler({ children }) {
1942
1946
  const cleanUrl = new URL(window.location.href);
1943
1947
  cleanUrl.searchParams.delete("paymentReturn");
1944
1948
  window.history.replaceState({}, "", cleanUrl.toString());
1949
+ cyclesRef.current = 0;
1945
1950
  setState("idle");
1946
1951
  return;
1947
1952
  }
1948
1953
  const delay = BACKOFF_MS[attempts - 1];
1949
1954
  if (delay === void 0) {
1950
- setState("waiting");
1955
+ if (cyclesRef.current >= MAX_CYCLES) {
1956
+ setState("timeout");
1957
+ } else {
1958
+ setState("waiting");
1959
+ }
1951
1960
  return;
1952
1961
  }
1953
1962
  setTimeout(tick, delay);
@@ -1958,11 +1967,18 @@ function PaymentReturnHandler({ children }) {
1958
1967
  if (typeof window === "undefined") return;
1959
1968
  const url = new URL(window.location.href);
1960
1969
  if (url.searchParams.get("paymentReturn") !== "1") return;
1970
+ cyclesRef.current = 0;
1961
1971
  runPoll();
1962
1972
  return () => {
1963
1973
  runIdRef.current++;
1964
1974
  };
1965
1975
  }, [runPoll]);
1976
+ const goHome = (0, import_react11.useCallback)(() => {
1977
+ const cleanUrl = new URL(window.location.href);
1978
+ cleanUrl.searchParams.delete("paymentReturn");
1979
+ cleanUrl.pathname = "/app/home";
1980
+ window.location.href = cleanUrl.toString();
1981
+ }, []);
1966
1982
  if (state === "confirming") {
1967
1983
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
1968
1984
  }
@@ -1972,6 +1988,45 @@ function PaymentReturnHandler({ children }) {
1972
1988
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
1973
1989
  ] }) });
1974
1990
  }
1991
+ if (state === "timeout") {
1992
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { role: "alert", "aria-live": "assertive", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { maxWidth: 360, textAlign: "center", lineHeight: 1.5 }, children: [
1993
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { marginBottom: 16 }, children: "Ainda n\xE3o conseguimos confirmar seu pagamento com o banco. Voc\xEA pode tentar de novo, voltar pro app, ou falar com a gente." }),
1994
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1995
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1996
+ "button",
1997
+ {
1998
+ type: "button",
1999
+ onClick: () => {
2000
+ cyclesRef.current = 0;
2001
+ runPoll();
2002
+ },
2003
+ style: buttonStyle,
2004
+ "data-testid": "payment-timeout-retry",
2005
+ children: "Tentar de novo"
2006
+ }
2007
+ ),
2008
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2009
+ "button",
2010
+ {
2011
+ type: "button",
2012
+ onClick: goHome,
2013
+ style: secondaryButtonStyle,
2014
+ "data-testid": "payment-timeout-home",
2015
+ children: "Voltar pro app"
2016
+ }
2017
+ ),
2018
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2019
+ "a",
2020
+ {
2021
+ href: SUPPORT_MAILTO,
2022
+ style: linkStyle,
2023
+ "data-testid": "payment-timeout-support",
2024
+ children: "Falar com suporte"
2025
+ }
2026
+ )
2027
+ ] })
2028
+ ] }) });
2029
+ }
1975
2030
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children });
1976
2031
  }
1977
2032
  var overlayStyle2 = {
@@ -1996,6 +2051,19 @@ var buttonStyle = {
1996
2051
  fontWeight: 600,
1997
2052
  cursor: "pointer"
1998
2053
  };
2054
+ var secondaryButtonStyle = {
2055
+ ...buttonStyle,
2056
+ background: "transparent",
2057
+ color: "white",
2058
+ border: "1px solid rgba(255,255,255,0.5)"
2059
+ };
2060
+ var linkStyle = {
2061
+ color: "white",
2062
+ textDecoration: "underline",
2063
+ fontSize: "0.9rem",
2064
+ marginTop: 4,
2065
+ textAlign: "center"
2066
+ };
1999
2067
 
2000
2068
  // src/AppRoot.tsx
2001
2069
  var import_jsx_runtime19 = require("react/jsx-runtime");
@@ -2332,18 +2400,24 @@ function useLoginForm() {
2332
2400
  const [password, setPassword] = (0, import_react14.useState)("");
2333
2401
  const [submitting, setSubmitting] = (0, import_react14.useState)(false);
2334
2402
  const [error, setError] = (0, import_react14.useState)(null);
2335
- const emailError = (0, import_react14.useMemo)(() => {
2403
+ const [touchedEmail, setTouchedEmail] = (0, import_react14.useState)(false);
2404
+ const [touchedPassword, setTouchedPassword] = (0, import_react14.useState)(false);
2405
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react14.useState)(false);
2406
+ const validateEmail = (0, import_react14.useMemo)(() => {
2336
2407
  if (email.length === 0) return null;
2337
2408
  if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
2338
2409
  return null;
2339
2410
  }, [email]);
2340
- const passwordError = (0, import_react14.useMemo)(() => {
2411
+ const validatePassword = (0, import_react14.useMemo)(() => {
2341
2412
  if (password.length === 0) return null;
2342
2413
  if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
2343
2414
  return null;
2344
2415
  }, [password]);
2345
- const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
2416
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2417
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
2418
+ const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
2346
2419
  const submit = (0, import_react14.useCallback)(async () => {
2420
+ setFormSubmitAttempted(true);
2347
2421
  if (!canSubmit) return false;
2348
2422
  setSubmitting(true);
2349
2423
  setError(null);
@@ -2361,9 +2435,12 @@ function useLoginForm() {
2361
2435
  email,
2362
2436
  setEmail,
2363
2437
  emailError,
2438
+ markEmailTouched: () => setTouchedEmail(true),
2364
2439
  password,
2365
2440
  setPassword,
2366
2441
  passwordError,
2442
+ markPasswordTouched: () => setTouchedPassword(true),
2443
+ formSubmitAttempted,
2367
2444
  submit,
2368
2445
  submitting,
2369
2446
  canSubmit,
@@ -2384,23 +2461,31 @@ function useSignupForm() {
2384
2461
  const [password, setPassword] = (0, import_react15.useState)("");
2385
2462
  const [submitting, setSubmitting] = (0, import_react15.useState)(false);
2386
2463
  const [error, setError] = (0, import_react15.useState)(null);
2387
- const nameError = (0, import_react15.useMemo)(() => {
2464
+ const [touchedName, setTouchedName] = (0, import_react15.useState)(false);
2465
+ const [touchedEmail, setTouchedEmail] = (0, import_react15.useState)(false);
2466
+ const [touchedPassword, setTouchedPassword] = (0, import_react15.useState)(false);
2467
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react15.useState)(false);
2468
+ const validateName = (0, import_react15.useMemo)(() => {
2388
2469
  if (name.length === 0) return null;
2389
2470
  if (name.trim().length < 2) return "Nome muito curto.";
2390
2471
  return null;
2391
2472
  }, [name]);
2392
- const emailError = (0, import_react15.useMemo)(() => {
2473
+ const validateEmail = (0, import_react15.useMemo)(() => {
2393
2474
  if (email.length === 0) return null;
2394
2475
  if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
2395
2476
  return null;
2396
2477
  }, [email]);
2397
- const passwordError = (0, import_react15.useMemo)(() => {
2478
+ const validatePassword = (0, import_react15.useMemo)(() => {
2398
2479
  if (password.length === 0) return null;
2399
2480
  if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
2400
2481
  return null;
2401
2482
  }, [password]);
2402
- const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
2483
+ const nameError = touchedName || formSubmitAttempted ? validateName : null;
2484
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2485
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
2486
+ const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
2403
2487
  const submit = (0, import_react15.useCallback)(async () => {
2488
+ setFormSubmitAttempted(true);
2404
2489
  if (!canSubmit) return false;
2405
2490
  setSubmitting(true);
2406
2491
  setError(null);
@@ -2418,12 +2503,16 @@ function useSignupForm() {
2418
2503
  name,
2419
2504
  setName,
2420
2505
  nameError,
2506
+ markNameTouched: () => setTouchedName(true),
2421
2507
  email,
2422
2508
  setEmail,
2423
2509
  emailError,
2510
+ markEmailTouched: () => setTouchedEmail(true),
2424
2511
  password,
2425
2512
  setPassword,
2426
2513
  passwordError,
2514
+ markPasswordTouched: () => setTouchedPassword(true),
2515
+ formSubmitAttempted,
2427
2516
  submit,
2428
2517
  submitting,
2429
2518
  canSubmit,
@@ -2442,13 +2531,17 @@ function useForgotForm() {
2442
2531
  const [submitting, setSubmitting] = (0, import_react16.useState)(false);
2443
2532
  const [sent, setSent] = (0, import_react16.useState)(false);
2444
2533
  const [error, setError] = (0, import_react16.useState)(null);
2445
- const emailError = (0, import_react16.useMemo)(() => {
2534
+ const [touchedEmail, setTouchedEmail] = (0, import_react16.useState)(false);
2535
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react16.useState)(false);
2536
+ const validateEmail = (0, import_react16.useMemo)(() => {
2446
2537
  if (email.length === 0) return null;
2447
2538
  if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
2448
2539
  return null;
2449
2540
  }, [email]);
2450
- const canSubmit = email.length > 0 && emailError === null && !submitting;
2541
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2542
+ const canSubmit = email.length > 0 && validateEmail === null && !submitting;
2451
2543
  const submit = (0, import_react16.useCallback)(async () => {
2544
+ setFormSubmitAttempted(true);
2452
2545
  if (!canSubmit) return false;
2453
2546
  setSubmitting(true);
2454
2547
  setError(null);
@@ -2467,6 +2560,8 @@ function useForgotForm() {
2467
2560
  email,
2468
2561
  setEmail,
2469
2562
  emailError,
2563
+ markEmailTouched: () => setTouchedEmail(true),
2564
+ formSubmitAttempted,
2470
2565
  submit,
2471
2566
  submitting,
2472
2567
  canSubmit,
@@ -2487,24 +2582,30 @@ function useResetForm() {
2487
2582
  const [submitting, setSubmitting] = (0, import_react17.useState)(false);
2488
2583
  const [done, setDone] = (0, import_react17.useState)(false);
2489
2584
  const [error, setError] = (0, import_react17.useState)(null);
2585
+ const [touchedPassword, setTouchedPassword] = (0, import_react17.useState)(false);
2586
+ const [touchedConfirm, setTouchedConfirm] = (0, import_react17.useState)(false);
2587
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react17.useState)(false);
2490
2588
  (0, import_react17.useEffect)(() => {
2491
2589
  if (typeof window === "undefined") return;
2492
2590
  const params = new URLSearchParams(window.location.search);
2493
2591
  const t = params.get("token");
2494
2592
  setToken(t && t.length > 0 ? t : null);
2495
2593
  }, []);
2496
- const passwordError = (0, import_react17.useMemo)(() => {
2594
+ const validatePassword = (0, import_react17.useMemo)(() => {
2497
2595
  if (password.length === 0) return null;
2498
2596
  if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2499
2597
  return null;
2500
2598
  }, [password]);
2501
- const confirmError = (0, import_react17.useMemo)(() => {
2599
+ const validateConfirm = (0, import_react17.useMemo)(() => {
2502
2600
  if (confirm.length === 0) return null;
2503
2601
  if (confirm !== password) return "Senhas n\xE3o coincidem.";
2504
2602
  return null;
2505
2603
  }, [confirm, password]);
2506
- const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
2604
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
2605
+ const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
2606
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
2507
2607
  const submit = (0, import_react17.useCallback)(async () => {
2608
+ setFormSubmitAttempted(true);
2508
2609
  if (!canSubmit || token === null) return;
2509
2610
  setSubmitting(true);
2510
2611
  setError(null);
@@ -2528,9 +2629,12 @@ function useResetForm() {
2528
2629
  password,
2529
2630
  setPassword,
2530
2631
  passwordError,
2632
+ markPasswordTouched: () => setTouchedPassword(true),
2531
2633
  confirm,
2532
2634
  setConfirm,
2533
2635
  confirmError,
2636
+ markConfirmTouched: () => setTouchedConfirm(true),
2637
+ formSubmitAttempted,
2534
2638
  submit,
2535
2639
  submitting,
2536
2640
  canSubmit,