six-degrees 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +32 -4
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +16 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/backup/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restore/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +3 -3
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +4 -4
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +18 -18
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +19 -19
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/insights/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/insights/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/insights/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/insights/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/insights/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/insights/page.js +13 -0
- package/.next/standalone/.next/server/app/insights/page.js.map +5 -0
- package/.next/standalone/.next/server/app/insights/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/insights/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/insights.html +1 -0
- package/.next/standalone/.next/server/app/insights.meta +26 -0
- package/.next/standalone/.next/server/app/insights.rsc +35 -0
- package/.next/standalone/.next/server/app/insights.segments/_full.segment.rsc +35 -0
- package/.next/standalone/.next/server/app/insights.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/insights.segments/insights/__PAGE__.segment.rsc +33 -0
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +18 -18
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +19 -19
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +19 -19
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +19 -19
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +21 -20
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +18 -18
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +19 -19
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +19 -19
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +19 -19
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +18 -18
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +18 -18
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +16 -16
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__063s0x7._.js → [root-of-the-server]__0r-0t-9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ra6xyg._.js +15 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20rwfdw._.js → [root-of-the-server]__0rcpjad._.js} +5 -2
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +9 -9
- package/.next/standalone/.next/server/chunks/{lib_rpc_0jnfp2e.js → lib_rpc_00nndai.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0b17r6v.js → lib_rpc_03ibb-x.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0doq0t-.js → lib_rpc_03ri3ji.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_0fd2rlw.js → lib_rpc_078ce48.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0a69mgr.js +3 -0
- package/.next/standalone/.next/server/chunks/{lib_rpc_005abye.js → lib_rpc_0cej3t8.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0cmh4g6.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0nx2_pc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r79-w1.js +3 -0
- package/.next/standalone/.next/server/chunks/{lib_rpc_0is4qoc.js → lib_rpc_0t552z6.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0yx8ynd.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_12p-o9-.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1466dnb.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1sryxv6.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1trn-x5.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1ygu_mv.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egamyq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_04am_h8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0gds903._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0klqt9z._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1k9qfsk._.js → _14jak5s._.js} +6 -2
- package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1cj2yog._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1hdqw0-._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0gy11ar._.js → _1r62six._.js} +6 -2
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_insights_page_actions_0-0tf75.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_01o3i5y._.js → app_components_settings_SectorPicker_1c-29qw.js} +2 -6
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_insights_page_1d_chja.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -14
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +3 -3
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +4 -0
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/prefetch-hints.json +14 -0
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/02wnf8dkxegxg.js +34 -0
- package/.next/standalone/.next/static/chunks/0838_s6w-i_ik.js +1 -0
- package/.next/{static/chunks/2eo9pqq9gay3r.js → standalone/.next/static/chunks/0ka4prpk3w9wn.js} +4 -4
- package/.next/standalone/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
- package/.next/standalone/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
- package/.next/standalone/.next/static/chunks/112mv-com-mqa.js +21 -0
- package/.next/{static/chunks/44yn0tcd9dfk3.js → standalone/.next/static/chunks/1bkt_hhjzvbh8.js} +1 -1
- package/.next/standalone/.next/static/chunks/1cmphb3kh2ers.css +3 -0
- package/.next/{static/chunks/1qbjermbf68xj.js → standalone/.next/static/chunks/1d8yv3k_atka7.js} +1 -1
- package/.next/standalone/.next/static/chunks/23hc51oa4vkhc.js +5 -0
- package/.next/standalone/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
- package/.next/standalone/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
- package/.next/{static/chunks/2zf4jmz9l3li2.js → standalone/.next/static/chunks/2w-y83mjlpnse.js} +1 -1
- package/.next/standalone/.next/static/chunks/2ws1xf293j-tm.js +5 -0
- package/.next/standalone/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
- package/.next/standalone/.next/static/chunks/3opgg1efrx8q6.js +1 -0
- package/.next/standalone/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
- package/.next/{static/chunks/0an_b6zi1r_qq.js → standalone/.next/static/chunks/3y40z6of3r9py.js} +1 -5
- package/.next/standalone/.next/static/chunks/403dpflln8ah5.js +1 -0
- package/.next/standalone/CHANGELOG.md +60 -0
- package/.next/standalone/README.md +25 -1
- package/.next/standalone/SECURITY.md +8 -0
- package/.next/standalone/app/api/outreach/route.js +13 -73
- package/.next/standalone/app/api/scraper/route.js +97 -4
- package/.next/standalone/app/components/AppHeader.js +22 -6
- package/.next/standalone/app/components/AppTabs.js +4 -0
- package/.next/standalone/app/components/AutoConnect.js +286 -0
- package/.next/standalone/app/components/ChainView.js +75 -2
- package/.next/standalone/app/components/FilterPanel.js +8 -1
- package/.next/standalone/app/components/LinkedInLimits.js +38 -13
- package/.next/standalone/app/components/OutlinkQuest.js +32 -11
- package/.next/standalone/app/components/ScanStatusBar.js +18 -3
- package/.next/standalone/app/components/ScanTrail.js +13 -3
- package/.next/standalone/app/components/SeparationView.js +6 -22
- package/.next/standalone/app/components/Sidebar.js +147 -38
- package/.next/standalone/app/components/insights/Boards.js +275 -0
- package/.next/standalone/app/components/insights/PeopleView.js +266 -0
- package/.next/standalone/app/components/insights/Rail.js +156 -0
- package/.next/standalone/app/components/insights/Report.js +382 -0
- package/.next/standalone/app/components/insights/parts.js +239 -0
- package/.next/standalone/app/components/settings/UsageSection.js +50 -3
- package/.next/standalone/app/globals.css +2 -3
- package/.next/standalone/app/insights/page.js +220 -0
- package/.next/standalone/app/page.js +1 -17
- package/.next/standalone/app/queue/page.js +30 -18
- package/.next/standalone/app/setup/page.js +16 -9
- package/.next/standalone/lib/auto-connect.js +145 -0
- package/.next/standalone/lib/companies.js +1 -1
- package/.next/standalone/lib/in-progress.js +46 -0
- package/.next/standalone/lib/insights-board.js +613 -0
- package/.next/standalone/lib/linkedin-limits.js +36 -11
- package/.next/standalone/lib/requests-client.js +10 -0
- package/.next/standalone/lib/requests.js +124 -0
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scraper-client.js +18 -5
- package/.next/standalone/lib/settings.js +3 -0
- package/.next/standalone/lib/usage.js +5 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +569 -9
- package/.next/static/chunks/02wnf8dkxegxg.js +34 -0
- package/.next/static/chunks/0838_s6w-i_ik.js +1 -0
- package/.next/{standalone/.next/static/chunks/2eo9pqq9gay3r.js → static/chunks/0ka4prpk3w9wn.js} +4 -4
- package/.next/static/chunks/0s_xxwp7z88vv.js +1 -0
- package/.next/static/chunks/1-nnl8amfm4yh.js +57 -0
- package/.next/static/chunks/112mv-com-mqa.js +21 -0
- package/.next/{standalone/.next/static/chunks/44yn0tcd9dfk3.js → static/chunks/1bkt_hhjzvbh8.js} +1 -1
- package/.next/static/chunks/1cmphb3kh2ers.css +3 -0
- package/.next/{standalone/.next/static/chunks/1qbjermbf68xj.js → static/chunks/1d8yv3k_atka7.js} +1 -1
- package/.next/static/chunks/23hc51oa4vkhc.js +5 -0
- package/.next/static/chunks/27n8k9wxlz5-7.js +1 -0
- package/.next/static/chunks/{0p3g5tfostsep.js → 2q9i7pcxcnph1.js} +2 -2
- package/.next/{standalone/.next/static/chunks/2zf4jmz9l3li2.js → static/chunks/2w-y83mjlpnse.js} +1 -1
- package/.next/static/chunks/2ws1xf293j-tm.js +5 -0
- package/.next/static/chunks/3cqn-r-y5rrw1.js +5 -0
- package/.next/static/chunks/3opgg1efrx8q6.js +1 -0
- package/.next/static/chunks/3p51fpxxi4d5y.js +34 -0
- package/.next/{standalone/.next/static/chunks/0an_b6zi1r_qq.js → static/chunks/3y40z6of3r9py.js} +1 -5
- package/.next/static/chunks/403dpflln8ah5.js +1 -0
- package/CHANGELOG.md +60 -0
- package/README.md +25 -1
- package/SECURITY.md +8 -0
- package/lib/auto-connect.js +145 -0
- package/lib/companies.js +1 -1
- package/lib/in-progress.js +46 -0
- package/lib/insights-board.js +613 -0
- package/lib/linkedin-limits.js +36 -11
- package/lib/requests-client.js +10 -0
- package/lib/requests.js +124 -0
- package/lib/scan-state.js +1 -0
- package/lib/scraper-client.js +18 -5
- package/lib/settings.js +3 -0
- package/lib/usage.js +5 -0
- package/package.json +1 -1
- package/scripts/scrape.py +569 -9
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_4y8z7._.js +0 -8
- package/.next/standalone/.next/server/chunks/_02twlca._.js +0 -10
- package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +0 -7
- package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +0 -3
- package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +0 -21
- package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
- package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +0 -34
- package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
- package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +0 -57
- package/.next/standalone/.next/static/chunks/260bomtr214m8.js +0 -34
- package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +0 -34
- package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +0 -1
- package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +0 -1
- package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +0 -3
- package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
- package/.next/static/chunks/0e3g0sil79cus.js +0 -21
- package/.next/static/chunks/0zd0t7e60bn4z.js +0 -1
- package/.next/static/chunks/163b-20vwa06i.js +0 -34
- package/.next/static/chunks/1pa-2v1ajvrko.js +0 -1
- package/.next/static/chunks/23_1z47al92nz.js +0 -57
- package/.next/static/chunks/260bomtr214m8.js +0 -34
- package/.next/static/chunks/2xk2ra4piiuod.js +0 -34
- package/.next/static/chunks/3b0-14cxqsprt.js +0 -1
- package/.next/static/chunks/3jarus1_38s2z.js +0 -1
- package/.next/static/chunks/3ssu341hav_sq.css +0 -3
- package/.next/static/chunks/3v1qd3sqhqn0d.js +0 -1
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_buildManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{ZVpLBK-2vpllv2HxOFs2d → 3NCyJyAPDCEYsYTwF1Ieu}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// Insights → Kingmakers, Gatekeepers, Companies and Industries: each board in
|
|
4
|
+
// full, a hundred rows at a time. The numbers are lib/insights-board.js's,
|
|
5
|
+
// with where they came from under each.
|
|
6
|
+
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import Avatar from '../Avatar';
|
|
9
|
+
import { TIER_COLORS } from '../../../lib/themes';
|
|
10
|
+
import { shortName } from '../../../lib/separation';
|
|
11
|
+
import {
|
|
12
|
+
fmt, one, pc, Hero, PersonCell, Src, NeedsCircles, OpenCircle, Bars, Card, LINE, SOFT_LINE, CARD_BG, ELLIPSIS,
|
|
13
|
+
} from './parts';
|
|
14
|
+
import { Logo } from './Rail';
|
|
15
|
+
import { PAGE } from './PeopleView';
|
|
16
|
+
|
|
17
|
+
/** A board's table: the header row on a computer, the rows, and Show more. */
|
|
18
|
+
function Table({ cols, head, right = [], rows, render, isMobile, source }) {
|
|
19
|
+
const step = isMobile ? 25 : PAGE;
|
|
20
|
+
const [pages, setPages] = useState(1);
|
|
21
|
+
const shown = pages * step;
|
|
22
|
+
const list = rows.slice(0, shown);
|
|
23
|
+
const left = rows.length - list.length;
|
|
24
|
+
return (
|
|
25
|
+
<div style={{ border: `1px solid ${LINE}`, borderRadius: 14, background: CARD_BG, overflow: 'hidden' }}>
|
|
26
|
+
{!isMobile && (
|
|
27
|
+
<div style={{ display: 'grid', gridTemplateColumns: cols, gap: 12, padding: '0 16px', alignItems: 'center', height: 36, borderBottom: `1px solid ${LINE}`, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.02)', fontSize: 10.5, color: 'var(--sd-fg-4, #6f7a88)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 700 }}>
|
|
28
|
+
{head.map((h, i) => <span key={i} style={{ textAlign: right.includes(i) ? 'right' : undefined }}>{h}</span>)}
|
|
29
|
+
</div>
|
|
30
|
+
)}
|
|
31
|
+
{list.map((r, i) => (
|
|
32
|
+
<div key={r.key ?? i} style={isMobile
|
|
33
|
+
? { padding: '10px 12px', borderBottom: `1px solid ${SOFT_LINE}` }
|
|
34
|
+
: { display: 'grid', gridTemplateColumns: cols, gap: 12, padding: '0 16px', alignItems: 'center', minHeight: 54, borderBottom: `1px solid ${SOFT_LINE}` }}>
|
|
35
|
+
{render(r, i)}
|
|
36
|
+
</div>
|
|
37
|
+
))}
|
|
38
|
+
{left > 0 && (
|
|
39
|
+
<button type="button" onClick={() => setPages((n) => n + 1)} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '12px 16px', border: 'none', borderBottom: `1px solid ${LINE}`, background: 'transparent', cursor: 'pointer', fontSize: 12.5, fontWeight: 600, color: 'var(--sd-blue, #3498DB)' }}>
|
|
40
|
+
Show {fmt(Math.min(step, left))} more → <span style={{ color: 'var(--sd-fg-4, #6f7a88)', fontWeight: 400, marginLeft: 6 }}>{fmt(left)} to go</span>
|
|
41
|
+
</button>
|
|
42
|
+
)}
|
|
43
|
+
<Src style={{ padding: '10px 16px 14px', marginTop: 0 }}>{source}</Src>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const Rank = ({ n, isMobile }) => <span style={{ fontSize: isMobile ? 11.5 : 13, fontWeight: 700, color: n <= 3 ? 'var(--sd-gold, #FFD700)' : 'var(--sd-fg-3, #8b9a9a)', fontVariantNumeric: 'tabular-nums' }}>#{fmt(n)}</span>;
|
|
49
|
+
const Num = ({ children, color, big }) => <b style={{ fontSize: big ? 16 : 13.5, fontWeight: big ? 800 : 700, color: color || 'var(--sd-fg-1, #fff)', fontVariantNumeric: 'tabular-nums', display: 'block', textAlign: 'right' }}>{children}</b>;
|
|
50
|
+
const Mute = ({ children, right }) => <span style={{ fontSize: 12, color: 'var(--sd-fg-3, #8b9a9a)', fontVariantNumeric: 'tabular-nums', ...(right ? { display: 'block', textAlign: 'right' } : null) }}>{children}</span>;
|
|
51
|
+
|
|
52
|
+
/** S and A behind someone, as one bar with the letters on it. */
|
|
53
|
+
function SABar({ S, A, max }) {
|
|
54
|
+
return <Bars max={max} parts={[
|
|
55
|
+
{ value: S, color: TIER_COLORS.S, label: 'S', ink: TIER_COLORS.S, title: `${fmt(S)} S` },
|
|
56
|
+
{ value: A, color: TIER_COLORS.A, label: 'A', ink: TIER_COLORS.A, title: `${fmt(A)} A` },
|
|
57
|
+
]} />;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Kingmakers ──────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export function KingmakersBoard({ data, source, isMobile }) {
|
|
63
|
+
const { kings } = data;
|
|
64
|
+
const cols = '52px minmax(0,1.5fr) 80px 56px 56px minmax(0,1.3fr) 70px 100px';
|
|
65
|
+
const max = kings.list[0]?.SA || 1;
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<Hero title="Kingmakers" isMobile={isMobile}>
|
|
69
|
+
Your connections by the S and A people in their circle: the doors with the most power behind them. A title says what someone holds; a circle says what they can open.
|
|
70
|
+
</Hero>
|
|
71
|
+
{kings.scanned === 0 ? <Card><NeedsCircles source={source} /></Card> : (
|
|
72
|
+
<Table isMobile={isMobile} cols={cols} rows={kings.list.map((k) => ({ ...k, key: k.row.id }))} source={kings.source}
|
|
73
|
+
head={['Rank', 'Connection', 'Circle', 'S', 'A', 'Behind them', 'S+A', '']} right={[2, 3, 4, 6]}
|
|
74
|
+
render={(k, i) => (isMobile ? (
|
|
75
|
+
<div style={{ display: 'grid', gridTemplateColumns: '36px minmax(0,1fr) auto', gap: 8, alignItems: 'center' }}>
|
|
76
|
+
<Rank n={i + 1} isMobile />
|
|
77
|
+
<PersonCell row={k.row} size={30} />
|
|
78
|
+
<Num big>{fmt(k.SA)}</Num>
|
|
79
|
+
<span />
|
|
80
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}><SABar S={k.S} A={k.A} max={max} /><Mute>of {fmt(k.size)}</Mute></span>
|
|
81
|
+
<OpenCircle id={k.row.id} compact />
|
|
82
|
+
</div>
|
|
83
|
+
) : (
|
|
84
|
+
<>
|
|
85
|
+
<Rank n={i + 1} />
|
|
86
|
+
<PersonCell row={k.row} />
|
|
87
|
+
<Mute right>{fmt(k.size)}</Mute>
|
|
88
|
+
<Num color={TIER_COLORS.S}>{fmt(k.S)}</Num>
|
|
89
|
+
<Num color={TIER_COLORS.A}>{fmt(k.A)}</Num>
|
|
90
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}><SABar S={k.S} A={k.A} max={max} /><Mute>{pc(100 * k.share)}</Mute></span>
|
|
91
|
+
<Num big>{fmt(k.SA)}</Num>
|
|
92
|
+
<span style={{ justifySelf: 'end' }}><OpenCircle id={k.row.id} /></span>
|
|
93
|
+
</>
|
|
94
|
+
))} />
|
|
95
|
+
)}
|
|
96
|
+
</>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Gatekeepers ─────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export function GatekeepersBoard({ data, source, isMobile }) {
|
|
103
|
+
const { gates, kings } = data;
|
|
104
|
+
const cols = '52px minmax(0,1.6fr) 70px 110px 76px minmax(0,1fr) 100px';
|
|
105
|
+
const others = Math.max(0, 100 - gates.top3Share);
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
<Hero title="Gatekeepers" isMobile={isMobile}>
|
|
109
|
+
{kings.scanned === 0 ? 'Who your S and A reach hangs on: the people who reach you through one connection only.'
|
|
110
|
+
: <><b style={{ color: 'var(--sd-fg-1, #fff)' }}>{pc(gates.onlyShare)}</b> of the S and A people you can reach have one way in. Three connections carry <b style={{ color: 'var(--sd-fg-1, #fff)' }}>{pc(gates.top3Share)}</b> of your S and A reach.</>}
|
|
111
|
+
</Hero>
|
|
112
|
+
{kings.scanned === 0 ? <Card><NeedsCircles source={source} /></Card> : (
|
|
113
|
+
<>
|
|
114
|
+
<div style={{ marginBottom: 14 }}>
|
|
115
|
+
<div style={{ display: 'flex', gap: 2, height: 30, borderRadius: 8, overflow: 'hidden' }}>
|
|
116
|
+
{gates.top3.map((g, i) => (
|
|
117
|
+
<span key={g.row.id} title={`${g.row.name}: ${pc(g.share)} of your S and A reach`} style={{ width: `${g.share}%`, background: ['#00ff88', '#00c46a', '#00984f'][i], display: 'flex', alignItems: 'center', paddingLeft: 10, overflow: 'hidden' }}>
|
|
118
|
+
<em style={{ fontStyle: 'normal', fontSize: 12, fontWeight: 750, color: '#00170b', whiteSpace: 'nowrap' }}>{shortName(g.row.name)} {pc(g.share)}</em>
|
|
119
|
+
</span>
|
|
120
|
+
))}
|
|
121
|
+
<span style={{ width: `${others}%`, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.1)', display: 'flex', alignItems: 'center', paddingLeft: 10, overflow: 'hidden' }}>
|
|
122
|
+
<em style={{ fontStyle: 'normal', fontSize: 12, fontWeight: 600, color: 'var(--sd-fg-2, #b8c4c4)', whiteSpace: 'nowrap' }}>{fmt(Math.max(0, gates.list.length - 3))} others {pc(others)}</em>
|
|
123
|
+
</span>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
<Table isMobile={isMobile} cols={cols} rows={gates.list.map((g) => ({ ...g, key: g.row.id }))} source={gates.source}
|
|
127
|
+
head={['Rank', 'Connection', 'Circle', 'Only via them', 'Of them S', 'Share of your S and A reach', '']} right={[2, 3, 4]}
|
|
128
|
+
render={(g, i) => (isMobile ? (
|
|
129
|
+
<div style={{ display: 'grid', gridTemplateColumns: '36px minmax(0,1fr) auto', gap: 8, alignItems: 'center' }}>
|
|
130
|
+
<Rank n={i + 1} isMobile />
|
|
131
|
+
<PersonCell row={g.row} size={30} />
|
|
132
|
+
<span style={{ textAlign: 'right' }}><Num big color="var(--sd-green, #00ff88)">{fmt(g.onlySA)}</Num><div style={{ fontSize: 10, color: 'var(--sd-fg-4, #6f7a88)' }}>only via them</div></span>
|
|
133
|
+
<span />
|
|
134
|
+
<Mute>{fmt(g.onlyS)} S · circle of {fmt(g.size)} · {pc((100 * g.credit) / Math.max(1, gates.totalSA))} of your reach</Mute>
|
|
135
|
+
<OpenCircle id={g.row.id} compact />
|
|
136
|
+
</div>
|
|
137
|
+
) : (
|
|
138
|
+
<>
|
|
139
|
+
<Rank n={i + 1} />
|
|
140
|
+
<PersonCell row={g.row} />
|
|
141
|
+
<Mute right>{fmt(g.size)}</Mute>
|
|
142
|
+
<Num big color="var(--sd-green, #00ff88)">{fmt(g.onlySA)}</Num>
|
|
143
|
+
<Num color={TIER_COLORS.S}>{fmt(g.onlyS)}</Num>
|
|
144
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
145
|
+
<Bars max={gates.top3[0]?.credit || 1} parts={[{ value: g.credit, color: 'var(--sd-green, #00ff88)' }]} height={8} />
|
|
146
|
+
<Mute>{pc((100 * g.credit) / Math.max(1, gates.totalSA))}</Mute>
|
|
147
|
+
</span>
|
|
148
|
+
<span style={{ justifySelf: 'end' }}><OpenCircle id={g.row.id} /></span>
|
|
149
|
+
</>
|
|
150
|
+
))} />
|
|
151
|
+
</>
|
|
152
|
+
)}
|
|
153
|
+
</>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Companies ───────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
const SCORE_FROM = { yours: 'your score', known: 'curated list', data: 'public dataset', estimate: 'estimated' };
|
|
160
|
+
|
|
161
|
+
export function CompaniesBoard({ heavy, isMobile }) {
|
|
162
|
+
const cols = '52px minmax(0,1.6fr) 96px 56px 56px 96px minmax(0,1.2fr) 92px';
|
|
163
|
+
if (!heavy) return <><Hero title="Company power" isMobile={isMobile}>Every company within two steps of you, with everyone’s power added up.</Hero><Card><div style={{ fontSize: 13, color: 'var(--sd-fg-3, #8b9a9a)' }}>Counting…</div></Card></>;
|
|
164
|
+
const { companies } = heavy;
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<Hero title="Company power" isMobile={isMobile}>
|
|
168
|
+
<b style={{ color: 'var(--sd-fg-1, #fff)' }}>{fmt(companies.count)}</b> companies someone within two steps of you works at now, with everyone’s power added up: where the power you can reach sits.
|
|
169
|
+
</Hero>
|
|
170
|
+
<Table isMobile={isMobile} cols={cols} rows={companies.list.map((c) => ({ ...c, key: c.name }))} source={`${companies.source} ${companies.bigSource}`}
|
|
171
|
+
head={['Rank', 'Company', 'People', 'S', 'A', 'Total power', 'Ways in', 'Score']} right={[2, 3, 4, 5, 7]}
|
|
172
|
+
render={(c, i) => {
|
|
173
|
+
const ways = (
|
|
174
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0, overflow: 'hidden' }} title={c.bridges.map((b) => `${b.bridge.name}: ${fmt(b.people)} there`).join('\n')}>
|
|
175
|
+
<Mute>{fmt(c.waysIn)}</Mute>
|
|
176
|
+
{c.bridges.slice(0, 2).map((b) => (
|
|
177
|
+
<span key={b.bridge.id} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11.5, color: 'var(--sd-fg-2, #b8c4c4)', whiteSpace: 'nowrap' }}>
|
|
178
|
+
<Avatar person={b.bridge} size={16} tierColors={TIER_COLORS} />{shortName(b.bridge.name)}
|
|
179
|
+
</span>
|
|
180
|
+
))}
|
|
181
|
+
{c.d1 > 0 && <span style={{ fontSize: 11, color: 'var(--sd-green, #7dffc0)', whiteSpace: 'nowrap' }}>{fmt(c.d1)} yours</span>}
|
|
182
|
+
</span>
|
|
183
|
+
);
|
|
184
|
+
const name = (
|
|
185
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
|
|
186
|
+
<Logo name={c.name} size={30} />
|
|
187
|
+
<span style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
|
188
|
+
<b style={{ fontSize: 13.5, fontWeight: 650, ...ELLIPSIS }}>{c.name}</b>
|
|
189
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 5, fontSize: 11.5, color: 'var(--sd-fg-3, #8b9a9a)', minWidth: 0 }}>
|
|
190
|
+
<i className="sd-dot-html" style={{ width: 7, height: 7, borderRadius: '50%', background: c.industry.color, flexShrink: 0 }} />
|
|
191
|
+
<span style={ELLIPSIS}>{c.industry.label}</span>
|
|
192
|
+
</span>
|
|
193
|
+
</span>
|
|
194
|
+
</span>
|
|
195
|
+
);
|
|
196
|
+
return isMobile ? (
|
|
197
|
+
<div style={{ display: 'grid', gridTemplateColumns: '36px minmax(0,1fr) auto', gap: 8, alignItems: 'center' }}>
|
|
198
|
+
<Rank n={i + 1} isMobile />
|
|
199
|
+
{name}
|
|
200
|
+
<span style={{ textAlign: 'right' }}><Num big>{fmt(Math.round(c.total))}</Num><div style={{ fontSize: 10, color: 'var(--sd-fg-4, #6f7a88)' }}>total power</div></span>
|
|
201
|
+
<span />
|
|
202
|
+
<Mute><span style={{ color: TIER_COLORS.S }}>{fmt(c.S)} S</span> · <span style={{ color: TIER_COLORS.A }}>{fmt(c.A)} A</span> · {fmt(c.people)} people · {fmt(c.waysIn)} ways in</Mute>
|
|
203
|
+
<Mute>{one(c.score).replace(/\.0$/, '')}/10</Mute>
|
|
204
|
+
</div>
|
|
205
|
+
) : (
|
|
206
|
+
<>
|
|
207
|
+
<Rank n={i + 1} />
|
|
208
|
+
{name}
|
|
209
|
+
<Mute right>{fmt(c.people)}</Mute>
|
|
210
|
+
<Num color={TIER_COLORS.S}>{fmt(c.S)}</Num>
|
|
211
|
+
<Num color={TIER_COLORS.A}>{fmt(c.A)}</Num>
|
|
212
|
+
<Num big>{fmt(Math.round(c.total))}</Num>
|
|
213
|
+
{ways}
|
|
214
|
+
<span style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
|
215
|
+
<Num>{one(c.score).replace(/\.0$/, '')}/10</Num>
|
|
216
|
+
<span style={{ fontSize: 10, color: 'var(--sd-fg-4, #6f7a88)', whiteSpace: 'nowrap' }}>{SCORE_FROM[c.scoreSource]}</span>
|
|
217
|
+
</span>
|
|
218
|
+
</>
|
|
219
|
+
);
|
|
220
|
+
}} />
|
|
221
|
+
</>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Industries ──────────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
export function IndustriesBoard({ heavy, isMobile }) {
|
|
228
|
+
const cols = 'minmax(0,1.5fr) 90px 90px 64px 64px minmax(0,1.4fr) 100px';
|
|
229
|
+
if (!heavy) return <><Hero title="Industries" isMobile={isMobile}>Where the power you can reach sits, by industry.</Hero><Card><div style={{ fontSize: 13, color: 'var(--sd-fg-3, #8b9a9a)' }}>Counting…</div></Card></>;
|
|
230
|
+
const { industries } = heavy;
|
|
231
|
+
const top = industries.list[0];
|
|
232
|
+
const max = Math.max(1, ...industries.list.map((e) => e.share));
|
|
233
|
+
return (
|
|
234
|
+
<>
|
|
235
|
+
<Hero title="Industries" isMobile={isMobile}>
|
|
236
|
+
Where the power you can reach sits.{top ? <> <b style={{ color: 'var(--sd-fg-1, #fff)' }}>{top.label}</b> holds {pc(top.share)} of your S and A.</> : null}
|
|
237
|
+
</Hero>
|
|
238
|
+
<Table isMobile={isMobile} cols={cols} rows={industries.list} source={industries.source}
|
|
239
|
+
head={['Industry', 'Companies', 'People', 'S', 'A', 'Share of your S and A', 'Total power']} right={[1, 2, 3, 4, 6]}
|
|
240
|
+
render={(e) => {
|
|
241
|
+
const label = (
|
|
242
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 9, minWidth: 0 }}>
|
|
243
|
+
<i className="sd-dot-html" style={{ width: 11, height: 11, borderRadius: 3, background: e.color, flexShrink: 0 }} />
|
|
244
|
+
<b style={{ fontSize: 13.5, fontWeight: 650, ...ELLIPSIS }}>{e.label}</b>
|
|
245
|
+
</span>
|
|
246
|
+
);
|
|
247
|
+
const bar = (
|
|
248
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
249
|
+
<Bars max={max} parts={[{ value: e.share, color: e.color, ink: e.color }]} height={10} />
|
|
250
|
+
<Mute>{pc(e.share)}</Mute>
|
|
251
|
+
</span>
|
|
252
|
+
);
|
|
253
|
+
return isMobile ? (
|
|
254
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0,1fr) auto', gap: 6, alignItems: 'center' }}>
|
|
255
|
+
{label}
|
|
256
|
+
<Num big>{pc(e.share)}</Num>
|
|
257
|
+
<Mute>{fmt(e.companies)} companies · {fmt(e.people)} people · <span style={{ color: TIER_COLORS.S }}>{fmt(e.S)} S</span> · <span style={{ color: TIER_COLORS.A }}>{fmt(e.A)} A</span></Mute>
|
|
258
|
+
<span />
|
|
259
|
+
</div>
|
|
260
|
+
) : (
|
|
261
|
+
<>
|
|
262
|
+
{label}
|
|
263
|
+
<Mute right>{fmt(e.companies)}</Mute>
|
|
264
|
+
<Mute right>{fmt(e.people)}</Mute>
|
|
265
|
+
<Num color={TIER_COLORS.S}>{fmt(e.S)}</Num>
|
|
266
|
+
<Num color={TIER_COLORS.A}>{fmt(e.A)}</Num>
|
|
267
|
+
{bar}
|
|
268
|
+
<Num big>{fmt(e.total)}</Num>
|
|
269
|
+
</>
|
|
270
|
+
);
|
|
271
|
+
}} />
|
|
272
|
+
</>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// Insights → People: the Power Index. Everyone within two steps of you, each
|
|
4
|
+
// once, ranked by power, with the filters above it and the rail of boards
|
|
5
|
+
// beside it (mock-up B). The table shows a hundred rows at a time (25 on a
|
|
6
|
+
// phone), so a network of thousands stays quick; ranks are the whole list's,
|
|
7
|
+
// so a filter never renumbers anyone.
|
|
8
|
+
|
|
9
|
+
import { memo, useDeferredValue, useMemo, useState } from 'react';
|
|
10
|
+
import { filterIndex, bandText } from '../../../lib/insights-board';
|
|
11
|
+
import { hasRequest } from '../../../lib/requests-client';
|
|
12
|
+
import { shortName, TIERS } from '../../../lib/separation';
|
|
13
|
+
import { RARITY, toggle } from '../../../lib/rarity';
|
|
14
|
+
import Avatar from '../Avatar';
|
|
15
|
+
import { TIER_COLORS } from '../../../lib/themes';
|
|
16
|
+
import {
|
|
17
|
+
fmt, one, TierChip, DegreeChip, RarityTag, PersonCell, Src, Hero, OpenCircle, ScanCircle, AskButton,
|
|
18
|
+
LINE, SOFT_LINE, CARD_BG, ELLIPSIS,
|
|
19
|
+
} from './parts';
|
|
20
|
+
import Rail from './Rail';
|
|
21
|
+
|
|
22
|
+
export const PAGE = 100;
|
|
23
|
+
const COLS = '58px minmax(0,1.55fr) 148px minmax(0,1.45fr) 118px 100px';
|
|
24
|
+
|
|
25
|
+
export default function PeopleView({ data, heavy, open, net, source, isMobile, onView, requests }) {
|
|
26
|
+
const { index } = data;
|
|
27
|
+
const [degree, setDegree] = useState('all');
|
|
28
|
+
const [tiers, setTiers] = useState(() => new Set());
|
|
29
|
+
const [rarities, setRarities] = useState(() => new Set());
|
|
30
|
+
const [query, setQuery] = useState('');
|
|
31
|
+
const q = useDeferredValue(query);
|
|
32
|
+
// A hundred rows at a time on a computer, twenty-five on a phone, where the boards sit under the table.
|
|
33
|
+
const step = isMobile ? 25 : PAGE;
|
|
34
|
+
const [pages, setPages] = useState(1);
|
|
35
|
+
const shown = pages * step;
|
|
36
|
+
const filtering = degree !== 'all' || tiers.size > 0 || rarities.size > 0 || q.trim() !== '';
|
|
37
|
+
const list = useMemo(() => (filtering ? filterIndex(index, { degree, tiers, rarities, query: q }) : index.people),
|
|
38
|
+
[index, filtering, degree, tiers, rarities, q]);
|
|
39
|
+
const bandAt = useMemo(() => new Map(index.bands.map((b) => [b.start, b])), [index]);
|
|
40
|
+
// A new filter starts from the top again.
|
|
41
|
+
const filterKey = `${degree}|${[...tiers].join()}|${[...rarities].join()}|${q}`;
|
|
42
|
+
const [lastKey, setLastKey] = useState(filterKey);
|
|
43
|
+
if (lastKey !== filterKey) { setLastKey(filterKey); setPages(1); }
|
|
44
|
+
|
|
45
|
+
const rows = list.slice(0, shown);
|
|
46
|
+
const left = list.length - rows.length;
|
|
47
|
+
const next = list[rows.length];
|
|
48
|
+
const sameTieLeft = next && rows.length && rows[rows.length - 1].score === next.score
|
|
49
|
+
? list.slice(rows.length).filter((p) => p.score === next.score).length : 0;
|
|
50
|
+
const scoped = source === 'own';
|
|
51
|
+
// No 2nd degree (a CSV import, or nothing scanned yet): no degree or rarity to filter by.
|
|
52
|
+
const second = index.counts.degree[2] > 0;
|
|
53
|
+
|
|
54
|
+
// "All →" on Hidden giants: the table, narrowed to them.
|
|
55
|
+
const showHidden = () => { setDegree(2); setTiers(new Set(['S'])); setRarities(new Set(['only'])); setQuery(''); window.scrollTo(0, 0); };
|
|
56
|
+
|
|
57
|
+
const filters = (
|
|
58
|
+
<div style={{
|
|
59
|
+
display: 'flex', alignItems: 'center', gap: isMobile ? 8 : 14, flexWrap: 'wrap', padding: isMobile ? '8px 10px' : '10px 12px',
|
|
60
|
+
border: `1px solid ${LINE}`, borderRadius: 12, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.025)', marginBottom: 14,
|
|
61
|
+
}}>
|
|
62
|
+
{second && (
|
|
63
|
+
<Group label="Degree" isMobile={isMobile}>
|
|
64
|
+
{[['all', 'All', index.total], [1, '1st', index.counts.degree[1]], [2, '2nd', index.counts.degree[2]]].map(([k, label, n]) => (
|
|
65
|
+
<Chip key={k} on={degree === k} onClick={() => setDegree(k)} count={n}>{label}</Chip>
|
|
66
|
+
))}
|
|
67
|
+
</Group>
|
|
68
|
+
)}
|
|
69
|
+
<Group label="Tier" isMobile={isMobile}>
|
|
70
|
+
{TIERS.map((t) => (
|
|
71
|
+
<Chip key={t} on={tiers.has(t)} onClick={() => setTiers((s) => toggle(s, t))} count={index.counts.tiers[t]} title={`${t} tier`}>
|
|
72
|
+
<TierChip tier={t} size={16} />
|
|
73
|
+
</Chip>
|
|
74
|
+
))}
|
|
75
|
+
</Group>
|
|
76
|
+
{second && (
|
|
77
|
+
<Group label="Rarity" isMobile={isMobile}>
|
|
78
|
+
{RARITY.map((r) => (
|
|
79
|
+
<Chip key={r.key} on={rarities.has(r.key)} onClick={() => setRarities((s) => toggle(s, r.key))} count={index.counts.rarity[r.key]}
|
|
80
|
+
title={`${r.label}: ${r.range} mutual connection${r.range === '1' ? '' : 's'}`}>
|
|
81
|
+
<i className="sd-dot-html" style={{ width: 7, height: 7, borderRadius: '50%', background: r.color, display: 'inline-block' }} />{r.label}
|
|
82
|
+
</Chip>
|
|
83
|
+
))}
|
|
84
|
+
</Group>
|
|
85
|
+
)}
|
|
86
|
+
<input type="search" value={query} onChange={(e) => setQuery(e.target.value)} aria-label="Search a name, company, or who knows them"
|
|
87
|
+
placeholder="Search a name or company" style={{
|
|
88
|
+
marginLeft: isMobile ? 0 : 'auto', width: isMobile ? '100%' : 240, height: isMobile ? 40 : 32, padding: '0 12px', borderRadius: 8, outline: 'none',
|
|
89
|
+
fontSize: isMobile ? 16 : 12.5, color: 'var(--sd-fg-1, #fff)', border: `1px solid ${LINE}`, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.04)',
|
|
90
|
+
}} />
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<Hero title="The Power Index" isMobile={isMobile}>
|
|
97
|
+
{second
|
|
98
|
+
? <>Everyone within two steps of you, ranked by power: <b style={{ color: 'var(--sd-fg-1, #fff)' }}>{fmt(index.total)}</b> people, each once.</>
|
|
99
|
+
: <>Your connections, ranked by power: <b style={{ color: 'var(--sd-fg-1, #fff)' }}>{fmt(index.total)}</b> people.{' '}
|
|
100
|
+
{source === 'csv' ? 'A CSV import has no circles, so the people one step further out aren’t here.' : 'Scan their circles to add the people one step further out.'}</>}
|
|
101
|
+
</Hero>
|
|
102
|
+
{filters}
|
|
103
|
+
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'minmax(0,1fr) 370px', gap: 16, alignItems: 'start' }}>
|
|
104
|
+
<div style={{ border: `1px solid ${LINE}`, borderRadius: 14, background: CARD_BG, overflow: 'hidden', minWidth: 0 }}>
|
|
105
|
+
{!isMobile && (
|
|
106
|
+
<div style={{ display: 'grid', gridTemplateColumns: COLS, gap: 10, padding: '0 16px', alignItems: 'center', height: 36, borderBottom: `1px solid ${LINE}`, background: 'rgba(var(--sd-ink, 255, 255, 255), 0.02)', fontSize: 10.5, color: 'var(--sd-fg-4, #6f7a88)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 700 }}>
|
|
107
|
+
<span>Rank</span><span>Person</span><span>Power</span><span>Way in</span><span>Rarity</span><span />
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
{rows.length === 0 && (
|
|
111
|
+
<div style={{ padding: '28px 16px', fontSize: 13, color: 'var(--sd-fg-3, #8b9a9a)' }}>Nobody matches these filters.</div>
|
|
112
|
+
)}
|
|
113
|
+
{rows.map((p, i) => {
|
|
114
|
+
const band = !filtering ? bandAt.get(i) : null;
|
|
115
|
+
return (
|
|
116
|
+
<div key={p.key}>
|
|
117
|
+
{band && <Band band={band} isMobile={isMobile} />}
|
|
118
|
+
<Row p={p} isMobile={isMobile} scoped={scoped} csv={source === 'csv'} asked={p.degree === 2 && hasRequest(p.row, requests)} />
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
122
|
+
{left > 0 && (
|
|
123
|
+
<button type="button" onClick={() => setPages((n) => n + 1)} style={{
|
|
124
|
+
display: 'block', width: '100%', textAlign: 'left', padding: '12px 16px', border: 'none', borderBottom: `1px solid ${LINE}`,
|
|
125
|
+
background: 'transparent', cursor: 'pointer', fontSize: 12.5, fontWeight: 600, color: 'var(--sd-blue, #3498DB)',
|
|
126
|
+
}}>
|
|
127
|
+
Show {fmt(Math.min(step, left))} more →
|
|
128
|
+
<span style={{ color: 'var(--sd-fg-4, #6f7a88)', fontWeight: 400, marginLeft: 8 }}>
|
|
129
|
+
{sameTieLeft ? `${fmt(sameTieLeft)} more at ${one(next.score)}, ${fmt(left)} in all` : `${fmt(left)} to go`}
|
|
130
|
+
</span>
|
|
131
|
+
</button>
|
|
132
|
+
)}
|
|
133
|
+
<Src style={{ padding: '10px 16px 14px', marginTop: 0 }}>{index.source}</Src>
|
|
134
|
+
</div>
|
|
135
|
+
<Rail data={data} heavy={heavy} open={open} source={source} onView={onView} onHidden={showHidden} isMobile={isMobile} net={net} />
|
|
136
|
+
</div>
|
|
137
|
+
</>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function Group({ label, children, isMobile }) {
|
|
142
|
+
// On a phone each group is one row that scrolls sideways, rather than four rows of chips.
|
|
143
|
+
return (
|
|
144
|
+
<div style={{ display: 'flex', gap: 5, alignItems: 'center', flexWrap: isMobile ? 'nowrap' : 'wrap', minWidth: 0, ...(isMobile ? { overflowX: 'auto', width: '100%', scrollbarWidth: 'none' } : null) }}>
|
|
145
|
+
<span style={{ fontSize: 10.5, color: 'var(--sd-fg-4, #6f7a88)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 700, marginRight: 3 }}>{label}</span>
|
|
146
|
+
{children}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function Chip({ on, onClick, count, children, title }) {
|
|
152
|
+
return (
|
|
153
|
+
<button type="button" aria-pressed={on} onClick={onClick} title={title} style={{
|
|
154
|
+
fontSize: 12, padding: '4px 9px', borderRadius: 16, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 5, whiteSpace: 'nowrap',
|
|
155
|
+
border: `1px solid ${on ? 'rgba(var(--sd-ink, 255, 255, 255), 0.55)' : LINE}`,
|
|
156
|
+
background: on ? 'rgba(var(--sd-ink, 255, 255, 255), 0.12)' : 'rgba(var(--sd-ink, 255, 255, 255), 0.03)',
|
|
157
|
+
color: on ? 'var(--sd-fg-1, #fff)' : 'var(--sd-fg-2, #b8c4c4)', fontWeight: on ? 700 : 500,
|
|
158
|
+
}}>
|
|
159
|
+
{children}
|
|
160
|
+
{count != null && <small style={{ color: 'var(--sd-fg-4, #6f7a88)', fontSize: 11 }}>{fmt(count)}</small>}
|
|
161
|
+
</button>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** A tie of many: how many share the score, and the working most of them share. */
|
|
166
|
+
function Band({ band, isMobile }) {
|
|
167
|
+
const t = bandText(band);
|
|
168
|
+
const mixed = band.yours > 0 && band.yours < band.count;
|
|
169
|
+
return (
|
|
170
|
+
<div style={{
|
|
171
|
+
display: 'flex', alignItems: isMobile ? 'flex-start' : 'center', gap: 12, padding: isMobile ? '10px 12px' : '10px 16px',
|
|
172
|
+
background: 'linear-gradient(90deg, rgba(255,215,0,0.1), rgba(255,215,0,0.02))',
|
|
173
|
+
borderTop: '1px solid rgba(255,215,0,0.22)', borderBottom: '1px solid rgba(255,215,0,0.22)',
|
|
174
|
+
}}>
|
|
175
|
+
<span style={{ fontSize: 15, fontWeight: 800, color: 'var(--sd-gold, #FFD700)', fontVariantNumeric: 'tabular-nums', flexShrink: 0 }}>#{fmt(band.rank)}=</span>
|
|
176
|
+
<span style={{ fontSize: 12.5, color: 'var(--sd-fg-1, #e9edf3)', lineHeight: 1.45 }}>
|
|
177
|
+
<b>{t.head}</b> {t.rest}{' '}
|
|
178
|
+
<span style={{ color: 'var(--sd-fg-3, #8b9a9a)' }}>{mixed ? 'Yours first, then most ways in.' : band.yours ? 'All yours.' : 'Most ways in first.'}</span>
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** One person: rank, who, power, way in, rarity and what to do. */
|
|
185
|
+
const Row = memo(function Row({ p, isMobile, scoped, csv, asked }) {
|
|
186
|
+
const rank = `#${fmt(p.rank)}${p.tied ? '=' : ''}`;
|
|
187
|
+
const podium = p.rank <= 5;
|
|
188
|
+
const power = (
|
|
189
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 7, flexShrink: 0 }}>
|
|
190
|
+
<b style={{ fontSize: isMobile ? 16 : 17, fontWeight: 800, minWidth: 30, textAlign: 'right', fontVariantNumeric: 'tabular-nums', color: 'var(--sd-fg-1, #fff)' }}>{one(p.score)}</b>
|
|
191
|
+
<TierChip tier={p.tier} />
|
|
192
|
+
{!isMobile && p.boost > 0 && (
|
|
193
|
+
<span title="Added for a strong circle: their scanned circle holds many S and A people (lib/scoring.js bridge boost, up to +2)" style={{
|
|
194
|
+
fontSize: 10, fontWeight: 700, color: 'var(--sd-green, #00ff88)', background: 'rgba(0,255,136,0.08)', border: '1px solid rgba(0,255,136,0.28)',
|
|
195
|
+
padding: '1px 6px', borderRadius: 10, whiteSpace: 'nowrap',
|
|
196
|
+
}}>+{p.boost} circle</span>
|
|
197
|
+
)}
|
|
198
|
+
</span>
|
|
199
|
+
);
|
|
200
|
+
const way = <Way p={p} max={isMobile ? 1 : 2} csv={csv} />;
|
|
201
|
+
const rarity = p.degree === 1
|
|
202
|
+
? <span style={{ fontSize: 11.5, color: 'var(--sd-fg-4, #6f7a88)', whiteSpace: 'nowrap' }}>Your connection</span>
|
|
203
|
+
: <RarityTag rarity={p.rarity} count={!isMobile} small={isMobile} />;
|
|
204
|
+
const action = <Action p={p} scoped={scoped} asked={asked} compact={isMobile} />;
|
|
205
|
+
|
|
206
|
+
if (isMobile) {
|
|
207
|
+
return (
|
|
208
|
+
<div style={{ display: 'grid', gridTemplateColumns: '40px minmax(0,1fr) 66px', columnGap: 8, rowGap: 6, padding: '10px 12px', borderBottom: `1px solid ${SOFT_LINE}` }}>
|
|
209
|
+
<span style={{ gridRow: '1 / span 2', paddingTop: 9, fontSize: 11.5, fontWeight: 700, color: podium ? 'var(--sd-gold, #FFD700)' : 'var(--sd-fg-3, #8b9a9a)', whiteSpace: 'nowrap' }}>{rank}</span>
|
|
210
|
+
<PersonCell row={p.row} size={32} chip={false} />
|
|
211
|
+
<span style={{ justifySelf: 'end' }}>{power}</span>
|
|
212
|
+
<div style={{ gridColumn: '2 / span 2', display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
|
|
213
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0, flex: 1, overflow: 'hidden', fontSize: 12, color: 'var(--sd-fg-2, #b8c4c4)' }}>{way}</span>
|
|
214
|
+
{p.degree === 2 && rarity}
|
|
215
|
+
{action}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return (
|
|
221
|
+
<div style={{ display: 'grid', gridTemplateColumns: COLS, gap: 10, padding: '0 16px', alignItems: 'center', minHeight: 56, borderBottom: `1px solid ${SOFT_LINE}` }}>
|
|
222
|
+
<span style={{ fontSize: 13, fontWeight: 700, color: podium ? 'var(--sd-gold, #FFD700)' : 'var(--sd-fg-3, #8b9a9a)', fontVariantNumeric: 'tabular-nums' }}>{rank}</span>
|
|
223
|
+
<PersonCell row={p.row} chip={false} />
|
|
224
|
+
{power}
|
|
225
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, fontSize: 12, color: 'var(--sd-fg-2, #b8c4c4)' }}>{way}</span>
|
|
226
|
+
<span style={{ minWidth: 0 }}>{rarity}</span>
|
|
227
|
+
<span style={{ justifySelf: 'end' }}>{action}</span>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
/** Yours: their circle. 2nd degree: who of yours knows them, best first. */
|
|
233
|
+
function Way({ p, max = 2, csv }) {
|
|
234
|
+
if (p.degree === 1) {
|
|
235
|
+
const c = p.circle;
|
|
236
|
+
// A CSV import has no circles to scan: the chip says enough.
|
|
237
|
+
if (csv) return <DegreeChip degree={1} />;
|
|
238
|
+
const text = c.state === 'scanned'
|
|
239
|
+
? <span style={{ whiteSpace: 'nowrap' }}>Circle {fmt(c.size)} · <b style={{ color: 'var(--sd-fg-1, #fff)' }}>{fmt(c.S + c.A)}</b> S+A</span>
|
|
240
|
+
: <span style={{ color: 'var(--sd-fg-4, #6f7a88)', fontSize: 11.5, whiteSpace: 'nowrap' }}>{c.state === 'hidden' ? 'Their list is hidden' : 'Circle not scanned'}</span>;
|
|
241
|
+
return <><DegreeChip degree={1} />{text}</>;
|
|
242
|
+
}
|
|
243
|
+
const named = p.routes.filter((r) => r.bridge);
|
|
244
|
+
const extra = p.routes.length - Math.min(max, named.length);
|
|
245
|
+
return (
|
|
246
|
+
<>
|
|
247
|
+
<DegreeChip degree={2} />
|
|
248
|
+
<span style={{ display: 'flex', gap: 8, minWidth: 0, overflow: 'hidden' }} title={p.routes.map((r) => (r.bridge ? r.bridge.name : 'a connection the app can’t name')).join('\n')}>
|
|
249
|
+
{named.slice(0, max).map((r) => (
|
|
250
|
+
<span key={r.id} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 12, color: 'var(--sd-fg-2, #b8c4c4)', whiteSpace: 'nowrap' }}>
|
|
251
|
+
<Avatar person={r.bridge} size={18} tierColors={TIER_COLORS} />{shortName(r.bridge.name)}
|
|
252
|
+
</span>
|
|
253
|
+
))}
|
|
254
|
+
{!named.length && <span style={{ fontSize: 11.5, color: 'var(--sd-fg-4, #6f7a88)', ...ELLIPSIS }}>a connection the app can’t name</span>}
|
|
255
|
+
{extra > 0 && <span style={{ fontSize: 11.5, color: 'var(--sd-fg-4, #6f7a88)', whiteSpace: 'nowrap' }}>+{fmt(extra)}</span>}
|
|
256
|
+
</span>
|
|
257
|
+
</>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function Action({ p, scoped, asked, compact }) {
|
|
262
|
+
if (p.degree === 2) return <AskButton row={p.row} bridgeId={p.routes[0]?.id ?? p.row.source_connection_id} asked={asked} compact={compact} canAuto={scoped} />;
|
|
263
|
+
if (p.circle.state === 'scanned') return <OpenCircle id={p.row.id} compact={compact} />;
|
|
264
|
+
if (p.circle.state === 'todo' && scoped) return <ScanCircle id={p.row.id} compact={compact} />;
|
|
265
|
+
return null;
|
|
266
|
+
}
|