Nothing12Man commited on
Commit
33b3aaa
·
1 Parent(s): dd496bc

chore: full deployment hardening - force webpack, fix suspense boundary, exclude m1 binaries

Browse files
lifeline-ai/package-lock.json CHANGED
@@ -1583,17 +1583,17 @@
1583
  }
1584
  },
1585
  "node_modules/@typescript-eslint/eslint-plugin": {
1586
- "version": "8.58.0",
1587
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz",
1588
- "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==",
1589
  "dev": true,
1590
  "license": "MIT",
1591
  "dependencies": {
1592
  "@eslint-community/regexpp": "^4.12.2",
1593
- "@typescript-eslint/scope-manager": "8.58.0",
1594
- "@typescript-eslint/type-utils": "8.58.0",
1595
- "@typescript-eslint/utils": "8.58.0",
1596
- "@typescript-eslint/visitor-keys": "8.58.0",
1597
  "ignore": "^7.0.5",
1598
  "natural-compare": "^1.4.0",
1599
  "ts-api-utils": "^2.5.0"
@@ -1606,7 +1606,7 @@
1606
  "url": "https://opencollective.com/typescript-eslint"
1607
  },
1608
  "peerDependencies": {
1609
- "@typescript-eslint/parser": "^8.58.0",
1610
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
1611
  "typescript": ">=4.8.4 <6.1.0"
1612
  }
@@ -1622,17 +1622,17 @@
1622
  }
1623
  },
1624
  "node_modules/@typescript-eslint/parser": {
1625
- "version": "8.58.0",
1626
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz",
1627
- "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==",
1628
  "dev": true,
1629
  "license": "MIT",
1630
  "peer": true,
1631
  "dependencies": {
1632
- "@typescript-eslint/scope-manager": "8.58.0",
1633
- "@typescript-eslint/types": "8.58.0",
1634
- "@typescript-eslint/typescript-estree": "8.58.0",
1635
- "@typescript-eslint/visitor-keys": "8.58.0",
1636
  "debug": "^4.4.3"
1637
  },
1638
  "engines": {
@@ -1648,14 +1648,14 @@
1648
  }
1649
  },
1650
  "node_modules/@typescript-eslint/project-service": {
1651
- "version": "8.58.0",
1652
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz",
1653
- "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==",
1654
  "dev": true,
1655
  "license": "MIT",
1656
  "dependencies": {
1657
- "@typescript-eslint/tsconfig-utils": "^8.58.0",
1658
- "@typescript-eslint/types": "^8.58.0",
1659
  "debug": "^4.4.3"
1660
  },
1661
  "engines": {
@@ -1670,14 +1670,14 @@
1670
  }
1671
  },
1672
  "node_modules/@typescript-eslint/scope-manager": {
1673
- "version": "8.58.0",
1674
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz",
1675
- "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==",
1676
  "dev": true,
1677
  "license": "MIT",
1678
  "dependencies": {
1679
- "@typescript-eslint/types": "8.58.0",
1680
- "@typescript-eslint/visitor-keys": "8.58.0"
1681
  },
1682
  "engines": {
1683
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1688,9 +1688,9 @@
1688
  }
1689
  },
1690
  "node_modules/@typescript-eslint/tsconfig-utils": {
1691
- "version": "8.58.0",
1692
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz",
1693
- "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==",
1694
  "dev": true,
1695
  "license": "MIT",
1696
  "engines": {
@@ -1705,15 +1705,15 @@
1705
  }
1706
  },
1707
  "node_modules/@typescript-eslint/type-utils": {
1708
- "version": "8.58.0",
1709
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz",
1710
- "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==",
1711
  "dev": true,
1712
  "license": "MIT",
1713
  "dependencies": {
1714
- "@typescript-eslint/types": "8.58.0",
1715
- "@typescript-eslint/typescript-estree": "8.58.0",
1716
- "@typescript-eslint/utils": "8.58.0",
1717
  "debug": "^4.4.3",
1718
  "ts-api-utils": "^2.5.0"
1719
  },
@@ -1730,9 +1730,9 @@
1730
  }
1731
  },
1732
  "node_modules/@typescript-eslint/types": {
1733
- "version": "8.58.0",
1734
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz",
1735
- "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==",
1736
  "dev": true,
1737
  "license": "MIT",
1738
  "engines": {
@@ -1744,16 +1744,16 @@
1744
  }
1745
  },
1746
  "node_modules/@typescript-eslint/typescript-estree": {
1747
- "version": "8.58.0",
1748
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz",
1749
- "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==",
1750
  "dev": true,
1751
  "license": "MIT",
1752
  "dependencies": {
1753
- "@typescript-eslint/project-service": "8.58.0",
1754
- "@typescript-eslint/tsconfig-utils": "8.58.0",
1755
- "@typescript-eslint/types": "8.58.0",
1756
- "@typescript-eslint/visitor-keys": "8.58.0",
1757
  "debug": "^4.4.3",
1758
  "minimatch": "^10.2.2",
1759
  "semver": "^7.7.3",
@@ -1824,16 +1824,16 @@
1824
  }
1825
  },
