PageSourceSearch

https://splashiq.ai/assets/admin.contractor-scrape-BAWJ4kU6.js

js splashiq.ai collected 2026-10-05 12:44:18 UTC 13,406 bytes, 1 lines download raw bytes

1import{j as e,r as n,B as X,L as be,cN as Ie,I as Pe,h as Y,ac as Ee,bn as Le}from"./index-CzAkNt2W.js";import{P as Me}from"./progress-BHg-3Ib_.js";import{C as W,a as ee,b as se,c as te}from"./card-DyZ6_8_C.js";import{C as $e}from"./checkbox-G3X53UcF.js";import{S as ye,a as Ne,b as ve,c as Se,e as ae,f as ne,d as E}from"./select-CgG1LT6p.js";import{R as Ae}from"./RequireRole-DhUlFC_T.js";import{H as Fe}from"./HelpHint-D0u0IIus.js";import{S as qe}from"./square-DbfvwTJ1.js";import{P as Be}from"./play-B4-hu3Ri.js";import{T as we}from"./triangle-alert-Cl8dz-Zm.js";import{C as Ze}from"./circle-check-gM2QolJ6.js";import{L as Ke}from"./loader-circle-B0UeYsVT.js";import"./circle-question-mark-CDYWTUQU.js";const re="__all_states__",ie=4,Ve=2e3;function De(o){const g=Ve*2**(o-1);return Math.round(g*(.8+Math.random()*.4))}const ke=o=>new Promise(g=>setTimeout(g,o));function He(){const[o,g]=n.useState([]),[w,z]=n.useState(["ak"]),[h,k]=n.useState(""),[T,b]=n.useState(""),[y,le]=n.useState(!1),[oe,L]=n.useState(!1),[M,Ce]=n.useState(3),[x,$]=n.useState({done:0,total:0}),[N,de]=n.useState({found:0,inserted:0,retried:0,failed:0}),[ce,A]=n.useState([]),[ue,me]=n.useState(null),[pe,F]=n.useState([]),[q,B]=n.useState(null),[Re,ze]=n.useState(null),[Z,Te]=n.useState(!1),K=5;n.useEffect(()=>{S({action:"list"}).then(({regions:s,bulkExcluded:c})=>{if(g(s),c&&z(c),s.length>0){const i=s.find(u=>u.key==="socal")??s[0];k(i.key),b(i.defaultLabel)}}).catch(s=>B(s instanceof Error?s.message:String(s)))},[]),n.useEffect(()=>{if(h===re){b("United States (all states, excl. Alaska)");return}const s=o.find(c=>c.key===h);s&&b(s.defaultLabel)},[h,o]);const V=n.useMemo(()=>o.filter(s=>s.builtIn),[o]),he=n.useMemo(()=>o.filter(s=>!s.builtIn),[o]),xe=o.find(s=>s.key===h),D=n.useMemo(()=>V.filter(s=>!w.includes(s.key)),[V,w]),f=h===re?D:xe?[xe]:[],_=f.reduce((s,c)=>s+c.zipCount,0),_e=async()=>{if(f.length===0)return;le(!0),L(!1),B(null),F([]),de({found:0,inserted:0,retried:0,failed:0}),$({done:0,total:0}),A(f.map(l=>({key:l.key,label:l.label,total:l.zipCount,done:0,found:0,inserted:0,retried:0,failed:0,status:"pending"})));const s=(l,m)=>A(C=>C.map(t=>t.key===l?{...t,...m}:t)),c=(l,m)=>A(C=>C.map(t=>t.key===l?{...t,done:t.done+(m.done??0),found:t.found+(m.found??0),inserted:t.inserted+(m.inserted??0),retried:t.retried+(m.retried??0),failed:t.failed+(m.failed??0)}:t));let i=null,u=!1,ge=0,H=0,O=0;const G=new Set;try{const{runId:l}=await S({action:"start",region:f[0].key,label:T.trim()||f[0].defaultLabel,zipsTotal:_||void 0});i=l,ze(l);const m=async(t,p,d)=>{try{const r=await S({action:"scrape",region:t.key,zip:p,runId:l});return{region:t.label,zip:p,attempt:d,...r}}catch(r){return{region:t.label,zip:p,attempt:d,found:0,inserted:0,error:r instanceof Error?r.message:String(r)}}},C=async(t,p,d)=>{const r=[];for(let j=0;j<p.length;j+=M){if(u||R.value){u=!0;break}const I=p.slice(j,j+M).filter(a=>!G.has(a));if(I.length===0)continue;const v=await Promise.all(I.map(a=>m(t,a,d))),U=v.reduce((a,Q)=>a+Q.found,0),je=v.reduce((a,Q)=>a+Q.inserted,0);let P=0,J=0;for(const a of v)a.error?d<ie?(a.retrying=!0,r.push(a.zip)):(J++,P++,G.add(a.zip)):(G.add(a.zip),P++);if(F(a=>[...v.slice().reverse(),...a].slice(0,400)),ge+=U,H+=P,de(a=>({found:a.found+U,inserted:a.inserted+je,retried:a.retried+(d>1?v.length:0),failed:a.failed+J})),c(t.key,{done:P,found:U,inserted:je,retried:d>1?v.length:0,failed:J}),$({done:H,total:O}),Z&&ge>=K){u=!0;break}if(R.value){u=!0;break}await ke(150)}return r};for(const t of f){if(u)break;me(t.label),s(t.key,{status:"running"});const{zips:p}=await S({action:"list",region:t.key});O+=p.length,s(t.key,{total:p.length}),$({done:H,total:O});let d=p.map(r=>r.zip);for(let r=1;r<=ie&&d.length>0&&!u;r++){if(r>1){const j=De(r-1);if(F(I=>[{region:t.label,zip:`${d.length} zip${d.length>1?"s":""}`,found:0,inserted:0,attempt:r,retrying:!0,error:`Retry ${r-1}/${ie-1} in ${Math.round(j/1e3)}s`},...I].slice(0,400)),await ke(j),R.value){u=!0;break}}d=await C(t,d,r)}s(t.key,{status:u?"stopped":"done"})}await S({action:"finish",runId:l})}catch(l){const m=l instanceof Error?l.message:String(l);B(m),i&&await S({action:"finish",runId:i,error:m}).catch(()=>{})}finally{me(null),le(!1),R.value=!1,L(!1)}},fe=x.total>0?Math.round(x.done/x.total*100):0;return e.jsxs("div",{className:"mx-auto max-w-4xl p-6 space-y-6",children:[e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{children:[e.jsxs("h1",{className:"font-display text-3xl font-bold tracking-tight inline-flex items-center gap-2",children:["Scrape pool contractors",e.jsx(Fe,{title:"Scrape contractors",description:"Runs Firecrawl across every ZIP in the chosen region, extracts pool service contractors, and upserts them into the directory.",tips:["All 50 states + DC ship with curated ZIP centroids — no setup needed.","Use Test mode (5 contractors) to validate cheaply, then run the full state.","Raise concurrency to burn Firecrawl credits faster when you have headroom."]})]}),e.jsxs("p",{className:"text-muted-foreground mt-1",children:["Pulls pool service contractors from the web via Firecrawl, one zip at a time within the selected regi
1on, and upserts them into the public directory at"," ",e.jsx("code",{className:"text-xs",children:"/pool-service-near-me"}),"."]})]}),e.jsx(X,{variant:"outline",asChild:!0,children:e.jsxs(be,{to:"/admin/scrape-regions",className:"gap-2",children:[e.jsx(Ie,{className:"h-4 w-4"}),"Manage regions"]})})]}),e.jsxs(W,{children:[e.jsx(ee,{children:e.jsx(se,{children:"Run a new scrape"})}),e.jsxs(te,{className:"space-y-4",children:[e.jsxs("div",{className:"grid sm:grid-cols-[260px_1fr_auto] gap-3 items-end",children:[e.jsxs("div",{children:[e.jsx("label",{className:"text-xs uppercase tracking-wider font-semibold text-muted-foreground mb-1.5 block",children:"Region"}),e.jsxs(ye,{value:h,onValueChange:k,disabled:y||o.length===0,children:[e.jsx(Ne,{children:e.jsx(ve,{placeholder:o.length===0?"Loading…":"Select region"})}),e.jsxs(Se,{className:"max-h-80",children:[e.jsxs(ae,{children:[e.jsx(ne,{children:"Nationwide"}),e.jsxs(E,{value:re,children:["All states, excl. Alaska (",D.length," states ·"," ",D.reduce((s,c)=>s+c.zipCount,0)," zips)"]})]}),he.length>0&&e.jsxs(ae,{children:[e.jsx(ne,{children:"Custom regions"}),he.map(s=>e.jsxs(E,{value:s.key,children:[s.label," (",s.zipCount," zips)"]},s.key))]}),e.jsxs(ae,{children:[e.jsx(ne,{children:"States"}),V.map(s=>e.jsxs(E,{value:s.key,children:[s.label," (",s.zipCount," zips)",w.includes(s.key)?" — excluded from bulk":""]},s.key))]})]})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"text-xs uppercase tracking-wider font-semibold text-muted-foreground mb-1.5 block",children:"Run label"}),e.jsx(Pe,{value:T,onChange:s=>b(s.target.value),disabled:y})]}),y?e.jsxs(X,{size:"lg",variant:"destructive",className:"gap-2",onClick:()=>{R.value=!0,L(!0)},disabled:oe,children:[e.jsx(qe,{className:"h-4 w-4"}),oe?"Stopping…":"Stop"]}):e.jsxs(X,{size:"lg",onClick:_e,disabled:f.length===0||_===0,className:"gap-2",children:[e.jsx(Be,{className:"h-4 w-4"}),Z?`Test scrape (${K} max)`:`Scrape ${_} zips`]})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-6",children:[e.jsxs("label",{className:"flex items-center gap-2 text-sm cursor-pointer select-none",children:[e.jsx($e,{checked:Z,onCheckedChange:s=>Te(s===!0),disabled:y}),e.jsxs("span",{children:[e.jsx("span",{className:"font-medium",children:"Test mode"})," ",e.jsxs("span",{className:"text-muted-foreground",children:["— stop after ",K," contractors are found."]})]})]}),e.jsxs("div",{className:"flex items-center gap-2 text-sm",children:[e.jsx("span",{className:"font-medium",children:"Concurrency"}),e.jsxs(ye,{value:String(M),onValueChange:s=>Ce(Number(s)),disabled:y,children:[e.jsx(Ne,{className:"w-24 h-8",children:e.jsx(ve,{})}),e.jsx(Se,{children:[1,2,3,4,6,8].map(s=>e.jsxs(E,{value:String(s),children:[s," zip",s>1?"s":""]},s))})]}),e.jsx("span",{className:"text-muted-foreground",children:"in parallel"})]})]}),_===0&&f.length>0&&e.jsxs("div",{className:"rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-sm flex gap-2",children:[e.jsx(we,{className:"h-4 w-4 mt-0.5 shrink-0"}),e.jsxs("div",{children:["This region has no zips yet."," ",e.jsx(be,{to:"/admin/scrape-regions",className:"underline font-medium",children:"Add zips"})," ","to get started."]})]}),x.total>0&&e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between text-sm",children:[e.jsxs("span",{className:"font-medium",children:[x.done," / ",x.total," zips · ",N.found," found · ",N.inserted," upserted",N.retried>0?` · ${N.retried} retried`:"",N.failed>0?` · ${N.failed} failed`:"",ue?` · ${ue}`:""]}),e.jsxs("span",{className:"text-muted-foreground",children:[fe,"%"]})]}),e.jsx(Me,{value:fe})]}),q&&e.jsxs("div",{className:"rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive flex gap-2",children:[e.jsx(we,{className:"h-4 w-4 mt-0.5 shrink-0"}),e.jsx("div",{children:q})]}),Re&&!y&&!q&&x.done===x.total&&x.total>0&&e.jsxs("div",{className:"rounded-lg border border-primary/30 bg-primary/5 p-3 text-sm flex gap-2",children:[e.jsx(Ze,{className:"h-4 w-4 mt-0.5 shrink-0 text-primary"}),e.jsxs("div",{children:["Done. View results on"," ",e.jsx("a",{href:"/pool-service-near-me",className:"underline font-medium",children:"/pool-service-near-me"}),"."]})]})]})]}),ce.length>0&&e.jsxs(W,{children:[e.jsx(ee,{children:e.jsx(se,{children:"Per-state rollup"})}),e.jsx(te,{children:e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"text-left text-xs uppercase tracking-wider text-muted-foreground",children:[e.jsx("th",{className:"py-2 pr-3 font-semibold",children:"State"}),e.jsx("th",{className:"py-2 pr-3 font-semibold",children:"Zips"}),e.jsx("th",{className:"py-2 pr-3 font-semibold",children:"Found"}),e.jsx("th",{className:"py-2 pr-3 font-semibold",children:"Saved"}),e.jsx("th",{className:"py-2 pr-3 font-semibold",children:"Retried"}),e.jsx("th",{className:"py-2 pr-3 font-semibold",children:"Failed"}),e.jsx("th",{className:"py-2 font-semibold",children:"Status"})]})}),e.jsx("tbody",{className:"divide-y divide-border",children:ce.map(s=>e.jsxs("tr",{children:[e.jsx("td",{className:"py-2 pr-3 font-medium",children:s.label}),e.jsxs("td",{className:"py-2 pr-3 tabular-nums text-muted-foreground",children:[s.done,"/",s.total]}),e.jsx("td",{className:"py-2 pr-3 tabular-nums",children:s.found}),e.jsx("td",{className:"py-2 pr-3 tabular-nums font-medium",children:s.inserted}),e.jsx("td",{className:"py-2 pr-3 tabular-nums text-muted-foreground",children:s.retried}),e.jsx("td",{className:"py-2 pr-3 tabular-nums",children:s.failed>0?e.jsx("span",{className:"text-destructive font-medium",children:s.failed}):"0"}),e.jsx("td",{className:"py-2",children:e.jsxs(Y,{variant:s.status==="done"?"default":s.status==="stopped"?"destructive":"outline",children:[s.status==="running"&&e.jsx(Ke,{className:"h-3 w-3 mr-1 animate-spin"}),s.status]})})]},s.key))})]})})})]}),pe.length>0&&e.jsxs(W,{children:[e.jsx(ee,{children:e.jsx(se,{children:"Per-zip results"})}),e.jsx(te,{children:e.jsx("ul",{className:"divide-y divide-border text-sm",children:pe.map((s,c)=>e.jsxs("li",{className:"py-3 space-y-2",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(Ee,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),e.jsx("span",{className:"font-mono font-semibold w-16",children:s.zip}),e.jsx("span",{className:"text-xs text-muted-foreground w-32 truncate",children:s.region}),s.error?e.jsx(Y,{variant:"destructive",children:s.error.slice(0,80)}):e.jsxs("span",{className:"text-muted-foreground",children:[s.found," found · ",e.jsxs("span",{className:"text-foreground font-medium",children:[s.inserted," saved"]})]})]}),s.contractors&&s.contractors.length>0&&e.jsx("ul",{className:"pl-7 space-y-1.5",children:s.contractors.map((i,u)=>e.jsxs("li",{className:"flex items-start gap-2",children:[e.jsx(Y,{variant:i.inserted?"default":"outline",className:"shrink-0 mt-0.5",children:i.inserted?"saved":"skipped"}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"font-medium truncate",children:i.name}),e.jsxs("div",{className:"text-xs text-muted-foreground flex flex-wrap gap-x-3",children:[e.jsx("a",{href:i.website,target:"_blank",rel:"noreferrer",className:"underline truncate",children:i.website.replace(/^https?:\/\//,"")}),i.phone&&e.jsx("span",{children:i.phone})]}),i.blurb&&e.jsx("div",{className:"text-xs text-muted-foreground mt-0.5 line-clamp-2",children:i.blurb})]})]},`${s.zip}-${u}`))})]},`${s.zip}-${c}`))})})]})]})}const R={value:!1};async function S(o){const{supabase:g}=await Le(async()=>{const{supabase:T}=await import("./index-CzAkNt2W.js").then(b=>b.ds);return{supabase:T}},[]),{data:{session:w}}=await g.auth.getSession(),z=w?.access_token;if(!z)throw new Error("You must be signed in as a SplashIQ superadmin to run this.");const h=await fetch("/api/public/admin/contractor-scrape",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${z}`},body:JSON.stringify(o)}),k=await h.json();if(!h.ok)throw new Error(k.error||"Scrape request failed");return k}const rs=()=>e.jsx(Ae,{allowed:["superadmin"],children:e.jsx(He,{})});
1export{rs as component};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.