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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// The sector picks, wherever you choose your field: Scores → Your sector
|
|
4
|
+
// (SectorSection.js) and the Scan page's question before the first scan
|
|
5
|
+
// (app/components/FieldStep.js). Your picks so far, then a search box, or the
|
|
6
|
+
// twelve broad industries, each opening to its narrower sectors from the
|
|
7
|
+
// directory (lib/sector-directory.js). Up to three picks. It only picks:
|
|
8
|
+
// saving is the page's (lib/settings-client.js).
|
|
9
|
+
|
|
10
|
+
import { useMemo, useState } from 'react';
|
|
11
|
+
import { Body, Btn, LINE } from '../ui';
|
|
12
|
+
import { INDUSTRIES } from '../../../lib/companies';
|
|
13
|
+
import { MAX_SECTORS } from '../../../lib/sector-focus';
|
|
14
|
+
import { DIRECTORY, SECTOR_KEYS, sectorByKey } from '../../../lib/sector-directory';
|
|
15
|
+
|
|
16
|
+
const plural = (n, one, many = `${one}s`) => `${n.toLocaleString()} ${n === 1 ? one : many}`;
|
|
17
|
+
|
|
18
|
+
// Each industry's narrower sectors, in the directory's order.
|
|
19
|
+
const SECTORS_OF = new Map(INDUSTRIES.map((g) => [g.key, DIRECTORY.filter((s) => s.group === g.key)]));
|
|
20
|
+
const GROUP_OF = new Map(DIRECTORY.map((s) => [s.key, s.group]));
|
|
21
|
+
// What the search box looks through: each sector's name, its words, jobs and
|
|
22
|
+
// companies, as they read (without the list's "(s)" and "$" marks), so
|
|
23
|
+
// "recruiter" finds HR & Recruiting.
|
|
24
|
+
const SEARCHABLE = DIRECTORY.map((s) => ({
|
|
25
|
+
sector: s,
|
|
26
|
+
label: s.label.toLowerCase(),
|
|
27
|
+
words: [...s.words, ...(s.roles || []), ...(s.names || []), ...s.companies].map((w) => ` ${w.replace(/\(s\)$|\$$/, '').toLowerCase()}`),
|
|
28
|
+
}));
|
|
29
|
+
// A sector of work every company has (HR & Recruiting, Legal…) counts firms in
|
|
30
|
+
// that line, not every company that employs someone doing it.
|
|
31
|
+
const FUNCTION_TITLE = 'Counts firms in this line of work, not every company with someone in the job';
|
|
32
|
+
|
|
33
|
+
/** Sectors (and whole industries) whose name has the query, or a word or company that starts with it. */
|
|
34
|
+
function searchSectors(query) {
|
|
35
|
+
const q = query.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
36
|
+
if (!q) return null;
|
|
37
|
+
return {
|
|
38
|
+
industries: INDUSTRIES.filter((g) => g.label.toLowerCase().includes(q)),
|
|
39
|
+
sectors: SEARCHABLE.filter((s) => s.label.includes(q) || s.words.some((w) => w.includes(` ${q}`))).map((s) => s.sector),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* `sectors` are the picks (keys), `onChange(sectors)` gets the new list.
|
|
45
|
+
* `suggest` is "Suggested from your network" as Scores loads it
|
|
46
|
+
* (/api/settings/sector-suggestions); left out, there are none to show.
|
|
47
|
+
*/
|
|
48
|
+
export default function SectorPicker({ sectors, onChange, disabled, suggest }) {
|
|
49
|
+
const [query, setQuery] = useState('');
|
|
50
|
+
// Industries shown open; those holding a pick start open.
|
|
51
|
+
const [open, setOpen] = useState(() => new Set(sectors.map((k) => GROUP_OF.get(k) || k)));
|
|
52
|
+
|
|
53
|
+
const results = useMemo(() => searchSectors(query), [query]);
|
|
54
|
+
|
|
55
|
+
function toggle(key) {
|
|
56
|
+
const on = sectors.includes(key);
|
|
57
|
+
if (!on && sectors.length >= MAX_SECTORS) return;
|
|
58
|
+
const next = on ? sectors.filter((k) => k !== key) : SECTOR_KEYS.filter((k) => k === key || sectors.includes(k));
|
|
59
|
+
// A sector picked from the suggestions or a search shows up in its industry too.
|
|
60
|
+
if (!on && GROUP_OF.has(key)) setOpen((was) => new Set(was).add(GROUP_OF.get(key)));
|
|
61
|
+
onChange(next);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function flip(group) {
|
|
65
|
+
setOpen((was) => {
|
|
66
|
+
const next = new Set(was);
|
|
67
|
+
if (next.has(group)) next.delete(group); else next.add(group);
|
|
68
|
+
return next;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const full = sectors.length >= MAX_SECTORS;
|
|
73
|
+
// One chip for any pick: an industry or a sector, wherever it's shown.
|
|
74
|
+
const chip = (key, label, { note, title } = {}) => {
|
|
75
|
+
const on = sectors.includes(key);
|
|
76
|
+
return (
|
|
77
|
+
<Chip key={key} color={sectorByKey(key).color} on={on} blocked={!on && full} disabled={disabled} title={title}
|
|
78
|
+
onClick={() => toggle(key)}>
|
|
79
|
+
{label}{note && <span style={{ fontWeight: 400, color: on ? '#dfe7e7' : '#8b9a9a' }}>{note}</span>}
|
|
80
|
+
</Chip>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 6 }}>
|
|
87
|
+
<span style={{ fontSize: 13, color: '#8b9a9a' }}>Your picks ({sectors.length} of {MAX_SECTORS}):</span>
|
|
88
|
+
{!sectors.length && <span style={{ fontSize: 13, color: '#667' }}>none yet</span>}
|
|
89
|
+
{sectors.map((k) => {
|
|
90
|
+
const s = sectorByKey(k);
|
|
91
|
+
return (
|
|
92
|
+
<button key={k} type="button" disabled={disabled} onClick={() => toggle(k)} aria-label={`Remove ${s.label}`}
|
|
93
|
+
style={{
|
|
94
|
+
display: 'flex', alignItems: 'center', gap: 6, padding: '4px 10px', borderRadius: 999, fontSize: 12.5,
|
|
95
|
+
fontWeight: 600, color: '#fff', background: `${s.color}33`, border: `1px solid ${s.color}`, cursor: 'pointer',
|
|
96
|
+
}}>
|
|
97
|
+
{s.label} <span aria-hidden="true" style={{ color: '#cfd8d8' }}>×</span>
|
|
98
|
+
</button>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{suggest && <Suggestions state={suggest} chip={chip} />}
|
|
104
|
+
|
|
105
|
+
<label htmlFor="sector-search" style={{ display: 'block', fontSize: 13, color: '#8b9a9a', margin: '16px 0 6px' }}>
|
|
106
|
+
Find a sector
|
|
107
|
+
</label>
|
|
108
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
109
|
+
<input id="sector-search" type="search" value={query} onChange={(e) => setQuery(e.target.value)}
|
|
110
|
+
placeholder="Try dentist, bakery or Stripe"
|
|
111
|
+
autoComplete="off" spellCheck={false}
|
|
112
|
+
style={{
|
|
113
|
+
flex: 1, minWidth: 0, padding: '8px 12px', borderRadius: 7, fontSize: 14, color: '#fff',
|
|
114
|
+
background: 'rgba(255,255,255,0.05)', border: LINE, outline: 'none',
|
|
115
|
+
}} />
|
|
116
|
+
{query && <Btn onClick={() => setQuery('')}>Clear</Btn>}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{results ? (
|
|
120
|
+
<div style={{ marginTop: 10 }} aria-live="polite">
|
|
121
|
+
{results.industries.length + results.sectors.length === 0 ? (
|
|
122
|
+
<Body style={{ fontSize: 12.5 }}>
|
|
123
|
+
Nothing in the list matches “{query.trim()}”. Try a job title, a kind of business or a company, or clear
|
|
124
|
+
the search and pick one of the twelve broad industries.
|
|
125
|
+
</Body>
|
|
126
|
+
) : (
|
|
127
|
+
<div role="group" aria-label="Sectors that match your search" style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
128
|
+
{results.industries.map((g) => chip(g.key, g.label, { note: ' · the whole industry' }))}
|
|
129
|
+
{results.sectors.map((s) => chip(s.key, s.label, { title: s.kind === 'function' ? FUNCTION_TITLE : `In ${sectorByKey(s.group).label}` }))}
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
) : (
|
|
134
|
+
<div style={{ marginTop: 10 }}>
|
|
135
|
+
{INDUSTRIES.map((g) => {
|
|
136
|
+
const inIndustry = SECTORS_OF.get(g.key);
|
|
137
|
+
const isOpen = open.has(g.key);
|
|
138
|
+
const inside = inIndustry.filter((s) => sectors.includes(s.key)).length;
|
|
139
|
+
return (
|
|
140
|
+
<div key={g.key} style={{ borderTop: LINE, padding: '8px 0' }}>
|
|
141
|
+
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
|
|
142
|
+
{chip(g.key, g.label, { title: 'The whole industry: companies its sectors place, or whose name or the well-known list says so' })}
|
|
143
|
+
<button type="button" aria-expanded={isOpen} aria-controls={`sectors-${g.key}`} onClick={() => flip(g.key)}
|
|
144
|
+
style={{
|
|
145
|
+
padding: '4px 6px', fontSize: 12.5, color: inside ? '#9fd3ff' : '#8b9a9a', background: 'none', border: 'none',
|
|
146
|
+
cursor: 'pointer',
|
|
147
|
+
}}>
|
|
148
|
+
{isOpen ? '▾' : '▸'} {plural(inIndustry.length, 'sector')}{inside ? ` · ${inside} picked` : ''}
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
{isOpen && (
|
|
152
|
+
<div id={`sectors-${g.key}`} role="group" aria-label={`Sectors in ${g.label}`}
|
|
153
|
+
style={{ display: 'flex', flexWrap: 'wrap', gap: 8, margin: '8px 0 2px 14px' }}>
|
|
154
|
+
{inIndustry.map((s) => chip(s.key, s.label, s.kind === 'function' ? { title: FUNCTION_TITLE } : undefined))}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
})}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A pick you can turn on and off: an industry or a sector, with its industry's colour. */
|
|
167
|
+
function Chip({ color, on, blocked, disabled, title, onClick, children }) {
|
|
168
|
+
return (
|
|
169
|
+
<button type="button" aria-pressed={on} disabled={blocked || disabled} onClick={onClick}
|
|
170
|
+
title={blocked ? `Up to ${MAX_SECTORS} sectors. Turn one off first.` : title}
|
|
171
|
+
style={{
|
|
172
|
+
display: 'flex', alignItems: 'center', gap: 7, padding: '6px 12px', borderRadius: 999, maxWidth: '100%',
|
|
173
|
+
fontSize: 12.5, fontWeight: 600, textAlign: 'left', cursor: blocked ? 'not-allowed' : 'pointer',
|
|
174
|
+
color: on ? '#fff' : blocked ? '#556' : '#b8c4c4',
|
|
175
|
+
background: on ? `${color}33` : 'rgba(255,255,255,0.04)',
|
|
176
|
+
border: on ? `1px solid ${color}` : LINE,
|
|
177
|
+
}}>
|
|
178
|
+
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, opacity: blocked ? 0.4 : 1, flexShrink: 0 }} />
|
|
179
|
+
<span>{children}</span>
|
|
180
|
+
</button>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** "Suggested from your network": the directory's sectors most of your scanned people work in. */
|
|
185
|
+
function Suggestions({ state, chip }) {
|
|
186
|
+
const note = { fontSize: 12.5, marginTop: 10 };
|
|
187
|
+
if (state.loading) return <Body style={note}>Looking through your network for suggestions…</Body>;
|
|
188
|
+
if (state.error) return <Body style={note}>Couldn’t load suggestions ({state.error}). You can still pick below.</Body>;
|
|
189
|
+
if (!state.scored) return <Body style={note}>Suggestions appear here once you’ve scanned your network.</Body>;
|
|
190
|
+
if (!state.list.length) return <Body style={note}>None of the list’s sectors stands out among the people you’ve scanned yet.</Body>;
|
|
191
|
+
return (
|
|
192
|
+
<div style={{ marginTop: 14 }}>
|
|
193
|
+
<div style={{ fontSize: 13, color: '#8b9a9a', marginBottom: 6 }}>Suggested from your network</div>
|
|
194
|
+
<div role="group" aria-label="Suggested from your network" style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
195
|
+
{state.list.map((s) => chip(s.key, s.label, {
|
|
196
|
+
note: `: ${plural(s.people, 'person', 'people')} at ${plural(s.companies, 'company', 'companies')}`,
|
|
197
|
+
title: `In ${sectorByKey(s.group).label}`,
|
|
198
|
+
}))}
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -10,16 +10,19 @@
|
|
|
10
10
|
// Before you save, the page asks the app what would change (a dry run that
|
|
11
11
|
// writes nothing). Saving stores the choice with your network and rescores
|
|
12
12
|
// everyone, the way a company score change does. Suggestions come from the
|
|
13
|
-
// people you've scanned (/api/settings/sector-suggestions).
|
|
13
|
+
// people you've scanned (/api/settings/sector-suggestions). The picks
|
|
14
|
+
// themselves are SectorPicker.js, which the Scan page's question about your
|
|
15
|
+
// field, before the first scan, shows too.
|
|
14
16
|
|
|
15
|
-
import { useEffect,
|
|
17
|
+
import { useEffect, useRef, useState } from 'react';
|
|
16
18
|
import { Section, Body, Status, Btn, LINE } from '../ui';
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import { DIRECTORY,
|
|
19
|
+
import SectorPicker from './SectorPicker';
|
|
20
|
+
import { sameFocus, focusFingerprint } from '../../../lib/sector-focus';
|
|
21
|
+
import { DIRECTORY, sectorByKey } from '../../../lib/sector-directory';
|
|
20
22
|
import { SECTOR_BONUS } from '../../../lib/scoring';
|
|
21
23
|
import { TIER_COLORS_CLASSIC } from '../../../lib/tiers';
|
|
22
24
|
import { csvNetworkSource } from '../../../lib/csv';
|
|
25
|
+
import { saveSettings } from '../../../lib/settings-client';
|
|
23
26
|
|
|
24
27
|
const STRENGTH_LABEL = { lean: 'Lean', strong: 'Strong' };
|
|
25
28
|
// What says the reader has taken over the page (see the jump to #sector).
|
|
@@ -27,33 +30,10 @@ const READER_INPUT = ['wheel', 'touchstart', 'keydown', 'pointerdown'];
|
|
|
27
30
|
const DEGREE = { 1: '1st', 2: '2nd', 3: '3rd' };
|
|
28
31
|
const plural = (n, one, many = `${one}s`) => `${n.toLocaleString()} ${n === 1 ? one : many}`;
|
|
29
32
|
|
|
30
|
-
//
|
|
31
|
-
const SECTORS_OF = new Map(INDUSTRIES.map((g) => [g.key, DIRECTORY.filter((s) => s.group === g.key)]));
|
|
32
|
-
const GROUP_OF = new Map(DIRECTORY.map((s) => [s.key, s.group]));
|
|
33
|
-
// What the search box looks through: each sector's name, its words, jobs and
|
|
34
|
-
// companies, as they read (without the list's "(s)" and "$" marks), so
|
|
35
|
-
// "recruiter" finds HR & Recruiting.
|
|
36
|
-
const SEARCHABLE = DIRECTORY.map((s) => ({
|
|
37
|
-
sector: s,
|
|
38
|
-
label: s.label.toLowerCase(),
|
|
39
|
-
words: [...s.words, ...(s.roles || []), ...(s.names || []), ...s.companies].map((w) => ` ${w.replace(/\(s\)$|\$$/, '').toLowerCase()}`),
|
|
40
|
-
}));
|
|
41
|
-
// A sector of work every company has (HR & Recruiting, Legal…) counts firms in
|
|
42
|
-
// that line, not every company that employs someone doing it.
|
|
43
|
-
const FUNCTION_TITLE = 'Counts firms in this line of work, not every company with someone in the job';
|
|
33
|
+
// The sectors that are work every kind of company has (HR & Recruiting, Legal…), for the notes below.
|
|
44
34
|
const FUNCTIONS = DIRECTORY.filter((s) => s.kind === 'function').map((s) => s.label);
|
|
45
35
|
const listed = (labels) => (labels.length > 1 ? `${labels.slice(0, -1).join(', ')} and ${labels.at(-1)}` : labels.join(''));
|
|
46
36
|
|
|
47
|
-
/** Sectors (and whole industries) whose name has the query, or a word or company that starts with it. */
|
|
48
|
-
function searchSectors(query) {
|
|
49
|
-
const q = query.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
50
|
-
if (!q) return null;
|
|
51
|
-
return {
|
|
52
|
-
industries: INDUSTRIES.filter((g) => g.label.toLowerCase().includes(q)),
|
|
53
|
-
sectors: SEARCHABLE.filter((s) => s.label.includes(q) || s.words.some((w) => w.includes(` ${q}`))).map((s) => s.sector),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
37
|
/** "120 people move up a tier and 4 move down." (or "moved", after a save) */
|
|
58
38
|
function tierLine(up, down, past) {
|
|
59
39
|
const [goes, go] = past ? ['moved', 'moved'] : ['moves', 'move'];
|
|
@@ -71,9 +51,6 @@ export default function SectorSection({ onSaved } = {}) {
|
|
|
71
51
|
const [result, setResult] = useState(null);
|
|
72
52
|
const [loadError, setLoadError] = useState(null);
|
|
73
53
|
const [suggest, setSuggest] = useState({ loading: true });
|
|
74
|
-
const [query, setQuery] = useState('');
|
|
75
|
-
// Industries shown open; those holding a saved pick start open.
|
|
76
|
-
const [open, setOpen] = useState(() => new Set());
|
|
77
54
|
// A CSV import or the sample network held in this window. This section only
|
|
78
55
|
// renders in the browser (the page waits in OnboardingGate), so reading
|
|
79
56
|
// sessionStorage here can't disagree with a server render.
|
|
@@ -91,7 +68,6 @@ export default function SectorSection({ onSaved } = {}) {
|
|
|
91
68
|
const focus = d.settings?.sectorFocus || { sectors: [], strength: 'lean' };
|
|
92
69
|
setSaved(focus);
|
|
93
70
|
setDraft(focus);
|
|
94
|
-
setOpen(new Set(focus.sectors.map((k) => GROUP_OF.get(k) || k)));
|
|
95
71
|
})
|
|
96
72
|
.catch((e) => { if (!off) setLoadError(e.message); });
|
|
97
73
|
// Suggestions read every scanned row, so they load on their own and never hold up the rest.
|
|
@@ -127,8 +103,6 @@ export default function SectorSection({ onSaved } = {}) {
|
|
|
127
103
|
return stop;
|
|
128
104
|
}, [draft, loadError]);
|
|
129
105
|
|
|
130
|
-
const results = useMemo(() => searchSectors(query), [query]);
|
|
131
|
-
|
|
132
106
|
// Every change asks for a fresh dry run, a moment after the last click.
|
|
133
107
|
// Only the newest answer is kept.
|
|
134
108
|
function change(next) {
|
|
@@ -153,56 +127,20 @@ export default function SectorSection({ onSaved } = {}) {
|
|
|
153
127
|
}, 250);
|
|
154
128
|
}
|
|
155
129
|
|
|
156
|
-
function toggle(key) {
|
|
157
|
-
const on = draft.sectors.includes(key);
|
|
158
|
-
if (!on && draft.sectors.length >= MAX_SECTORS) return;
|
|
159
|
-
const sectors = on ? draft.sectors.filter((k) => k !== key) : SECTOR_KEYS.filter((k) => k === key || draft.sectors.includes(k));
|
|
160
|
-
// A sector picked from the suggestions or a search shows up in its industry too.
|
|
161
|
-
if (!on && GROUP_OF.has(key)) setOpen((was) => new Set(was).add(GROUP_OF.get(key)));
|
|
162
|
-
change({ ...draft, sectors });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function flip(group) {
|
|
166
|
-
setOpen((was) => {
|
|
167
|
-
const next = new Set(was);
|
|
168
|
-
if (next.has(group)) next.delete(group); else next.add(group);
|
|
169
|
-
return next;
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
130
|
async function save() {
|
|
174
131
|
setSaving(true);
|
|
175
132
|
setResult(null);
|
|
176
133
|
clearTimeout(timer.current);
|
|
177
134
|
ticket.current++;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (!r.ok) { setResult({ tone: 'bad', text: d.error || 'Could not save.' }); return; }
|
|
185
|
-
setResult({ tone: 'ok', text: savedLine(d.settings.sectorFocus, d.effects?.sectorFocus) });
|
|
186
|
-
onSaved?.();
|
|
187
|
-
} catch {
|
|
188
|
-
setResult({ tone: 'bad', text: 'Could not reach the app. Reload this page to see what is saved.' });
|
|
189
|
-
} finally {
|
|
190
|
-
setSaving(false);
|
|
191
|
-
}
|
|
135
|
+
const d = await saveSettings({ sectorFocus: draft });
|
|
136
|
+
setSaving(false);
|
|
137
|
+
if (d.settings) { setSaved(d.settings.sectorFocus); setDraft(d.settings.sectorFocus); setPreview(null); }
|
|
138
|
+
if (d.error) { setResult({ tone: 'bad', text: d.error }); return; }
|
|
139
|
+
setResult({ tone: 'ok', text: savedLine(d.settings.sectorFocus, d.effects?.sectorFocus) });
|
|
140
|
+
onSaved?.();
|
|
192
141
|
}
|
|
193
142
|
|
|
194
143
|
const changed = draft && saved && !sameFocus(draft, saved);
|
|
195
|
-
const full = draft && draft.sectors.length >= MAX_SECTORS;
|
|
196
|
-
// One chip for any pick: an industry or a sector, wherever it's shown.
|
|
197
|
-
const chip = (key, label, { note, title } = {}) => {
|
|
198
|
-
const on = draft.sectors.includes(key);
|
|
199
|
-
return (
|
|
200
|
-
<Chip key={key} color={sectorByKey(key).color} on={on} blocked={!on && full} disabled={saving} title={title}
|
|
201
|
-
onClick={() => toggle(key)}>
|
|
202
|
-
{label}{note && <span style={{ fontWeight: 400, color: on ? '#dfe7e7' : '#8b9a9a' }}>{note}</span>}
|
|
203
|
-
</Chip>
|
|
204
|
-
);
|
|
205
|
-
};
|
|
206
144
|
|
|
207
145
|
return (
|
|
208
146
|
<Section id="sector" title="Your sector"
|
|
@@ -212,82 +150,8 @@ export default function SectorSection({ onSaved } = {}) {
|
|
|
212
150
|
|
|
213
151
|
{draft && (
|
|
214
152
|
<>
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
{!draft.sectors.length && <span style={{ fontSize: 13, color: '#667' }}>none yet</span>}
|
|
218
|
-
{draft.sectors.map((k) => {
|
|
219
|
-
const s = sectorByKey(k);
|
|
220
|
-
return (
|
|
221
|
-
<button key={k} type="button" disabled={saving} onClick={() => toggle(k)} aria-label={`Remove ${s.label}`}
|
|
222
|
-
style={{
|
|
223
|
-
display: 'flex', alignItems: 'center', gap: 6, padding: '4px 10px', borderRadius: 999, fontSize: 12.5,
|
|
224
|
-
fontWeight: 600, color: '#fff', background: `${s.color}33`, border: `1px solid ${s.color}`, cursor: 'pointer',
|
|
225
|
-
}}>
|
|
226
|
-
{s.label} <span aria-hidden="true" style={{ color: '#cfd8d8' }}>×</span>
|
|
227
|
-
</button>
|
|
228
|
-
);
|
|
229
|
-
})}
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
<Suggestions state={suggest} chip={chip} />
|
|
233
|
-
|
|
234
|
-
<label htmlFor="sector-search" style={{ display: 'block', fontSize: 13, color: '#8b9a9a', margin: '16px 0 6px' }}>
|
|
235
|
-
Find a sector
|
|
236
|
-
</label>
|
|
237
|
-
<div style={{ display: 'flex', gap: 8 }}>
|
|
238
|
-
<input id="sector-search" type="search" value={query} onChange={(e) => setQuery(e.target.value)}
|
|
239
|
-
placeholder="Try dentist, bakery or Stripe"
|
|
240
|
-
autoComplete="off" spellCheck={false}
|
|
241
|
-
style={{
|
|
242
|
-
flex: 1, minWidth: 0, padding: '8px 12px', borderRadius: 7, fontSize: 14, color: '#fff',
|
|
243
|
-
background: 'rgba(255,255,255,0.05)', border: LINE, outline: 'none',
|
|
244
|
-
}} />
|
|
245
|
-
{query && <Btn onClick={() => setQuery('')}>Clear</Btn>}
|
|
246
|
-
</div>
|
|
247
|
-
|
|
248
|
-
{results ? (
|
|
249
|
-
<div style={{ marginTop: 10 }} aria-live="polite">
|
|
250
|
-
{results.industries.length + results.sectors.length === 0 ? (
|
|
251
|
-
<Body style={{ fontSize: 12.5 }}>
|
|
252
|
-
Nothing in the list matches “{query.trim()}”. Try a job title, a kind of business or a company, or clear
|
|
253
|
-
the search and pick one of the twelve broad industries.
|
|
254
|
-
</Body>
|
|
255
|
-
) : (
|
|
256
|
-
<div role="group" aria-label="Sectors that match your search" style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
257
|
-
{results.industries.map((g) => chip(g.key, g.label, { note: ' · the whole industry' }))}
|
|
258
|
-
{results.sectors.map((s) => chip(s.key, s.label, { title: s.kind === 'function' ? FUNCTION_TITLE : `In ${sectorByKey(s.group).label}` }))}
|
|
259
|
-
</div>
|
|
260
|
-
)}
|
|
261
|
-
</div>
|
|
262
|
-
) : (
|
|
263
|
-
<div style={{ marginTop: 10 }}>
|
|
264
|
-
{INDUSTRIES.map((g) => {
|
|
265
|
-
const sectors = SECTORS_OF.get(g.key);
|
|
266
|
-
const isOpen = open.has(g.key);
|
|
267
|
-
const inside = sectors.filter((s) => draft.sectors.includes(s.key)).length;
|
|
268
|
-
return (
|
|
269
|
-
<div key={g.key} style={{ borderTop: LINE, padding: '8px 0' }}>
|
|
270
|
-
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 8 }}>
|
|
271
|
-
{chip(g.key, g.label, { title: 'The whole industry: companies its sectors place, or whose name or the well-known list says so' })}
|
|
272
|
-
<button type="button" aria-expanded={isOpen} aria-controls={`sectors-${g.key}`} onClick={() => flip(g.key)}
|
|
273
|
-
style={{
|
|
274
|
-
padding: '4px 6px', fontSize: 12.5, color: inside ? '#9fd3ff' : '#8b9a9a', background: 'none', border: 'none',
|
|
275
|
-
cursor: 'pointer',
|
|
276
|
-
}}>
|
|
277
|
-
{isOpen ? '▾' : '▸'} {plural(sectors.length, 'sector')}{inside ? ` · ${inside} picked` : ''}
|
|
278
|
-
</button>
|
|
279
|
-
</div>
|
|
280
|
-
{isOpen && (
|
|
281
|
-
<div id={`sectors-${g.key}`} role="group" aria-label={`Sectors in ${g.label}`}
|
|
282
|
-
style={{ display: 'flex', flexWrap: 'wrap', gap: 8, margin: '8px 0 2px 14px' }}>
|
|
283
|
-
{sectors.map((s) => chip(s.key, s.label, s.kind === 'function' ? { title: FUNCTION_TITLE } : undefined))}
|
|
284
|
-
</div>
|
|
285
|
-
)}
|
|
286
|
-
</div>
|
|
287
|
-
);
|
|
288
|
-
})}
|
|
289
|
-
</div>
|
|
290
|
-
)}
|
|
153
|
+
<SectorPicker sectors={draft.sectors} onChange={(sectors) => change({ ...draft, sectors })} disabled={saving}
|
|
154
|
+
suggest={suggest} />
|
|
291
155
|
|
|
292
156
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 8, marginTop: 14 }}>
|
|
293
157
|
<span style={{ fontSize: 13, color: '#8b9a9a' }}>How much:</span>
|
|
@@ -352,44 +216,6 @@ export default function SectorSection({ onSaved } = {}) {
|
|
|
352
216
|
);
|
|
353
217
|
}
|
|
354
218
|
|
|
355
|
-
/** A pick you can turn on and off: an industry or a sector, with its industry's colour. */
|
|
356
|
-
function Chip({ color, on, blocked, disabled, title, onClick, children }) {
|
|
357
|
-
return (
|
|
358
|
-
<button type="button" aria-pressed={on} disabled={blocked || disabled} onClick={onClick}
|
|
359
|
-
title={blocked ? `Up to ${MAX_SECTORS} sectors. Turn one off first.` : title}
|
|
360
|
-
style={{
|
|
361
|
-
display: 'flex', alignItems: 'center', gap: 7, padding: '6px 12px', borderRadius: 999, maxWidth: '100%',
|
|
362
|
-
fontSize: 12.5, fontWeight: 600, textAlign: 'left', cursor: blocked ? 'not-allowed' : 'pointer',
|
|
363
|
-
color: on ? '#fff' : blocked ? '#556' : '#b8c4c4',
|
|
364
|
-
background: on ? `${color}33` : 'rgba(255,255,255,0.04)',
|
|
365
|
-
border: on ? `1px solid ${color}` : LINE,
|
|
366
|
-
}}>
|
|
367
|
-
<span style={{ width: 8, height: 8, borderRadius: '50%', background: color, opacity: blocked ? 0.4 : 1, flexShrink: 0 }} />
|
|
368
|
-
<span>{children}</span>
|
|
369
|
-
</button>
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/** "Suggested from your network": the directory's sectors most of your scanned people work in. */
|
|
374
|
-
function Suggestions({ state, chip }) {
|
|
375
|
-
const note = { fontSize: 12.5, marginTop: 10 };
|
|
376
|
-
if (state.loading) return <Body style={note}>Looking through your network for suggestions…</Body>;
|
|
377
|
-
if (state.error) return <Body style={note}>Couldn’t load suggestions ({state.error}). You can still pick below.</Body>;
|
|
378
|
-
if (!state.scored) return <Body style={note}>Suggestions appear here once you’ve scanned your network.</Body>;
|
|
379
|
-
if (!state.list.length) return <Body style={note}>None of the list’s sectors stands out among the people you’ve scanned yet.</Body>;
|
|
380
|
-
return (
|
|
381
|
-
<div style={{ marginTop: 14 }}>
|
|
382
|
-
<div style={{ fontSize: 13, color: '#8b9a9a', marginBottom: 6 }}>Suggested from your network</div>
|
|
383
|
-
<div role="group" aria-label="Suggested from your network" style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
384
|
-
{state.list.map((s) => chip(s.key, s.label, {
|
|
385
|
-
note: `: ${plural(s.people, 'person', 'people')} at ${plural(s.companies, 'company', 'companies')}`,
|
|
386
|
-
title: `In ${sectorByKey(s.group).label}`,
|
|
387
|
-
}))}
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
219
|
/** "Saved: …. Rescored your network (N people). …" */
|
|
394
220
|
function savedLine(focus, effect) {
|
|
395
221
|
// The labels have commas of their own ("Tech, Software & AI"), so a dot
|
|
@@ -11,6 +11,7 @@ import { useEffect, useState } from 'react';
|
|
|
11
11
|
import { Section, Body, Status, LINE } from '../ui';
|
|
12
12
|
import { CURVE, CURVE_FLOOR } from '../../../lib/scoring';
|
|
13
13
|
import { csvNetworkSource } from '../../../lib/csv';
|
|
14
|
+
import { saveSettings } from '../../../lib/settings-client';
|
|
14
15
|
|
|
15
16
|
const pct = (x) => `${Math.round(x * 100)}%`;
|
|
16
17
|
const [S, A, B] = CURVE.map(([, share]) => share);
|
|
@@ -53,21 +54,15 @@ export default function TierSection({ onSaved } = {}) {
|
|
|
53
54
|
if (saving || key === saved) return;
|
|
54
55
|
setSaving(true);
|
|
55
56
|
setResult(null);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
if (d.settings) setSaved(d.settings.tierScale);
|
|
62
|
-
if (!r.ok) { setResult({ tone: 'bad', text: d.error || 'Could not save.' }); return; }
|
|
63
|
-
const e = d.effects?.tierScale;
|
|
57
|
+
const { settings, effects, error } = await saveSettings({ tierScale: key });
|
|
58
|
+
if (settings) setSaved(settings.tierScale);
|
|
59
|
+
if (error) setResult({ tone: 'bad', text: error });
|
|
60
|
+
else {
|
|
61
|
+
const e = effects?.tierScale;
|
|
64
62
|
setResult({ tone: 'ok', text: e ? `Saved. ${plural(e.moved, 'person', 'people')} changed tier.` : 'Saved.' });
|
|
65
63
|
onSaved?.();
|
|
66
|
-
} catch {
|
|
67
|
-
setResult({ tone: 'bad', text: 'Could not reach the app. Reload this page to see what is saved.' });
|
|
68
|
-
} finally {
|
|
69
|
-
setSaving(false);
|
|
70
64
|
}
|
|
65
|
+
setSaving(false);
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
return (
|
|
@@ -36,8 +36,8 @@ export const VIEWS = {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* The order they appear in the menu.
|
|
40
|
-
*
|
|
39
|
+
* The order they appear in the menu. Orbit is the first Degrees view, so it is
|
|
40
|
+
* also where resolveView lands when the chosen view isn't a Degrees one
|
|
41
41
|
* (Galaxy, say, carried over from Network Circle).
|
|
42
42
|
*/
|
|
43
43
|
const ORDER = ['galaxy', 'orbit', 'separation', 'chain', 'rings', 'list'];
|
|
@@ -19,6 +19,13 @@ body:has(> div > [data-map]) {
|
|
|
19
19
|
overflow: hidden;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/* The Scan page's field question holds Continue and Skip for now at the foot
|
|
23
|
+
of the window (app/components/FieldStep.js). Without this, a pick reached
|
|
24
|
+
with Tab can stop behind them. */
|
|
25
|
+
html:has([data-field-question]) {
|
|
26
|
+
scroll-padding-bottom: 80px;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
::-webkit-scrollbar {
|
|
23
30
|
width: 6px;
|
|
24
31
|
}
|
|
@@ -125,7 +125,7 @@ function HeroSection() {
|
|
|
125
125
|
transition: 'all 1s cubic-bezier(0.16, 1, 0.3, 1) 0.15s',
|
|
126
126
|
letterSpacing: '-0.03em',
|
|
127
127
|
}}>
|
|
128
|
-
|
|
128
|
+
Six Degrees of<br />Separation
|
|
129
129
|
</h1>
|
|
130
130
|
|
|
131
131
|
{/* Subtitle */}
|
|
@@ -315,7 +315,7 @@ function GalaxySection() {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
// ═══════════════════════════════════════════════════════════════
|
|
318
|
-
// BRIDGE CHAINS —
|
|
318
|
+
// BRIDGE CHAINS — six degrees visualization
|
|
319
319
|
// ═══════════════════════════════════════════════════════════════
|
|
320
320
|
function ChainSection() {
|
|
321
321
|
const [ref, inView] = useInView();
|