1826
  "node_modules/@typescript-eslint/utils": {
1827
- "version": "8.58.0",
1828
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz",
1829
- "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==",
1830
  "dev": true,
1831
  "license": "MIT",
1832
  "dependencies": {
1833
  "@eslint-community/eslint-utils": "^4.9.1",
1834
- "@typescript-eslint/scope-manager": "8.58.0",
1835
- "@typescript-eslint/types": "8.58.0",
1836
- "@typescript-eslint/typescript-estree": "8.58.0"
1837
  },
1838
  "engines": {
1839
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1848,13 +1848,13 @@
1848
  }
1849
  },
1850
  "node_modules/@typescript-eslint/visitor-keys": {
1851
- "version": "8.58.0",
1852
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz",
1853
- "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==",
1854
  "dev": true,
1855
  "license": "MIT",
1856
  "dependencies": {
1857
- "@typescript-eslint/types": "8.58.0",
1858
  "eslint-visitor-keys": "^5.0.0"
1859
  },
1860
  "engines": {
@@ -2479,9 +2479,9 @@
2479
  "license": "MIT"
2480
  },
2481
  "node_modules/baseline-browser-mapping": {
2482
- "version": "2.10.16",
2483
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz",
2484
- "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==",
2485
  "license": "Apache-2.0",
2486
  "bin": {
2487
  "baseline-browser-mapping": "dist/cli.cjs"
@@ -2550,15 +2550,15 @@
2550
  }
2551
  },
2552
  "node_modules/call-bind": {
2553
- "version": "1.0.8",
2554
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
2555
- "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
2556
  "dev": true,
2557
  "license": "MIT",
2558
  "dependencies": {
2559
- "call-bind-apply-helpers": "^1.0.0",
2560
- "es-define-property": "^1.0.0",
2561
- "get-intrinsic": "^1.2.4",
2562
  "set-function-length": "^1.2.2"
2563
  },
2564
  "engines": {
@@ -2610,9 +2610,9 @@
2610
  }
2611
  },
2612
  "node_modules/caniuse-lite": {
2613
- "version": "1.0.30001786",
2614
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001786.tgz",
2615
- "integrity": "sha512-4oxTZEvqmLLrERwxO76yfKM7acZo310U+v4kqexI2TL1DkkUEMT8UijrxxcnVdxR3qkVf5awGRX+4Z6aPHVKrA==",
2616
  "funding": [
2617
  {
2618
  "type": "opencollective",
@@ -2869,9 +2869,9 @@
2869
  }
2870
  },
2871
  "node_modules/electron-to-chromium": {
2872
- "version": "1.5.331",
2873
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
2874
- "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==",
2875
  "dev": true,
2876
  "license": "ISC"
2877
  },
@@ -2897,9 +2897,9 @@
2897
  }
2898
  },
