@hook-sdk/template 0.13.0 → 0.14.1

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
@@ -147,7 +147,11 @@ var DeepLinksSchema = import_zod.z.object({
147
147
  var AppConfigSchema = import_zod.z.object({
148
148
  slug: import_zod.z.string().regex(/^[a-z0-9-]+$/),
149
149
  name: import_zod.z.string().min(1),
150
- branding: import_zod.z.object({ primaryColor: import_zod.z.string(), logoUrl: import_zod.z.string().url() }),
150
+ branding: import_zod.z.object({
151
+ primaryColor: import_zod.z.string(),
152
+ logoUrl: import_zod.z.string().url(),
153
+ iconUrl: import_zod.z.string().url().optional()
154
+ }),
151
155
  authFlow: AuthFlowSchema,
152
156
  paywall: PaywallSchema,
153
157
  persistedKeys: import_zod.z.array(PersistedKeySchema),
@@ -567,6 +571,7 @@ var import_react8 = require("react");
567
571
 
568
572
  // src/hooks/useInstallPrompt.ts
569
573
  var import_react6 = require("react");
574
+ var ANDROID_PROMPT_WAIT_MS = 3e3;
570
575
  var IOS_RE = /iPad|iPhone|iPod/;
571
576
  var IOS_NON_SAFARI_RE = /CriOS|FxiOS|EdgiOS/;
572
577
  var ANDROID_RE = /Android/;
@@ -636,11 +641,12 @@ function track(event, props) {
636
641
  if (typeof window === "undefined") return;
637
642
  window.posthog?.capture?.(event, props);
638
643
  }
639
- function pickVariant(state) {
644
+ function pickVariant(state, promptWaitElapsed) {
640
645
  if (state.isInstalled) return "none";
641
646
  switch (state.platform) {
642
647
  case "android":
643
- return state.isInstallable ? "android-native" : "android-manual";
648
+ if (state.isInstallable) return "android-native";
649
+ return promptWaitElapsed ? "android-manual" : "android-pending";
644
650
  case "ios-safari":
645
651
  return "ios-safari";
646
652
  case "ios-other":
@@ -710,6 +716,15 @@ function useInstallPrompt(slug) {
710
716
  const [isDismissedSession, setIsDismissedSession] = (0, import_react6.useState)(() => readSessionSkip(slug));
711
717
  const [isDismissedPermanent, setIsDismissedPermanent] = (0, import_react6.useState)(() => readPermanentDismiss(slug).dismissed);
712
718
  const [skipCount, setSkipCount] = (0, import_react6.useState)(() => readSkipCount(slug));
719
+ const [promptWaitElapsed, setPromptWaitElapsed] = (0, import_react6.useState)(() => {
720
+ if (typeof window === "undefined") return true;
721
+ return window.__pwaInstallPrompt != null;
722
+ });
723
+ (0, import_react6.useEffect)(() => {
724
+ if (promptWaitElapsed) return;
725
+ const id = setTimeout(() => setPromptWaitElapsed(true), ANDROID_PROMPT_WAIT_MS);
726
+ return () => clearTimeout(id);
727
+ }, [promptWaitElapsed]);
713
728
  (0, import_react6.useEffect)(() => {
714
729
  if (typeof window === "undefined") return;
715
730
  if (window.__pwaInstallPrompt) {
@@ -747,17 +762,20 @@ function useInstallPrompt(slug) {
747
762
  mq.addEventListener?.("change", handler);
748
763
  return () => mq.removeEventListener?.("change", handler);
749
764
  }, [slug]);
750
- const variant = pickVariant({
751
- platform,
752
- iosBrowser,
753
- androidBrowser,
754
- inAppApp,
755
- isInstallable,
756
- isInstalled,
757
- isDismissedSession,
758
- isDismissedPermanent,
759
- skipCount
760
- });
765
+ const variant = pickVariant(
766
+ {
767
+ platform,
768
+ iosBrowser,
769
+ androidBrowser,
770
+ inAppApp,
771
+ isInstallable,
772
+ isInstalled,
773
+ isDismissedSession,
774
+ isDismissedPermanent,
775
+ skipCount
776
+ },
777
+ promptWaitElapsed
778
+ );
761
779
  const promptInstall = (0, import_react6.useCallback)(async () => {
762
780
  if (typeof window === "undefined") return false;
763
781
  const prompt = window.__pwaInstallPrompt;
@@ -1293,15 +1311,51 @@ function Step({ n, icon, children }) {
1293
1311
  );
1294
1312
  }
1295
1313
 
1296
- // src/components/InstallGate/Step.tsx
1314
+ // src/components/InstallGate/variants/AndroidPendingVariant.tsx
1297
1315
  var import_jsx_runtime10 = require("react/jsx-runtime");
1316
+ function AndroidPendingVariant() {
1317
+ const copy = INSTALL_COPY.android.native;
1318
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(InstallSplash, { title: copy.title, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1319
+ "div",
1320
+ {
1321
+ style: {
1322
+ display: "flex",
1323
+ flexDirection: "column",
1324
+ alignItems: "center",
1325
+ gap: 16,
1326
+ padding: "24px 0"
1327
+ },
1328
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, {})
1329
+ }
1330
+ ) });
1331
+ }
1332
+ function Spinner() {
1333
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1334
+ "div",
1335
+ {
1336
+ "aria-hidden": true,
1337
+ style: {
1338
+ width: 28,
1339
+ height: 28,
1340
+ borderRadius: "50%",
1341
+ border: "3px solid #e5e5e7",
1342
+ borderTopColor: "var(--hook-color-primary)",
1343
+ animation: "hook-install-spin 0.8s linear infinite"
1344
+ },
1345
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `@keyframes hook-install-spin { to { transform: rotate(360deg); } }` })
1346
+ }
1347
+ );
1348
+ }
1349
+
1350
+ // src/components/InstallGate/Step.tsx
1351
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1298
1352
  function Step2({
1299
1353
  n,
1300
1354
  title,
1301
1355
  subtitle,
1302
1356
  visual
1303
1357
  }) {
1304
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1358
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1305
1359
  "div",
1306
1360
  {
1307
1361
  style: {
@@ -1312,7 +1366,7 @@ function Step2({
1312
1366
  textAlign: "left"
1313
1367
  },
1314
1368
  children: [
1315
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1369
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1316
1370
  "div",
1317
1371
  {
1318
1372
  style: {
@@ -1331,9 +1385,9 @@ function Step2({
1331
1385
  children: n
1332
1386
  }
1333
1387
  ),
1334
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { flex: 1 }, children: [
1335
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
1336
- subtitle && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
1388
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { flex: 1 }, children: [
1389
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { margin: 0, fontSize: 15, fontWeight: 500, color: "#111", lineHeight: 1.3 }, children: title }),
1390
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { margin: "4px 0 0 0", fontSize: 13, color: "#777" }, children: subtitle }),
1337
1391
  visual
1338
1392
  ] })
1339
1393
  ]
@@ -1342,21 +1396,21 @@ function Step2({
1342
1396
  }
1343
1397
 
1344
1398
  // src/components/InstallGate/variants/IOSafariVariant.tsx
1345
- var import_jsx_runtime11 = require("react/jsx-runtime");
1399
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1346
1400
  function IOSafariVariant({
1347
1401
  state,
1348
1402
  actions
1349
1403
  }) {
1350
1404
  const copy = INSTALL_COPY.iosSafari;
1351
1405
  const showPermanent = shouldShowPermanentOption(state);
1352
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1353
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1406
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1407
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1354
1408
  Step2,
1355
1409
  {
1356
1410
  n: 1,
1357
1411
  title: copy.step1.title,
1358
1412
  subtitle: copy.step1.subtitle,
1359
- visual: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1413
+ visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1360
1414
  "div",
1361
1415
  {
1362
1416
  style: {
@@ -1368,17 +1422,17 @@ function IOSafariVariant({
1368
1422
  padding: "12px 0",
1369
1423
  marginTop: 8
1370
1424
  },
1371
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1425
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1372
1426
  }
1373
1427
  )
1374
1428
  }
1375
1429
  ),
1376
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1430
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1377
1431
  Step2,
1378
1432
  {
1379
1433
  n: 2,
1380
1434
  title: copy.step2.title,
1381
- visual: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1435
+ visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1382
1436
  "div",
1383
1437
  {
1384
1438
  style: {
@@ -1391,19 +1445,19 @@ function IOSafariVariant({
1391
1445
  marginTop: 8
1392
1446
  },
1393
1447
  children: [
1394
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1395
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1448
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1449
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1396
1450
  ]
1397
1451
  }
1398
1452
  )
1399
1453
  }
1400
1454
  ),
1401
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1455
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1402
1456
  Step2,
1403
1457
  {
1404
1458
  n: 3,
1405
1459
  title: copy.step3.title,
1406
- visual: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1460
+ visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1407
1461
  "div",
1408
1462
  {
1409
1463
  style: {
@@ -1414,7 +1468,7 @@ function IOSafariVariant({
1414
1468
  padding: "10px 14px",
1415
1469
  marginTop: 8
1416
1470
  },
1417
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1471
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1418
1472
  "span",
1419
1473
  {
1420
1474
  style: {
@@ -1429,7 +1483,7 @@ function IOSafariVariant({
1429
1483
  )
1430
1484
  }
1431
1485
  ),
1432
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1486
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1433
1487
  "button",
1434
1488
  {
1435
1489
  "data-testid": "install-prompt-skip-session",
@@ -1439,7 +1493,7 @@ function IOSafariVariant({
1439
1493
  children: copy.skip
1440
1494
  }
1441
1495
  ),
1442
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1496
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1443
1497
  "button",
1444
1498
  {
1445
1499
  "data-testid": "install-prompt-skip-permanent",
@@ -1453,21 +1507,21 @@ function IOSafariVariant({
1453
1507
  }
1454
1508
 
1455
1509
  // src/components/InstallGate/variants/IOSOtherVariant.tsx
1456
- var import_jsx_runtime12 = require("react/jsx-runtime");
1510
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1457
1511
  function IOSOtherVariant({
1458
1512
  state,
1459
1513
  actions
1460
1514
  }) {
1461
1515
  const copy = INSTALL_COPY.iosOther;
1462
1516
  const showPermanent = shouldShowPermanentOption(state);
1463
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1464
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1517
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(InstallSplash, { title: copy.title, subtitle: copy.subtitle, children: [
1518
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1465
1519
  Step2,
1466
1520
  {
1467
1521
  n: 1,
1468
1522
  title: copy.step1.title,
1469
1523
  subtitle: copy.step1.subtitle,
1470
- visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1524
+ visual: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1471
1525
  "div",
1472
1526
  {
1473
1527
  style: {
@@ -1479,17 +1533,17 @@ function IOSOtherVariant({
1479
1533
  padding: "12px 0",
1480
1534
  marginTop: 8
1481
1535
  },
1482
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1536
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ShareIconIOS, { size: 32, style: { color: "var(--hook-color-primary)" } })
1483
1537
  }
1484
1538
  )
1485
1539
  }
1486
1540
  ),
1487
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1541
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1488
1542
  Step2,
1489
1543
  {
1490
1544
  n: 2,
1491
1545
  title: copy.step2.title,
1492
- visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1546
+ visual: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1493
1547
  "div",
1494
1548
  {
1495
1549
  style: {
@@ -1502,19 +1556,19 @@ function IOSOtherVariant({
1502
1556
  marginTop: 8
1503
1557
  },
1504
1558
  children: [
1505
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1506
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1559
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SquarePlusIcon, { size: 22, style: { color: "#555" } }),
1560
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: { fontSize: 14, color: "#333" }, children: copy.step2.iconLabel })
1507
1561
  ]
1508
1562
  }
1509
1563
  )
1510
1564
  }
1511
1565
  ),
1512
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1566
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1513
1567
  Step2,
1514
1568
  {
1515
1569
  n: 3,
1516
1570
  title: copy.step3.title,
1517
- visual: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1571
+ visual: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1518
1572
  "div",
1519
1573
  {
1520
1574
  style: {
@@ -1525,7 +1579,7 @@ function IOSOtherVariant({
1525
1579
  padding: "10px 14px",
1526
1580
  marginTop: 8
1527
1581
  },
1528
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1582
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1529
1583
  "span",
1530
1584
  {
1531
1585
  style: {
@@ -1540,7 +1594,7 @@ function IOSOtherVariant({
1540
1594
  )
1541
1595
  }
1542
1596
  ),
1543
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1597
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1544
1598
  "button",
1545
1599
  {
1546
1600
  "data-testid": "install-prompt-skip-session",
@@ -1550,7 +1604,7 @@ function IOSOtherVariant({
1550
1604
  children: copy.skip
1551
1605
  }
1552
1606
  ),
1553
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1607
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1554
1608
  "button",
1555
1609
  {
1556
1610
  "data-testid": "install-prompt-skip-permanent",
@@ -1565,7 +1619,7 @@ function IOSOtherVariant({
1565
1619
 
1566
1620
  // src/components/InstallGate/variants/InAppBrowserVariant.tsx
1567
1621
  var import_react7 = require("react");
1568
- var import_jsx_runtime13 = require("react/jsx-runtime");
1622
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1569
1623
  function InAppBrowserVariant({
1570
1624
  state,
1571
1625
  actions
@@ -1581,10 +1635,10 @@ function InAppBrowserVariant({
1581
1635
  setTimeout(() => setCopied(false), 2e3);
1582
1636
  };
1583
1637
  const DotsIcon = app === "facebook" || app === "telegram" ? MenuDotsVerticalIcon : MenuDotsHorizontalIcon;
1584
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(InstallSplash, { title: appCopy.title, children: [
1585
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Step3, { n: 1, icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DotsIcon, { size: 20 }), children: appCopy.step1 }),
1586
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Step3, { n: 2, icon: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
1587
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1638
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(InstallSplash, { title: appCopy.title, children: [
1639
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Step3, { n: 1, icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DotsIcon, { size: 20 }), children: appCopy.step1 }),
1640
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Step3, { n: 2, icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ExternalLinkIcon, { size: 18 }), children: appCopy.step2 }),
1641
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1588
1642
  "button",
1589
1643
  {
1590
1644
  "data-testid": "install-prompt-cta-inapp-copy",
@@ -1594,7 +1648,7 @@ function InAppBrowserVariant({
1594
1648
  children: copied ? copy.copiedToast : copy.copy
1595
1649
  }
1596
1650
  ),
1597
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1651
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1598
1652
  "button",
1599
1653
  {
1600
1654
  "data-testid": "install-prompt-skip-session",
@@ -1604,7 +1658,7 @@ function InAppBrowserVariant({
1604
1658
  children: copy.skip
1605
1659
  }
1606
1660
  ),
1607
- showPermanent && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1661
+ showPermanent && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1608
1662
  "button",
1609
1663
  {
1610
1664
  "data-testid": "install-prompt-skip-permanent",
@@ -1621,7 +1675,7 @@ function Step3({
1621
1675
  icon,
1622
1676
  children
1623
1677
  }) {
1624
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1678
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1625
1679
  "div",
1626
1680
  {
1627
1681
  style: {
@@ -1635,7 +1689,7 @@ function Step3({
1635
1689
  textAlign: "left"
1636
1690
  },
1637
1691
  children: [
1638
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1692
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1639
1693
  "div",
1640
1694
  {
1641
1695
  style: {
@@ -1654,15 +1708,15 @@ function Step3({
1654
1708
  children: n
1655
1709
  }
1656
1710
  ),
1657
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1658
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1711
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { flex: 1, fontSize: 14, color: "#333" }, children }),
1712
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { color: "#888", flexShrink: 0 }, children: icon })
1659
1713
  ]
1660
1714
  }
1661
1715
  );
1662
1716
  }
1663
1717
 
1664
1718
  // src/components/InstallGate/variants/DesktopVariant.tsx
1665
- var import_jsx_runtime14 = require("react/jsx-runtime");
1719
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1666
1720
  function DesktopVariant({
1667
1721
  state,
1668
1722
  actions
@@ -1671,21 +1725,21 @@ function DesktopVariant({
1671
1725
  const copy = INSTALL_COPY.desktop;
1672
1726
  const iconUrl = theme.icon_url || theme.logo_url || null;
1673
1727
  if (!state.isInstallable) return null;
1674
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1728
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1675
1729
  "div",
1676
1730
  {
1677
1731
  role: "complementary",
1678
1732
  "aria-label": copy.title,
1679
1733
  style: bannerStyle,
1680
1734
  children: [
1681
- iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1735
+ iconUrl ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1682
1736
  "img",
1683
1737
  {
1684
1738
  src: iconUrl,
1685
1739
  alt: "",
1686
1740
  style: { width: 40, height: 40, borderRadius: 10, objectFit: "cover", flexShrink: 0 }
1687
1741
  }
1688
- ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1742
+ ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1689
1743
  "div",
1690
1744
  {
1691
1745
  style: {
@@ -1704,11 +1758,11 @@ function DesktopVariant({
1704
1758
  children: name.charAt(0).toUpperCase()
1705
1759
  }
1706
1760
  ),
1707
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
1708
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
1709
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
1761
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { flex: 1, minWidth: 0 }, children: [
1762
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { fontSize: 14, fontWeight: 600, color: "#111" }, children: copy.title }),
1763
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: copy.subtitle })
1710
1764
  ] }),
1711
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1765
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1712
1766
  "button",
1713
1767
  {
1714
1768
  "data-testid": "install-prompt-cta-desktop",
@@ -1729,12 +1783,12 @@ function DesktopVariant({
1729
1783
  flexShrink: 0
1730
1784
  },
1731
1785
  children: [
1732
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DownloadIcon, { size: 14 }),
1786
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DownloadIcon, { size: 14 }),
1733
1787
  copy.cta
1734
1788
  ]
1735
1789
  }
1736
1790
  ),
1737
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1791
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1738
1792
  "button",
1739
1793
  {
1740
1794
  "data-testid": "install-prompt-desktop-close",
@@ -1749,7 +1803,7 @@ function DesktopVariant({
1749
1803
  padding: 4,
1750
1804
  flexShrink: 0
1751
1805
  },
1752
- children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(XIcon, { size: 16 })
1806
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(XIcon, { size: 16 })
1753
1807
  }
1754
1808
  )
1755
1809
  ]
@@ -1773,7 +1827,7 @@ var bannerStyle = {
1773
1827
  };
1774
1828
 
1775
1829
  // src/components/InstallGate/InstallGate.tsx
1776
- var import_jsx_runtime15 = require("react/jsx-runtime");
1830
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1777
1831
  function InstallGate({ children }) {
1778
1832
  const { slug, features_enabled } = useTemplateConfig();
1779
1833
  const enabled = features_enabled.includes("install_prompt");
@@ -1794,30 +1848,32 @@ function InstallGate({ children }) {
1794
1848
  variant: installState.variant
1795
1849
  });
1796
1850
  }, [shouldBlock, slug, installState.variant, installState.platform, installState.iosBrowser, installState.androidBrowser, installState.inAppApp]);
1797
- if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1798
- if (installState.isInstalled) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1851
+ if (!enabled) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1852
+ if (installState.isInstalled) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1799
1853
  if (installState.variant === "desktop") {
1800
1854
  const showBanner = !installState.isDismissedSession && !installState.isDismissedPermanent;
1801
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1855
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1802
1856
  children,
1803
- showBanner && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DesktopVariant, { state: installState, actions: installState })
1857
+ showBanner && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DesktopVariant, { state: installState, actions: installState })
1804
1858
  ] });
1805
1859
  }
1806
- if (!shouldBlock) return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1860
+ if (!shouldBlock) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1807
1861
  switch (installState.variant) {
1808
1862
  case "android-native":
1809
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AndroidNativeVariant, { state: installState, actions: installState });
1863
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AndroidNativeVariant, { state: installState, actions: installState });
1810
1864
  case "android-manual":
1811
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AndroidManualVariant, { state: installState, actions: installState });
1865
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AndroidManualVariant, { state: installState, actions: installState });
1866
+ case "android-pending":
1867
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AndroidPendingVariant, {});
1812
1868
  case "ios-safari":
1813
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IOSafariVariant, { state: installState, actions: installState });
1869
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IOSafariVariant, { state: installState, actions: installState });
1814
1870
  case "ios-other":
1815
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IOSOtherVariant, { state: installState, actions: installState });
1871
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IOSOtherVariant, { state: installState, actions: installState });
1816
1872
  case "in-app":
1817
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(InAppBrowserVariant, { state: installState, actions: installState });
1873
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(InAppBrowserVariant, { state: installState, actions: installState });
1818
1874
  case "none":
1819
1875
  default:
1820
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children });
1876
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, { children });
1821
1877
  }
1822
1878
  }
1823
1879
 
@@ -1829,7 +1885,7 @@ function PushPrompt() {
1829
1885
  // src/internal/SessionExpiredBanner.tsx
1830
1886
  var import_react9 = require("react");
1831
1887
  var import_sdk3 = require("@hook-sdk/sdk");
1832
- var import_jsx_runtime16 = require("react/jsx-runtime");
1888
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1833
1889
  var DISMISS_KEY = "hook:session-expired-dismissed-until";
1834
1890
  var DISMISS_TTL_MS = 60 * 60 * 1e3;
1835
1891
  function SessionExpiredBanner() {
@@ -1856,13 +1912,13 @@ function SessionExpiredBanner() {
1856
1912
  localStorage.setItem(DISMISS_KEY, String(Date.now() + DISMISS_TTL_MS));
1857
1913
  setShow(false);
1858
1914
  }
1859
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { role: "alert", className: "fixed top-0 inset-x-0 bg-red-600 text-white px-4 py-2 flex items-center justify-between gap-3 text-sm shadow", style: { zIndex: 10001 }, children: [
1860
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { children: [
1861
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("strong", { children: "Sua sess\xE3o expirou." }),
1915
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { role: "alert", className: "fixed top-0 inset-x-0 bg-red-600 text-white px-4 py-2 flex items-center justify-between gap-3 text-sm shadow", style: { zIndex: 10001 }, children: [
1916
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { children: [
1917
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: "Sua sess\xE3o expirou." }),
1862
1918
  " Fa\xE7a login novamente para continuar."
1863
1919
  ] }),
1864
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
1865
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1920
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
1921
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1866
1922
  "button",
1867
1923
  {
1868
1924
  type: "button",
@@ -1871,7 +1927,7 @@ function SessionExpiredBanner() {
1871
1927
  children: "Fazer login"
1872
1928
  }
1873
1929
  ),
1874
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1930
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1875
1931
  "button",
1876
1932
  {
1877
1933
  type: "button",
@@ -1887,7 +1943,7 @@ function SessionExpiredBanner() {
1887
1943
 
1888
1944
  // src/defaults/ErrorBoundary.tsx
1889
1945
  var import_react10 = require("react");
1890
- var import_jsx_runtime17 = require("react/jsx-runtime");
1946
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1891
1947
  var ErrorBoundary = class extends import_react10.Component {
1892
1948
  state = { error: null };
1893
1949
  static getDerivedStateFromError(error) {
@@ -1905,28 +1961,32 @@ var ErrorBoundary = class extends import_react10.Component {
1905
1961
  }
1906
1962
  render() {
1907
1963
  if (this.state.error) {
1908
- return this.props.fallback ?? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
1909
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h2", { children: "Algo deu errado" }),
1910
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
1964
+ return this.props.fallback ?? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { role: "alert", style: { padding: 24, textAlign: "center" }, children: [
1965
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { children: "Algo deu errado" }),
1966
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { style: { opacity: 0.7 }, children: "Recarregue a p\xE1gina pra tentar de novo." })
1911
1967
  ] });
1912
1968
  }
1913
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children: this.props.children });
1969
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children: this.props.children });
1914
1970
  }
1915
1971
  };
1916
1972
 
1917
1973
  // src/internal/PaymentReturnHandler.tsx
1918
1974
  var import_react11 = require("react");
1919
1975
  var import_sdk4 = require("@hook-sdk/sdk");
1920
- var import_jsx_runtime18 = require("react/jsx-runtime");
1976
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1921
1977
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
1978
+ var MAX_CYCLES = 3;
1979
+ var SUPPORT_MAILTO = "mailto:suporte@usehook.net?subject=Pagamento%20pendente";
1922
1980
  function PaymentReturnHandler({ children }) {
1923
1981
  const { subscription } = (0, import_sdk4.useHook)();
1924
1982
  const subRef = (0, import_react11.useRef)(subscription);
1925
1983
  subRef.current = subscription;
1926
1984
  const runIdRef = (0, import_react11.useRef)(0);
1985
+ const cyclesRef = (0, import_react11.useRef)(0);
1927
1986
  const [state, setState] = (0, import_react11.useState)("idle");
1928
1987
  const runPoll = (0, import_react11.useCallback)(() => {
1929
1988
  const runId = ++runIdRef.current;
1989
+ cyclesRef.current += 1;
1930
1990
  setState("confirming");
1931
1991
  let attempts = 0;
1932
1992
  const tick = async () => {
@@ -1942,12 +2002,17 @@ function PaymentReturnHandler({ children }) {
1942
2002
  const cleanUrl = new URL(window.location.href);
1943
2003
  cleanUrl.searchParams.delete("paymentReturn");
1944
2004
  window.history.replaceState({}, "", cleanUrl.toString());
2005
+ cyclesRef.current = 0;
1945
2006
  setState("idle");
1946
2007
  return;
1947
2008
  }
1948
2009
  const delay = BACKOFF_MS[attempts - 1];
1949
2010
  if (delay === void 0) {
1950
- setState("waiting");
2011
+ if (cyclesRef.current >= MAX_CYCLES) {
2012
+ setState("timeout");
2013
+ } else {
2014
+ setState("waiting");
2015
+ }
1951
2016
  return;
1952
2017
  }
1953
2018
  setTimeout(tick, delay);
@@ -1958,21 +2023,67 @@ function PaymentReturnHandler({ children }) {
1958
2023
  if (typeof window === "undefined") return;
1959
2024
  const url = new URL(window.location.href);
1960
2025
  if (url.searchParams.get("paymentReturn") !== "1") return;
2026
+ cyclesRef.current = 0;
1961
2027
  runPoll();
1962
2028
  return () => {
1963
2029
  runIdRef.current++;
1964
2030
  };
1965
2031
  }, [runPoll]);
2032
+ const goHome = (0, import_react11.useCallback)(() => {
2033
+ const cleanUrl = new URL(window.location.href);
2034
+ cleanUrl.searchParams.delete("paymentReturn");
2035
+ cleanUrl.pathname = "/app/home";
2036
+ window.location.href = cleanUrl.toString();
2037
+ }, []);
1966
2038
  if (state === "confirming") {
1967
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
2039
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: "Confirmando pagamento\u2026" });
1968
2040
  }
1969
2041
  if (state === "waiting") {
1970
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
1971
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
1972
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { role: "status", "aria-live": "polite", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { maxWidth: 320, textAlign: "center", lineHeight: 1.5 }, children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { marginBottom: 16 }, children: "Pagamento aceito. Estamos confirmando com o banco \u2014 pode levar alguns minutos." }),
2044
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", onClick: runPoll, style: buttonStyle, children: "Atualizar" })
2045
+ ] }) });
2046
+ }
2047
+ if (state === "timeout") {
2048
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { role: "alert", "aria-live": "assertive", style: overlayStyle2, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { maxWidth: 360, textAlign: "center", lineHeight: 1.5 }, children: [
2049
+ /* @__PURE__ */ (0, import_jsx_runtime19.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." }),
2050
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
2051
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2052
+ "button",
2053
+ {
2054
+ type: "button",
2055
+ onClick: () => {
2056
+ cyclesRef.current = 0;
2057
+ runPoll();
2058
+ },
2059
+ style: buttonStyle,
2060
+ "data-testid": "payment-timeout-retry",
2061
+ children: "Tentar de novo"
2062
+ }
2063
+ ),
2064
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2065
+ "button",
2066
+ {
2067
+ type: "button",
2068
+ onClick: goHome,
2069
+ style: secondaryButtonStyle,
2070
+ "data-testid": "payment-timeout-home",
2071
+ children: "Voltar pro app"
2072
+ }
2073
+ ),
2074
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2075
+ "a",
2076
+ {
2077
+ href: SUPPORT_MAILTO,
2078
+ style: linkStyle,
2079
+ "data-testid": "payment-timeout-support",
2080
+ children: "Falar com suporte"
2081
+ }
2082
+ )
2083
+ ] })
1973
2084
  ] }) });
