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
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// (lib/island.js). It only reports: the pacing and the caps live in the scanner
|
|
14
14
|
// (scripts/scrape.py, lib/linkedin-limits.js).
|
|
15
15
|
|
|
16
|
-
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
16
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
17
17
|
import { usePathname } from 'next/navigation';
|
|
18
18
|
import Link from 'next/link';
|
|
19
19
|
import { watchScanner, scannerNow, stopScrape } from '../../lib/scraper-client';
|
|
@@ -35,6 +35,7 @@ const WHAT = {
|
|
|
35
35
|
photos: 'Saving photos',
|
|
36
36
|
messages: 'Reading your messages list',
|
|
37
37
|
'messages-full': 'Reading your whole messages history',
|
|
38
|
+
connect: 'Sending a connection request',
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
// Green while there's plenty left, amber past 60%, red past 90%.
|
|
@@ -59,6 +60,7 @@ export default function ScanStatusBar() {
|
|
|
59
60
|
// be swapped for a new one, or wrap to two rows.
|
|
60
61
|
const pathname = usePathname();
|
|
61
62
|
const [top, setTop] = useState(null);
|
|
63
|
+
const barRef = useRef(null);
|
|
62
64
|
const [centre, setCentre] = useState(null);
|
|
63
65
|
useEffect(() => {
|
|
64
66
|
let header = null;
|
|
@@ -82,7 +84,13 @@ export default function ScanStatusBar() {
|
|
|
82
84
|
};
|
|
83
85
|
const later = () => { if (!queued) queued = requestAnimationFrame(measure); };
|
|
84
86
|
later();
|
|
85
|
-
|
|
87
|
+
// Measuring waits for the next frame, which drew the notch once over the
|
|
88
|
+
// blank page: hidden here, before that frame is painted, the moment the
|
|
89
|
+
// header goes (and shown again the moment it's back).
|
|
90
|
+
const mo = new MutationObserver(() => {
|
|
91
|
+
if (barRef.current) barRef.current.style.visibility = document.querySelector('header') ? '' : 'hidden';
|
|
92
|
+
later();
|
|
93
|
+
});
|
|
86
94
|
mo.observe(document.body, { childList: true, subtree: true });
|
|
87
95
|
window.addEventListener('resize', later);
|
|
88
96
|
return () => { cancelAnimationFrame(queued); ro?.disconnect(); mo.disconnect(); window.removeEventListener('resize', later); };
|
|
@@ -98,6 +106,11 @@ export default function ScanStatusBar() {
|
|
|
98
106
|
return () => setNotchShown(false);
|
|
99
107
|
}, [showing]);
|
|
100
108
|
if (!status && !tabs) return null;
|
|
109
|
+
// Only with a page under it: between tabs the next page has no header until
|
|
110
|
+
// it has loaded, and the notch would hang over a blank screen (Blake,
|
|
111
|
+
// 2026-10-03: "the notch is seen the whole time as the screen blanks out
|
|
112
|
+
// loading to the next … have it appear with the section"). It fades in with it.
|
|
113
|
+
if (top == null) return null;
|
|
101
114
|
|
|
102
115
|
const p = job?.progress;
|
|
103
116
|
const step = p?.kind === 'batch' && p.total ? `${p.current || p.done || 0} of ${p.total}`
|
|
@@ -118,10 +131,12 @@ export default function ScanStatusBar() {
|
|
|
118
131
|
|
|
119
132
|
return (
|
|
120
133
|
<div
|
|
134
|
+
ref={barRef}
|
|
121
135
|
data-glass-panel="bar"
|
|
136
|
+
className="notch-in"
|
|
122
137
|
onMouseLeave={() => setOpen(false)}
|
|
123
138
|
style={{
|
|
124
|
-
position: 'fixed', left, top
|
|
139
|
+
position: 'fixed', left, top, transform: 'translateX(-50%)', zIndex: 60,
|
|
125
140
|
maxWidth: 'calc(100vw - 16px)',
|
|
126
141
|
padding: expanded && status ? '4px 6px 10px' : '4px 6px 5px',
|
|
127
142
|
borderRadius: '0 0 14px 14px', border: `1px solid ${running ? 'rgba(0,255,136,0.25)' : 'rgba(var(--sd-ink, 255, 255, 255), 0.1)'}`, borderTop: 'none',
|
|
@@ -57,9 +57,17 @@ export default function ScanTrail({ inset }) {
|
|
|
57
57
|
useEffect(() => {
|
|
58
58
|
const el = boxRef.current;
|
|
59
59
|
if (!el) return undefined;
|
|
60
|
-
|
|
60
|
+
// On a computer the tabs sit on this same line (AppHeader): the dots stop short of them.
|
|
61
|
+
const fit = () => {
|
|
62
|
+
const box = el.getBoundingClientRect();
|
|
63
|
+
const tabs = document.getElementById('main-tabs')?.getBoundingClientRect();
|
|
64
|
+
const width = tabs && tabs.left > box.left ? Math.min(box.width, tabs.left - box.left - 12) : box.width;
|
|
65
|
+
setRoom(Math.max(8, Math.floor(width / STEP) - 4));
|
|
66
|
+
};
|
|
67
|
+
const ro = new ResizeObserver(fit);
|
|
61
68
|
ro.observe(el);
|
|
62
|
-
|
|
69
|
+
window.addEventListener('resize', fit);
|
|
70
|
+
return () => { ro.disconnect(); window.removeEventListener('resize', fit); };
|
|
63
71
|
}, []);
|
|
64
72
|
|
|
65
73
|
const { pages } = shown;
|
|
@@ -69,7 +77,9 @@ export default function ScanTrail({ inset }) {
|
|
|
69
77
|
for (let i = first; i < pages; i++) dots.push(i);
|
|
70
78
|
const title = `${shown.label}: ${pages === 0 ? 'starting' : `${pages.toLocaleString('en-US')} page${pages === 1 ? '' : 's'} read`}. Open the Scan page`;
|
|
71
79
|
return (
|
|
72
|
-
|
|
80
|
+
// Above the page under the header: the dots straddle its line, and a page
|
|
81
|
+
// drawn after the header (Paths' panel) covered their lower half.
|
|
82
|
+
<div ref={boxRef} aria-hidden={!shown.on} style={{ position: 'absolute', left: inset, right: inset, bottom: -4, height: 7, pointerEvents: 'none', zIndex: 5 }}>
|
|
73
83
|
<style>{CSS}</style>
|
|
74
84
|
<Link href="/setup" title={title} tabIndex={shown.on ? 0 : -1} className="scan-trail" style={{
|
|
75
85
|
position: 'absolute', left: 0, top: 0, height: 7, display: 'flex', alignItems: 'center', gap: STEP - 5,
|
|
@@ -380,28 +380,13 @@ export default function SeparationView({ connections = [], degree2 = [], fullDeg
|
|
|
380
380
|
flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column',
|
|
381
381
|
background: 'var(--sd-page)', color: 'var(--sd-fg-1, #fff)', overflow: 'hidden',
|
|
382
382
|
}}>
|
|
383
|
-
{/* ──
|
|
383
|
+
{/* ── Top: the search box, and on a phone what it ranks ── */}
|
|
384
384
|
<div style={{ padding: `${notch ? 48 : 14}px ${SIDE}px 8px`, flexShrink: 0 }}>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}}>Separation</h2>
|
|
391
|
-
<span style={{ fontSize: 12, color: 'var(--sd-fg-3, #aaa)', ...(isMobile ? {} : { flex: 1, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }) }}>
|
|
392
|
-
{isMobile
|
|
393
|
-
? `${plural(summary.people, 'person', 'people')} · ${fmt(summary.byTier.S)} S · ${fmt(summary.multi)} with 2+ ways`
|
|
394
|
-
: `${plural(summary.people, 'person', 'people')} two steps away · ${fmt(summary.byTier.S)} S-tier · ${fmt(summary.multi)} reachable 2+ ways · through ${fmt(summary.bridges)} of your connections`}
|
|
395
|
-
</span>
|
|
396
|
-
{!isMobile && searchBox}
|
|
397
|
-
</div>
|
|
398
|
-
{/* The whole population at a glance: one segment per tier, to scale. */}
|
|
399
|
-
<div aria-hidden="true" style={{ display: 'flex', gap: 1, height: 6, borderRadius: 3, overflow: 'hidden', margin: '8px 0 6px', background: 'rgba(var(--sd-ink, 255, 255, 255), 0.04)' }}>
|
|
400
|
-
{TIERS.filter((t) => summary.byTier[t] > 0).map((t) => (
|
|
401
|
-
<div key={t} style={{ flex: summary.byTier[t], background: tierColors[t] || '#555' }} />
|
|
402
|
-
))}
|
|
403
|
-
</div>
|
|
404
|
-
{/* On a computer the line says it on its own; what it ranks is the title's tooltip (RANK_NOTE). */}
|
|
385
|
+
{/* Blake, 2026-10-03: no title, count line or tier strip up here ("remove this
|
|
386
|
+
line of text … and the actual line of visual of the tiers"); the map and the
|
|
387
|
+
slider say it. The search box keeps the row. */}
|
|
388
|
+
{!isMobile && <div style={{ display: 'flex', justifyContent: 'flex-end' }}>{searchBox}</div>}
|
|
389
|
+
{/* What it ranks, on a phone, where there's room under the search box. */}
|
|
405
390
|
{isMobile && (
|
|
406
391
|
<div style={{ fontSize: 11, color: 'var(--sd-fg-4, #777)', lineHeight: 1.4 }}>Ranks reachability, not people · lists scanned so far</div>
|
|
407
392
|
)}
|
|
@@ -710,7 +695,6 @@ const MAP_CSS = `
|
|
|
710
695
|
.sepmap .in { animation: none; }
|
|
711
696
|
}
|
|
712
697
|
`;
|
|
713
|
-
const RANK_NOTE = 'Ranked by each person’s own score (title, company, headline). It ranks reachability, not people. From the lists scanned so far.';
|
|
714
698
|
const HALO = { paintOrder: 'stroke', stroke: 'var(--sd-bg)', strokeWidth: 3, strokeLinejoin: 'round' };
|
|
715
699
|
const at = (x, y) => ({ transform: `translate(${x}px, ${y}px)` });
|
|
716
700
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
3
|
+
import { useState, useEffect, useMemo, useCallback, useSyncExternalStore } from 'react';
|
|
4
4
|
import Link from 'next/link';
|
|
5
|
-
import { beginScrape, scraperStatus, notReadyMessage, resumePoint, scansCircleOf, isCircleScan, busyReason } from '../../lib/scraper-client';
|
|
5
|
+
import { beginScrape, scraperStatus, notReadyMessage, resumePoint, scansCircleOf, isCircleScan, busyReason, watchScanner, scannerNow } from '../../lib/scraper-client';
|
|
6
|
+
import { scanBoxTitle, stoppedLine } from '../../lib/in-progress';
|
|
6
7
|
import useScanner from './useScanner';
|
|
7
8
|
import useRequests from './useRequests';
|
|
8
9
|
import TheirCircle from './TheirCircle';
|
|
@@ -17,6 +18,7 @@ import Avatar from './Avatar';
|
|
|
17
18
|
import NoteDetail from './NoteDetail';
|
|
18
19
|
import CirclePanel from './CirclePanel';
|
|
19
20
|
import { localPhoto } from '../../lib/photos';
|
|
21
|
+
import ConnectChoice, { secondaryLook } from './AutoConnect';
|
|
20
22
|
|
|
21
23
|
export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending, scanNotes, csvSource = null, onOpenCircle, onShowInSeparation, note = null, onCloseNote, circle = null }) {
|
|
22
24
|
const isDegreesMode = mode === 'degrees';
|
|
@@ -54,6 +56,12 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
54
56
|
// Separation, the circle and the Outlink queue at once (lib/requests-client.js).
|
|
55
57
|
const requests = useRequests();
|
|
56
58
|
const isRequested = useCallback((row) => hasRequest(row, requests), [requests]);
|
|
59
|
+
// Where Resume would carry on with the person open, asked once for the whole
|
|
60
|
+
// card: the line by their name and the scan box below both show it. Only for
|
|
61
|
+
// whoever gets a scan box (the same test as below). Above the early returns,
|
|
62
|
+
// so the hook order never changes.
|
|
63
|
+
const scansHere = Boolean(selected) && canScan && (selected.degree === 1 || selected.outreach_status === 'accepted');
|
|
64
|
+
const { resume, scanning: scanningThem } = useResume(scansHere ? selected : null);
|
|
57
65
|
|
|
58
66
|
// A notification someone clicked takes the panel over until they go back (app/components/NoteDetail.js).
|
|
59
67
|
if (note) {
|
|
@@ -151,6 +159,9 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
151
159
|
</div>
|
|
152
160
|
</div>
|
|
153
161
|
|
|
162
|
+
{/* Their scan stopped partway: said by their name, where it's seen without scrolling. */}
|
|
163
|
+
{scansHere && resume && <StoppedPartway resume={resume} scanning={scanningThem} />}
|
|
164
|
+
|
|
154
165
|
{/* Their circle, out to six degrees: who they know, who you added from
|
|
155
166
|
it, and who those people know (TheirCircle.js). Your connections only:
|
|
156
167
|
a 2nd-degree person's circle isn't yours to scan until they accept. */}
|
|
@@ -224,9 +235,15 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
224
235
|
<div style={row}>
|
|
225
236
|
<span style={label}>Scanned</span>
|
|
226
237
|
<span style={{ flex: 1 }}>{scanned}</span>
|
|
227
|
-
{
|
|
238
|
+
{/* Stopped partway: carry on from that page, the line by their name's own
|
|
239
|
+
Resume. The Scan page's Scan one circle reads the list again from page 1,
|
|
240
|
+
which spends the searches already spent; that stays for anyone else. */}
|
|
241
|
+
{i.bars !== 5 && canScan && (scansHere && resume ? (
|
|
242
|
+
<button type="button" style={act} title={`Resume from page ${resume.nextPage}`}
|
|
243
|
+
onClick={() => resumeInScanBox(scanningThem)}>Resume →</button>
|
|
244
|
+
) : (resume === null || i.bars == null || !scansHere) && (
|
|
228
245
|
<a href={`/setup?scan=${encodeURIComponent(selected.id)}`} style={act}>{i.bars == null ? 'Scan →' : 'Finish →'}</a>
|
|
229
|
-
)}
|
|
246
|
+
))}
|
|
230
247
|
</div>
|
|
231
248
|
{(() => {
|
|
232
249
|
const since = selected.connected_date ? new Date(selected.connected_date) : null;
|
|
@@ -468,21 +485,27 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
468
485
|
Didn't send it? Undo
|
|
469
486
|
</button>
|
|
470
487
|
) : (
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
488
|
+
// Auto first, the better pick; Connect beside it as an outline
|
|
489
|
+
// (AutoConnect.js; Blake, 2026-10-03). Your own network only.
|
|
490
|
+
<ConnectChoice person={selected} canAuto={canScan} size="card" connect={(second) => (
|
|
491
|
+
<a href={selected.profile_url} target="_blank" rel="noopener noreferrer"
|
|
492
|
+
title={second ? 'Opens their LinkedIn profile, to connect yourself' : undefined}
|
|
493
|
+
style={second ? { ...secondaryLook('card'), flex: 1 } : {
|
|
494
|
+
display: 'block', padding: '10px 16px',
|
|
495
|
+
background: 'linear-gradient(135deg, #FFD700, #FF6B35)',
|
|
496
|
+
color: '#000', borderRadius: 8, textDecoration: 'none',
|
|
497
|
+
fontWeight: 700, fontSize: 13,
|
|
498
|
+
}}
|
|
499
|
+
onClick={() => {
|
|
500
|
+
// Opens their profile to connect, and marks the request
|
|
501
|
+
// sent, through the bridge whose circle you found them in.
|
|
502
|
+
markRequested(selected, { bridgeId: selected.source_connection_id }).catch(() => {});
|
|
503
|
+
}}
|
|
504
|
+
>
|
|
505
|
+
{/* Beside Auto there's room for one word (a 320 px panel). */}
|
|
506
|
+
{second ? 'Connect' : 'Connect to Unlock Path'}
|
|
507
|
+
</a>
|
|
508
|
+
)} />
|
|
486
509
|
)}
|
|
487
510
|
</div>
|
|
488
511
|
);
|
|
@@ -491,7 +514,7 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
491
514
|
{/* Create Cluster — for D1 connections OR accepted D2 (promoted, ready to bridge for D3) */}
|
|
492
515
|
{(selected.degree === 1 || selected.outreach_status === 'accepted') && (
|
|
493
516
|
canScan
|
|
494
|
-
? <CreateClusterCard key={selected.id} selected={selected} degree2={degree2} />
|
|
517
|
+
? <CreateClusterCard key={selected.id} selected={selected} degree2={degree2} resume={resume} />
|
|
495
518
|
: <ScansNeedYourNetwork csvSource={csvSource} style={{ marginTop: 16 }} />
|
|
496
519
|
)}
|
|
497
520
|
|
|
@@ -1075,6 +1098,87 @@ function PathBox({ routes, selected, tierColors, onSelect }) {
|
|
|
1075
1098
|
);
|
|
1076
1099
|
}
|
|
1077
1100
|
|
|
1101
|
+
const noWatch = () => () => {};
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Where Resume would carry on with this person ({ nextPage, pagesRead, legacy };
|
|
1105
|
+
* null when there is nothing to carry on with; undefined until known), and
|
|
1106
|
+
* whether their circle is being scanned right now. One question to the server
|
|
1107
|
+
* for the whole card (GET /api/scraper?resume=<id>), asked again whenever a
|
|
1108
|
+
* scan ends, since every read moves it. Only those two facts are taken from the
|
|
1109
|
+
* scanner, never its whole answer: that changes every time a running scan's log
|
|
1110
|
+
* grows, and this panel would redraw with it.
|
|
1111
|
+
*/
|
|
1112
|
+
function useResume(person) {
|
|
1113
|
+
const id = person?.id ?? null;
|
|
1114
|
+
const subscribe = id ? watchScanner : noWatch;
|
|
1115
|
+
const lastEnded = useSyncExternalStore(subscribe, () => scannerNow().finished[0]?.startedAt ?? null, () => null);
|
|
1116
|
+
const scanning = useSyncExternalStore(subscribe, () => {
|
|
1117
|
+
const now = scannerNow();
|
|
1118
|
+
return Boolean(now.running && person && scansCircleOf(now, person));
|
|
1119
|
+
}, () => false);
|
|
1120
|
+
const [got, setGot] = useState({ id: null, resume: undefined });
|
|
1121
|
+
useEffect(() => {
|
|
1122
|
+
if (!id) return undefined;
|
|
1123
|
+
let live = true;
|
|
1124
|
+
resumePoint(id).then((r) => { if (live) setGot({ id, resume: r }); }, () => { if (live) setGot({ id, resume: null }); });
|
|
1125
|
+
return () => { live = false; };
|
|
1126
|
+
}, [id, lastEnded]);
|
|
1127
|
+
// Another person's answer never shows on this one's card while theirs is asked.
|
|
1128
|
+
return { resume: id && got.id === id ? got.resume : undefined, scanning: Boolean(id) && scanning };
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Resume, from anywhere on the card: bring the scan box into view and press its
|
|
1133
|
+
* own Resume, so the same checks run and the scan's log or any problem shows
|
|
1134
|
+
* where it's seen. While their scan runs (or Resume is greyed out) it only
|
|
1135
|
+
* brings the box into view, which says why.
|
|
1136
|
+
*/
|
|
1137
|
+
function resumeInScanBox(scanning = false) {
|
|
1138
|
+
const box = document.getElementById('scan-box');
|
|
1139
|
+
if (!box) return;
|
|
1140
|
+
box.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1141
|
+
box.animate?.([{ boxShadow: '0 0 0 3px rgba(255,215,0,0.75)' }, { boxShadow: '0 0 0 0 rgba(255,215,0,0)' }], { duration: 1600 });
|
|
1142
|
+
if (!scanning) box.querySelector('button[data-resume]:not([disabled])')?.click();
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* The line by their name when the read of their list stopped partway (Blake,
|
|
1147
|
+
* 2026-10-03: "if someone was scanned but not finished it needs to show that in
|
|
1148
|
+
* their profile card"). The scan box is far down the card, so this says it where
|
|
1149
|
+
* it's seen, and Resume here presses the box's own Resume: the same checks, and
|
|
1150
|
+
* the box, brought into view, is where the scan's log and any problem show.
|
|
1151
|
+
* While their scan runs it says so, and only takes you to the box.
|
|
1152
|
+
*/
|
|
1153
|
+
function StoppedPartway({ resume, scanning }) {
|
|
1154
|
+
const gold = 'var(--sd-gold, #FFD700)';
|
|
1155
|
+
return (
|
|
1156
|
+
<button type="button" onClick={() => resumeInScanBox(scanning)}
|
|
1157
|
+
aria-label={scanning ? 'Scanning their circle now: go to the scan' : `${stoppedLine(resume)}. Resume from page ${resume.nextPage}`}
|
|
1158
|
+
style={{
|
|
1159
|
+
display: 'flex', alignItems: 'center', gap: 9, width: '100%', boxSizing: 'border-box', margin: '-6px 0 16px',
|
|
1160
|
+
padding: '8px 11px', borderRadius: 8, cursor: 'pointer', textAlign: 'left', font: 'inherit',
|
|
1161
|
+
background: 'rgba(255,215,0,0.07)', border: '1px solid rgba(255,215,0,0.32)', color: 'var(--sd-fg-2, #ddd)',
|
|
1162
|
+
}}>
|
|
1163
|
+
{/* A pause sign while stopped; a pulsing dot while it runs. */}
|
|
1164
|
+
{scanning ? (
|
|
1165
|
+
// ScanLog's keyframes: the box below shows its log while their scan runs.
|
|
1166
|
+
<span aria-hidden="true" className="scanlog-dot" style={{ width: 8, height: 8, borderRadius: '50%', background: gold, flexShrink: 0, animation: 'scanlogPulse 1s ease-in-out infinite' }} />
|
|
1167
|
+
) : (
|
|
1168
|
+
<span aria-hidden="true" style={{ display: 'flex', gap: 2, flexShrink: 0 }}>
|
|
1169
|
+
<span style={{ width: 3, height: 10, borderRadius: 1, background: gold }} />
|
|
1170
|
+
<span style={{ width: 3, height: 10, borderRadius: 1, background: gold }} />
|
|
1171
|
+
</span>
|
|
1172
|
+
)}
|
|
1173
|
+
<span style={{ flex: 1, minWidth: 0, fontSize: 11.5, lineHeight: 1.35 }}>
|
|
1174
|
+
<b style={{ color: gold }}>{scanning ? 'Scanning their circle now' : `${stoppedLine(resume)}.`}</b>
|
|
1175
|
+
{!scanning && <span style={{ color: 'var(--sd-fg-3, #999)' }}> Their list isn't finished.</span>}
|
|
1176
|
+
</span>
|
|
1177
|
+
<span style={{ fontSize: 11.5, fontWeight: 800, color: gold, whiteSpace: 'nowrap' }}>{scanning ? 'See it ↓' : 'Resume ↓'}</span>
|
|
1178
|
+
</button>
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1078
1182
|
// Scan one person's circle, or carry on with it.
|
|
1079
1183
|
//
|
|
1080
1184
|
// LinkedIn lists someone else's connections in its own order, with no dates,
|
|
@@ -1085,11 +1189,18 @@ function PathBox({ routes, selected, tierColors, onSelect }) {
|
|
|
1085
1189
|
// Whether a scan is running comes from the scanner (useScanner), not from this
|
|
1086
1190
|
// card: close the card mid-scan and open it again and the scan is still here,
|
|
1087
1191
|
// and every other card greys its buttons out until it ends.
|
|
1088
|
-
|
|
1192
|
+
//
|
|
1193
|
+
// `resume` is where Resume carries on (useResume, asked once for the card),
|
|
1194
|
+
// null when there is nothing to resume, undefined until it's known. A read that
|
|
1195
|
+
// stopped partway is the first thing the box says: SCAN IN PROGRESS, in gold,
|
|
1196
|
+
// with Resume as the main button (Blake, 2026-10-03: "if someone was scanned but
|
|
1197
|
+
// not finished it needs to show that in their profile card ... showing resume
|
|
1198
|
+
// scanner in that actual card"). It used to sit under CLUSTER ACTIVE, which
|
|
1199
|
+
// reads as done.
|
|
1200
|
+
function CreateClusterCard({ selected, degree2, resume }) {
|
|
1089
1201
|
const scan = useScanner();
|
|
1090
1202
|
const [checking, setChecking] = useState(false);
|
|
1091
1203
|
const [problem, setProblem] = useState(null); // why a scan didn't start: { text, offline }
|
|
1092
|
-
const [resume, setResume] = useState(undefined); // where Resume carries on; null: nothing to resume
|
|
1093
1204
|
const clusterCount = degree2?.filter(d => d.source_connection_id === selected.id).length || 0;
|
|
1094
1205
|
const hasCluster = clusterCount > 0;
|
|
1095
1206
|
|
|
@@ -1097,14 +1208,6 @@ function CreateClusterCard({ selected, degree2 }) {
|
|
|
1097
1208
|
const busy = scan.running && !mine ? busyReason(scan) : null;
|
|
1098
1209
|
const last = scan.finished.find((j) => scansCircleOf(j, selected));
|
|
1099
1210
|
|
|
1100
|
-
// Every read moves where Resume would carry on, so ask again whenever one ends.
|
|
1101
|
-
const lastEnded = scan.finished[0]?.startedAt ?? null;
|
|
1102
|
-
useEffect(() => {
|
|
1103
|
-
let live = true;
|
|
1104
|
-
resumePoint(selected.id).then((r) => { if (live) setResume(r); }, () => { if (live) setResume(null); });
|
|
1105
|
-
return () => { live = false; };
|
|
1106
|
-
}, [selected.id, lastEnded]);
|
|
1107
|
-
|
|
1108
1211
|
async function start(action) {
|
|
1109
1212
|
setProblem(null);
|
|
1110
1213
|
setChecking(true);
|
|
@@ -1122,27 +1225,32 @@ function CreateClusterCard({ selected, degree2 }) {
|
|
|
1122
1225
|
}
|
|
1123
1226
|
|
|
1124
1227
|
const read = hasCluster || Boolean(resume);
|
|
1125
|
-
|
|
1228
|
+
// Gold while a read is stopped partway, green once a circle is in, purple before.
|
|
1229
|
+
const accent = resume ? '#FFD700' : hasCluster ? '#00ff88' : '#9B59B6';
|
|
1230
|
+
// Gold words vanish on a light look; the theme's own gold reads on either.
|
|
1231
|
+
const ink = resume ? 'var(--sd-gold, #FFD700)' : accent;
|
|
1126
1232
|
const off = Boolean(busy) || checking;
|
|
1127
1233
|
const button = (primary) => ({
|
|
1128
1234
|
flex: 1, padding: '11px 8px', borderRadius: 8, fontWeight: 700, fontSize: primary ? 13 : 12,
|
|
1129
1235
|
cursor: off ? 'not-allowed' : 'pointer', opacity: off ? 0.45 : 1,
|
|
1130
1236
|
...(primary
|
|
1131
1237
|
? { border: 'none', background: 'linear-gradient(135deg, #9B59B6, #3498DB)', color: '#fff' }
|
|
1132
|
-
: { border: `1px solid ${accent}55`, background: `${accent}14`, color:
|
|
1238
|
+
: { border: `1px solid ${accent}55`, background: `${accent}14`, color: ink }),
|
|
1133
1239
|
});
|
|
1134
1240
|
|
|
1135
1241
|
return (
|
|
1136
1242
|
<div id="scan-box" style={{
|
|
1137
|
-
background: hasCluster ? 'rgba(0,255,136,0.06)' : 'rgba(155,89,182,0.08)',
|
|
1138
|
-
border: `1px solid ${hasCluster ? 'rgba(0,255,136,0.2)' : 'rgba(155,89,182,0.3)'}`,
|
|
1243
|
+
background: resume ? 'rgba(255,215,0,0.06)' : hasCluster ? 'rgba(0,255,136,0.06)' : 'rgba(155,89,182,0.08)',
|
|
1244
|
+
border: `1px solid ${resume ? 'rgba(255,215,0,0.32)' : hasCluster ? 'rgba(0,255,136,0.2)' : 'rgba(155,89,182,0.3)'}`,
|
|
1139
1245
|
borderRadius: 10, padding: 16, marginTop: 16,
|
|
1140
1246
|
}}>
|
|
1141
|
-
<div style={{ fontSize: 10, color:
|
|
1142
|
-
{
|
|
1247
|
+
<div style={{ fontSize: 10, color: ink, fontWeight: 700, letterSpacing: 1, marginBottom: 4, textAlign: 'center' }}>
|
|
1248
|
+
{scanBoxTitle({ resume, hasCluster })}
|
|
1143
1249
|
</div>
|
|
1144
1250
|
<div style={{ fontSize: 12, color: 'var(--sd-fg-3, #aaa)', marginBottom: 12, textAlign: 'center' }}>
|
|
1145
|
-
{
|
|
1251
|
+
{resume
|
|
1252
|
+
? (hasCluster ? `${clusterCount} connections mapped so far` : 'Their list is only partly read')
|
|
1253
|
+
: hasCluster
|
|
1146
1254
|
? `${clusterCount} connections mapped in Six Degrees`
|
|
1147
1255
|
: <>Scan {selected.name}'s connections to map their network</>}
|
|
1148
1256
|
</div>
|
|
@@ -1169,7 +1277,8 @@ function CreateClusterCard({ selected, degree2 }) {
|
|
|
1169
1277
|
{last && <LastRead job={last} />}
|
|
1170
1278
|
<div style={{ display: 'flex', gap: 8 }}>
|
|
1171
1279
|
{resume && (
|
|
1172
|
-
|
|
1280
|
+
// data-resume: the line by their name presses this one (StoppedPartway).
|
|
1281
|
+
<button data-resume onClick={() => start('resume')} disabled={off} style={button(true)}>
|
|
1173
1282
|
Resume from page {resume.nextPage}
|
|
1174
1283
|
</button>
|
|
1175
1284
|
)}
|