2899
  "node_modules/es-abstract": {
2900
- "version": "1.24.1",
2901
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
2902
- "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
2903
  "dev": true,
2904
  "license": "MIT",
2905
  "dependencies": {
@@ -3289,7 +3289,6 @@
3289
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
3290
  "dev": true,
3291
  "license": "MIT",
3292
- "peer": true,
3293
  "dependencies": {
3294
  "@rtsao/scc": "^1.1.0",
3295
  "array-includes": "^3.1.9",
@@ -5391,9 +5390,9 @@
5391
  }
5392
  },
5393
  "node_modules/postcss": {
5394
- "version": "8.5.8",
5395
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
5396
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
5397
  "dev": true,
5398
  "funding": [
5399
  {
@@ -5855,14 +5854,14 @@
5855
  }
5856
  },
5857
  "node_modules/side-channel-list": {
5858
- "version": "1.0.0",
5859
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
5860
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
5861
  "dev": true,
5862
  "license": "MIT",
5863
  "dependencies": {
5864
  "es-errors": "^1.3.0",
5865
- "object-inspect": "^1.13.3"
5866
  },
5867
  "engines": {
5868
  "node": ">= 0.4"
@@ -6147,14 +6146,14 @@
6147
  }
6148
  },
6149
  "node_modules/tinyglobby": {
6150
- "version": "0.2.15",
6151
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
6152
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
6153
  "dev": true,
6154
  "license": "MIT",
6155
  "dependencies": {
6156
  "fdir": "^6.5.0",
6157
- "picomatch": "^4.0.3"
6158
  },
6159
  "engines": {
6160
  "node": ">=12.0.0"
@@ -6360,16 +6359,16 @@
6360
  }
6361
  },
6362
  "node_modules/typescript-eslint": {
6363
- "version": "8.58.0",
6364
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz",
6365
- "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==",
6366
  "dev": true,
6367
  "license": "MIT",
6368
  "dependencies": {
6369
- "@typescript-eslint/eslint-plugin": "8.58.0",
6370
- "@typescript-eslint/parser": "8.58.0",
6371
- "@typescript-eslint/typescript-estree": "8.58.0",
6372
- "@typescript-eslint/utils": "8.58.0"
6373
  },
6374
  "engines": {
6375
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 
1583
  }
1584
  },
1585
  "node_modules/@typescript-eslint/eslint-plugin": {
1586
+ "version": "8.58.1",
1587
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz",
1588
+ "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==",
1589
  "dev": true,
1590
  "license": "MIT",
1591
  "dependencies": {
1592
  "@eslint-community/regexpp": "^4.12.2",
1593
+ "@typescript-eslint/scope-manager": "8.58.1",
1594
+ "@typescript-eslint/type-utils": "8.58.1",
1595
+ "@typescript-eslint/utils": "8.58.1",
1596
+ "@typescript-eslint/visitor-keys": "8.58.1",
1597
  "ignore": "^7.0.5",
1598
  "natural-compare": "^1.4.0",
1599
  "ts-api-utils": "^2.5.0"
 
1606
  "url": "https://opencollective.com/typescript-eslint"
1607
  },
1608
  "peerDependencies": {
1609
+ "@typescript-eslint/parser": "^8.58.1",
1610
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
1611
  "typescript": ">=4.8.4 <6.1.0"
1612
  }
 
1622
  }
1623
  },
1624
  "node_modules/@typescript-eslint/parser": {
1625
+ "version": "8.58.1",
1626
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz",
1627
+ "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==",
1628
  "dev": true,
1629
  "license": "MIT",
1630
  "peer": true,
1631
  "dependencies": {
1632
+ "@typescript-eslint/scope-manager": "8.58.1",
1633
+ "@typescript-eslint/types": "8.58.1",
1634
+ "@typescript-eslint/typescript-estree": "8.58.1",
1635
+ "@typescript-eslint/visitor-keys": "8.58.1",
1636
  "debug": "^4.4.3"
1637
  },
1638
  "engines": {
 
1648
  }
1649
  },
1650
  "node_modules/@typescript-eslint/project-service": {
1651
+ "version": "8.58.1",
1652
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz",
1653
+ "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==",
1654
  "dev": true,
1655
  "license": "MIT",
1656
  "dependencies": {
1657
+ "@typescript-eslint/tsconfig-utils": "^8.58.1",
1658
+ "@typescript-eslint/types": "^8.58.1",
1659
  "debug": "^4.4.3"
1660
  },
1661
  "engines": {
 
1670
  }
1671
  },
1672
  "node_modules/@typescript-eslint/scope-manager": {
1673
+ "version": "8.58.1",
1674
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz",
1675
+ "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==",
1676
  "dev": true,
1677
  "license": "MIT",
1678
  "dependencies": {
1679
+ "@typescript-eslint/types": "8.58.1",
1680
+ "@typescript-eslint/visitor-keys": "8.58.1"
1681
  },
1682
  "engines": {
1683
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 
1688
  }
1689
  },
1690
  "node_modules/@typescript-eslint/tsconfig-utils": {
1691
+ "version": "8.58.1",
1692
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz",
1693
+ "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==",
1694
  "dev": true,
1695
  "license": "MIT",
1696
  "engines": {
 
1705
  }
1706
  },
1707
  "node_modules/@typescript-eslint/type-utils": {
1708
+ "version": "8.58.1",
1709
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz",
1710
+ "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==",
1711
  "dev": true,
1712
  "license": "MIT",
1713
  "dependencies": {
1714
+ "@typescript-eslint/types": "8.58.1",
1715
+ "@typescript-eslint/typescript-estree": "8.58.1",
1716
+ "@typescript-eslint/utils": "8.58.1",
1717
  "debug": "^4.4.3",
1718
  "ts-api-utils": "^2.5.0"
1719
  },
 
1730
  }
1731
  },
1732
  "node_modules/@typescript-eslint/types": {
1733
+ "version": "8.58.1",
1734
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz",
1735
+ "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==",
1736
  "dev": true,
1737
  "license": "MIT",
1738
  "engines": {
 
1744
  }
1745
  },
1746
  "node_modules/@typescript-eslint/typescript-estree": {
1747
+ "version": "8.58.1",
1748
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz",
1749
+ "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==",
1750
  "dev": true,
1751
  "license": "MIT",
1752
  "dependencies": {
1753
+ "@typescript-eslint/project-service": "8.58.1",
1754
+ "@typescript-eslint/tsconfig-utils": "8.58.1",
1755
+ "@typescript-eslint/types": "8.58.1",
1756
+ "@typescript-eslint/visitor-keys": "8.58.1",
1757
  "debug": "^4.4.3",
1758
  "minimatch": "^10.2.2",
1759
  "semver": "^7.7.3",
 
1824
  }
1825
  },
1826
  "node_modules/@typescript-eslint/utils": {
1827
+ "version": "8.58.1",
1828
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz",
1829
+ "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==",
1830
  "dev": true,
1831
  "license": "MIT",
1832
  "dependencies": {
1833
  "@eslint-community/eslint-utils": "^4.9.1",
1834
+ "@typescript-eslint/scope-manager": "8.58.1",
1835
+ "@typescript-eslint/types": "8.58.1",
1836
+ "@typescript-eslint/typescript-estree": "8.58.1"
1837
  },
1838
  "engines": {
1839
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 
1848
  }
1849
  },