1974
2085
  }
1975
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_jsx_runtime18.Fragment, { children });
2086
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children });
1976
2087
  }
1977
2088
  var overlayStyle2 = {
1978
2089
  position: "fixed",
@@ -1996,9 +2107,22 @@ var buttonStyle = {
1996
2107
  fontWeight: 600,
1997
2108
  cursor: "pointer"
1998
2109
  };
2110
+ var secondaryButtonStyle = {
2111
+ ...buttonStyle,
2112
+ background: "transparent",
2113
+ color: "white",
2114
+ border: "1px solid rgba(255,255,255,0.5)"
2115
+ };
2116
+ var linkStyle = {
2117
+ color: "white",
2118
+ textDecoration: "underline",
2119
+ fontSize: "0.9rem",
2120
+ marginTop: 4,
2121
+ textAlign: "center"
2122
+ };
1999
2123
 
2000
2124
  // src/AppRoot.tsx
2001
- var import_jsx_runtime19 = require("react/jsx-runtime");
2125
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2002
2126
  function buildLegacyConfigShim(config) {
2003
2127
  const paywall = config.paywall;
2004
2128
  const isFree = paywall.mode === "free";
@@ -2008,7 +2132,16 @@ function buildLegacyConfigShim(config) {
2008
2132
  slug: config.slug,
2009
2133
  name: config.name,
2010
2134
  email_alias: config.slug,
2011
- theme: { primary_color: config.branding.primaryColor },
2135
+ // Map branding into the legacy theme shape so InstallSplash (and
2136
+ // anything else reading theme.icon_url / theme.logo_url) can surface
2137
+ // the app icon instead of the generic "first letter of name" fallback.
2138
+ // Falls back to logoUrl when iconUrl is unset — apps that haven't
2139
+ // adopted iconUrl keep their previous behavior unchanged.
2140
+ theme: {
2141
+ primary_color: config.branding.primaryColor,
2142
+ icon_url: config.branding.iconUrl ?? config.branding.logoUrl,
2143
+ logo_url: config.branding.logoUrl
2144
+ },
2012
2145
  features_enabled: config.features_enabled ?? [],
2013
2146
  dependencies_allowlist: ["react", "react-dom"],
2014
2147
  subscription: {
@@ -2066,10 +2199,10 @@ function AppRoot(props) {
2066
2199
  const Router = testRouter === "memory" ? import_react_router_dom2.MemoryRouter : import_react_router_dom2.BrowserRouter;
2067
2200
  const basename = `/app/${config.slug}`;
2068
2201
  const routerProps = testRouter === "memory" ? { basename, initialEntries: testInitialEntries } : { basename };
2069
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(AppConfigProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PersistenceRegistry, { config: config.persistedKeys, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(Router, { ...routerProps, children: [
2070
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DeepLinkHandler, { deepLinks: config.deepLinks }),
2071
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SessionExpiredBanner, {}),
2072
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(InstallGate, { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2202
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AppConfigProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TemplateConfigProvider, { config: legacyShim, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PersistenceRegistry, { config: config.persistedKeys, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(Router, { ...routerProps, children: [
2203
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(DeepLinkHandler, { deepLinks: config.deepLinks }),
2204
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SessionExpiredBanner, {}),
2205
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(InstallGate, { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2073
2206
  AuthGated,
2074
2207
  {
2075
2208
  config,
@@ -2081,9 +2214,9 @@ function AppRoot(props) {
2081
2214
  Paywall,
2082
2215
  Onboarding,
2083
2216
  PreAuthFlow,
2084
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(SubscriptionGate, { Paywall: Paywall ?? FallbackPaywall, children: [
2217
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(SubscriptionGate, { Paywall: Paywall ?? FallbackPaywall, children: [
2085
2218
  children,
2086
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PushPrompt, {})
2219
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PushPrompt, {})
2087
2220
  ] })
2088
2221
  }
2089
2222
  ) })
@@ -2103,25 +2236,25 @@ function AuthGated({
2103
2236
  if (authStatus === "loading") return null;
2104
2237
  if (authStatus !== "authenticated") {
2105
2238
  if (config.onboarding?.trigger === "pre_signup_custom" && PreAuthFlow) {
2106
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_router_dom2.Routes, { children: [
2107
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/signin", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Login, {}) }),
2108
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Signup, {}) }),
2109
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Forgot, {}) }),
2110
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Reset, {}) }),
2111
- EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(EmailVerify, {}) }) : null,
2112
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/*", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PreAuthFlow, {}) })
2239
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_router_dom2.Routes, { children: [
2240
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/signin", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Login, {}) }),
2241
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Signup, {}) }),
2242
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Forgot, {}) }),
2243
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Reset, {}) }),
2244
+ EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(EmailVerify, {}) }) : null,
2245
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/*", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PreAuthFlow, {}) })
2113
2246
  ] });
2114
2247
  }
2115
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_react_router_dom2.Routes, { children: [
2116
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Login, {}) }),
2117
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Signup, {}) }),
2118
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Forgot, {}) }),
2119
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Reset, {}) }),
2120
- EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(EmailVerify, {}) }) : null,
2121
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_react_router_dom2.Navigate, { to: "/", replace: true }) })
2248
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react_router_dom2.Routes, { children: [
2249
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Login, {}) }),
2250
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/signup", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Signup, {}) }),
2251
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/forgot", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Forgot, {}) }),
2252
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/reset", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Reset, {}) }),
2253
+ EmailVerify ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "/verify", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(EmailVerify, {}) }) : null,
2254
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_react_router_dom2.Navigate, { to: "/", replace: true }) })
2122
2255
  ] });
2123
2256
  }
2124
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children });
2257
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_jsx_runtime20.Fragment, { children });
2125
2258
  }
2126
2259
  function FallbackPaywall() {
2127
2260
  return null;
@@ -2212,7 +2345,7 @@ function usePush() {
2212
2345
  }
2213
2346
 
2214
2347
  // src/components/PushPrompt.tsx
2215
- var import_jsx_runtime20 = require("react/jsx-runtime");
2348
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2216
2349
  function platformRecoveryCopy(texts) {
2217
2350
  if (typeof navigator === "undefined") return null;
2218
2351
  const ua = navigator.userAgent || "";
@@ -2235,28 +2368,28 @@ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, clas
2235
2368
  const { state, subscribe } = usePush();
2236
2369
  if (state.kind === "subscribed" || state.kind === "dismissed") return null;
2237
2370
  if (state.kind === "ios_needs_install") {
2238
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2239
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { children: texts.iosInstallTitle }),
2240
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: texts.iosInstallBody }),
2241
- onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2371
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
2372
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { children: texts.iosInstallTitle }),
2373
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: texts.iosInstallBody }),
2374
+ onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
2242
2375
  ] });
2243
2376
  }
2244
2377
  if (state.kind === "denied") {
2245
2378
  const recovery = platformRecoveryCopy(texts);
2246
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
2247
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { children: texts.deniedTitle }),
2248
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: texts.deniedBody }),
2249
- recovery && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { "data-testid": "denied-recovery", children: recovery })
2379
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
2380
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { children: texts.deniedTitle }),
2381
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: texts.deniedBody }),
2382
+ recovery && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { "data-testid": "denied-recovery", children: recovery })
2250
2383
  ] });
2251
2384
  }
2252
2385
  if (state.kind === "unsupported") {
2253
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className, role: "region", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: texts.unsupportedBody }) });
2386
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className, role: "region", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: texts.unsupportedBody }) });
2254
2387
  }
2255
2388
  if (state.kind === "error") {
2256
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: state.message }) });
2389
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: state.message }) });
2257
2390
  }
2258
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className, role: "region", children: [
2259
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2391
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className, role: "region", children: [
2392
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2260
2393
  "button",
2261
2394
  {
2262
2395
  type: "button",
@@ -2270,23 +2403,23 @@ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, clas
2270
2403
  children: texts.cta
2271
2404
  }
2272
2405
  ),
2273
- onDeclined && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2406
+ onDeclined && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
2274
2407
  ] });
2275
2408
  }
2276
2409
 
2277
2410
  // src/defaults/LoadingState.tsx
2278
- var import_jsx_runtime21 = require("react/jsx-runtime");
2411
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2279
2412
  function LoadingState({ message }) {
2280
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: message ?? "Carregando..." }) });
2413
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { role: "status", "aria-live": "polite", style: { padding: 24, textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: message ?? "Carregando..." }) });
2281
2414
  }
2282
2415
 
2283
2416
  // src/defaults/EmptyState.tsx
2284
- var import_jsx_runtime22 = require("react/jsx-runtime");
2417
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2285
2418
  function EmptyState({ title, description, action }) {
2286
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2287
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
2288
- description && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { style: { opacity: 0.7 }, children: description }),
2289
- action && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { style: { marginTop: 16 }, children: action })
2419
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
2420
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
2421
+ description && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { style: { opacity: 0.7 }, children: description }),
2422
+ action && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { marginTop: 16 }, children: action })
2290
2423
  ] });
2291
2424
  }
2292
2425
 
@@ -2332,18 +2465,24 @@ function useLoginForm() {
2332
2465
  const [password, setPassword] = (0, import_react14.useState)("");
2333
2466
  const [submitting, setSubmitting] = (0, import_react14.useState)(false);
2334
2467
  const [error, setError] = (0, import_react14.useState)(null);
2335
- const emailError = (0, import_react14.useMemo)(() => {
2468
+ const [touchedEmail, setTouchedEmail] = (0, import_react14.useState)(false);
2469
+ const [touchedPassword, setTouchedPassword] = (0, import_react14.useState)(false);
2470
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react14.useState)(false);
2471
+ const validateEmail = (0, import_react14.useMemo)(() => {
2336
2472
  if (email.length === 0) return null;
2337
2473
  if (!EMAIL_RE.test(email)) return "Formato de e-mail inv\xE1lido.";
2338
2474
  return null;
2339
2475
  }, [email]);
2340
- const passwordError = (0, import_react14.useMemo)(() => {
2476
+ const validatePassword = (0, import_react14.useMemo)(() => {
2341
2477
  if (password.length === 0) return null;
2342
2478
  if (password.length < MIN_PASSWORD) return `M\xEDnimo de ${MIN_PASSWORD} caracteres.`;
2343
2479
  return null;
2344
2480
  }, [password]);
2345
- const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && emailError === null && passwordError === null && !submitting;
2481
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2482
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
2483
+ const canSubmit = email.length > 0 && password.length >= MIN_PASSWORD && validateEmail === null && validatePassword === null && !submitting;
2346
2484
  const submit = (0, import_react14.useCallback)(async () => {
2485
+ setFormSubmitAttempted(true);
2347
2486
  if (!canSubmit) return false;
2348
2487
  setSubmitting(true);
2349
2488
  setError(null);
@@ -2361,9 +2500,12 @@ function useLoginForm() {
2361
2500
  email,
2362
2501
  setEmail,
2363
2502
  emailError,
2503
+ markEmailTouched: () => setTouchedEmail(true),
2364
2504
  password,
2365
2505
  setPassword,
2366
2506
  passwordError,
2507
+ markPasswordTouched: () => setTouchedPassword(true),
2508
+ formSubmitAttempted,
2367
2509
  submit,
2368
2510
  submitting,
2369
2511
  canSubmit,
@@ -2384,23 +2526,31 @@ function useSignupForm() {
2384
2526
  const [password, setPassword] = (0, import_react15.useState)("");
2385
2527
  const [submitting, setSubmitting] = (0, import_react15.useState)(false);
2386
2528
  const [error, setError] = (0, import_react15.useState)(null);
2387
- const nameError = (0, import_react15.useMemo)(() => {
2529
+ const [touchedName, setTouchedName] = (0, import_react15.useState)(false);
2530
+ const [touchedEmail, setTouchedEmail] = (0, import_react15.useState)(false);
2531
+ const [touchedPassword, setTouchedPassword] = (0, import_react15.useState)(false);
2532
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react15.useState)(false);
2533
+ const validateName = (0, import_react15.useMemo)(() => {
2388
2534
  if (name.length === 0) return null;
2389
2535
  if (name.trim().length < 2) return "Nome muito curto.";
2390
2536
  return null;
2391
2537
  }, [name]);
2392
- const emailError = (0, import_react15.useMemo)(() => {
2538
+ const validateEmail = (0, import_react15.useMemo)(() => {
2393
2539
  if (email.length === 0) return null;
2394
2540
  if (!EMAIL_RE2.test(email)) return "Formato de e-mail inv\xE1lido.";
2395
2541
  return null;
2396
2542
  }, [email]);
2397
- const passwordError = (0, import_react15.useMemo)(() => {
2543
+ const validatePassword = (0, import_react15.useMemo)(() => {
2398
2544
  if (password.length === 0) return null;
2399
2545
  if (password.length < MIN_PASSWORD2) return `M\xEDnimo de ${MIN_PASSWORD2} caracteres.`;
2400
2546
  return null;
2401
2547
  }, [password]);
2402
- const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && nameError === null && emailError === null && passwordError === null && !submitting;
2548
+ const nameError = touchedName || formSubmitAttempted ? validateName : null;
2549
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2550
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
2551
+ const canSubmit = name.trim().length >= 2 && email.length > 0 && password.length >= MIN_PASSWORD2 && validateName === null && validateEmail === null && validatePassword === null && !submitting;
2403
2552
  const submit = (0, import_react15.useCallback)(async () => {
2553
+ setFormSubmitAttempted(true);
2404
2554
  if (!canSubmit) return false;
2405
2555
  setSubmitting(true);
2406
2556
  setError(null);
@@ -2418,12 +2568,16 @@ function useSignupForm() {
2418
2568
  name,
2419
2569
  setName,
2420
2570
  nameError,
2571
+ markNameTouched: () => setTouchedName(true),
2421
2572
  email,
2422
2573
  setEmail,
2423
2574
  emailError,
2575
+ markEmailTouched: () => setTouchedEmail(true),
2424
2576
  password,
2425
2577
  setPassword,
2426
2578
  passwordError,
2579
+ markPasswordTouched: () => setTouchedPassword(true),
2580
+ formSubmitAttempted,
2427
2581
  submit,
2428
2582
  submitting,
2429
2583
  canSubmit,
@@ -2442,13 +2596,17 @@ function useForgotForm() {
2442
2596
  const [submitting, setSubmitting] = (0, import_react16.useState)(false);
2443
2597
  const [sent, setSent] = (0, import_react16.useState)(false);
2444
2598
  const [error, setError] = (0, import_react16.useState)(null);
2445
- const emailError = (0, import_react16.useMemo)(() => {
2599
+ const [touchedEmail, setTouchedEmail] = (0, import_react16.useState)(false);
2600
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react16.useState)(false);
2601
+ const validateEmail = (0, import_react16.useMemo)(() => {
2446
2602
  if (email.length === 0) return null;
2447
2603
  if (!EMAIL_RE3.test(email)) return "Formato de e-mail inv\xE1lido.";
2448
2604
  return null;
2449
2605
  }, [email]);
2450
- const canSubmit = email.length > 0 && emailError === null && !submitting;
2606
+ const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2607
+ const canSubmit = email.length > 0 && validateEmail === null && !submitting;
2451
2608
  const submit = (0, import_react16.useCallback)(async () => {
2609
+ setFormSubmitAttempted(true);
2452
2610
  if (!canSubmit) return false;
2453
2611
  setSubmitting(true);
2454
2612
  setError(null);
@@ -2467,6 +2625,8 @@ function useForgotForm() {
2467
2625
  email,
2468
2626
  setEmail,
2469
2627
  emailError,
2628
+ markEmailTouched: () => setTouchedEmail(true),
2629
+ formSubmitAttempted,
2470
2630
  submit,
2471
2631
  submitting,
2472
2632
  canSubmit,
@@ -2487,24 +2647,30 @@ function useResetForm() {
2487
2647
  const [submitting, setSubmitting] = (0, import_react17.useState)(false);
2488
2648
  const [done, setDone] = (0, import_react17.useState)(false);
2489
2649
  const [error, setError] = (0, import_react17.useState)(null);
2650
+ const [touchedPassword, setTouchedPassword] = (0, import_react17.useState)(false);
2651
+ const [touchedConfirm, setTouchedConfirm] = (0, import_react17.useState)(false);
2652
+ const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react17.useState)(false);
2490
2653
  (0, import_react17.useEffect)(() => {
2491
2654
  if (typeof window === "undefined") return;
2492
2655
  const params = new URLSearchParams(window.location.search);
2493
2656
  const t = params.get("token");
2494
2657
  setToken(t && t.length > 0 ? t : null);
2495
2658
  }, []);
2496
- const passwordError = (0, import_react17.useMemo)(() => {
2659
+ const validatePassword = (0, import_react17.useMemo)(() => {
2497
2660
  if (password.length === 0) return null;
2498
2661
  if (password.length < MIN_PASSWORD3) return `M\xEDnimo de ${MIN_PASSWORD3} caracteres.`;
2499
2662
  return null;
2500
2663
  }, [password]);
2501
- const confirmError = (0, import_react17.useMemo)(() => {
2664
+ const validateConfirm = (0, import_react17.useMemo)(() => {
2502
2665
  if (confirm.length === 0) return null;
2503
2666
  if (confirm !== password) return "Senhas n\xE3o coincidem.";
2504
2667
  return null;
2505
2668
  }, [confirm, password]);
2506
- const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && passwordError === null && confirmError === null && !submitting && !done;
2669
+ const passwordError = touchedPassword || formSubmitAttempted ? validatePassword : null;
2670
+ const confirmError = touchedConfirm || formSubmitAttempted ? validateConfirm : null;
2671
+ const canSubmit = token !== null && password.length >= MIN_PASSWORD3 && confirm === password && validatePassword === null && validateConfirm === null && !submitting && !done;
2507
2672
  const submit = (0, import_react17.useCallback)(async () => {
2673
+ setFormSubmitAttempted(true);
2508
2674
  if (!canSubmit || token === null) return;
2509
2675
  setSubmitting(true);
2510
2676
  setError(null);
@@ -2528,9 +2694,12 @@ function useResetForm() {
2528
2694
  password,
2529
2695
  setPassword,
2530
2696
  passwordError,
2697
+ markPasswordTouched: () => setTouchedPassword(true),
2531
2698
  confirm,
2532
2699
  setConfirm,
2533
2700
  confirmError,
2701
+ markConfirmTouched: () => setTouchedConfirm(true),
2702
+ formSubmitAttempted,
2534
2703
  submit,
2535
2704
  submitting,
2536
2705
  canSubmit,
@@ -2677,20 +2846,20 @@ function useToast() {
2677
2846
 
2678
2847
  // src/RouteBoundary.tsx
2679
2848
  var import_react_router_dom3 = require("react-router-dom");
2680
- var import_jsx_runtime23 = require("react/jsx-runtime");
2849
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2681
2850
  function RouteBoundary({ children }) {
2682
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_router_dom3.Routes, { children: [
2851
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react_router_dom3.Routes, { children: [
2683
2852
  children,
2684
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_router_dom3.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DefaultNotFound, {}) })
2853
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_router_dom3.Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DefaultNotFound, {}) })
2685
2854
  ] });
2686
2855
  }
2687
2856
  function DefaultNotFound() {
2688
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
2857
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { role: "alert", children: "P\xE1gina n\xE3o encontrada" });
2689
2858
  }
2690
2859
 
2691
2860
  // src/PreAuthShell.tsx
2692
2861
  var import_react_router_dom4 = require("react-router-dom");
2693
- var import_jsx_runtime24 = require("react/jsx-runtime");
2862
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2694
2863
  function PreAuthShell({
2695
2864
  basename,
2696
2865
  testRouter,
@@ -2698,9 +2867,9 @@ function PreAuthShell({
2698
2867
  children
2699
2868
  }) {
2700
2869
  if (testRouter === "memory") {
2701
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_router_dom4.MemoryRouter, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_router_dom4.Routes, { children }) });
2870
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_router_dom4.MemoryRouter, { basename, initialEntries: testInitialEntries, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_router_dom4.Routes, { children }) });
2702
2871
  }
2703
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_router_dom4.BrowserRouter, { basename, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_react_router_dom4.Routes, { children }) });
2872
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_router_dom4.BrowserRouter, { basename, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_router_dom4.Routes, { children }) });
2704
2873
  }
2705
2874
 
2706
2875
  // src/OnboardingFlow.tsx
@@ -2721,7 +2890,7 @@ function useOnboardingStep() {
2721
2890
  }
2722
2891
 
2723
2892
  // src/OnboardingFlow.tsx
2724
- var import_jsx_runtime25 = require("react/jsx-runtime");
2893
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2725
2894
  var isFilled = (v) => v != null && v !== "";
2726
2895
  var CURRENT_STEP_FIELD = "currentStep";
2727
2896
  function readPersistedStepIdx(draft) {
@@ -2799,7 +2968,7 @@ function OnboardingFlow({
2799
2968
  `[hook-template] OnboardingFlow: missing screen component for step '${step.id}' (expected key '${step.screen}' in screens prop)`
2800
2969
  );
2801
2970
  }
2802
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Screen, {}) });
2971
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(OnboardingStepContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Screen, {}) });
2803
2972
  }
2804
2973
 
2805
2974
  // src/hooks/useFeature.ts