six-degrees 0.3.0 → 0.4.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 +35 -7
- package/.next/standalone/.next/required-server-files.json +6 -1
- package/.next/standalone/.next/routes-manifest.json +18 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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 +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +10 -10
- 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/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/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 +1 -1
- 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/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 +13 -13
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +13 -13
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- 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 +13 -13
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- 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 +13 -13
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- 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 +13 -13
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- package/.next/standalone/.next/server/app/scores/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/scores/page/build-manifest.json +20 -0
- package/.next/standalone/.next/server/app/scores/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/scores/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/scores/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/scores/page.js +13 -0
- package/.next/standalone/.next/server/app/scores/page.js.map +5 -0
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/scores.html +1 -0
- package/.next/standalone/.next/server/app/scores.meta +26 -0
- package/.next/standalone/.next/server/app/scores.rsc +24 -0
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -0
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +28 -0
- 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 +13 -13
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- 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 +13 -13
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0i5hdu4._.js → [root-of-the-server]__03ydxxh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bat8nx._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uag-k5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/_02twlca._.js +10 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +8 -6
- package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +13 -8
- package/.next/standalone/.next/server/chunks/{lib_rpc_02ndsg2.js → lib_rpc_03i8xef.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_08q5vp6.js → lib_rpc_08fc3rp.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_00tx2sm.js → lib_rpc_08r2t_q.js} +1 -1
- package/.next/standalone/.next/server/chunks/{lib_rpc_02-qa4d.js → lib_rpc_0btpv1t.js} +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0an6blh._.js → [root-of-the-server]__0zzcbqg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1194jps._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1z1j3ek._.js → [root-of-the-server]__19ip1qq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_01f38ty._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_06w_gl4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0hw_qnj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_13kzrwv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_15xuwkv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1cyxocj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gpbbw9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1vpfyd-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_scores_page_actions_0x-w58n.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +11 -9
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +5 -5
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +3 -0
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_1bn38gs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0x3i8za._.js +131 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_1bunoni._.js → node_modules_next_dist_1v8aef8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_client_components_server-async-storage_0t1dato.js +3 -0
- 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/0ax_9zzboyv6d.js +44 -0
- package/.next/standalone/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/standalone/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/standalone/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/standalone/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/standalone/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/standalone/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/standalone/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/standalone/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/standalone/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/standalone/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/standalone/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/standalone/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/.next/standalone/CHANGELOG.md +286 -0
- package/.next/standalone/CONTRIBUTING.md +15 -4
- package/.next/standalone/README.md +50 -29
- package/.next/standalone/SECURITY.md +10 -0
- package/.next/standalone/app/api/company-scores/legacy/route.js +1 -1
- package/.next/standalone/app/api/company-scores/route.js +1 -1
- package/.next/standalone/app/api/ingest/route.js +23 -2
- package/.next/standalone/app/api/outreach/route.js +107 -44
- package/.next/standalone/app/api/scraper/route.js +146 -17
- package/.next/standalone/app/api/settings/sector-preview/route.js +3 -2
- package/.next/standalone/app/api/settings/sector-suggestions/route.js +1 -1
- package/.next/standalone/app/api/update-images/route.js +41 -7
- package/.next/standalone/app/avatars/[file]/route.js +6 -1
- package/.next/standalone/app/components/Avatar.js +5 -2
- package/.next/standalone/app/components/ChainView.js +659 -469
- package/.next/standalone/app/components/CompanyScores.js +16 -10
- package/.next/standalone/app/components/EmptyState.js +1 -1
- package/.next/standalone/app/components/FieldStep.js +94 -0
- package/.next/standalone/app/components/ForceGraph.js +183 -768
- package/.next/standalone/app/components/GridView.js +9 -7
- package/.next/standalone/app/components/LegacyScoresCard.js +1 -1
- package/.next/standalone/app/components/LinkedInLimits.js +22 -7
- package/.next/standalone/app/components/ListView.js +6 -4
- package/.next/standalone/app/components/MappingProgress.js +5 -6
- package/.next/standalone/app/components/OnboardingGate.js +1 -1
- package/.next/standalone/app/components/OrbitGraph.js +33 -1
- package/.next/standalone/app/components/OutlinkQuest.js +5 -4
- package/.next/standalone/app/components/PathsAnalyzer.js +1 -1
- package/.next/standalone/app/components/RingsView.js +9 -8
- package/.next/standalone/app/components/SeparationView.js +96 -14
- package/.next/standalone/app/components/Sidebar.js +366 -287
- package/.next/standalone/app/components/TheirCircle.js +300 -0
- package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
- package/.next/standalone/app/components/settings/SectorSection.js +21 -195
- package/.next/standalone/app/components/settings/TierSection.js +99 -0
- package/.next/standalone/app/components/useRequests.js +12 -0
- package/.next/standalone/app/components/useScanner.js +13 -0
- package/.next/standalone/app/components/views.js +3 -5
- package/.next/standalone/app/globals.css +7 -0
- package/.next/standalone/app/launch/page.js +6 -3
- package/.next/standalone/app/page.js +183 -74
- package/.next/standalone/app/paths/page.js +20 -14
- package/.next/standalone/app/profile/page.js +3 -3
- package/.next/standalone/app/queue/page.js +56 -60
- package/.next/standalone/app/scores/page.js +72 -0
- package/.next/standalone/app/settings/page.js +15 -3
- package/.next/standalone/app/setup/page.js +423 -260
- package/.next/standalone/db/schema.js +2 -1
- package/.next/standalone/eslint.config.mjs +3 -3
- package/.next/standalone/install.sh +3 -3
- package/.next/standalone/lib/chain-layout.js +139 -0
- package/.next/standalone/lib/circle.js +169 -0
- package/.next/standalone/lib/companies.js +1 -1
- package/.next/standalone/lib/csv.js +21 -10
- package/.next/standalone/lib/data-export.js +2 -1
- package/.next/standalone/lib/data-folder.js +4 -2
- package/.next/standalone/lib/db-client.js +1 -0
- package/.next/standalone/lib/galaxy.js +12 -0
- package/.next/standalone/lib/ingest.js +27 -8
- package/.next/standalone/lib/legacy-offer.js +7 -6
- package/.next/standalone/lib/legacy-scores.js +2 -2
- package/.next/standalone/lib/linkedin-limits.js +30 -8
- package/.next/standalone/lib/photos.js +47 -0
- package/.next/standalone/lib/quest.js +8 -3
- package/.next/standalone/lib/rarity.js +80 -0
- package/.next/standalone/lib/reach.js +113 -0
- package/.next/standalone/lib/requests-client.js +113 -0
- package/.next/standalone/lib/rpc.js +26 -15
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scanner-setup.js +20 -1
- package/.next/standalone/lib/score-guess.js +60 -0
- package/.next/standalone/lib/scoring.js +1761 -69
- package/.next/standalone/lib/scraper-client.js +235 -17
- package/.next/standalone/lib/sector-directory.js +1 -1
- package/.next/standalone/lib/sector-focus.js +17 -3
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/settings-client.js +21 -0
- package/.next/standalone/lib/settings-effects.js +19 -1
- package/.next/standalone/lib/settings.js +7 -2
- package/.next/standalone/next.config.mjs +25 -3
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/public/demo-data.json +1 -1
- package/.next/standalone/scripts/build-app.mjs +11 -2
- package/.next/standalone/scripts/check-node.mjs +1 -1
- package/.next/standalone/scripts/gen-synthetic.mjs +1 -1
- package/.next/standalone/scripts/image_store.py +77 -21
- package/.next/standalone/scripts/scrape.py +305 -29
- package/.next/standalone/server.js +1 -1
- package/.next/static/chunks/0ax_9zzboyv6d.js +44 -0
- package/.next/static/chunks/0cuuo2jwc9h94.js +1 -0
- package/.next/static/chunks/0l1568qff0efj.js +1 -0
- package/.next/static/chunks/0muwt9bykzr-1.js +1 -0
- package/.next/static/chunks/0w76iraw7ij3k.js +18 -0
- package/.next/static/chunks/135n87ydxjrra.js +2 -0
- package/.next/static/chunks/18xu0fbgo_qz-.js +1 -0
- package/.next/static/chunks/28bu26ausqzmn.js +1 -0
- package/.next/static/chunks/{40nvbgfn7-072.css → 2p6o90z0vgcwr.css} +1 -1
- package/.next/static/chunks/2pnbm1z6j7xab.js +1 -0
- package/.next/static/chunks/2xjho0lroiwlq.js +1 -0
- package/.next/static/chunks/3e8zg6ctvvb8p.js +33 -0
- package/.next/static/chunks/3qh78g2aqhprd.js +2 -0
- package/CHANGELOG.md +286 -0
- package/README.md +50 -29
- package/SECURITY.md +10 -0
- package/db/schema.js +2 -1
- package/lib/chain-layout.js +139 -0
- package/lib/circle.js +169 -0
- package/lib/companies.js +1 -1
- package/lib/csv.js +21 -10
- package/lib/data-export.js +2 -1
- package/lib/data-folder.js +4 -2
- package/lib/db-client.js +1 -0
- package/lib/galaxy.js +12 -0
- package/lib/ingest.js +27 -8
- package/lib/legacy-offer.js +7 -6
- package/lib/legacy-scores.js +2 -2
- package/lib/linkedin-limits.js +30 -8
- package/lib/photos.js +47 -0
- package/lib/quest.js +8 -3
- package/lib/rarity.js +80 -0
- package/lib/reach.js +113 -0
- package/lib/requests-client.js +113 -0
- package/lib/rpc.js +26 -15
- package/lib/scan-state.js +1 -0
- package/lib/scanner-setup.js +20 -1
- package/lib/score-guess.js +60 -0
- package/lib/scoring.js +1761 -69
- package/lib/scraper-client.js +235 -17
- package/lib/sector-directory.js +1 -1
- package/lib/sector-focus.js +17 -3
- package/lib/separation.js +4 -2
- package/lib/settings-client.js +21 -0
- package/lib/settings-effects.js +19 -1
- package/lib/settings.js +7 -2
- package/package.json +2 -2
- package/public/demo-data.json +1 -1
- package/scripts/check-node.mjs +1 -1
- package/scripts/image_store.py +77 -21
- package/scripts/scrape.py +305 -29
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1t3rugi._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0_8i3ft.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0huvw-3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0ip2vdh.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0jzisgh.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0yi9xvd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1jyhd0o.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1kn59-i.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1q2jkza.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1vpm1sk.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1x6p_31.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_07u8byv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_08r_mlf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0942v8n._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0hl_t7r._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0tb83dw._.js +0 -132
- package/.next/standalone/.next/server/chunks/ssr/_1-87bs1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_17gui6a._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_16vay6l._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1kciwn4._.js +0 -3
- package/.next/standalone/.next/static/chunks/06x3jiiz96m5t.js +0 -1
- package/.next/standalone/.next/static/chunks/08k1crid-05dt.js +0 -1
- package/.next/standalone/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
- package/.next/standalone/.next/static/chunks/0q-_khndfxubv.js +0 -2
- package/.next/standalone/.next/static/chunks/1c79veqedvubt.js +0 -1
- package/.next/standalone/.next/static/chunks/1chp3nx45meg5.js +0 -44
- package/.next/standalone/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
- package/.next/standalone/.next/static/chunks/1uadqylrod5_v.js +0 -1
- package/.next/standalone/.next/static/chunks/23u0zclgwpc4d.js +0 -1
- package/.next/standalone/.next/static/chunks/289ktj97di6zg.js +0 -1
- package/.next/standalone/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
- package/.next/standalone/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
- package/.next/standalone/app/components/BridgeRing.js +0 -753
- package/.next/static/chunks/06x3jiiz96m5t.js +0 -1
- package/.next/static/chunks/08k1crid-05dt.js +0 -1
- package/.next/static/chunks/0ngx9s6-zgshe.js +0 -33
- package/.next/static/chunks/0q-_khndfxubv.js +0 -2
- package/.next/static/chunks/1c79veqedvubt.js +0 -1
- package/.next/static/chunks/1chp3nx45meg5.js +0 -44
- package/.next/static/chunks/1irqj0lf-fu3d.js +0 -1
- package/.next/static/chunks/1uadqylrod5_v.js +0 -1
- package/.next/static/chunks/23u0zclgwpc4d.js +0 -1
- package/.next/static/chunks/289ktj97di6zg.js +0 -1
- package/.next/static/chunks/2b3lmf8e6uj8m.js +0 -16
- package/.next/static/chunks/3npq5yd7c4dmn.js +0 -2
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{qqqcxvzBUizkECrT4swyK → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
import { Suspense, useState, useEffect, useCallback, useRef } from 'react';
|
|
4
|
+
import { useSearchParams } from 'next/navigation';
|
|
4
5
|
import OnboardingGate from '../components/OnboardingGate';
|
|
5
6
|
import Link from 'next/link';
|
|
6
|
-
import { stopScrape } from '../../lib/scraper-client';
|
|
7
|
-
import { setupStep } from '../../lib/scanner-setup';
|
|
7
|
+
import { stopScrape, pickedPerson } from '../../lib/scraper-client';
|
|
8
|
+
import { setupStep, askForField } from '../../lib/scanner-setup';
|
|
9
|
+
import { IS_DEMO } from '../../lib/demo';
|
|
10
|
+
import { circleScanCost } from '../../lib/reach';
|
|
8
11
|
import { BudgetBox, CooldownBanner, PausedList } from '../components/LinkedInLimits';
|
|
12
|
+
import FieldStep, { FieldAnswer } from '../components/FieldStep';
|
|
9
13
|
|
|
10
14
|
// Everything here runs through /api/scraper. There is deliberately no second
|
|
11
15
|
// server and no command to copy: the step where people gave up was starting a
|
|
@@ -15,6 +19,7 @@ const BG = '#0a0a1a';
|
|
|
15
19
|
const LINE = '1px solid rgba(255,255,255,0.1)';
|
|
16
20
|
|
|
17
21
|
const ACTION_LABELS = {
|
|
22
|
+
photos: 'Saving profile photos to this computer',
|
|
18
23
|
install: 'Installing the scanner',
|
|
19
24
|
setup: 'Setting up the scanner',
|
|
20
25
|
login: 'Waiting for you to sign in',
|
|
@@ -27,7 +32,9 @@ const ACTION_LABELS = {
|
|
|
27
32
|
};
|
|
28
33
|
|
|
29
34
|
export default function SetupPage() {
|
|
30
|
-
|
|
35
|
+
// Suspense because SetupInner reads the address's ?scan= (useSearchParams),
|
|
36
|
+
// which Next requires to sit inside one for the page to build.
|
|
37
|
+
return <OnboardingGate><Suspense><SetupInner /></Suspense></OnboardingGate>;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
function SetupInner() {
|
|
@@ -60,6 +67,28 @@ function SetupInner() {
|
|
|
60
67
|
const [finish, setFinish] = useRemembered('six-degrees-bridge-finish', true);
|
|
61
68
|
const [error, setError] = useState(null);
|
|
62
69
|
const logRef = useRef(null);
|
|
70
|
+
// What's saved, for the question about your field: undefined while it
|
|
71
|
+
// loads, null when it couldn't be read. And the answer given here, if any.
|
|
72
|
+
const [settings, setSettings] = useState(undefined);
|
|
73
|
+
const [field, setField] = useState(null);
|
|
74
|
+
// Someone sent here to have their circle scanned (Bridge Chains, the Degrees
|
|
75
|
+
// panel's Ready to scan): /setup?scan=<id>. Nothing starts until it's confirmed.
|
|
76
|
+
// The router's search params rather than window.location: every way here is a
|
|
77
|
+
// click, and the address bar only changes after this page has rendered, so the
|
|
78
|
+
// pick was lost on all of them and only a reload showed it.
|
|
79
|
+
const pickId = useSearchParams().get('scan');
|
|
80
|
+
const [pick, setPick] = useState(null);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!pickId) return undefined;
|
|
83
|
+
let live = true;
|
|
84
|
+
pickedPerson(pickId).then((d) => { if (live) setPick(d?.person ? d : null); }, () => {});
|
|
85
|
+
return () => { live = false; };
|
|
86
|
+
}, [pickId]);
|
|
87
|
+
// Next's router follows history.replaceState, so pickId drops ?scan= as well.
|
|
88
|
+
function unpick() {
|
|
89
|
+
setPick(null);
|
|
90
|
+
try { window.history.replaceState(null, '', window.location.pathname); } catch { /* the link stays */ }
|
|
91
|
+
}
|
|
63
92
|
|
|
64
93
|
const poll = useCallback(async () => {
|
|
65
94
|
try {
|
|
@@ -76,6 +105,16 @@ function SetupInner() {
|
|
|
76
105
|
return () => clearInterval(t);
|
|
77
106
|
}, [poll]);
|
|
78
107
|
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (IS_DEMO) return;
|
|
110
|
+
let off = false;
|
|
111
|
+
fetch('/api/settings')
|
|
112
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
113
|
+
.then((d) => { if (!off) setSettings(d?.settings || null); })
|
|
114
|
+
.catch(() => { if (!off) setSettings(null); });
|
|
115
|
+
return () => { off = true; };
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
79
118
|
useEffect(() => {
|
|
80
119
|
if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
|
|
81
120
|
}, [s?.log?.length]);
|
|
@@ -86,6 +125,13 @@ function SetupInner() {
|
|
|
86
125
|
poll();
|
|
87
126
|
}
|
|
88
127
|
|
|
128
|
+
// The question is answered: the steps take its place, from the top, since
|
|
129
|
+
// it may have been answered from far down the picker.
|
|
130
|
+
function answered(sectors) {
|
|
131
|
+
setField({ sectors });
|
|
132
|
+
window.scrollTo(0, 0);
|
|
133
|
+
}
|
|
134
|
+
|
|
89
135
|
async function run(action, extra = {}) {
|
|
90
136
|
setError(null);
|
|
91
137
|
setBusy(true);
|
|
@@ -123,6 +169,9 @@ function SetupInner() {
|
|
|
123
169
|
// you know, then the people found through them, then company scans.
|
|
124
170
|
const net = s?.network || { first: 0, second: 0, third: 0 };
|
|
125
171
|
const mapped = net.first;
|
|
172
|
+
// Your field, asked once before the first scan (lib/scanner-setup.js
|
|
173
|
+
// askForField). The steps wait while that isn't known yet (null).
|
|
174
|
+
const askField = IS_DEMO || field ? false : askForField(s, settings);
|
|
126
175
|
// A run that ended badly, and the line that says why — the last thing it
|
|
127
176
|
// printed before stopping. Shown as a box, not left for someone to find in the log.
|
|
128
177
|
const failed = s && !running && s.exitCode != null && s.exitCode !== 0;
|
|
@@ -157,276 +206,318 @@ function SetupInner() {
|
|
|
157
206
|
This reads your own LinkedIn connections in a real Chrome window on this
|
|
158
207
|
machine and saves them here. Nothing leaves your computer, and you will never
|
|
159
208
|
be asked for your password — you sign in yourself, once.
|
|
160
|
-
{!mapped &&
|
|
209
|
+
{!mapped && (askField
|
|
210
|
+
? ' One optional question, then three steps and your galaxy appears.'
|
|
211
|
+
: ' Three steps and your galaxy appears; each one ticks itself off.')}
|
|
161
212
|
</p>
|
|
162
213
|
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
214
|
+
{/* The first look at the scanner can take a while (it looks for Python): say so, never a blank page. */}
|
|
215
|
+
{askField === null && <p style={{ color: '#778', fontSize: 13 }}>Checking your setup…</p>}
|
|
216
|
+
{askField && <FieldStep onDone={answered} />}
|
|
217
|
+
{field && <FieldAnswer sectors={field.sectors} />}
|
|
218
|
+
|
|
219
|
+
{askField === false && <>
|
|
220
|
+
{/* By id: the server finds them by their profile, since two connections can share a name. */}
|
|
221
|
+
{pick?.person.id === pickId && (
|
|
222
|
+
<ScanOne
|
|
223
|
+
pick={pick} pages={pages} setPages={setPages} li={li} running={running} s={s}
|
|
224
|
+
canSearch={canSearch} busy={busy} onUnpick={unpick}
|
|
225
|
+
onStart={() => run('bridge', { id: pick.person.id, maxPages: pages })}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
{mapped > 0 && !running && (
|
|
230
|
+
<Box>
|
|
231
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
|
|
232
|
+
<div style={{ flex: 1, minWidth: 200 }}>
|
|
233
|
+
<b>{mapped.toLocaleString()} connections</b>
|
|
234
|
+
{net.second > 0 && <>, plus {net.second.toLocaleString()} people in their circles</>}
|
|
235
|
+
{net.third > 0 && <> and {net.third.toLocaleString()} from company scans</>}
|
|
236
|
+
.{' '}
|
|
237
|
+
<span style={{ color: '#9aa' }}>Your galaxy is ready.</span>
|
|
238
|
+
</div>
|
|
239
|
+
<Link href="/" style={{
|
|
240
|
+
padding: '9px 18px', borderRadius: 7, fontSize: 13.5, fontWeight: 700,
|
|
241
|
+
color: '#0a0a1a', textDecoration: 'none',
|
|
242
|
+
background: 'linear-gradient(135deg, #FFD700, #FF6B35)',
|
|
243
|
+
}}>See your network →</Link>
|
|
172
244
|
</div>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{notFound && (
|
|
196
|
-
<Box tone="bad">
|
|
197
|
-
<b>Can’t find the scanner files.</b><br />
|
|
198
|
-
<span style={{ color: '#9aa' }}>
|
|
199
|
-
Expected <code style={code}>scripts/scrape.py</code> next to the app. If you
|
|
200
|
-
downloaded a zip, run the app from inside the project folder.
|
|
201
|
-
</span>
|
|
202
|
-
</Box>
|
|
203
|
-
)}
|
|
204
|
-
|
|
205
|
-
{needsChrome && (
|
|
206
|
-
<Box tone="bad">
|
|
207
|
-
<b>Google Chrome isn’t installed.</b><br />
|
|
208
|
-
<span style={{ color: '#9aa' }}>
|
|
209
|
-
The scanner drives your real Chrome. Install it from{' '}
|
|
210
|
-
<a href="https://www.google.com/chrome/" target="_blank" rel="noreferrer"
|
|
211
|
-
style={{ color: '#3498DB' }}>google.com/chrome</a>, then reload this page.
|
|
212
|
-
</span>
|
|
213
|
-
</Box>
|
|
214
|
-
)}
|
|
215
|
-
|
|
216
|
-
{/* The Python inside the app didn't work: what happened, and what to do instead.
|
|
217
|
-
Red only while there is something to do; once another Python runs the
|
|
218
|
-
scanner it is just news. */}
|
|
219
|
-
{s && step1.note && (
|
|
220
|
-
<Box tone={step1.done ? undefined : 'bad'}>{step1.note}</Box>
|
|
221
|
-
)}
|
|
222
|
-
|
|
223
|
-
{/* ---- step 1 : the scanner itself ---- */}
|
|
224
|
-
<Step
|
|
225
|
-
n={1}
|
|
226
|
-
done={step1.done}
|
|
227
|
-
title="Set up the scanner"
|
|
228
|
-
body={s ? step1.text : ''}
|
|
229
|
-
action={
|
|
230
|
-
s && step1.button && (
|
|
231
|
-
<Btn onClick={() => run(step1.button.action)} disabled={busy || running}>
|
|
232
|
-
{step1.button.label}
|
|
233
|
-
</Btn>
|
|
234
|
-
)
|
|
235
|
-
}
|
|
236
|
-
/>
|
|
237
|
-
|
|
238
|
-
{/* ---- step 2 : sign in ---- */}
|
|
239
|
-
<Step
|
|
240
|
-
n={2}
|
|
241
|
-
done={!!c.signedIn}
|
|
242
|
-
title="Sign into LinkedIn"
|
|
243
|
-
body={
|
|
244
|
-
c.signedIn
|
|
245
|
-
? 'Signed in on this machine. If LinkedIn ever asks for a security check, or a scan says you were signed out, open LinkedIn here and finish it by hand.'
|
|
246
|
-
: 'Opens a Chrome window. Sign in with your email and password — “Continue with Google” cannot work here, because Google blocks its sign-in inside automated browsers.'
|
|
247
|
-
}
|
|
248
|
-
action={
|
|
249
|
-
// Shown after sign-in too: a security check survives the session
|
|
250
|
-
// cookie, so "signed in" can still need a person (TRAPS §35).
|
|
251
|
-
c.dependencies && (
|
|
252
|
-
<Btn onClick={() => run('login')} disabled={busy || running}>
|
|
253
|
-
{running && s.action === 'login' ? 'Waiting for you…' : 'Open LinkedIn'}
|
|
254
|
-
</Btn>
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
/>
|
|
258
|
-
|
|
259
|
-
{/* ---- step 3 : the people you know ---- */}
|
|
260
|
-
<Step
|
|
261
|
-
n={3}
|
|
262
|
-
done={mapped > 0}
|
|
263
|
-
title="1st degree — the people you know"
|
|
264
|
-
body="The first scan walks your whole connections list, about a minute and a half for 750 people. After that, Check for new only looks at what has been added since."
|
|
265
|
-
action={
|
|
266
|
-
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
|
267
|
-
<Btn onClick={() => run('full')} disabled={!canSearch} primary>
|
|
268
|
-
{running && s.action === 'full' ? 'Scanning…' : 'Scan my whole network'}
|
|
269
|
-
</Btn>
|
|
270
|
-
<Btn onClick={() => run('refresh')} disabled={!canSearch}>
|
|
271
|
-
{running && s.action === 'refresh' ? 'Checking…' : 'Check for new'}
|
|
272
|
-
</Btn>
|
|
273
|
-
</div>
|
|
274
|
-
}
|
|
275
|
-
/>
|
|
276
|
-
|
|
277
|
-
{/* ---- step 4 : who they know ---- */}
|
|
278
|
-
<Step
|
|
279
|
-
n={4}
|
|
280
|
-
done={false}
|
|
281
|
-
title="2nd degree — the people they know"
|
|
282
|
-
body={
|
|
283
|
-
<>
|
|
284
|
-
This is what fills <b>Degrees</b> and <b>Outlink</b>: it opens each of your
|
|
285
|
-
connections in turn and reads who <i>they</i> know. Most people hide their
|
|
286
|
-
connections — those are noted and never tried again.
|
|
287
|
-
<br /><br />
|
|
288
|
-
It is slow on purpose, about two minutes between each person, because
|
|
289
|
-
this is the part LinkedIn notices. During development a real account was
|
|
290
|
-
temporarily restricted after roughly <b>19 people in one sitting</b>.
|
|
291
|
-
Run a batch, leave it for a day, run another — and stop the moment
|
|
292
|
-
LinkedIn mentions unusual activity. It always picks up where it left
|
|
293
|
-
off: anyone still without a mapped circle, in the order you choose below —
|
|
294
|
-
your newest connections first, or highest tier first. Each person’s
|
|
295
|
-
list is read to the end, saving every 10 pages, and a read that is stopped
|
|
296
|
-
carries on from the same page next time.
|
|
297
|
-
</>
|
|
298
|
-
}
|
|
299
|
-
action={
|
|
300
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
301
|
-
<CooldownBanner
|
|
302
|
-
cooldown={li?.cooldown}
|
|
303
|
-
disabled={busy}
|
|
304
|
-
onLift={() => run('lift-cooldown')}
|
|
305
|
-
/>
|
|
306
|
-
<BudgetBox li={li} disabled={busy} onSetLimits={(l) => run('set-limits', l)} />
|
|
307
|
-
{order === 'score' && <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
308
|
-
<span style={{ fontSize: 12, color: '#8b9a9a' }}>Work through</span>
|
|
309
|
-
{['S', 'A', 'B', 'C', 'D'].map((t) => {
|
|
310
|
-
const on = tiers.includes(t);
|
|
311
|
-
return (
|
|
312
|
-
<button
|
|
313
|
-
key={t}
|
|
314
|
-
onClick={() => setTiers((v) => (v.includes(t) ? v.filter((x) => x !== t) : [...v, t]))}
|
|
315
|
-
disabled={running}
|
|
316
|
-
style={{
|
|
317
|
-
width: 30, height: 28, borderRadius: 7, fontSize: 12, fontWeight: 700,
|
|
318
|
-
cursor: running ? 'not-allowed' : 'pointer', border: LINE,
|
|
319
|
-
background: on ? 'rgba(52,152,219,0.22)' : 'rgba(255,255,255,0.05)',
|
|
320
|
-
color: on ? '#cfe6f7' : '#667',
|
|
321
|
-
}}
|
|
322
|
-
>{t}</button>
|
|
323
|
-
);
|
|
324
|
-
})}
|
|
325
|
-
<span style={{ fontSize: 11.5, color: '#667' }}>
|
|
326
|
-
{tiers.length ? '' : 'pick at least one'}
|
|
327
|
-
</span>
|
|
328
|
-
</div>}
|
|
329
|
-
{order === 'newest' && (
|
|
330
|
-
<div style={{ fontSize: 12, color: '#8b9a9a', lineHeight: 1.6 }}>
|
|
331
|
-
Goes by the date you connected, newest first, across every tier. Run
|
|
332
|
-
{' '}<b>Check for new</b> first so your latest connections are in the list.
|
|
245
|
+
</Box>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{/* Photos an older version kept as links to LinkedIn. The app shows
|
|
249
|
+
only photos saved here (lib/photos.js), so until then those people
|
|
250
|
+
show initials. Every scan saves them at its end; this is the way
|
|
251
|
+
without scanning. */}
|
|
252
|
+
{s?.photosWaiting > 0 && !running && (
|
|
253
|
+
<Box>
|
|
254
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
|
|
255
|
+
<div style={{ flex: 1, minWidth: 200 }}>
|
|
256
|
+
<b>{s.photosWaiting.toLocaleString()} {s.photosWaiting === 1 ? 'photo isn’t' : 'photos aren’t'} saved on this computer yet.</b>{' '}
|
|
257
|
+
<span style={{ color: '#9aa' }}>
|
|
258
|
+
An older version kept them as links to LinkedIn. The app doesn’t load
|
|
259
|
+
photos from LinkedIn while you browse, so those people show initials until
|
|
260
|
+
the photos are saved here: now, or at the end of your next scan. An expired
|
|
261
|
+
link can’t be saved; that person’s photo comes back when they’re next scanned.
|
|
262
|
+
</span>
|
|
263
|
+
{s && !c.dependencies && (
|
|
264
|
+
<div style={{ color: '#8b9a9a', fontSize: 12.5, marginTop: 6 }}>Set up the scanner first (step 1 below): it saves them.</div>
|
|
265
|
+
)}
|
|
333
266
|
</div>
|
|
267
|
+
<Btn onClick={() => run('photos')} disabled={busy || !c.dependencies}>Save photos</Btn>
|
|
268
|
+
</div>
|
|
269
|
+
</Box>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{failed && (
|
|
273
|
+
<Box tone="bad">
|
|
274
|
+
<b>The last run stopped before it finished.</b>
|
|
275
|
+
{failReason && (
|
|
276
|
+
<div style={{
|
|
277
|
+
marginTop: 6, color: '#e8c4c4', whiteSpace: 'pre-wrap',
|
|
278
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', fontSize: 12.5,
|
|
279
|
+
}}>{failReason}</div>
|
|
334
280
|
)}
|
|
281
|
+
<div style={{ color: '#9aa', marginTop: 6 }}>The full log is below.</div>
|
|
282
|
+
</Box>
|
|
283
|
+
)}
|
|
284
|
+
|
|
285
|
+
{notFound && (
|
|
286
|
+
<Box tone="bad">
|
|
287
|
+
<b>Can’t find the scanner files.</b><br />
|
|
288
|
+
<span style={{ color: '#9aa' }}>
|
|
289
|
+
Expected <code style={code}>scripts/scrape.py</code> next to the app. If you
|
|
290
|
+
downloaded a zip, run the app from inside the project folder.
|
|
291
|
+
</span>
|
|
292
|
+
</Box>
|
|
293
|
+
)}
|
|
294
|
+
|
|
295
|
+
{needsChrome && (
|
|
296
|
+
<Box tone="bad">
|
|
297
|
+
<b>Google Chrome isn’t installed.</b><br />
|
|
298
|
+
<span style={{ color: '#9aa' }}>
|
|
299
|
+
The scanner drives your real Chrome. Install it from{' '}
|
|
300
|
+
<a href="https://www.google.com/chrome/" target="_blank" rel="noreferrer"
|
|
301
|
+
style={{ color: '#3498DB' }}>google.com/chrome</a>, then reload this page.
|
|
302
|
+
</span>
|
|
303
|
+
</Box>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
{/* The Python inside the app didn't work: what happened, and what to do instead.
|
|
307
|
+
Red only while there is something to do; once another Python runs the
|
|
308
|
+
scanner it is just news. */}
|
|
309
|
+
{s && step1.note && (
|
|
310
|
+
<Box tone={step1.done ? undefined : 'bad'}>{step1.note}</Box>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
{/* ---- step 1 : the scanner itself ---- */}
|
|
314
|
+
<Step
|
|
315
|
+
n={1}
|
|
316
|
+
done={step1.done}
|
|
317
|
+
title="Set up the scanner"
|
|
318
|
+
body={s ? step1.text : ''}
|
|
319
|
+
action={
|
|
320
|
+
s && step1.button && (
|
|
321
|
+
<Btn onClick={() => run(step1.button.action)} disabled={busy || running}>
|
|
322
|
+
{step1.button.label}
|
|
323
|
+
</Btn>
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
/>
|
|
327
|
+
|
|
328
|
+
{/* ---- step 2 : sign in ---- */}
|
|
329
|
+
<Step
|
|
330
|
+
n={2}
|
|
331
|
+
done={!!c.signedIn}
|
|
332
|
+
title="Sign into LinkedIn"
|
|
333
|
+
body={
|
|
334
|
+
c.signedIn
|
|
335
|
+
? 'Signed in on this machine. If LinkedIn ever asks for a security check, or a scan says you were signed out, open LinkedIn here and finish it by hand.'
|
|
336
|
+
: 'Opens a Chrome window. Sign in with your email and password — “Continue with Google” cannot work here, because Google blocks its sign-in inside automated browsers.'
|
|
337
|
+
}
|
|
338
|
+
action={
|
|
339
|
+
// Shown after sign-in too: a security check survives the session
|
|
340
|
+
// cookie, so "signed in" can still need a person (TRAPS §35).
|
|
341
|
+
c.dependencies && (
|
|
342
|
+
<Btn onClick={() => run('login')} disabled={busy || running}>
|
|
343
|
+
{running && s.action === 'login' ? 'Waiting for you…' : 'Open LinkedIn'}
|
|
344
|
+
</Btn>
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
/>
|
|
348
|
+
|
|
349
|
+
{/* ---- step 3 : the people you know ---- */}
|
|
350
|
+
<Step
|
|
351
|
+
n={3}
|
|
352
|
+
done={mapped > 0}
|
|
353
|
+
title="1st degree — the people you know"
|
|
354
|
+
body="The first scan walks your whole connections list, about a minute and a half for 750 people. After that, Check for new only looks at what has been added since."
|
|
355
|
+
action={
|
|
356
|
+
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
|
357
|
+
<Btn onClick={() => run('full')} disabled={!canSearch} primary>
|
|
358
|
+
{running && s.action === 'full' ? 'Scanning…' : 'Scan my whole network'}
|
|
359
|
+
</Btn>
|
|
360
|
+
<Btn onClick={() => run('refresh')} disabled={!canSearch}>
|
|
361
|
+
{running && s.action === 'refresh' ? 'Checking…' : 'Check for new'}
|
|
362
|
+
</Btn>
|
|
363
|
+
</div>
|
|
364
|
+
}
|
|
365
|
+
/>
|
|
366
|
+
|
|
367
|
+
{/* ---- step 4 : who they know ---- */}
|
|
368
|
+
<Step
|
|
369
|
+
n={4}
|
|
370
|
+
done={false}
|
|
371
|
+
title="2nd degree — the people they know"
|
|
372
|
+
body={
|
|
373
|
+
<>
|
|
374
|
+
This is what fills <b>Degrees</b> and <b>Outlink</b>: it opens each of your
|
|
375
|
+
connections in turn and reads who <i>they</i> know. Most people hide their
|
|
376
|
+
connections — those are noted and never tried again.
|
|
377
|
+
<br /><br />
|
|
378
|
+
It is slow on purpose, about two minutes between each person, because
|
|
379
|
+
this is the part LinkedIn notices. During development a real account was
|
|
380
|
+
temporarily restricted after roughly <b>19 people in one sitting</b>.
|
|
381
|
+
Run a batch, leave it for a day, run another — and stop the moment
|
|
382
|
+
LinkedIn mentions unusual activity. It always picks up where it left
|
|
383
|
+
off: anyone still without a mapped circle, in the order you choose below —
|
|
384
|
+
your newest connections first, or highest tier first. Each person’s
|
|
385
|
+
list is read to the end, saving every 10 pages, and a read that is stopped
|
|
386
|
+
carries on from the same page next time.
|
|
387
|
+
</>
|
|
388
|
+
}
|
|
389
|
+
action={
|
|
390
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
391
|
+
<CooldownBanner
|
|
392
|
+
cooldown={li?.cooldown}
|
|
393
|
+
disabled={busy}
|
|
394
|
+
onLift={() => run('lift-cooldown')}
|
|
395
|
+
/>
|
|
396
|
+
<BudgetBox li={li} disabled={busy} onSetLimits={(l) => run('set-limits', l)} />
|
|
397
|
+
{order === 'score' && <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
398
|
+
<span style={{ fontSize: 12, color: '#8b9a9a' }}>Work through</span>
|
|
399
|
+
{['S', 'A', 'B', 'C', 'D'].map((t) => {
|
|
400
|
+
const on = tiers.includes(t);
|
|
401
|
+
return (
|
|
402
|
+
<button
|
|
403
|
+
key={t}
|
|
404
|
+
onClick={() => setTiers((v) => (v.includes(t) ? v.filter((x) => x !== t) : [...v, t]))}
|
|
405
|
+
disabled={running}
|
|
406
|
+
style={{
|
|
407
|
+
width: 30, height: 28, borderRadius: 7, fontSize: 12, fontWeight: 700,
|
|
408
|
+
cursor: running ? 'not-allowed' : 'pointer', border: LINE,
|
|
409
|
+
background: on ? 'rgba(52,152,219,0.22)' : 'rgba(255,255,255,0.05)',
|
|
410
|
+
color: on ? '#cfe6f7' : '#667',
|
|
411
|
+
}}
|
|
412
|
+
>{t}</button>
|
|
413
|
+
);
|
|
414
|
+
})}
|
|
415
|
+
<span style={{ fontSize: 11.5, color: '#667' }}>
|
|
416
|
+
{tiers.length ? '' : 'pick at least one'}
|
|
417
|
+
</span>
|
|
418
|
+
</div>}
|
|
419
|
+
{order === 'newest' && (
|
|
420
|
+
<div style={{ fontSize: 12, color: '#8b9a9a', lineHeight: 1.6 }}>
|
|
421
|
+
Goes by the date you connected, newest first, across every tier. Run
|
|
422
|
+
{' '}<b>Check for new</b> first so your latest connections are in the list.
|
|
423
|
+
</div>
|
|
424
|
+
)}
|
|
425
|
+
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
426
|
+
<span style={{ fontSize: 12, color: '#8b9a9a' }}>Start with</span>
|
|
427
|
+
<select value={order} onChange={(e) => setOrder(e.target.value)} disabled={running} style={selectStyle}>
|
|
428
|
+
<option value="newest">Newest connections first</option>
|
|
429
|
+
<option value="score">Highest tier first</option>
|
|
430
|
+
</select>
|
|
431
|
+
<span style={{ fontSize: 12, color: '#8b9a9a' }}>Read up to</span>
|
|
432
|
+
<select value={pages} onChange={(e) => setPages(Number(e.target.value))} disabled={running} style={selectStyle}>
|
|
433
|
+
<option value={100}>every page, to the end of their list</option>
|
|
434
|
+
<option value={50}>50 pages each</option>
|
|
435
|
+
<option value={25}>25 pages each</option>
|
|
436
|
+
<option value={10}>10 pages (~100 people) each</option>
|
|
437
|
+
</select>
|
|
438
|
+
</div>
|
|
439
|
+
<label style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 12.5, color: '#cfd8d8', cursor: running ? 'not-allowed' : 'pointer' }}>
|
|
440
|
+
<input type="checkbox" checked={finish} onChange={(e) => setFinish(e.target.checked)} disabled={running} />
|
|
441
|
+
Also finish people already mapped, from the page each one stopped at
|
|
442
|
+
</label>
|
|
443
|
+
<div style={{ fontSize: 12, color: '#FFD700', lineHeight: 1.6 }}>
|
|
444
|
+
Every page is a LinkedIn search, so it rests 20 seconds before each one and a
|
|
445
|
+
minute after every 10, and a long list can take
|
|
446
|
+
{' '}about {circleScanCost(pages).minutes} minutes a person.
|
|
447
|
+
LinkedIn shows 100 pages of anyone’s connections at most. Free accounts
|
|
448
|
+
have a monthly search limit: if LinkedIn says it has been reached, the scan saves
|
|
449
|
+
what it read and stops, and carries on from that page next time. Keep batches small.
|
|
450
|
+
</div>
|
|
335
451
|
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
<option value={
|
|
346
|
-
<option value={10}>10
|
|
452
|
+
<Btn onClick={() => run('auto-bridge', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish })} disabled={!canSearch || (order === 'score' && !tiers.length)} primary>
|
|
453
|
+
{running && s.action === 'auto-bridge' ? 'Mapping…' : 'Map 2nd degree'}
|
|
454
|
+
</Btn>
|
|
455
|
+
<select
|
|
456
|
+
value={batch}
|
|
457
|
+
onChange={(e) => setBatch(Number(e.target.value))}
|
|
458
|
+
disabled={running}
|
|
459
|
+
style={selectStyle}
|
|
460
|
+
>
|
|
461
|
+
<option value={5}>5 people</option>
|
|
462
|
+
<option value={10}>10 people</option>
|
|
463
|
+
<option value={25}>25 people</option>
|
|
464
|
+
<option value={0}>everyone — not advised</option>
|
|
347
465
|
</select>
|
|
466
|
+
<Btn onClick={() => run('auto-bridge-retry', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish })} disabled={!canSearch || (order === 'score' && !tiers.length)}>
|
|
467
|
+
Retry hidden ones
|
|
468
|
+
</Btn>
|
|
348
469
|
</div>
|
|
349
|
-
<
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
minute after every 10, and a long list can take
|
|
356
|
-
{' '}{pages >= 100 ? 'about 55 minutes a person' : `about ${Math.max(5, Math.round(pages * 0.55))} minutes a person`}.
|
|
357
|
-
LinkedIn shows 100 pages of anyone’s connections at most. Free accounts
|
|
358
|
-
have a monthly search limit: if LinkedIn says it has been reached, the scan saves
|
|
359
|
-
what it read and stops, and carries on from that page next time. Keep batches small.
|
|
470
|
+
<PausedList
|
|
471
|
+
paused={s?.paused || []}
|
|
472
|
+
disabled={!canSearch}
|
|
473
|
+
onResume={(p) => run('resume', { id: p.id })}
|
|
474
|
+
onResumeAll={() => run('resume-all', { maxBridges: batch })}
|
|
475
|
+
/>
|
|
360
476
|
</div>
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
{running && s.action === 'auto-bridge' ? 'Mapping…' : 'Map 2nd degree'}
|
|
364
|
-
</Btn>
|
|
365
|
-
<select
|
|
366
|
-
value={batch}
|
|
367
|
-
onChange={(e) => setBatch(Number(e.target.value))}
|
|
368
|
-
disabled={running}
|
|
369
|
-
style={selectStyle}
|
|
370
|
-
>
|
|
371
|
-
<option value={5}>5 people</option>
|
|
372
|
-
<option value={10}>10 people</option>
|
|
373
|
-
<option value={25}>25 people</option>
|
|
374
|
-
<option value={0}>everyone — not advised</option>
|
|
375
|
-
</select>
|
|
376
|
-
<Btn onClick={() => run('auto-bridge-retry', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish })} disabled={!canSearch || (order === 'score' && !tiers.length)}>
|
|
377
|
-
Retry hidden ones
|
|
378
|
-
</Btn>
|
|
379
|
-
</div>
|
|
380
|
-
<PausedList
|
|
381
|
-
paused={s?.paused || []}
|
|
382
|
-
disabled={!canSearch}
|
|
383
|
-
onResume={(p) => run('resume', { id: p.id })}
|
|
384
|
-
onResumeAll={() => run('resume-all', { maxBridges: batch })}
|
|
385
|
-
/>
|
|
386
|
-
</div>
|
|
387
|
-
}
|
|
388
|
-
/>
|
|
477
|
+
}
|
|
478
|
+
/>
|
|
389
479
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
480
|
+
{/* ---- stop: one control, always where the log is ---- */}
|
|
481
|
+
{running && (
|
|
482
|
+
<div style={{
|
|
483
|
+
display: 'flex', alignItems: 'center', gap: 12, marginTop: 20,
|
|
484
|
+
padding: '14px 16px', borderRadius: 8,
|
|
485
|
+
background: 'rgba(255,255,255,0.05)', border: LINE,
|
|
486
|
+
}}>
|
|
487
|
+
<Spinner />
|
|
488
|
+
<div style={{ flex: 1, fontSize: 13.5 }}>
|
|
489
|
+
<b>{ACTION_LABELS[s.action] || 'Working'}</b>
|
|
490
|
+
{s.progress && <Progress p={s.progress} action={s.action} />}
|
|
491
|
+
<div style={{ color: '#8b9a9a', fontSize: 12.5, marginTop: 2 }}>
|
|
492
|
+
Stopping closes the browser cleanly and keeps everything found so far.
|
|
493
|
+
</div>
|
|
403
494
|
</div>
|
|
495
|
+
<Btn onClick={stop} tone="bad">Stop</Btn>
|
|
404
496
|
</div>
|
|
405
|
-
|
|
406
|
-
</div>
|
|
407
|
-
)}
|
|
497
|
+
)}
|
|
408
498
|
|
|
409
|
-
|
|
499
|
+
{error && <Box tone="bad">{error}</Box>}
|
|
410
500
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
501
|
+
{(s?.log?.length > 0) && (
|
|
502
|
+
<>
|
|
503
|
+
<div style={{
|
|
504
|
+
display: 'flex', alignItems: 'center', gap: 10,
|
|
505
|
+
margin: '28px 0 8px', fontSize: 12, color: '#788',
|
|
506
|
+
textTransform: 'uppercase', letterSpacing: 0.6,
|
|
507
|
+
}}>
|
|
508
|
+
{running && <Spinner />}
|
|
509
|
+
{running ? 'Working' : 'Last run'}
|
|
510
|
+
</div>
|
|
511
|
+
<pre ref={logRef} style={{
|
|
512
|
+
background: 'rgba(0,0,0,0.45)', border: LINE, borderRadius: 8,
|
|
513
|
+
padding: 14, maxHeight: 280, overflow: 'auto', margin: 0,
|
|
514
|
+
fontSize: 12.5, lineHeight: 1.7, color: '#b9c6c6',
|
|
515
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
516
|
+
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
517
|
+
}}>{s.log.join('\n')}</pre>
|
|
518
|
+
</>
|
|
519
|
+
)}
|
|
520
|
+
</>}
|
|
430
521
|
|
|
431
522
|
<div style={{ marginTop: 32, paddingTop: 20, borderTop: LINE, fontSize: 13.5, color: '#8b9a9a' }}>
|
|
432
523
|
Checking for updates has moved to{' '}
|
|
@@ -447,6 +538,78 @@ function SetupInner() {
|
|
|
447
538
|
);
|
|
448
539
|
}
|
|
449
540
|
|
|
541
|
+
// One person's circle, picked elsewhere and scanned only once it's confirmed
|
|
542
|
+
// here, beside what it costs and what the budget has left (backlog 2.4). Their
|
|
543
|
+
// read goes as deep as "Read up to" below says; Bridge Chains shows it filling in.
|
|
544
|
+
function ScanOne({ pick, pages, setPages, li, running, s, canSearch, busy, onStart, onUnpick }) {
|
|
545
|
+
const { person, circle } = pick;
|
|
546
|
+
const first = String(person.name || '').trim().split(/\s+/)[0] || 'them';
|
|
547
|
+
const cost = circleScanCost(pages);
|
|
548
|
+
const theirs = running && s?.target?.id === person.id;
|
|
549
|
+
const short = li?.leftToday != null && li.leftToday < cost.searches;
|
|
550
|
+
return (
|
|
551
|
+
<div style={{
|
|
552
|
+
margin: '16px 0', padding: '16px 18px', borderRadius: 10, fontSize: 13.5, lineHeight: 1.6,
|
|
553
|
+
background: 'rgba(0,255,136,0.05)', border: '1px solid rgba(0,255,136,0.3)',
|
|
554
|
+
}}>
|
|
555
|
+
<div style={{ fontSize: 11, fontWeight: 700, color: '#00ff88', letterSpacing: 0.6, textTransform: 'uppercase', marginBottom: 4 }}>
|
|
556
|
+
Scan one circle
|
|
557
|
+
</div>
|
|
558
|
+
<div style={{ fontSize: 16, fontWeight: 700 }}>
|
|
559
|
+
{person.name} <span style={{ fontSize: 12, color: TIER[person.tier] || '#888' }}>{person.tier}-tier</span>
|
|
560
|
+
</div>
|
|
561
|
+
{person.unlocked_from_name && (
|
|
562
|
+
<div style={{ fontSize: 12.5, color: '#9aa' }}>Was in {person.unlocked_from_name}’s circle</div>
|
|
563
|
+
)}
|
|
564
|
+
{theirs ? (
|
|
565
|
+
<div style={{ marginTop: 10 }}>
|
|
566
|
+
<b>Scanning {first}’s circle.</b> It saves every 10 pages, and{' '}
|
|
567
|
+
<Link href={`/?chain=${encodeURIComponent(person.id)}`} style={{ color: '#00ff88' }}>Bridge Chains shows it filling in →</Link>
|
|
568
|
+
</div>
|
|
569
|
+
) : (
|
|
570
|
+
<>
|
|
571
|
+
<div style={{ color: '#b8c4c4', marginTop: 10 }}>
|
|
572
|
+
It costs <b>{cost.profileViews} profile view</b> to find their list, then <b>one LinkedIn search for
|
|
573
|
+
every page</b> of it: up to {cost.searches} {cost.searches === 1 ? 'page' : 'pages'}, about {cost.minutes} minutes.
|
|
574
|
+
{li?.leftToday != null && <> <b>{li.leftToday}</b> of your {li.limits?.daily} searches a day are left{short ? '; the scan stops when they run out, and can carry on from that page later' : ''}.</>}
|
|
575
|
+
</div>
|
|
576
|
+
{circle === 'scanned' && (
|
|
577
|
+
<div style={{ color: '#FFD700', fontSize: 12.5, marginTop: 6 }}>
|
|
578
|
+
Their circle is already scanned. This reads their whole list again from page 1; to carry on
|
|
579
|
+
from where a read stopped, use Resume on their card or in the Paused list below.
|
|
580
|
+
</div>
|
|
581
|
+
)}
|
|
582
|
+
{circle === 'hidden' && (
|
|
583
|
+
<div style={{ color: '#FFD700', fontSize: 12.5, marginTop: 6 }}>
|
|
584
|
+
Their list was hidden last time it was tried. Trying again costs a profile view.
|
|
585
|
+
</div>
|
|
586
|
+
)}
|
|
587
|
+
<div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginTop: 12 }}>
|
|
588
|
+
<Btn onClick={onStart} disabled={!canSearch || busy} primary>Scan {first}’s circle</Btn>
|
|
589
|
+
<select value={pages} onChange={(e) => setPages(Number(e.target.value))} disabled={running} style={selectStyle}
|
|
590
|
+
aria-label="How many pages of their list to read">
|
|
591
|
+
<option value={100}>every page, to the end of their list</option>
|
|
592
|
+
<option value={50}>up to 50 pages</option>
|
|
593
|
+
<option value={25}>up to 25 pages</option>
|
|
594
|
+
<option value={10}>up to 10 pages (~100 people)</option>
|
|
595
|
+
</select>
|
|
596
|
+
<Btn onClick={onUnpick}>Not now</Btn>
|
|
597
|
+
</div>
|
|
598
|
+
{!canSearch && (
|
|
599
|
+
<div style={{ fontSize: 12.5, color: '#8b9a9a', marginTop: 8 }}>
|
|
600
|
+
{running ? 'Something else is running. One scan at a time: this one can start when it finishes.'
|
|
601
|
+
: li?.cooldown ? 'Scanning is paused for now (see below).'
|
|
602
|
+
: 'The scanner isn’t ready yet: finish the steps below first.'}
|
|
603
|
+
</div>
|
|
604
|
+
)}
|
|
605
|
+
</>
|
|
606
|
+
)}
|
|
607
|
+
</div>
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const TIER = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
|
|
612
|
+
|
|
450
613
|
// A setting this browser remembers between visits: the order and depth someone
|
|
451
614
|
// picked should not reset every time the page opens. localStorage can be missing
|
|
452
615
|
// or throw (private windows), and then it is simply not remembered.
|