1850
  "node_modules/@typescript-eslint/visitor-keys": {
1851
+ "version": "8.58.1",
1852
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz",
1853
+ "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==",
1854
  "dev": true,
1855
  "license": "MIT",
1856
  "dependencies": {
1857
+ "@typescript-eslint/types": "8.58.1",
1858
  "eslint-visitor-keys": "^5.0.0"
1859
  },
1860
  "engines": {
 
2479
  "license": "MIT"
2480
  },
2481
  "node_modules/baseline-browser-mapping": {
2482
+ "version": "2.10.17",
2483
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.17.tgz",
2484
+ "integrity": "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==",
2485
  "license": "Apache-2.0",
2486
  "bin": {
2487
  "baseline-browser-mapping": "dist/cli.cjs"
 
2550
  }
2551
  },
2552
  "node_modules/call-bind": {
2553
+ "version": "1.0.9",
2554
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
2555
+ "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
2556
  "dev": true,
2557
  "license": "MIT",
2558
  "dependencies": {
2559
+ "call-bind-apply-helpers": "^1.0.2",
2560
+ "es-define-property": "^1.0.1",
2561
+ "get-intrinsic": "^1.3.0",
2562
  "set-function-length": "^1.2.2"
2563
  },
2564
  "engines": {
 
2610
  }
2611
  },
2612
  "node_modules/caniuse-lite": {
2613
+ "version": "1.0.30001787",
2614
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz",
2615
+ "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==",
2616
  "funding": [
2617
  {
2618
  "type": "opencollective",
 
2869
  }
2870
  },
2871
  "node_modules/electron-to-chromium": {
2872
+ "version": "1.5.334",
2873
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz",
2874
+ "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==",
2875
  "dev": true,
2876
  "license": "ISC"
2877
  },
 
2897
  }
2898
  },
