six-degrees 0.4.0-beta.1 → 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/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +5 -5
- package/.next/standalone/.next/required-server-files.json +6 -1
- package/.next/standalone/.next/routes-manifest.json +12 -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 +1 -1
- 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.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 +13 -13
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +13 -13
- 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 +12 -12
- 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/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]__0rc27za._.js → [root-of-the-server]__0bat8nx._.js} +2 -2
- 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/_1_fynia._.js +7 -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 +12 -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/_01f38ty._.js +1 -1
- 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/_1gpbbw9._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1hgd5dp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0cu6-an._.js → _1vpfyd-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_20w06i-._.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 +1 -1
- 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 +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 +1 -1
- 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/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- 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 +152 -0
- package/.next/standalone/CONTRIBUTING.md +15 -4
- package/.next/standalone/README.md +21 -15
- package/.next/standalone/SECURITY.md +10 -0
- package/.next/standalone/app/api/ingest/route.js +6 -1
- package/.next/standalone/app/api/scraper/route.js +79 -11
- 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/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/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 +3 -1
- package/.next/standalone/app/components/OutlinkQuest.js +5 -4
- package/.next/standalone/app/components/RingsView.js +9 -8
- package/.next/standalone/app/components/Sidebar.js +172 -112
- package/.next/standalone/app/components/TheirCircle.js +18 -11
- package/.next/standalone/app/components/settings/SectorPicker.js +202 -0
- package/.next/standalone/app/components/settings/SectorSection.js +17 -191
- package/.next/standalone/app/components/settings/TierSection.js +7 -12
- package/.next/standalone/app/components/views.js +2 -2
- package/.next/standalone/app/globals.css +7 -0
- package/.next/standalone/app/launch/page.js +2 -2
- package/.next/standalone/app/page.js +130 -25
- package/.next/standalone/app/paths/page.js +3 -2
- package/.next/standalone/app/queue/page.js +18 -10
- package/.next/standalone/app/setup/page.js +423 -260
- package/.next/standalone/db/schema.js +1 -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/csv.js +4 -6
- package/.next/standalone/lib/data-export.js +2 -1
- package/.next/standalone/lib/data-folder.js +4 -2
- package/.next/standalone/lib/galaxy.js +12 -0
- package/.next/standalone/lib/ingest.js +15 -8
- 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/reach.js +113 -0
- package/.next/standalone/lib/scan-state.js +1 -0
- package/.next/standalone/lib/scanner-setup.js +20 -1
- package/.next/standalone/lib/scoring.js +769 -18
- package/.next/standalone/lib/scraper-client.js +27 -0
- package/.next/standalone/lib/sector-focus.js +13 -0
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/settings-client.js +21 -0
- package/.next/standalone/lib/settings.js +3 -1
- package/.next/standalone/next.config.mjs +25 -3
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.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/image_store.py +77 -21
- package/.next/standalone/scripts/scrape.py +243 -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 +152 -0
- package/README.md +21 -15
- package/SECURITY.md +10 -0
- package/db/schema.js +1 -1
- package/lib/chain-layout.js +139 -0
- package/lib/csv.js +4 -6
- package/lib/data-export.js +2 -1
- package/lib/data-folder.js +4 -2
- package/lib/galaxy.js +12 -0
- package/lib/ingest.js +15 -8
- package/lib/linkedin-limits.js +30 -8
- package/lib/photos.js +47 -0
- package/lib/quest.js +8 -3
- package/lib/reach.js +113 -0
- package/lib/scan-state.js +1 -0
- package/lib/scanner-setup.js +20 -1
- package/lib/scoring.js +769 -18
- package/lib/scraper-client.js +27 -0
- package/lib/sector-focus.js +13 -0
- package/lib/separation.js +4 -2
- package/lib/settings-client.js +21 -0
- package/lib/settings.js +3 -1
- package/package.json +1 -1
- package/scripts/check-node.mjs +1 -1
- package/scripts/image_store.py +77 -21
- package/scripts/scrape.py +243 -29
- 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_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/_0223v9s._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0bcf13g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0wxbq78._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0z88zcl._.js +0 -3
- 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/static/chunks/0_yurn4q91vq8.js +0 -16
- package/.next/standalone/.next/static/chunks/0d_mh-06srh2k.js +0 -1
- package/.next/standalone/.next/static/chunks/0x3oadfikvb1c.js +0 -1
- package/.next/standalone/.next/static/chunks/1d5hl1io798n2.js +0 -1
- package/.next/standalone/.next/static/chunks/1qt67cvj9sjwi.js +0 -1
- package/.next/standalone/.next/static/chunks/1roznk7hkjes1.js +0 -44
- package/.next/standalone/.next/static/chunks/2_bf2n4gstad9.js +0 -33
- package/.next/standalone/.next/static/chunks/36gbwek52x_w-.js +0 -2
- package/.next/standalone/.next/static/chunks/3a55suosthntc.js +0 -1
- package/.next/standalone/.next/static/chunks/3fq1zq-6cfwbc.js +0 -1
- package/.next/standalone/.next/static/chunks/3nbnio971zlji.js +0 -2
- package/.next/standalone/.next/static/chunks/3s6awp3qq9s7w.js +0 -1
- package/.next/static/chunks/0_yurn4q91vq8.js +0 -16
- package/.next/static/chunks/0d_mh-06srh2k.js +0 -1
- package/.next/static/chunks/0x3oadfikvb1c.js +0 -1
- package/.next/static/chunks/1d5hl1io798n2.js +0 -1
- package/.next/static/chunks/1qt67cvj9sjwi.js +0 -1
- package/.next/static/chunks/1roznk7hkjes1.js +0 -44
- package/.next/static/chunks/2_bf2n4gstad9.js +0 -33
- package/.next/static/chunks/36gbwek52x_w-.js +0 -2
- package/.next/static/chunks/3a55suosthntc.js +0 -1
- package/.next/static/chunks/3fq1zq-6cfwbc.js +0 -1
- package/.next/static/chunks/3nbnio971zlji.js +0 -2
- package/.next/static/chunks/3s6awp3qq9s7w.js +0 -1
- /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
- /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_buildManifest.js +0 -0
- /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{NnWlxbDF24KhrU1eHiEnv → WseE_5HuU-1q0ywC9CP3U}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
4
5
|
import { beginScrape, scraperStatus, notReadyMessage, resumePoint, scansCircleOf, isCircleScan, busyReason } from '../../lib/scraper-client';
|
|
5
6
|
import useScanner from './useScanner';
|
|
6
7
|
import useRequests from './useRequests';
|
|
@@ -9,10 +10,16 @@ import { hasRequest, markRequested, undoRequest } from '../../lib/requests-clien
|
|
|
9
10
|
import { useUser } from './UserProvider';
|
|
10
11
|
import { routeIndex, routesFor } from '../../lib/separation';
|
|
11
12
|
import { topCompanies } from '../../lib/scoring';
|
|
13
|
+
import { reachIndex, reachState, circleState, readyToScan, circleScanCost } from '../../lib/reach';
|
|
12
14
|
import Avatar from './Avatar';
|
|
15
|
+
import { localPhoto } from '../../lib/photos';
|
|
13
16
|
|
|
14
|
-
export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending }) {
|
|
17
|
+
export default function Sidebar({ selected, stats, tierColors, connections, degree2 = [], mode, collapsed, filter, pending = [], onToggle, onSelect, onSwitchMode, onFocusNode, onMarkSent, onUndoPending, scanNotes, csvSource = null }) {
|
|
15
18
|
const isDegreesMode = mode === 'degrees';
|
|
19
|
+
// 'sample' or 'csv' while the sample or a CSV import is open in this window,
|
|
20
|
+
// null for your own network. Only your own can be scanned: the scanner looks
|
|
21
|
+
// the person up in the database, and these two never reach it.
|
|
22
|
+
const canScan = !csvSource;
|
|
16
23
|
const { userId, userProfile: ctxProfile } = useUser();
|
|
17
24
|
const userProfile = ctxProfile || { name: 'User', sectors: [], goals: [] };
|
|
18
25
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -94,9 +101,9 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
94
101
|
</div>
|
|
95
102
|
{/* Profile photo + name */}
|
|
96
103
|
<div style={{ display: 'flex', gap: 12, alignItems: 'center', marginBottom: 16 }}>
|
|
97
|
-
{selected.profile_image_url ? (
|
|
104
|
+
{localPhoto(selected.profile_image_url) ? (
|
|
98
105
|
<img
|
|
99
|
-
src={selected.profile_image_url}
|
|
106
|
+
src={localPhoto(selected.profile_image_url)}
|
|
100
107
|
alt={selected.name}
|
|
101
108
|
style={{ width: 52, height: 52, borderRadius: '50%', objectFit: 'cover', border: `2px solid ${tierColors[selected.tier] || '#555'}`, flexShrink: 0 }}
|
|
102
109
|
onError={(e) => { e.target.style.display = 'none'; e.target.nextSibling.style.display = 'flex'; }}
|
|
@@ -105,7 +112,7 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
105
112
|
<div style={{
|
|
106
113
|
width: 52, height: 52, borderRadius: '50%', flexShrink: 0,
|
|
107
114
|
background: tierColors[selected.tier] || '#555',
|
|
108
|
-
display: selected.profile_image_url ? 'none' : 'flex',
|
|
115
|
+
display: localPhoto(selected.profile_image_url) ? 'none' : 'flex',
|
|
109
116
|
alignItems: 'center', justifyContent: 'center',
|
|
110
117
|
fontSize: 20, fontWeight: 700, color: selected.tier === 'S' ? '#000' : '#fff',
|
|
111
118
|
}}>
|
|
@@ -128,7 +135,7 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
128
135
|
{selected.degree === 1 && (
|
|
129
136
|
<TheirCircle
|
|
130
137
|
person={selected} connections={connections} degree2={degree2}
|
|
131
|
-
tierColors={tierColors} onSelect={onSelect} isRequested={isRequested}
|
|
138
|
+
tierColors={tierColors} onSelect={onSelect} isRequested={isRequested} canScan={canScan}
|
|
132
139
|
/>
|
|
133
140
|
)}
|
|
134
141
|
|
|
@@ -257,8 +264,8 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
257
264
|
onMouseEnter={e => e.currentTarget.style.background = 'rgba(255,255,255,0.08)'}
|
|
258
265
|
onMouseLeave={e => e.currentTarget.style.background = 'rgba(255,255,255,0.03)'}
|
|
259
266
|
>
|
|
260
|
-
{m.profile_image_url ? (
|
|
261
|
-
<img src={m.profile_image_url} alt="" style={{
|
|
267
|
+
{localPhoto(m.profile_image_url) ? (
|
|
268
|
+
<img src={localPhoto(m.profile_image_url)} alt="" style={{
|
|
262
269
|
width: 22, height: 22, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
|
|
263
270
|
border: `1px solid ${tierColors[m.tier] || '#555'}`,
|
|
264
271
|
}} onError={e => { e.target.style.display = 'none'; e.target.nextSibling.style.display = 'flex'; }} />
|
|
@@ -266,7 +273,7 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
266
273
|
<div style={{
|
|
267
274
|
width: 22, height: 22, borderRadius: '50%', flexShrink: 0, fontSize: 9, fontWeight: 700,
|
|
268
275
|
background: tierColors[m.tier] || '#555', color: m.tier === 'S' ? '#000' : '#fff',
|
|
269
|
-
display: m.profile_image_url ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center',
|
|
276
|
+
display: localPhoto(m.profile_image_url) ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center',
|
|
270
277
|
}}>{m.name?.charAt(0)}</div>
|
|
271
278
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
272
279
|
<div style={{ fontSize: 10, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.name}</div>
|
|
@@ -327,7 +334,7 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
327
334
|
<div style={{ fontSize: 13, color: '#ccc', marginBottom: 12 }}>
|
|
328
335
|
{asked
|
|
329
336
|
? `Waiting for ${selected.name} to accept. The next scan of your own connections notices when they do.`
|
|
330
|
-
: `Connect with ${selected.name} to unlock their network and extend your
|
|
337
|
+
: `Connect with ${selected.name} to unlock their network and extend your six degrees.`
|
|
331
338
|
}
|
|
332
339
|
</div>
|
|
333
340
|
<div style={{
|
|
@@ -370,7 +377,9 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
370
377
|
|
|
371
378
|
{/* Create Cluster — for D1 connections OR accepted D2 (promoted, ready to bridge for D3) */}
|
|
372
379
|
{(selected.degree === 1 || selected.outreach_status === 'accepted') && (
|
|
373
|
-
|
|
380
|
+
canScan
|
|
381
|
+
? <CreateClusterCard key={selected.id} selected={selected} degree2={degree2} />
|
|
382
|
+
: <ScansNeedYourNetwork csvSource={csvSource} style={{ marginTop: 16 }} />
|
|
374
383
|
)}
|
|
375
384
|
|
|
376
385
|
{selected.profile_url && (
|
|
@@ -403,8 +412,10 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
403
412
|
Your bridge connections and who they unlock.
|
|
404
413
|
</p>
|
|
405
414
|
|
|
406
|
-
{/*
|
|
407
|
-
|
|
415
|
+
{/* Who to scan next: people you reached, whose own circle is next */}
|
|
416
|
+
{canScan
|
|
417
|
+
? <ReadyToScan connections={connections} degree2={degree2} scanNotes={scanNotes} tierColors={tierColors} />
|
|
418
|
+
: <ScansNeedYourNetwork csvSource={csvSource} style={{ marginBottom: 16 }} />}
|
|
408
419
|
|
|
409
420
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 12 }}>
|
|
410
421
|
<StatBox label="Degree-2 Found" value={degree2.length} />
|
|
@@ -457,8 +468,8 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
457
468
|
onMouseEnter={e => e.currentTarget.style.background = 'rgba(255,255,255,0.06)'}
|
|
458
469
|
onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
|
|
459
470
|
>
|
|
460
|
-
{m.profile_image_url ? (
|
|
461
|
-
<img src={m.profile_image_url} alt="" style={{ width: 24, height: 24, borderRadius: '50%', objectFit: 'cover' }} />
|
|
471
|
+
{localPhoto(m.profile_image_url) ? (
|
|
472
|
+
<img src={localPhoto(m.profile_image_url)} alt="" style={{ width: 24, height: 24, borderRadius: '50%', objectFit: 'cover' }} />
|
|
462
473
|
) : (
|
|
463
474
|
<div style={{ width: 24, height: 24, borderRadius: '50%', background: tierColors[m.tier] || '#555', fontSize: 9, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>{m.name?.charAt(0)}</div>
|
|
464
475
|
)}
|
|
@@ -493,8 +504,8 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
493
504
|
border: '1px solid rgba(255,107,53,0.12)',
|
|
494
505
|
}}>
|
|
495
506
|
{/* Photo */}
|
|
496
|
-
{p.profile_image_url ? (
|
|
497
|
-
<img src={p.profile_image_url} alt="" style={{
|
|
507
|
+
{localPhoto(p.profile_image_url) ? (
|
|
508
|
+
<img src={localPhoto(p.profile_image_url)} alt="" style={{
|
|
498
509
|
width: 32, height: 32, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
|
|
499
510
|
border: `2px solid ${tierColors[p.tier] || '#555'}`,
|
|
500
511
|
}} onError={e => { e.target.style.display = 'none'; }} />
|
|
@@ -675,8 +686,8 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
675
686
|
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.08)'}
|
|
676
687
|
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
|
|
677
688
|
>
|
|
678
|
-
{c.profile_image_url ? (
|
|
679
|
-
<img src={c.profile_image_url} alt="" style={{
|
|
689
|
+
{localPhoto(c.profile_image_url) ? (
|
|
690
|
+
<img src={localPhoto(c.profile_image_url)} alt="" style={{
|
|
680
691
|
width: 24, height: 24, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
|
|
681
692
|
border: `1.5px solid ${tierColors[c.tier] || '#555'}`,
|
|
682
693
|
}} onError={e => { e.target.style.display = 'none'; }} />
|
|
@@ -705,6 +716,11 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
705
716
|
{(() => {
|
|
706
717
|
// Track per-tier rank numbering
|
|
707
718
|
const tierRankCounter = {};
|
|
719
|
+
// Counted once. It used to be counted again for every person in the
|
|
720
|
+
// list: 900 million steps at 30,000 people, about 20 s on every render
|
|
721
|
+
// of the page while nothing was selected (opening the Filter panel, say).
|
|
722
|
+
const tierCounts = {};
|
|
723
|
+
allRanked.forEach(r => { tierCounts[r.tier] = (tierCounts[r.tier] || 0) + 1; });
|
|
708
724
|
return allRanked.map((c, i) => {
|
|
709
725
|
const prevTier = i > 0 ? allRanked[i - 1].tier : null;
|
|
710
726
|
const showHeader = c.tier !== prevTier;
|
|
@@ -712,8 +728,6 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
712
728
|
tierRankCounter[c.tier] = (tierRankCounter[c.tier] || 0) + 1;
|
|
713
729
|
const tierRank = tierRankCounter[c.tier];
|
|
714
730
|
const tierNames = { S: 'S-Tier — Elite', A: 'A-Tier — High Value', B: 'B-Tier — Notable', C: 'C-Tier — Standard', D: 'D-Tier — Entry' };
|
|
715
|
-
const tierCounts = {};
|
|
716
|
-
allRanked.forEach(r => { tierCounts[r.tier] = (tierCounts[r.tier] || 0) + 1; });
|
|
717
731
|
return (
|
|
718
732
|
<div key={c.id}>
|
|
719
733
|
{showHeader && (
|
|
@@ -745,9 +759,9 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
745
759
|
#{tierRank}
|
|
746
760
|
</span>
|
|
747
761
|
{/* Profile photo or initial */}
|
|
748
|
-
{c.profile_image_url ? (
|
|
762
|
+
{localPhoto(c.profile_image_url) ? (
|
|
749
763
|
<img
|
|
750
|
-
src={c.profile_image_url}
|
|
764
|
+
src={localPhoto(c.profile_image_url)}
|
|
751
765
|
alt=""
|
|
752
766
|
style={{ width: 28, height: 28, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
|
|
753
767
|
border: `1.5px solid ${tierColors[c.tier] || '#555'}` }}
|
|
@@ -757,7 +771,7 @@ export default function Sidebar({ selected, stats, tierColors, connections, degr
|
|
|
757
771
|
<div style={{
|
|
758
772
|
width: 28, height: 28, borderRadius: '50%', flexShrink: 0,
|
|
759
773
|
background: tierColors[c.tier] || '#555',
|
|
760
|
-
display: c.profile_image_url ? 'none' : 'flex',
|
|
774
|
+
display: localPhoto(c.profile_image_url) ? 'none' : 'flex',
|
|
761
775
|
alignItems: 'center', justifyContent: 'center',
|
|
762
776
|
fontSize: 12, fontWeight: 700, color: c.tier === 'S' ? '#000' : '#fff',
|
|
763
777
|
}}>
|
|
@@ -1006,7 +1020,7 @@ function CreateClusterCard({ selected, degree2 }) {
|
|
|
1006
1020
|
</div>
|
|
1007
1021
|
<div style={{ fontSize: 12, color: '#aaa', marginBottom: 12, textAlign: 'center' }}>
|
|
1008
1022
|
{hasCluster
|
|
1009
|
-
? `${clusterCount} connections mapped in
|
|
1023
|
+
? `${clusterCount} connections mapped in Six Degrees`
|
|
1010
1024
|
: <>Scan {selected.name}'s connections to map their network</>}
|
|
1011
1025
|
</div>
|
|
1012
1026
|
|
|
@@ -1066,6 +1080,22 @@ function CreateClusterCard({ selected, degree2 }) {
|
|
|
1066
1080
|
);
|
|
1067
1081
|
}
|
|
1068
1082
|
|
|
1083
|
+
// In place of the Scan buttons while the sample or a CSV import is open. Both
|
|
1084
|
+
// live in this window only, so a scan could only fail: the scanner looks the
|
|
1085
|
+
// person up in the database, and says "Bridge '…' not found in database".
|
|
1086
|
+
function ScansNeedYourNetwork({ csvSource, style }) {
|
|
1087
|
+
return (
|
|
1088
|
+
<div style={{
|
|
1089
|
+
padding: '10px 12px', borderRadius: 8, fontSize: 11, color: '#999', lineHeight: 1.5, textAlign: 'center',
|
|
1090
|
+
background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)', ...style,
|
|
1091
|
+
}}>
|
|
1092
|
+
{csvSource === 'sample'
|
|
1093
|
+
? 'Scanning needs your own network: everyone in the sample is invented.'
|
|
1094
|
+
: 'Scanning needs your own network: a CSV import lives only in this window. Close it (× at the top) and open Scan.'}
|
|
1095
|
+
</div>
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1069
1099
|
/** A running scan's log, newest at the bottom. */
|
|
1070
1100
|
function ScanLog({ title, log, small = false }) {
|
|
1071
1101
|
return (
|
|
@@ -1095,7 +1125,7 @@ function LastRead({ job, who = null }) {
|
|
|
1095
1125
|
color: ok ? '#00ff88' : '#ff8080',
|
|
1096
1126
|
}}>
|
|
1097
1127
|
{ok
|
|
1098
|
-
? `${who ? `${who}’s circle` : 'The scan'} finished.
|
|
1128
|
+
? `${who ? `${who}’s circle` : 'The scan'} finished. What it found is on the map.`
|
|
1099
1129
|
: `${who ? `${who}’s scan` : 'The scan'} stopped before the end${why ? `: ${why}` : '.'}`}
|
|
1100
1130
|
</div>
|
|
1101
1131
|
);
|
|
@@ -1117,8 +1147,8 @@ function ClusterCard({ cs, rank, tierColors, onSelect }) {
|
|
|
1117
1147
|
style={{ cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}
|
|
1118
1148
|
>
|
|
1119
1149
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
1120
|
-
{cs.bridge.profile_image_url ? (
|
|
1121
|
-
<img src={cs.bridge.profile_image_url} alt="" style={{
|
|
1150
|
+
{localPhoto(cs.bridge.profile_image_url) ? (
|
|
1151
|
+
<img src={localPhoto(cs.bridge.profile_image_url)} alt="" style={{
|
|
1122
1152
|
width: 28, height: 28, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
|
|
1123
1153
|
border: `1.5px solid ${tierColors[cs.bridge.tier] || '#555'}`,
|
|
1124
1154
|
}} onError={e => { e.target.style.display = 'none'; }} />
|
|
@@ -1161,8 +1191,8 @@ function ClusterCard({ cs, rank, tierColors, onSelect }) {
|
|
|
1161
1191
|
{(cs.sTier.length > 0 || cs.aTier.length > 0) && (
|
|
1162
1192
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 8 }}>
|
|
1163
1193
|
{[...cs.sTier, ...cs.aTier].slice(0, 4).map(m => (
|
|
1164
|
-
m.profile_image_url ? (
|
|
1165
|
-
<img key={m.id} src={m.profile_image_url} alt={m.name}
|
|
1194
|
+
localPhoto(m.profile_image_url) ? (
|
|
1195
|
+
<img key={m.id} src={localPhoto(m.profile_image_url)} alt={m.name}
|
|
1166
1196
|
style={{ width: 22, height: 22, borderRadius: '50%', objectFit: 'cover',
|
|
1167
1197
|
border: `1.5px solid ${tierColors[m.tier] || '#555'}` }}
|
|
1168
1198
|
onError={(e) => { e.target.style.display = 'none'; }}
|
|
@@ -1212,8 +1242,8 @@ function ClusterCard({ cs, rank, tierColors, onSelect }) {
|
|
|
1212
1242
|
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.06)'}
|
|
1213
1243
|
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
|
|
1214
1244
|
>
|
|
1215
|
-
{m.profile_image_url ? (
|
|
1216
|
-
<img src={m.profile_image_url} alt="" style={{
|
|
1245
|
+
{localPhoto(m.profile_image_url) ? (
|
|
1246
|
+
<img src={localPhoto(m.profile_image_url)} alt="" style={{
|
|
1217
1247
|
width: 20, height: 20, borderRadius: '50%', objectFit: 'cover', flexShrink: 0,
|
|
1218
1248
|
border: `1px solid ${tierColors[m.tier] || '#555'}`,
|
|
1219
1249
|
}} onError={(e) => { e.target.style.display = 'none'; e.target.nextSibling.style.display = 'flex'; }} />
|
|
@@ -1221,7 +1251,7 @@ function ClusterCard({ cs, rank, tierColors, onSelect }) {
|
|
|
1221
1251
|
<div style={{
|
|
1222
1252
|
width: 20, height: 20, borderRadius: '50%', flexShrink: 0, fontSize: 8, fontWeight: 700,
|
|
1223
1253
|
background: tierColors[m.tier] || '#555', color: m.tier === 'S' ? '#000' : '#fff',
|
|
1224
|
-
display: m.profile_image_url ? 'none' : 'flex',
|
|
1254
|
+
display: localPhoto(m.profile_image_url) ? 'none' : 'flex',
|
|
1225
1255
|
alignItems: 'center', justifyContent: 'center',
|
|
1226
1256
|
}}>{m.name?.charAt(0)}</div>
|
|
1227
1257
|
<div style={{ minWidth: 0, flex: 1 }}>
|
|
@@ -1318,100 +1348,130 @@ function generateInsights(person, user, allConnections, degree2, routes = []) {
|
|
|
1318
1348
|
return insights.slice(0, 5); // Max 5 insights
|
|
1319
1349
|
}
|
|
1320
1350
|
|
|
1321
|
-
//
|
|
1322
|
-
//
|
|
1323
|
-
|
|
1351
|
+
// Who to scan next, in the Degrees panel. It replaced "Auto-Bridge Next", which
|
|
1352
|
+
// started a scan of the top unmapped S/A connection in one click, by name, with
|
|
1353
|
+
// no cost shown, and offered hidden lists again after every run.
|
|
1354
|
+
//
|
|
1355
|
+
// This lists everyone you reached through a circle whose own circle can be
|
|
1356
|
+
// scanned now (lib/reach.js), strongest first, with the circle you found them
|
|
1357
|
+
// in and what a scan asks of LinkedIn. Each opens the Scan page with them
|
|
1358
|
+
// picked, and nothing starts until it's confirmed there, beside the budget and
|
|
1359
|
+
// the cooldown. Hidden lists stay in view, greyed, so nobody seems forgotten.
|
|
1360
|
+
// It's also the way to them from a keyboard or a screen reader: the orbs in
|
|
1361
|
+
// Bridge Chains aren't.
|
|
1362
|
+
function ReadyToScan({ connections, degree2, scanNotes, tierColors }) {
|
|
1324
1363
|
const scan = useScanner();
|
|
1325
|
-
const [
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
//
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1364
|
+
const [all, setAll] = useState(false);
|
|
1365
|
+
const reach = useMemo(() => reachIndex(connections, degree2, scanNotes), [connections, degree2, scanNotes]);
|
|
1366
|
+
const ready = useMemo(() => readyToScan(connections, reach), [connections, reach]);
|
|
1367
|
+
const hidden = useMemo(() => connections.filter((c) => reachState(c, reach) === 'hidden'), [connections, reach]);
|
|
1368
|
+
// Everyone else whose circle isn't scanned: the Scan page's batches.
|
|
1369
|
+
const others = useMemo(
|
|
1370
|
+
() => connections.filter((c) => reachState(c, reach) === null && circleState(c, reach) === 'todo').length,
|
|
1371
|
+
[connections, reach],
|
|
1372
|
+
);
|
|
1373
|
+
const byId = useMemo(() => new Map(connections.map((c) => [c.id, c])), [connections]);
|
|
1374
|
+
const cost = circleScanCost();
|
|
1336
1375
|
|
|
1337
1376
|
const circle = scan.running && isCircleScan(scan);
|
|
1338
1377
|
const busy = scan.running && !circle ? busyReason(scan) : null;
|
|
1339
1378
|
const last = scan.finished.find(isCircleScan);
|
|
1379
|
+
const shown = all ? ready : ready.slice(0, 5);
|
|
1380
|
+
const origin = (p) => byId.get(p.unlocked_from_bridge_id)?.name || p.unlocked_from_name;
|
|
1381
|
+
const first = (name) => String(name || '').trim().split(/\s+/)[0];
|
|
1340
1382
|
|
|
1341
|
-
if (unbridged.length === 0 && !scan.running && !last) {
|
|
1342
|
-
return (
|
|
1343
|
-
<div style={{
|
|
1344
|
-
padding: '10px', borderRadius: 8, marginBottom: 16,
|
|
1345
|
-
background: 'rgba(0,255,136,0.06)', border: '1px solid rgba(0,255,136,0.2)',
|
|
1346
|
-
color: '#00ff88', fontSize: 11, fontWeight: 600, textAlign: 'center',
|
|
1347
|
-
}}>
|
|
1348
|
-
All S/A-tier bridges mapped
|
|
1349
|
-
</div>
|
|
1350
|
-
);
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
async function startAutoBridge() {
|
|
1354
|
-
const next = unbridged[0];
|
|
1355
|
-
if (!next) return;
|
|
1356
|
-
setProblem(null);
|
|
1357
|
-
setChecking(true);
|
|
1358
|
-
const blocked = notReadyMessage(await scraperStatus().catch(() => null));
|
|
1359
|
-
setChecking(false);
|
|
1360
|
-
if (blocked) {
|
|
1361
|
-
setProblem(blocked);
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
try {
|
|
1365
|
-
await beginScrape('bridge', { name: next.name, id: next.id });
|
|
1366
|
-
} catch (e) {
|
|
1367
|
-
setProblem(e.message || 'Could not start the scan.');
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
const off = Boolean(busy) || checking;
|
|
1372
1383
|
return (
|
|
1373
1384
|
<div style={{
|
|
1374
|
-
background: 'rgba(
|
|
1385
|
+
background: 'rgba(0,255,136,0.05)', border: '1px solid rgba(0,255,136,0.2)',
|
|
1375
1386
|
borderRadius: 10, padding: 14, marginBottom: 16,
|
|
1376
1387
|
}}>
|
|
1388
|
+
<div style={{ fontSize: 12, fontWeight: 800, color: '#00ff88', marginBottom: 4 }}>
|
|
1389
|
+
Ready to scan ({ready.length})
|
|
1390
|
+
</div>
|
|
1391
|
+
<div style={{ fontSize: 10.5, color: '#999', lineHeight: 1.5, marginBottom: 10 }}>
|
|
1392
|
+
People you added through a circle whose own circle isn't scanned yet. Scanning one
|
|
1393
|
+
brings in who they know: 3rd degree, through the circle you found them in.
|
|
1394
|
+
</div>
|
|
1395
|
+
|
|
1377
1396
|
{circle ? (
|
|
1378
|
-
<
|
|
1397
|
+
<div style={{ marginBottom: 10 }}>
|
|
1398
|
+
<ScanLog small title={scan.pending ? 'Starting…' : `Scanning ${scan.target?.name || 'a circle'}…`} log={scan.log} />
|
|
1399
|
+
</div>
|
|
1400
|
+
) : last && <LastRead job={last} who={last.target?.name} />}
|
|
1401
|
+
|
|
1402
|
+
{ready.length === 0 ? (
|
|
1403
|
+
<div style={{ fontSize: 11, color: '#aaa', lineHeight: 1.5 }}>
|
|
1404
|
+
Nobody yet. When someone you asked from a circle accepts, ↻ <b>Check for new</b> brings
|
|
1405
|
+
them into your connections, and they show here.
|
|
1406
|
+
</div>
|
|
1379
1407
|
) : (
|
|
1380
|
-
|
|
1381
|
-
{
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
+
<div role="list" style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
1409
|
+
{shown.map((p) => {
|
|
1410
|
+
const now = circle && scansCircleOf(scan, p);
|
|
1411
|
+
const from = origin(p);
|
|
1412
|
+
return (
|
|
1413
|
+
<Link key={p.id} role="listitem" href={`/setup?scan=${encodeURIComponent(p.id)}`}
|
|
1414
|
+
aria-label={`Scan ${p.name}’s circle: opens the Scan page to confirm`}
|
|
1415
|
+
style={{
|
|
1416
|
+
display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderRadius: 8,
|
|
1417
|
+
textDecoration: 'none', color: '#fff',
|
|
1418
|
+
background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(0,255,136,0.18)',
|
|
1419
|
+
}}>
|
|
1420
|
+
<span style={{ width: 8, height: 8, borderRadius: '50%', flexShrink: 0, background: tierColors[p.tier] || '#888', boxShadow: '0 0 0 2px rgba(0,255,136,0.45)' }} />
|
|
1421
|
+
<span style={{ flex: 1, minWidth: 0 }}>
|
|
1422
|
+
<span style={{ fontSize: 12, fontWeight: 700 }}>{p.name}</span>
|
|
1423
|
+
<span style={{ fontSize: 10, color: tierColors[p.tier] || '#888', fontWeight: 700, marginLeft: 6 }}>{p.tier}</span>
|
|
1424
|
+
{from && (
|
|
1425
|
+
<span style={{ display: 'block', fontSize: 10, color: '#888', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
1426
|
+
was in {first(from)}'s circle
|
|
1427
|
+
</span>
|
|
1428
|
+
)}
|
|
1429
|
+
</span>
|
|
1430
|
+
<span style={{ fontSize: 10.5, fontWeight: 700, color: '#00ff88', whiteSpace: 'nowrap' }}>
|
|
1431
|
+
{now ? 'Scanning…' : 'Scan… →'}
|
|
1432
|
+
</span>
|
|
1433
|
+
</Link>
|
|
1434
|
+
);
|
|
1435
|
+
})}
|
|
1436
|
+
{ready.length > 5 && (
|
|
1437
|
+
<button type="button" onClick={() => setAll((v) => !v)} style={{
|
|
1438
|
+
background: 'none', border: 'none', color: '#888', fontSize: 10.5, fontWeight: 600, cursor: 'pointer', padding: '2px 0', textAlign: 'left',
|
|
1439
|
+
}}>
|
|
1440
|
+
{all ? 'Show fewer' : `Show all ${ready.length}`}
|
|
1441
|
+
</button>
|
|
1408
1442
|
)}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1443
|
+
</div>
|
|
1444
|
+
)}
|
|
1445
|
+
|
|
1446
|
+
{ready.length > 0 && (
|
|
1447
|
+
<div style={{ fontSize: 10, color: '#888', lineHeight: 1.5, marginTop: 8 }}>
|
|
1448
|
+
Each scan: {cost.profileViews} profile view, then one LinkedIn search for every page of their
|
|
1449
|
+
list, up to {cost.searches} pages (about {cost.minutes} min for a whole list). Nothing starts
|
|
1450
|
+
until you confirm it on the Scan page.
|
|
1451
|
+
</div>
|
|
1452
|
+
)}
|
|
1453
|
+
|
|
1454
|
+
{hidden.length > 0 && (
|
|
1455
|
+
<div style={{ marginTop: 10, fontSize: 10.5, color: '#777', lineHeight: 1.6 }}>
|
|
1456
|
+
{hidden.slice(0, 3).map((p) => (
|
|
1457
|
+
<div key={p.id} style={{ opacity: 0.7 }}>
|
|
1458
|
+
<span aria-hidden="true">🔒</span> {p.name} <span style={{ color: '#666' }}>· their list is hidden</span>
|
|
1412
1459
|
</div>
|
|
1413
|
-
)}
|
|
1414
|
-
|
|
1460
|
+
))}
|
|
1461
|
+
{hidden.length > 3 && <div style={{ opacity: 0.7 }}>🔒 and {hidden.length - 3} more with hidden lists</div>}
|
|
1462
|
+
</div>
|
|
1463
|
+
)}
|
|
1464
|
+
|
|
1465
|
+
{others > 0 && (
|
|
1466
|
+
<div style={{ fontSize: 10.5, color: '#888', lineHeight: 1.5, marginTop: 10 }}>
|
|
1467
|
+
{others.toLocaleString('en-US')} of your other connections aren't scanned yet.{' '}
|
|
1468
|
+
<Link href="/setup" style={{ color: '#3498DB', textDecoration: 'none' }}>Map them in batches on the Scan page →</Link>
|
|
1469
|
+
</div>
|
|
1470
|
+
)}
|
|
1471
|
+
{busy && (
|
|
1472
|
+
<div style={{ fontSize: 10, color: '#bbb', marginTop: 8 }}>
|
|
1473
|
+
{busy}. One scan at a time.
|
|
1474
|
+
</div>
|
|
1415
1475
|
)}
|
|
1416
1476
|
</div>
|
|
1417
1477
|
);
|
|
@@ -23,7 +23,7 @@ const CLASSIC = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#B
|
|
|
23
23
|
const TIERS = ['S', 'A', 'B', 'C', 'D'];
|
|
24
24
|
const GREEN = '#00ff88';
|
|
25
25
|
|
|
26
|
-
export default function TheirCircle({ person, connections = [], degree2 = [], tierColors = CLASSIC, onSelect, isRequested = requestedByDefault }) {
|
|
26
|
+
export default function TheirCircle({ person, connections = [], degree2 = [], tierColors = CLASSIC, onSelect, isRequested = requestedByDefault, canScan = true }) {
|
|
27
27
|
const index = useMemo(() => circleIndex(connections, degree2), [connections, degree2]);
|
|
28
28
|
const ways = useMemo(() => routeIndex(degree2), [degree2]);
|
|
29
29
|
const { rings, totals } = useMemo(() => circleRings(person, index, { isRequested }), [person, index, isRequested]);
|
|
@@ -50,6 +50,8 @@ export default function TheirCircle({ person, connections = [], degree2 = [], ti
|
|
|
50
50
|
const guess = useMemo(() => scoreGuess(person), [person]);
|
|
51
51
|
|
|
52
52
|
if (!person) return null;
|
|
53
|
+
// With the sample or a CSV import open there is no scan box below to point
|
|
54
|
+
// at (Sidebar.js says why in its place), so this only says what is known.
|
|
53
55
|
if (!rings.length) {
|
|
54
56
|
const first = person.name?.split(' ')[0] || 'them';
|
|
55
57
|
return (
|
|
@@ -57,22 +59,27 @@ export default function TheirCircle({ person, connections = [], degree2 = [], ti
|
|
|
57
59
|
<Label>Their circle</Label>
|
|
58
60
|
{guess ? (
|
|
59
61
|
<>
|
|
60
|
-
<div style={{ fontSize: 11.5, color: '#d8ccb0', lineHeight: 1.5, marginBottom: 9 }}>
|
|
61
|
-
<b style={{ color: '#FFD700' }}>Their score is a guess.</b> {guess.reason}
|
|
62
|
-
who you can reach through them, and a strong
|
|
62
|
+
<div style={{ fontSize: 11.5, color: '#d8ccb0', lineHeight: 1.5, marginBottom: canScan ? 9 : 0 }}>
|
|
63
|
+
<b style={{ color: '#FFD700' }}>Their score is a guess.</b> {guess.reason}
|
|
64
|
+
{canScan && <> Scanning {first}'s circle shows who you can reach through them, and a strong
|
|
65
|
+
circle adds up to +2 to their score.</>}
|
|
63
66
|
</div>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
{canScan && (
|
|
68
|
+
<button type="button" onClick={toScanBox} style={{
|
|
69
|
+
width: '100%', padding: '8px 10px', borderRadius: 8, cursor: 'pointer', fontSize: 12, fontWeight: 700,
|
|
70
|
+
border: '1px solid rgba(255,215,0,0.4)', background: 'rgba(255,215,0,0.08)', color: '#FFD700',
|
|
71
|
+
}}>
|
|
72
|
+
Scan their circle ↓
|
|
73
|
+
</button>
|
|
74
|
+
)}
|
|
70
75
|
</>
|
|
71
|
-
) : (
|
|
76
|
+
) : canScan ? (
|
|
72
77
|
<div style={{ fontSize: 11, color: '#999', lineHeight: 1.5 }}>
|
|
73
78
|
Not scanned yet. Scan {first}'s circle below to see who they know, and
|
|
74
79
|
everyone you reach through them after that.
|
|
75
80
|
</div>
|
|
81
|
+
) : (
|
|
82
|
+
<div style={{ fontSize: 11, color: '#999', lineHeight: 1.5 }}>Not scanned.</div>
|
|
76
83
|
)}
|
|
77
84
|
</div>
|
|
78
85
|
);
|