2899
  "node_modules/es-abstract": {
2900
+ "version": "1.24.2",
2901
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
2902
+ "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==",
2903
  "dev": true,
2904
  "license": "MIT",
2905
  "dependencies": {
 
3289
  "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
3290
  "dev": true,
3291
  "license": "MIT",
 
3292
  "dependencies": {
3293
  "@rtsao/scc": "^1.1.0",
3294
  "array-includes": "^3.1.9",
 
5390
  }
5391
  },
5392
  "node_modules/postcss": {
5393
+ "version": "8.5.9",
5394
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
5395
+ "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
5396
  "dev": true,
5397
  "funding": [
5398
  {
 
5854
  }
5855
  },
5856
  "node_modules/side-channel-list": {
5857
+ "version": "1.0.1",
5858
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
5859
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
5860
  "dev": true,
5861
  "license": "MIT",
5862
  "dependencies": {
5863
  "es-errors": "^1.3.0",
5864
+ "object-inspect": "^1.13.4"
5865
  },
5866
  "engines": {
5867
  "node": ">= 0.4"
 
6146
  }
6147
  },
6148
  "node_modules/tinyglobby": {
6149
+ "version": "0.2.16",
6150
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
6151
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
6152
  "dev": true,
6153
  "license": "MIT",
6154
  "dependencies": {
6155
  "fdir": "^6.5.0",
6156
+ "picomatch": "^4.0.4"
6157
  },
6158
  "engines": {
6159
  "node": ">=12.0.0"
 
6359
  }
6360
  },
6361
  "node_modules/typescript-eslint": {
6362
+ "version": "8.58.1",
6363
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.1.tgz",
6364
+ "integrity": "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==",
6365
  "dev": true,
6366
  "license": "MIT",
6367
  "dependencies": {
6368
+ "@typescript-eslint/eslint-plugin": "8.58.1",
6369
+ "@typescript-eslint/parser": "8.58.1",
6370
+ "@typescript-eslint/typescript-estree": "8.58.1",
6371
+ "@typescript-eslint/utils": "8.58.1"
6372
  },
6373
  "engines": {
6374
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
lifeline-ai/package.json CHANGED
@@ -6,7 +6,7 @@
6
  "dev": "next dev",
7
  "clean": "rm -rf node_modules package-lock.json .next",
8
  "reinstall": "npm run clean && npm install",
9
- "build": "next build",
10
  "start": "next start",
11
  "lint": "eslint"
12
  },
 
6
  "dev": "next dev",
7
  "clean": "rm -rf node_modules package-lock.json .next",
8
  "reinstall": "npm run clean && npm install",
9
+ "build": "TURBOPACK=0 next build",
10
  "start": "next start",
11
  "lint": "eslint"
12
  },
lifeline-ai/src/app/hospitals/HospitalsClient.tsx ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { useSearchParams } from "next/navigation";
5
+ import { getHospitals, Hospital } from "@/lib/api";
6
+ import { Button, Card, Container, Pill, TopNav } from "@/components/ui";
7
+ import { SOSButton } from "@/components/SOSButton";
8
+
9
+ function Stars({ rating }: { rating: number }) {
10
+ const full = Math.round(rating);
11
+ return (
12
+ <div className="flex items-center gap-1">
13
+ {Array.from({ length: 5 }).map((_, i) => (
14
+ <span key={i} className={i < full ? "text-amber-500" : "text-slate-200"}>
15
+
16
+ </span>
17
+ ))}
18
+ <span className="ml-2 text-xs font-semibold text-slate-600">{rating.toFixed(1)}</span>
19
+ </div>
20
+ );
21
+ }
22
+
23
+ function AvailabilityPill({ v }: { v: Hospital["availability"] }) {
24
+ if (v === "busy") return <Pill className="bg-orange-500 text-white">Busy</Pill>;
25
+ if (v === "limited") return <Pill className="bg-blue-600 text-white">Limited</Pill>;
26
+ return <Pill className="bg-emerald-600 text-white">Open</Pill>;
27
+ }
28
+
29
+ export default function HospitalsClient() {
30
+ const sp = useSearchParams();
31
+ const initialLocation = sp.get("location") || "Downtown";
32
+
33
+ const [location, setLocation] = useState(initialLocation);
34
+ const [sort, setSort] = useState<"best_rated" | "closest" | "fastest_route">("closest");
35
+ const [hospitals, setHospitals] = useState<Hospital[]>([]);
36
+ const [geo, setGeo] = useState<{ lat: number; lng: number } | null>(null);
37
+
38
+ useEffect(() => {
39
+ let cancelled = false;
40
+ getHospitals(location, sort, geo?.lat ?? null, geo?.lng ?? null)
41
+ .then((r) => {
42
+ if (cancelled) return;
43
+ setHospitals(r.hospitals);
44
+ })
45
+ .catch(() => {
46
+ if (cancelled) return;
47
+ setHospitals([]);
48
+ });
49
+ return () => {
50
+ cancelled = true;
51
+ };
52
+ }, [location, sort, geo]);
53
+
54
+ function enableGeolocation() {
55
+ if (!navigator.geolocation) return;
56
+ navigator.geolocation.getCurrentPosition((p) => {
57
+ const lat = p.coords.latitude;
58
+ const lng = p.coords.longitude;
59
+ setGeo({ lat, lng });
60
+ setLocation(`${lat.toFixed(4)},${lng.toFixed(4)}`);
61
+ getHospitals(`${lat.toFixed(4)},${lng.toFixed(4)}`, sort, lat, lng)
62
+ .then((r) => setHospitals(r.hospitals))
63
+ .catch(() => {});
64
+ });
65
+ }
66
+
67
+ const bounds = useMemo(() => {
68
+ if (!hospitals.length) return null;
69
+ const lats = hospitals.map((h) => h.lat);
70
+ const lngs = hospitals.map((h) => h.lng);
71
+ return {
72
+ minLat: Math.min(...lats),
73
+ maxLat: Math.max(...lats),
74
+ minLng: Math.min(...lngs),
75
+ maxLng: Math.max(...lngs),
76
+ };
77
+ }, [hospitals]);
78
+
79
+ return (
80
+ <div className="min-h-full bg-gradient-to-b from-white to-slate-50">
81
+ <TopNav />
82
+ <main className="py-10 sm:py-14">
83
+ <Container>
84
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
85
+ <div>
86
+ <Pill className="bg-blue-50 text-blue-700">Nearby hospital finder</Pill>
87
+ <h1 className="mt-3 text-3xl font-extrabold tracking-tight text-slate-900">Hospitals near you</h1>
88
+ <p className="mt-2 text-slate-600">
89
+ Compare <span className="font-semibold">best rated</span>, <span className="font-semibold">closest</span>, or{" "}
90
+ <span className="font-semibold">fastest route</span>.
91
+ </p>
92
+ </div>
93
+ <div className="grid gap-3 sm:grid-cols-2">
94
+ <div>
95
+ <div className="text-xs font-semibold text-slate-600">Location</div>
96
+ <input
97
+ value={location}
98
+ onChange={(e) => setLocation(e.target.value)}
99
+ aria-label="Location"
100
+ className="mt-2 w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100"
101
+ />
102
+ </div>
103
+ <div>
104
+ <div className="text-xs font-semibold text-slate-600">Sort</div>
105
+ <div className="mt-2 flex gap-2">
106
+ <Button variant={sort === "best_rated" ? "primary" : "secondary"} onClick={() => setSort("best_rated")}>
107
+ Best rated
108
+ </Button>
109
+ <Button variant={sort === "closest" ? "primary" : "secondary"} onClick={() => setSort("closest")}>
110
+ Closest
111
+ </Button>
112
+ <Button variant={sort === "fastest_route" ? "primary" : "secondary"} onClick={() => setSort("fastest_route")}>
113
+ Fastest
114
+ </Button>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ <div className="sm:ml-auto">
119
+ <Button variant="secondary" onClick={enableGeolocation}>
120
+ Use my location
121
+ </Button>
122
+ </div>
123
+ </div>
124
+
125
+ <div className="mt-8 grid gap-6 lg:grid-cols-3">
126
+ <div className="lg:col-span-2 space-y-4">
127
+ {!hospitals.length && <div className="text-sm text-slate-600">Loading hospitals…</div>}
128
+ {hospitals.map((h) => {
129
+ const erWait = Math.max(8, Math.round(h.eta_minutes * 1.2 + (5 - h.rating) * 4));
130
+ const doctorMins = Math.max(6, Math.round(h.eta_minutes * 0.65));
131
+ const beds =
132
+ h.availability === "open"
133
+ ? "Beds: 12 available"
134
+ : h.availability === "limited"
135
+ ? "Beds: 4 available"
136
+ : "Beds: 1 available";
137
+ return (
138
+ <Card key={h.id}>
139
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between group">
140
+ <div className="space-y-2">
141
+ <div className="flex items-center gap-2">
142
+ <div className="text-lg font-extrabold text-slate-900">{h.name}</div>
143
+ <AvailabilityPill v={h.availability} />
144
+ <Pill className="bg-emerald-100 text-emerald-800">Verified hospital</Pill>
145
+ </div>
146
+ <Stars rating={h.rating} />
147
+ <div className="text-sm text-slate-600">{h.address}</div>
148
+ <div className="flex flex-wrap gap-2 text-xs">
149
+ <Pill className="bg-blue-50 text-blue-700">Doctor available in {doctorMins} mins</Pill>
150
+ <Pill className="bg-slate-100 text-slate-700">ER wait {erWait} mins</Pill>
151
+ <Pill className="bg-violet-100 text-violet-700">{beds}</Pill>
152
+ </div>
153
+ <div className="flex flex-wrap gap-2">
154
+ {h.specialties.slice(0, 5).map((s) => (
155
+ <Pill key={s} className="bg-slate-100 text-slate-700">
156
+ {s} • verified specialist
157
+ </Pill>
158
+ ))}
159
+ </div>
160
+ </div>
161
+ <div className="grid gap-2 rounded-2xl border border-slate-200 bg-slate-50 p-4 transition group-hover:-translate-y-0.5">
162
+ <div className="text-xs font-semibold text-slate-600">Distance</div>
163
+ <div className="text-sm font-extrabold text-slate-900">{h.distance_km.toFixed(1)} km</div>
164
+ <div className="text-xs font-semibold text-slate-600">ETA</div>
165
+ <div className="text-sm font-extrabold text-slate-900">{h.eta_minutes} min</div>
166
+ {geo && (
167
+ <a
168
+ className="text-xs font-semibold text-blue-700 hover:underline"
169
+ target="_blank"
170
+ rel="noreferrer"
171
+ href={`https://www.google.com/maps/dir/?api=1&origin=${geo.lat},${geo.lng}&destination=${h.lat},${h.lng}&travelmode=driving`}
172
+ >
173
+ Open live route
174
+ </a>
175
+ )}
176
+ <a className="text-xs font-semibold text-blue-700 hover:underline" href={`tel:${h.phone}`}>
177
+ Call {h.phone}
178
+ </a>
179
+ </div>
180
+ </div>
181
+ </Card>
182
+ );
183
+ })}
184
+ </div>
185
+
186
+ <div className="space-y-6">
187
+ <Card>
188
+ <div className="text-sm font-semibold text-slate-900">Map (demo)</div>
189
+ <p className="mt-2 text-sm text-slate-600">
190
+ Demo map pins. In production, replace with Google Maps / Mapbox. Pins are plotted from mock hospital coordinates.
191
+ </p>
192
+ <div className="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-slate-50">
193
+ <div className="relative h-[280px] w-full">
194
+ <div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_20%,rgba(59,130,246,0.18),transparent_35%),radial-gradient(circle_at_70%_70%,rgba(16,185,129,0.18),transparent_40%)]" />
195
+ <div className="absolute inset-0 p-4">
196
+ {bounds &&
197
+ hospitals.map((h) => {
198
+ const x = ((h.lng - bounds.minLng) / (bounds.maxLng - bounds.minLng || 1)) * 100;
199
+ const y = (1 - (h.lat - bounds.minLat) / (bounds.maxLat - bounds.minLat || 1)) * 100;
200
+ return (
201
+ <div
202
+ key={h.id}
203
+ className="absolute -translate-x-1/2 -translate-y-1/2"
204
+ style={{ left: `${x}%`, top: `${y}%` }}
205
+ title={h.name}
206
+ >
207
+ <div className="grid h-9 w-9 place-items-center rounded-full bg-blue-600 text-white shadow-md">
208
+ +
209
+ </div>
210
+ </div>
211
+ );
212
+ })}
213
+ <div className="absolute bottom-3 left-3 rounded-xl bg-white/90 px-3 py-2 text-xs font-semibold text-slate-700 shadow-sm">
214
+ {sort === "best_rated" ? "Best rated" : sort === "fastest_route" ? "Fastest route" : "Closest"}
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </Card>
220
+ </div>
221
+ </div>
222
+ </Container>
223
+ </main>
224
+ <SOSButton />
225
+ </div>
226
+ );
227
+ }
lifeline-ai/src/app/hospitals/page.tsx CHANGED
@@ -1,220 +1,21 @@
1
- "use client";
 
2
 
3
- import { useEffect, useMemo, useState } from "react";
4
- import { useSearchParams } from "next/navigation";
5
- import { getHospitals, Hospital } from "@/lib/api";
6
- import { Button, Card, Container, Pill, TopNav } from "@/components/ui";
7
- import { SOSButton } from "@/components/SOSButton";
8
-
9
- function Stars({ rating }: { rating: number }) {
10
- const full = Math.round(rating);
11
- return (
12
- <div className="flex items-center gap-1">
13
- {Array.from({ length: 5 }).map((_, i) => (
14
- <span key={i} className={i < full ? "text-amber-500" : "text-slate-200"}>
15
-
16
- </span>
17
- ))}
18
- <span className="ml-2 text-xs font-semibold text-slate-600">{rating.toFixed(1)}</span>
19
- </div>
20
- );
21
- }
22
-
23
- function AvailabilityPill({ v }: { v: Hospital["availability"] }) {
24
- if (v === "busy") return <Pill className="bg-orange-500 text-white">Busy</Pill>;
25
- if (v === "limited") return <Pill className="bg-blue-600 text-white">Limited</Pill>;
26
- return <Pill className="bg-emerald-600 text-white">Open</Pill>;
27
- }
28
 
29
  export default function HospitalsPage() {
30
- const sp = useSearchParams();
31
- const initialLocation = sp.get("location") || "Downtown";
32
-
33
- const [location, setLocation] = useState(initialLocation);
34
- const [sort, setSort] = useState<"best_rated" | "closest" | "fastest_route">("closest");
35
- const [hospitals, setHospitals] = useState<Hospital[]>([]);
36
- const [geo, setGeo] = useState<{ lat: number; lng: number } | null>(null);
37
-
38
- useEffect(() => {
39
- let cancelled = false;
40
- getHospitals(location, sort, geo?.lat ?? null, geo?.lng ?? null)
41
- .then((r) => {
42
- if (cancelled) return;
43
- setHospitals(r.hospitals);
44
- })
45
- .catch(() => {
46
- if (cancelled) return;
47
- setHospitals([]);
48
- });
49
- return () => {
50
- cancelled = true;
51
- };
52
- }, [location, sort, geo]);
53
-
54
- function enableGeolocation() {
55
- if (!navigator.geolocation) return;
56
- navigator.geolocation.getCurrentPosition((p) => {
57
- const lat = p.coords.latitude;
58
- const lng = p.coords.longitude;
59
- setGeo({ lat, lng });
60
- // Update textual location for visibility
61
- setLocation(`${lat.toFixed(4)},${lng.toFixed(4)}`);
62
- // Immediately fetch hospitals for this location
63
- getHospitals(`${lat.toFixed(4)},${lng.toFixed(4)}`, sort, lat, lng).then((r) => setHospitals(r.hospitals)).catch(() => {});
64
- });
65
- }
66
-
67
- const bounds = useMemo(() => {
68
- if (!hospitals.length) return null;
69
- const lats = hospitals.map((h) => h.lat);
70
- const lngs = hospitals.map((h) => h.lng);
71
- const minLat = Math.min(...lats);
72
- const maxLat = Math.max(...lats);
73
- const minLng = Math.min(...lngs);
74
- const maxLng = Math.max(...lngs);
75
- return { minLat, maxLat, minLng, maxLng };
76
- }, [hospitals]);
77
-
78
  return (
79
- <div className="min-h-full bg-gradient-to-b from-white to-slate-50">
80
- <TopNav />
81
- <main className="py-10 sm:py-14">
82
- <Container>
83
- <div className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
84
- <div>
85
- <Pill className="bg-blue-50 text-blue-700">Nearby hospital finder</Pill>
86
- <h1 className="mt-3 text-3xl font-extrabold tracking-tight text-slate-900">Hospitals near you</h1>
87
- <p className="mt-2 text-slate-600">
88
- Compare <span className="font-semibold">best rated</span>, <span className="font-semibold">closest</span>, or{" "}
89
- <span className="font-semibold">fastest route</span>.
90
- </p>
91
- </div>
92
- <div className="grid gap-3 sm:grid-cols-2">
93
- <div>
94
- <div className="text-xs font-semibold text-slate-600">Location</div>
95
- <input
96
- value={location}
97
- onChange={(e) => setLocation(e.target.value)}
98
- aria-label="Location"
99
- className="mt-2 w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-100"
100
- />
101
- </div>
102
- <div>
103
- <div className="text-xs font-semibold text-slate-600">Sort</div>
104
- <div className="mt-2 flex gap-2">
105
- <Button variant={sort === "best_rated" ? "primary" : "secondary"} onClick={() => setSort("best_rated")}>
106
- Best rated
107
- </Button>
108
- <Button variant={sort === "closest" ? "primary" : "secondary"} onClick={() => setSort("closest")}>
109
- Closest
110
- </Button>
111
- <Button variant={sort === "fastest_route" ? "primary" : "secondary"} onClick={() => setSort("fastest_route")}>
112
- Fastest
113
- </Button>
114
- </div>
115
- </div>
116
- </div>
117
- <div className="sm:ml-auto">
118
- <Button variant="secondary" onClick={enableGeolocation}>
119
- Use my location
120
- </Button>
121
- </div>
122
- </div>
123
-
124
- <div className="mt-8 grid gap-6 lg:grid-cols-3">
125
- <div className="lg:col-span-2 space-y-4">
126
- {!hospitals.length && <div className="text-sm text-slate-600">Loading hospitals…</div>}
127
- {hospitals.map((h) => {
128
- const erWait = Math.max(8, Math.round(h.eta_minutes * 1.2 + (5 - h.rating) * 4));
129
- const doctorMins = Math.max(6, Math.round(h.eta_minutes * 0.65));
130
- const beds = h.availability === "open" ? "Beds: 12 available" : h.availability === "limited" ? "Beds: 4 available" : "Beds: 1 available";
131
- return (
132
- <Card key={h.id}>
133
- <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between group">
134
- <div className="space-y-2">
135
- <div className="flex items-center gap-2">
136
- <div className="text-lg font-extrabold text-slate-900">{h.name}</div>
137
- <AvailabilityPill v={h.availability} />
138
- <Pill className="bg-emerald-100 text-emerald-800">Verified hospital</Pill>
139
- </div>
140
- <Stars rating={h.rating} />
141
- <div className="text-sm text-slate-600">{h.address}</div>
142
- <div className="flex flex-wrap gap-2 text-xs">
143
- <Pill className="bg-blue-50 text-blue-700">Doctor available in {doctorMins} mins</Pill>
144
- <Pill className="bg-slate-100 text-slate-700">ER wait {erWait} mins</Pill>
145
- <Pill className="bg-violet-100 text-violet-700">{beds}</Pill>
146
- </div>
147
- <div className="flex flex-wrap gap-2">
148
- {h.specialties.slice(0, 5).map((s) => (
149
- <Pill key={s} className="bg-slate-100 text-slate-700">
150
- {s} • verified specialist
151
- </Pill>
152
- ))}
153
- </div>
154
- </div>
155
- <div className="grid gap-2 rounded-2xl border border-slate-200 bg-slate-50 p-4 transition group-hover:-translate-y-0.5">
156
- <div className="text-xs font-semibold text-slate-600">Distance</div>
157
- <div className="text-sm font-extrabold text-slate-900">{h.distance_km.toFixed(1)} km</div>
158
- <div className="text-xs font-semibold text-slate-600">ETA</div>
159
- <div className="text-sm font-extrabold text-slate-900">{h.eta_minutes} min</div>
160
- {geo && (
161
- <a
162
- className="text-xs font-semibold text-blue-700 hover:underline"
163
- target="_blank"
164
- rel="noreferrer"
165
- href={`https://www.google.com/maps/dir/?api=1&origin=${geo.lat},${geo.lng}&destination=${h.lat},${h.lng}&travelmode=driving`}
166
- >
167
- Open live route
168
- </a>
169
- )}
170
- <a className="text-xs font-semibold text-blue-700 hover:underline" href={`tel:${h.phone}`}>
171
- Call {h.phone}
172
- </a>
173
- </div>
174
- </div>
175
- </Card>
176
- )})}
177
- </div>
178
-
179
- <div className="space-y-6">
180
- <Card>
181
- <div className="text-sm font-semibold text-slate-900">Map (demo)</div>
182
- <p className="mt-2 text-sm text-slate-600">
183
- Demo map pins. In production, replace with Google Maps / Mapbox. Pins are plotted from mock hospital coordinates.
184
- </p>
185
- <div className="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-slate-50">
186
- <div className="relative h-[280px] w-full">
187
- <div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_20%,rgba(59,130,246,0.18),transparent_35%),radial-gradient(circle_at_70%_70%,rgba(16,185,129,0.18),transparent_40%)]" />
188
- <div className="absolute inset-0 p-4">
189
- {bounds && hospitals.map((h) => {
190
- const x = ((h.lng - bounds.minLng) / (bounds.maxLng - bounds.minLng || 1)) * 100;
191
- const y = (1 - (h.lat - bounds.minLat) / (bounds.maxLat - bounds.minLat || 1)) * 100;
192
- return (
193
- <div
194
- key={h.id}
195
- className="absolute -translate-x-1/2 -translate-y-1/2"
196
- style={{ left: `${x}%`, top: `${y}%` }}
197
- title={h.name}
198
- >
199
- <div className="grid h-9 w-9 place-items-center rounded-full bg-blue-600 text-white shadow-md">
200
- +
201
- </div>
202
- </div>
203
- );
204
- })}
205
- <div className="absolute bottom-3 left-3 rounded-xl bg-white/90 px-3 py-2 text-xs font-semibold text-slate-700 shadow-sm">
206
- {sort === "best_rated" ? "Best rated" : sort === "fastest_route" ? "Fastest route" : "Closest"}
207
- </div>
208
- </div>
209
- </div>
210
- </div>
211
- </Card>
212
- </div>
213
- </div>
214
- </Container>
215
- </main>
216
- <SOSButton />
217
- </div>
218
  );
219
  }
220
-
 
1
+ import { Suspense } from "react";
2
+ import HospitalsClient from "./HospitalsClient";
3
 
4
+ export const metadata = {
5
+ title: "Hospitals Near You | LifeLine AI",
6
+ description: "Find the best rated, closest, or fastest route to nearby hospitals.",
7
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  export default function HospitalsPage() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  return (
11
+ <Suspense
12
+ fallback={
13
+ <div className="flex min-h-screen items-center justify-center text-slate-600 text-sm">
14
+ Loading hospitals…
15
+ </div>
16
+ }
17
+ >
18
+ <HospitalsClient />
19
+ </Suspense>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  );
21
  }