1import{j as e,a as Ke,u as V}from"./vendor-query-nkyHp8gL.js";import{r as o}from"./vendor-react-BNT0nzvx.js";import{W as nt,af as D,i as Ae,h as it,s as m,B as f,n as _,Y as ot,o as Pe}from"./index-UlqsakHc.js";import{D as lt}from"./DashboardLayout-BwqfxABs.js";import{I as X}from"./input-O_nO28FK.js";import{C as Z}from"./card-CUo4-uIn.js";import{T as ct}from"./textarea-DFMIf01q.js";import{B as M}from"./badge-BgKH0HTc.js";import{P as dt}from"./progress-Ddh1eiEh.js";import{C as oe}from"./checkbox-D_YIIhgX.js";import{A as le,h as ce,a as de,b as me,c as ue,d as pe,e as xe,f as fe,g as he}from"./alert-dialog-Cot7QkN9.js";import{D as mt,a as ut,b as pt,c as xt,d as ft}from"./dialog-DV4xISIc.js";import{d as ht,f as gt}from"./banned-domains-B4X8fXeo.js";import{T as yt}from"./target-nBqdJqET.js";import{G as ze}from"./globe-D5yIUDNO.js";import{C as Fe}from"./circle-x-D07oMaC7.js";import{M as wt}from"./mail-bMhm6fVu.js";import{P as bt}from"./phone-CROm9Gxj.js";import{S as ge}from"./send-DDsiLTn-.js";import{E as jt}from"./eye-off-BjQ6hvXq.js";import{T as Ie}from"./trash-2-omFn8pi_.js";import{C as vt}from"./chevron-left-HX6_oF1L.js";import{C as Nt}from"./chevron-right-Cm0QdkHJ.js";import{E as St}from"./eye-Ca0Gb1H3.js";import{B as kt}from"./ban-CppMiYEX.js";import{C as Ct}from"./circle-check-Cghreag5.js";import"./vendor-ui-DPPUfW-O.js";import"./vendor-charts-B_MCcq71.js";import"./vendor-supabase-BRDBMtg7.js";import"./input-otp-zCOwu37W.js";import"./shield-check-D_dnqQxv.js";import"./shield-DMA6z9Kg.js";import"./mail-check-B9b1HxOk.js";import"./users-C1fVyj3Z.js";import"./newspaper-DDM9CgLF.js";import"./crown-DVNd9quJ.js";import"./list-CEIXZF2y.js";import"./flag-CwDWn1bT.js";import"./index-DcdyMenS.js";import"./check-7w7r-eFz.js";/** 2 * @license lucide-react v0.462.0 - ISC 3 * 4 * This source code is licensed under the ISC license. 5 * See the LICENSE file in the root directory of this source tree. 6 */const _t=nt("RotateCcw",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]]),we=o.forwardRef(({className:r,...l},n)=>e.jsx("div",{className:"relative w-full overflow-auto",children:e.jsx("table",{ref:n,className:D("w-full caption-bottom text-sm",r),...l})}));we.displayName="Table";const be=o.forwardRef(({className:r,...l},n)=>e.jsx("thead",{ref:n,className:D("[&_tr]:border-b",r),...l}));be.displayName="TableHeader";const je=o.forwardRef(({className:r,...l},n)=>e.jsx("tbody",{ref:n,className:D("[&_tr:last-child]:border-0",r),...l}));je.displayName="TableBody";const $t=o.forwardRef(({className:r,...l},n)=>e.jsx("tfoot",{ref:n,className:D("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",r),...l}));$t.displayName="TableFooter";const U=o.forwardRef(({className:r,...l},n)=>e.jsx("tr",{ref:n,className:D("border-b transition-colors data-[state=selected]:bg-muted hover:bg-muted/50",r),...l}));U.displayName="TableRow";const w=o.forwardRef(({className:r,...l},n)=>e.jsx("th",{ref:n,className:D("h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",r),...l}));w.displayName="TableHead";const b=o.forwardRef(({className:r,...l},n)=>e.jsx("td",{ref:n,className:D("p-4 align-middle [&:has([role=checkbox])]:pr-0",r),...l}));b.displayName="TableCell";const qt=o.forwardRef(({className:r,...l},n)=>e.jsx("caption",{ref:n,className:D("mt-4 text-sm text-muted-foreground",r),...l}));qt.displayName="TableCaption";const A=100,Rt=[{value:"all",label:"All Status"},{value:"new",label:"New"},{value:"contacted",label:"Emailed"},{value:"opened",label:"Opened"},{value:"ignored",label:"Excluded"},{value:"converted",label:"Has Account"}],Dt=r=>{if(!r)return"â";const l=r.replace(/\D/g,"");return l.length===10?`(${l.slice(0,3)}) ${l.slice(3,6)}-${l.slice(6)}`:l.length===11&&l.startsWith("1")?`(${l.slice(1,4)}) ${l.slice(4,7)}-${l.slice(7)}`:r},Ee=r=>{switch(r){case"converted":return"bg-green-600 text-white border-green-600";case"contacted":return"bg-blue-600 text-white border-blue-600";case"opened":return"bg-yellow-500 text-white border-yellow-500";case"ignored":return"bg-muted text-muted-foreground";case"new":return"";default:return""}},ye=r=>{const l=r?.context?.status??r?.status;if(l===401||l===403)return!0;const n=`${r?.name??""} ${r?.message??""}`.toLowerCase();return n.includes("unauthorized")||n.includes("forbidden")||n.includes("admin access required")},bs=()=>{const{toast:r}=Ae(),{user:l}=it(),n=Ke(),[N,z]=o.useState(""),[S,v]=o.useState(!1),[p,x]=o.use
6State(new Set),[h,c]=o.useState(!1),[g,T]=o.useState(!1),[ve,Ne]=o.useState(!1);o.useState(!1);const[Se,ke]=o.useState(!1),Ce=o.useRef(!1),[k,ee]=o.useState("all"),[$,Qe]=o.useState(""),[q,te]=o.useState(0),[_e,Be]=o.useState(!1);o.useEffect(()=>{te(0),x(new Set),c(!1)},[k,$]);const[I,$e]=o.useState(null),[j,se]=o.useState(null),L=o.useRef(null),W=o.useRef(!1),H=o.useRef(0),qe=o.useRef(new Set),ae=o.useRef(new Set),J=o.useRef(!1),[G,Oe]=o.useState("Quick question about your advertising"),[F,Me]=o.useState(`Hi there, 7 8My name is **Julia**, I'm the **Marketing Director** here at RubPage. I reached out recently and wanted to follow up. 9 10We're a growing platform with **lower fees** than most competitors â no monthly subscriptions, no contracts, and your **first listing activation is just $20 for 30 days**. 11 12Our bump-to-top costs just **$4** vs $5+ on other platforms. No hidden fees, no commitments â just give it a try and see if it works for you.`),Ue=o.useMemo(()=>`<!DOCTYPE html> 13<html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/></head> 14<body style="margin:0;padding:0;background:#f5f6f8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;"> 15<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f5f6f8;"> 16<tr><td align="center" style="padding:24px 16px;"> 17<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 4px 24px rgba(0,0,0,0.08);"> 18<tr><td style="background:linear-gradient(135deg,#1d2131 0%,#2e3347 100%);padding:24px 40px;text-align:center;"> 19 <h1 style="margin:0;font-family:Georgia,'Times New Roman',serif;font-style:italic;font-weight:600;font-size:28px;color:#ffffff;letter-spacing:-0.5px;">RubPage</h1> 20</td></tr> 21<tr><td style="padding:36px 40px 24px;"> 22 <div style="font-size:15px;color:#333;line-height:1.8;">${F.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/\n/g,"<br/>").replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>")}</div> 23</td></tr> 24<tr><td style="padding:0 40px 24px;"> 25 <p style="margin:0 0 16px;font-size:15px;color:#333;line-height:1.8;">Here's a quick look at how we compare â I think you'll see why providers are making the switch:</p> 26 <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e2e4e9;border-radius:8px;overflow:hidden;"> 27 <tr style="background:#f5f6f8;"> 28 <td style="padding:12px 16px;font-size:12px;font-weight:700;color:#888;text-transform:uppercase;letter-spacing:1px;border-bottom:1px solid #e2e4e9;"> </td> 29 <td style="padding:12px 16px;font-size:12px;font-weight:700;color:#888;text-transform:uppercase;letter-spacing:1px;border-bottom:1px solid #e2e4e9;text-align:center;">Other Platforms</td> 30 <td style="padding:12px 16px;font-size:12px;font-weight:700;color:#f0a818;text-transform:uppercase;letter-spacing:1px;border-bottom:1px solid #e2e4e9;text-align:center;">RubPage</td> 31 </tr> 32 <tr> 33 <td style="padding:12px 16px;font-size:14px;color:#333;border-bottom:1px solid #f0f0f0;">Standard Listing</td> 34 <td style="padding:12px 16px;font-size:14px;color:#999;text-align:center;border-bottom:1px solid #f0f0f0;text-decoration:line-through;">$20/mo</td> 35 <td style="padding:12px 16px;font-size:14px;color:#1d2131;font-weight:700;text-align:center;border-bottom:1px solid #f0f0f0;">$20 / 30 days</td> 36 </tr> 37 <tr> 38 <td style="padding:12px 16px;font-size:14px;color:#333;border-bottom:1px solid #f0f0f0;">Featured Placement</td> 39 <td style="padding:12px 16px;font-size:14px;color:#999;text-align:center;border-bottom:1px solid #f0f0f0;text-decoration:line-through;">$60/mo</td> 40 <td style="padding:12px 16px;font-size:14px;color:#1d2131;font-weight:700;text-align:center;border-bottom:1px solid #f0f0f0;">From $5</td> 41 </tr> 42 <tr> 43 <td style="padding:12px 16px;font-size:14px;color:#333;
43border-bottom:1px solid #f0f0f0;">Weekly Highlights</td> 44 <td style="padding:12px 16px;font-size:14px;color:#999;text-align:center;border-bottom:1px solid #f0f0f0;text-decoration:line-through;">$7/wk ($28/mo)</td> 45 <td style="padding:12px 16px;font-size:14px;color:#1d2131;font-weight:700;text-align:center;border-bottom:1px solid #f0f0f0;">$4 one-time bump</td> 46 </tr> 47 <tr style="background:#f5f6f8;"> 48 <td style="padding:14px 16px;font-size:14px;color:#1d2131;font-weight:700;">Total Monthly</td> 49 <td style="padding:14px 16px;font-size:16px;color:#cc4444;font-weight:700;text-align:center;">$98+</td> 50 <td style="padding:14px 16px;font-size:16px;color:#2e8b57;font-weight:700;text-align:center;">$14</td> 51 </tr> 52 </table> 53 <div style="text-align:center;margin-top:12px;"> 54 <span style="display:inline-block;background:#f0a818;color:#1d2131;font-size:12px;font-weight:800;padding:6px 16px;border-radius:20px;letter-spacing:0.5px;">SAVE 85%</span> 55 </div> 56</td></tr> 57<tr><td style="padding:0 40px 32px;text-align:center;"> 58 <a href="https://rubpage.com/register?role=advertiser&ref=outreach" style="display:inline-block;padding:16px 48px;background:#f0a818;color:#1d2131;text-decoration:none;border-radius:10px;font-size:16px;font-weight:800;letter-spacing:0.3px;box-shadow:0 4px 16px rgba(240,168,24,0.3);"> 59 Create Your Free Listing â 60 </a> 61</td></tr> 62<tr><td style="padding:0 40px 36px;"> 63 <div style="font-size:15px;color:#333;line-height:1.8;"> 64 <p style="margin:0 0 20px;">Looking forward to welcoming you,</p> 65 <p style="margin:0;"><strong>Julia</strong><br/> 66 <span style="color:#888;font-size:14px;">Marketing Director, RubPage</span></p> 67 </div> 68</td></tr> 69<tr><td style="background:#f5f6f8;padding:24px 40px;text-align:center;border-top:1px solid #e2e4e9;"> 70 <p style="margin:0;font-family:Georgia,'Times New Roman',serif;font-style:italic;font-size:18px;color:#1d2131;font-weight:600;">RubPage</p> 71 <p style="margin:8px 0 0;font-size:12px;color:#999;line-height:1.5;"> 72 <a href="https://rubpage.com" style="color:#999;">rubpage.com</a> 73 </p> 74 <p style="margin:12px 0 0;font-size:11px;color:#bbb;"> 75 If you don't want to hear from us, simply ignore this message. 76 </p> 77</td></tr> 78</table> 79</td></tr></table> 80</body></html>`,[F]),{data:E,isLoading:Lt}=V({queryKey:["competitor-scrape-jobs"],queryFn:async()=>{const{data:t,error:s}=await m.from("competitor_scrape_jobs").select("*").in("status",["running","completed","pending","cancelled"]).order("created_at",{ascending:!1}).limit(10);if(s)throw s;return t},refetchInterval:6e4,refetchIntervalInBackground:!1}),{data:Re,isLoading:We}=V({queryKey:["competitor-leads",k,$,q],queryFn:async()=>{let t=m.from("competitor_leads").select("*",{count:"exact"}).not("email","like","phone:%").order("created_at",{ascending:!1}).range(q*A,(q+1)*A-1);k!=="all"&&(t=t.eq("status",k)),$&&(t=t.or(`email.ilike.%${$}%,source_domain.ilike.%${$}%`));const{data:s,error:a,count:i}=await t;if(a)throw a;return{data:s,count:i}},refetchInterval:6e4,refetchIntervalInBackground:!1}),R=Re?.data??[],C=Re?.count??0,re=Math.ceil(C/A),{data:De}=V({queryKey:["competitor-leads-counts"],queryFn:async()=>{const t=["new","contacted","opened","ignored","converted"],s=d=>d.not("email","like","phone:%"),[a,...i]=await Promise.all([s(m.from("competitor_leads").select("*",{count:"exact",head:!0})),...t.map(d=>s(m.from("competitor_leads").select("*",{count:"exact",head:!0})).eq("status",d))]),u={};return t.forEach((d,y)=>{u[d]=i[y].count??0}),{total:a.count??0,counts:u}},refetchInterval:6e4,refetchIntervalInBackground:!1}),K=De?.total||0,Q=De?.counts||{},P=o.useCallback(()=>{L.current&&(clearInterval(L.current),L.current=null),W.current=!1,H.current=0,$e(null),se(null),v(!1)},[]),Y=o.useCallback(t=>{$e(t),se({pages:0,total:0,emails:0,newLeads:0,failed:0}),L.current=setInterval(async()=>{if(!W.current){W.current=!0;try{const{data:s,error:a}=await m.functions.invoke("scrape-competitors",{body:{mode:"check",jobId:t}});if(a)throw a;if(H.current=0,s.status==="completed"){if(qe.current.has(t))return;qe.current.add(t),P();const i=[];s.pages_crawled>0&&i.push(`${s.pages_crawled} pages scraped`),s.total_pages>0&&i.push(`of ${s.total_pages} total`),s.emails_found>0&&i.push(`${s.emails_found} contacts`),i.push(`${s.new_leads||0} new leads`),s.duplicates>0&&i.push(`${s.duplicates} duplicates`),r({title:"Scrape complete",description:i.join(", ")}),n.invalidateQueries({queryKey:["competitor-scrape-jobs"]}),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]})}else s.status==="failed"||s.status==="cancelled"?(P(),ae.current.has(t)||(ae.current.add(t),r({title:s.status==="cancelled"?"Scrape paused":"Crawl failed",description:s.error||"Unknown error",variant:s.status==="cancelled"?"default":"destructive"})),n.invalidateQueries({queryKey:["competitor-scrape-jobs"]})):se({pages:s.pages_crawled||0,total:s.total_pages||0,emails:s.emails_found||0,newLeads:s.new_leads||0,failed:s.pages_failed||0})}catch(s){console.error("Poll error:",s),H.current+=1;const a=ye(s),i=s?.message?.includes("non-2xx")||s?.context?.status===404,u=H.current>=3;(a||u||i)&&(P(),i?n.invalidateQueries({queryKey:["competitor-scrape-jobs"]}):(r({title:a?"Session expired":"Scrape connection lost",description:a?"Please log in again, then continue the scrape.":"Polling failed repeatedly. Please click Continue to resume.",variant:"destructive"}),n.invalidateQueries({queryKey:["competitor-scrape-jobs"]})))}finally{W.current=!1}}},3e3)},[P,r,n]);o.useEffect(()=>{if(I||L.current||J.current)return;const t=E?.filter(a=>a.status==="running")||[];for(const a of t)if(!a.crawl_id&&a.scrape_mode!=="map"&&Date.now()-new Date(a.created_at).getTime()>
805*60*1e3){m.functions.invoke("scrape-competitors",{body:{mode:"cancel",domain:a.domain}}).then(()=>{n.invalidateQueries({queryKey:["competitor-scrape-jobs"]})});return}const s=t.find(a=>a.crawl_id||a.scrape_mode==="map");s&&(v(!0),Y(s.id))},[E,I,Y,n]),o.useEffect(()=>()=>{L.current&&clearInterval(L.current)},[]),o.useEffect(()=>{Ce.current||!K||K===0||(Ce.current=!0,m.functions.invoke("scrape-competitors",{body:{mode:"check-converted"}}).then(()=>{n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]})}).catch(()=>{}))},[K,n]);const He=async()=>{if(N.trim()){J.current=!1,v(!0);try{_e&&await m.functions.invoke("scrape-competitors",{body:{url:N.trim(),mode:"clear-history"}});const{data:t,error:s}=await m.functions.invoke("scrape-competitors",{body:{url:N.trim(),mode:"map"}});if(s)throw s;if(t?.error)throw new Error(t.error);if(t.status==="started"){z(""),Y(t.jobId);const a=[`Discovered ${t.total_urls?.toLocaleString()||"?"} URLs`];t.already_scraped>0&&a.push(`${t.already_scraped.toLocaleString()} already scraped`),t.new_urls!==void 0&&a.push(`${t.new_urls.toLocaleString()} new to scrape`),r({title:"Scrape started",description:a.join(" · ")}),n.invalidateQueries({queryKey:["competitor-scrape-jobs"]});return}z(""),n.invalidateQueries({queryKey:["competitor-scrape-jobs"]}),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]})}catch(t){if(ye(t))P(),r({title:"Session expired",description:"Please log in again, then retry the scrape.",variant:"destructive"});else if(t.message?.includes("409")||t.message?.toLowerCase().includes("already running")||t.message?.toLowerCase().includes("already has a running"))try{let s=N.trim();(s.startsWith("http://")||s.startsWith("https://"))&&(s=new URL(s).hostname),await m.functions.invoke("scrape-competitors",{body:{mode:"cancel",domain:s}}),r({title:"Cleared stuck job",description:"Previous job cancelled. Try scraping again."}),n.invalidateQueries({queryKey:["competitor-scrape-jobs"]})}catch{r({title:"Scrape failed",description:"A job is already running. Try stopping it first.",variant:"destructive"})}else r({title:"Scrape failed",description:t.message,variant:"destructive"})}finally{I||v(!1)}}},Je=async()=>{J.current=!0,I&&ae.current.add(I),P();let t=N.trim();if(!t){const s=E?.find(a=>a.status==="running");s&&(t=s.domain)}if(t)try{(t.startsWith("http://")||t.startsWith("https://"))&&(t=new URL(t).hostname);const{data:s}=await m.functions.invoke("scrape-competitors",{body:{mode:"cancel",domain:t}});r({title:"Paused",description:s?.jobs_cancelled?`Paused ${s.jobs_cancelled} job(s). You can continue them later.`:"No running jobs found for this domain."})}catch{r({title:"Paused",description:"Scrape paused."})}else r({title:"Cancelled",description:"Scrape/search stopped."});n.invalidateQueries({queryKey:["competitor-scrape-jobs"]})},Ge=async t=>{J.current=!1,v(!0);try{const{data:s,error:a}=await m.functions.invoke("scrape-competitors",{body:{mode:"continue",jobId:t}});if(a)throw a;if(s?.error)throw new Error(s.error);if(s.status==="started"){Y(s.jobId);const i=[`Total ${s.total_urls?.toLocaleString()||"?"} URLs`];s.already_scraped>0&&i.push(`${s.already_scraped.toLocaleString()} already done`),s.new_urls>0&&i.push(`${s.new_urls.toLocaleString()} remaining`),r({title:"Scrape resumed",description:i.join(" · ")}),n.invalidateQueries({queryKey:["competitor-scrape-jobs"]})}}catch(s){const a=ye(s);r({title:a?"Session expired":"Continue failed",description:a?"Please log in again, then continue the scrape.":s.message,variant:"destructive"}),a&&P(),v(!1)}},ne=async()=>{const t=[];let s=0;for(;;){let a=m.from("competitor_leads").select("id").not("email","like","phone:%").neq("status","converted").range(s,s+999);k!=="all"&&(a=a.eq("status",k)),$&&(a=a.or(`email.ilike.%${$}%,source_domain.ilike.%${$}%`));const{data:i}=await a;if(!i||i.length===0)break;for(const u of i)t.push(u.id);if(i.length<1e3)break;s+=1e3}return t},Ye=async t=>{const s=h?await ne():t;for(let a=0;a<s.length;a+=500){const i=s.slice(a,a+500);await m.from("competitor_leads").delete().in("id",i)}r({title:`Deleted ${s.length} lead(s)`}),x(new Set),c(!1),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]})},Ve=async()=>{ke(!0);try{const{data:t,error:s}
80=await m.functions.invoke("scrape-competitors",{body:{mode:"backfill-phones"}});if(s)throw s;r({title:"Backfill complete",description:`Found phones for ${t.phones_matched} lead(s) across ${t.pages_scraped} pages.`}),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]})}catch(t){r({title:"Backfill failed",description:t.message,variant:"destructive"})}finally{ke(!1)}},Xe=async()=>{Ne(!0);try{const{error:t}=await m.functions.invoke("send-email",{body:{action:"test-outreach",to:l?.email??"",subject:G,body:F}});if(t)throw t;const s=[];let a=0;for(;;){const{data:i}=await m.from("competitor_leads").select("id").eq("status","contacted").is("opened_at",null).not("email","like","phone:%").range(a,a+999);if(!i||i.length===0)break;for(const u of i)s.push(u.id);if(i.length<1e3)break;a+=1e3}for(let i=0;i<s.length;i+=500){const u=s.slice(i,i+500);await m.from("competitor_leads").update({status:"new",emailed_at:null}).in("id",u)}r({title:"Follow-up ready",description:`Test email sent to admin. ${s.length} non-opener leads reset to "new" â use "Send Outreach" to email them.`}),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]})}catch(t){r({title:"Follow-up failed",description:t.message,variant:"destructive"})}finally{Ne(!1)}},Ze=async()=>{const t=h?await ne():[...p];for(let s=0;s<t.length;s+=500){const a=t.slice(s,s+500);await m.from("competitor_leads").update({status:"ignored"}).in("id",a)}r({title:`Excluded ${t.length} lead(s)`}),x(new Set),c(!1),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]})},Te=async()=>{let t;if(h){const i=await ne(),u=[];for(let d=0;d<i.length;d+=500){const y=i.slice(d,d+500),{data:Le}=await m.from("competitor_leads").select("*").in("id",y);Le&&u.push(...Le)}t=u.filter(d=>d.status!=="converted"&&!d.email.startsWith("phone:"))}else{const i=R.filter(y=>p.has(y.id)&&!y.email.startsWith("phone:")),u=i.filter(y=>y.status!=="converted"),d=i.length-u.length;d>0&&r({title:`Skipped ${d} lead(s) â already registered users`}),t=u}if(!t.length){r({title:"No valid leads to email",variant:"destructive"});return}T(!0);let s=0,a=0;for(const i of t){try{const{error:u}=await m.functions.invoke("send-email",{body:{action:"outreach-email",to:i.email,subject:G,body:F,leadId:i.id}});if(u)throw u;await m.from("competitor_leads").update({status:"contacted",emailed_at:new Date().toISOString()}).eq("id",i.id),s++}catch{a++}t.indexOf(i)<t.length-1&&await new Promise(u=>setTimeout(u,3500))}r({title:"Outreach complete",description:`Sent: ${s}, Failed: ${a}`}),x(new Set),c(!1),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]}),T(!1)},et=async()=>{if(h)return Te();const t=R.filter(d=>d.email.startsWith("phone:")||d.status==="converted"?!1:p.size>0?p.has(d.id):d.status==="new"),s=p.size>0?R.filter(d=>p.has(d.id)&&d.status==="converted").length:0;s>0&&r({title:`Skipped ${s} lead(s) â already registered users`});const a=t;if(!a?.length){r({title:"No leads to email",variant:"destructive"});return}T(!0);let i=0,u=0;for(const d of a){try{const{error:y}=await m.functions.invoke("send-email",{body:{action:"outreach-email",to:d.email,subject:G,body:F,leadId:d.id}});if(y)throw y;await m.from("competitor_leads").update({status:"contacted",emailed_at:new Date().toISOString()}).eq("id",d.id),i++}catch{u++}a.indexOf(d)<a.length-1&&await new Promise(y=>setTimeout(y,3500))}r({title:"Outreach complete",description:`Sent: ${i}, Failed: ${u}`}),x(new Set),n.invalidateQueries({queryKey:["competitor-leads"]}),n.invalidateQueries({queryKey:["competitor-leads-counts"]}),T(!1)},B=R.filter(t=>t.status!=="converted"),ie=B.length>0&&B.every(t=>p.has(t.id)),tt=()=>{if(!R)return;const t=B.map(s=>s.id);ie?(x(new Set),c(!1)):(x(new Set(t)),c(!1))},st=t=>{const s=new Set(p);s.has(t)?s.delete(t):s.add(t),x(s),c(!1)},at=t=>{switch(t){case"completed":return e.jsx(Ct,{className:"w-4 h-4 text-green-500"});case"failed":return e.jsx(Fe,{className:"w-4 h-4 text-destructive"});case"cancelled":return e.jsx(Pe,{className:"w-4 h-4 text-yellow-500"});case"running":return e.jsx(_,{className:"w-4 h-4 animate-spin text-primary"});default:return e.jsx(Pe,{className:"w-4 h-4 text-muted-foreground"})}},rt=C-(Q.converted||0),O=h?rt:p.size;return e.jsxs(lt,{children:[e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{children:[e.jsxs("h1",{className:"text-2xl font-bold text-foreground flex items-center gap-2",children:[e.jsx(yt,{className:"w-6 h-6"}),"Competition"]}),e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:"Scrape competitor websites for emails and send outreach campaigns."})]}),e.jsxs(Z,{className:"p-6",children:[e.jsxs("h2",{className:"text-lg font-semibold text-foreground mb-4 flex items-center gap-2",children:[e.jsx(ze,{className:"w-5 h-5"}),"Scrape Competitor Website"]}),e.jsx("p",{className:"text-sm text-muted-foreground mb-4",children:"Enter a competitor URL to find all contact emails and phone numbers."}),e.jsxs("div",{className:"flex gap-3",children:[e.jsx(X,{value:N,onChange:t=>
80z(t.target.value),placeholder:"https://competitor-site.com",className:"flex-1",disabled:S}),e.jsx(f,{onClick:He,disabled:S||!N.trim(),children:S?e.jsxs(e.Fragment,{children:[e.jsx(_,{className:"w-4 h-4 animate-spin"}),"Scraping..."]}):e.jsxs(e.Fragment,{children:[e.jsx(ze,{className:"w-4 h-4"}),"Scrape"]})}),(S||E?.some(t=>t.status==="running"))&&e.jsxs(f,{variant:"destructive",onClick:Je,children:[e.jsx(Fe,{className:"w-4 h-4"}),"Stop"]})]}),e.jsxs("div",{className:"flex items-center gap-2 mt-3",children:[e.jsx(oe,{id:"fresh-scrape",checked:_e,onCheckedChange:t=>Be(!!t),disabled:S}),e.jsx("label",{htmlFor:"fresh-scrape",className:"text-sm text-muted-foreground cursor-pointer select-none",children:"Fresh scrape (clear URL history â re-visit all pages)"})]}),S&&j&&e.jsxs("div",{className:"mt-4 space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between text-sm text-muted-foreground",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[e.jsx(_,{className:"w-3 h-3 animate-spin"}),"Scraping pages..."]}),e.jsxs("span",{children:[j.pages,j.total>0?` / ${j.total}`:""," pages",j.failed>0?` (${j.failed} failed)`:"",j.emails>0?` · ${j.emails} emails found`:""," · ",j.newLeads," new leads"]})]}),e.jsx(dt,{value:j.total>0?j.pages/j.total*100:0,className:"h-2"})]}),(E?.length??0)>0&&e.jsxs("div",{className:"mt-4",children:[e.jsx("h3",{className:"text-sm font-medium text-foreground mb-2",children:"Recent Jobs"}),e.jsxs(we,{children:[e.jsx(be,{children:e.jsxs(U,{children:[e.jsx(w,{children:"Domain"}),e.jsx(w,{children:"Status"}),e.jsx(w,{children:"Pages"}),e.jsx(w,{children:"Contacts"}),e.jsx(w,{children:"Date"}),e.jsx(w,{})]})}),e.jsx(je,{children:E?.map(t=>{const s=t.total_urls_discovered||0,a=(t.status==="cancelled"||t.status==="completed")&&!S;return e.jsxs(U,{children:[e.jsx(b,{className:"font-mono text-xs",children:t.domain}),e.jsx(b,{children:e.jsxs("div",{className:"flex items-center gap-1.5",children:[at(t.status),e.jsx("span",{className:"text-xs capitalize",children:t.status==="cancelled"?"Paused":t.status})]})}),e.jsxs(b,{children:[e.jsx("span",{children:t.pages_crawled||0}),s>0&&e.jsxs("span",{className:"text-[10px] text-muted-foreground ml-1",children:["/ ",s.toLocaleString()]})]}),e.jsxs(b,{children:[e.jsxs("span",{children:[(t.new_leads_count??0)+(t.duplicates_count??0)," found"]}),e.jsxs("span",{className:"text-[10px] text-green-600 ml-1",children:["(",t.new_leads_count??0," new)"]}),(t.duplicates_count??0)>0&&e.jsxs("span",{className:"text-[10px] text-muted-foreground ml-1",children:["(",t.duplicates_count," dupes)"]})]}),e.jsx(b,{className:"text-xs text-muted-foreground",children:new Date(t.created_at).toLocaleDateString()}),e.jsx(b,{children:a&&e.jsx(f,{variant:"outline",size:"sm",className:"h-7 text-xs",onClick:()=>Ge(t.id),children:"Continue"})})]},t.id)})})]})]})]}),e.jsxs(Z,{className:"p-6",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2 mb-4",children:[e.jsxs("h2",{className:"text-lg font-semibold text-foreground flex items-center gap-2 mr-auto",children:[e.jsx(wt,{className:"w-5 h-5"}),"Collected Leads",e.jsx(M,{variant:"secondary",className:"ml-2",children:C})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(f,{variant:"outline",size:"sm",onClick:Ve,disabled:Se,children:Se?e.jsxs(e.Fragment,{children:[e.jsx(_,{className:"w-4 h-4 animate-spin"})," Backfilling..."]}):e.jsxs(e.Fragment,{children:[e.jsx(bt,{className:"w-4 h-4"})," Backfill Phones"]})}),e.jsxs(le,{children:[e.jsx(ce,{asChild:!0,children:e.jsx(f,{variant:"outline",size:"sm",disabled:ve||!(Q.contacted>0),children:ve?e.jsxs(e.Fragment,{children:[e.jsx(_,{className:"w-4 h-4 animate-spin"})," Preparing..."]}):e.jsxs(e.Fragment,{children:[e.jsx(_t,{className:"w-4 h-4"})," Follow-up Non-Openers (",Q.contacted||0,")"]})})}),e.jsxs(de,{children:[e.jsxs(me,{children:[e.jsx(ue,{children:"Follow up with non-openers?"}),e.jsxs(pe,{children:["This will: 1) Send a test email to admin for approval, 2) Reset ",Q.contacted||0,` contacted (non-opened) leads back to "new" so you can re-send with the updated email copy. You'll then use "Send Outreach" to actually send.`]})]}),e.jsxs(xe,{children:[e.jsx(fe,{children:"Cancel"}),e.jsx(he,{onClick:Xe,children:"Send Test & Reset Leads"})]})]})]}),O>0&&e.jsxs(e.Fragment,{children:[e.jsx(f,{variant:"default",size:"sm",onClick:Te,disabled:g,children:g?e.jsxs(e.Fragment,{children:[e.jsx(_,{className:"w-4 h-4 animate-spin"})," Sending..."]}):e.jsxs(e.Fragment,{children:[e.jsx(ge,{className:"w-4 h-4"})," Email (",O,")"]})}),e.jsxs(f,{variant:"outline",size:"sm",onClick:Ze,children:[e.jsx(jt,{className:"w-4 h-4"}),"Exclude (",O,")"]}),e.jsxs(le,{children:[e.jsx(ce,{asChild:!0,children:e.jsxs(f,{variant:"destructive",size:"sm",children:[e.jsx(Ie,{className:"w-4 h-4"}),"Delete (",O,")"]})}),e.jsxs(de,{children:[e.jsxs(me,{children:[e.jsx(ue,{children:"Delete selected leads?"}),e.jsxs(pe,{children:["This will permanently remove ",O," lead(s)."]})]}),e.jsxs(xe,{children:[e.jsx(fe,{children:"Cancel"}),e.jsx(he,{onClick:()=>
80Ye([...p]),children:"Delete"})]})]})]})]})]})]}),K>0&&e.jsxs("div",{className:"flex flex-wrap gap-3 mb-4",children:[e.jsxs("div",{className:`flex items-center gap-1.5 text-xs cursor-pointer hover:opacity-80 ${k==="all"?"ring-2 ring-primary rounded-full px-1":""}`,onClick:()=>ee("all"),children:[e.jsx(M,{variant:"outline",className:"text-[10px]",children:K}),e.jsx("span",{className:"text-muted-foreground",children:"All"})]}),["new","contacted","opened","ignored","converted"].map(t=>{const s={new:"New",contacted:"Emailed",opened:"Opened",ignored:"Excluded",converted:"Has Account"};return e.jsxs("div",{className:`flex items-center gap-1.5 text-xs cursor-pointer hover:opacity-80 ${k===t?"ring-2 ring-primary rounded-full px-1":""}`,onClick:()=>ee(k===t?"all":t),children:[e.jsx(M,{className:`${Ee(t)} text-[10px]`,children:Q[t]||0}),e.jsx("span",{className:"text-muted-foreground",children:s[t]})]},t)})]}),e.jsxs("div",{className:"flex gap-3 mb-4",children:[e.jsx(X,{value:$,onChange:t=>Qe(t.target.value),placeholder:"Search emails or domains...",className:"max-w-xs"}),e.jsx("select",{value:k,onChange:t=>ee(t.target.value),className:"h-10 rounded-md border border-input bg-background px-3 text-sm",children:Rt.map(t=>e.jsx("option",{value:t.value,children:t.label},t.value))})]}),ie&&!h&&C>A&&e.jsxs("div",{className:"bg-muted/50 border rounded-md px-4 py-2 mb-3 flex items-center justify-between text-sm",children:[e.jsxs("span",{children:["All ",B.length," leads on this page are selected."]}),e.jsxs(f,{variant:"link",size:"sm",className:"text-primary font-semibold",onClick:()=>c(!0),children:["Select all ",C," leads matching this filter"]})]}),h&&e.jsxs("div",{className:"bg-primary/10 border border-primary/30 rounded-md px-4 py-2 mb-3 flex items-center justify-between text-sm",children:[e.jsxs("span",{className:"font-medium",children:["All ",C," leads matching this filter are selected."]}),e.jsx(f,{variant:"link",size:"sm",onClick:()=>{c(!1),x(new Set)},children:"Clear selection"})]}),We?e.jsx("div",{className:"flex justify-center py-8",children:e.jsx(_,{className:"w-5 h-5 animate-spin text-muted-foreground"})}):R.length>0?e.jsxs(e.Fragment,{children:[e.jsxs(we,{children:[e.jsx(be,{children:e.jsxs(U,{children:[e.jsx(w,{className:"w-10",children:e.jsx(oe,{checked:ie||h,onCheckedChange:tt})}),e.jsx(w,{children:"Email"}),e.jsx(w,{children:"Phone"}),e.jsx(w,{children:"Source"}),e.jsx(w,{children:"Status"}),e.jsx(w,{children:"Date"})]})}),e.jsx(je,{children:R.map(t=>{const s=t.status==="converted";return e.jsxs(U,{className:s?"opacity-60":"",children:[e.jsx(b,{children:e.jsx(oe,{checked:h||p.has(t.id),onCheckedChange:()=>{if(h){c(!1);const a=new Set(B.map(i=>i.id));a.delete(t.id),x(a)}else st(t.id)},disabled:s})}),e.jsx(b,{className:"font-mono text-xs",children:t.email}),e.jsx(b,{className:"text-xs text-muted-foreground",children:Dt(t.phone)}),e.jsx(b,{className:"text-xs text-muted-foreground",children:t.source_domain}),e.jsx(b,{children:e.jsx(M,{className:`${Ee(t.status)} text-[10px]`,children:{new:"new",contacted:"emailed",opened:"opened",ignored:"excluded",converted:"has account"}[t.status]||t.status})}),e.jsx(b,{className:"text-xs text-muted-foreground",children:new Date(t.created_at).toLocaleDateString()})]},t.id)})})]}),re>1&&e.jsxs("div",{className:"flex items-center justify-between mt-4 pt-4 border-t",children:[e.jsxs("p",{className:"text-sm text-muted-foreground",children:["Showing ",q*A+1,"â",Math.min((q+1)*A,C)," of ",C]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs(f,{variant:"outline",size:"sm",onClick:()=>{te(t=>t-1),x(new Set),c(!1),window.scrollTo({top:0,behavior:"instant"})},disabled:q===0,children:[e.jsx(vt,{className:"w-4 h-4"}),"Previous"]}),e.jsxs("span",{className:"text-sm text-muted-foreground",children:["Page ",q+1," of ",re]}),e.jsxs(f,{variant:"outline",size:"sm",onClick:()=>{te(t=>t+1),x(new Set),c(!1),window.scrollTo({top:0,behavior:"instant"})},disabled:q>=re-1,children:["Next",e.jsx(Nt,{className:"w-4 h-4"})]})]})]})]}):e.jsx("p",{className:"text-sm text-muted-foreground text-center py-8",children:K>0?"No leads match this filter.":"No leads yet. Scrape a competitor website to get started."})]}),e.jsxs(Z,{className:"p-6",children:[e.jsxs("h2",{className:"text-lg font-semibold text-foreground mb-4 flex items-center gap-2",children:[e.jsx(ge,{className:"w-5 h-5"}),"Send Outreach Email"]}),e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx("label",{className:"text-sm font-medium text-foreground",children:"Recipients"}),e.jsx("p",{className:"text-xs text-muted-foreground mt-0.5",children:h?`All ${C} leads matching filter (excluding registered users)`:p.size>0?`${p.size} selected lead(s) (excluding registered users)`:`All "new" leads (${R.filter(t=>t.status==="new").length}) â registered users auto-excluded`})]}),e.jsxs("div",{children:[e.jsx("label",{className:"text-sm font-medium text-foreground",children:"Subject"}),e.jsx(X,{value:G,onChange:t=>Oe(t.target.value),className:"mt-1"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"text-sm font-medium text-foreground",children:"Message"}),e.jsx(ct,{value:F,onChange:t=>Me(t.target.value),rows:12,className:"mt-1 font-mono text-xs"})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs(mt,{children:[e.jsx(ut,{asChild:!0,children:e.jsxs(f,{variant:"outline",children:[e.jsx(St,{className:"w-4 h-4"}),"Preview Email"]})}),e.jsxs(pt,{className:"max-w-3xl max-h-[85vh] overflow-y-auto p-0",children:[e.jsx(xt,{className:"p-4 pb-0",children:e.jsx(ft,{children:"Email Preview â What Recipients Will See"})}),e.jsx("iframe",{srcDoc:Ue,className:"w-full border-0 rounded-b-lg",style:{height:"70vh"},title:"Email preview",sandbox:""})]})]}),e.jsxs(le,{children:[e.jsx(ce,{asChild:!0,children:e.jsx(f,{disabled:g,children:g?e.jsxs(e.Fragment,{children:[e.jsx(_,{className:"w-4 h-4 animate-spin"}),"Sending..."]}):e.jsxs(e.Fragment,{children:[e.jsx(ge,{className:"w-4 h-4"}),"Send Outreach"]})})}),e.jsxs(de,{children:[e.jsxs(me,{children:[e.jsx(ue,{children:"Send outreach emails?"}),e.jsxs(pe,{children:["This will send emails to"," ",h?`all ${C} leads matching this filter`:p.size>0?`${p.size} selected lead(s)`:'all "new" leads',". Each email includes a tracking pixel. This action cannot be undone."]})]}),e.jsxs(xe,{children:[e.jsx(fe,{children:"Cancel"}),e.jsx(he,{onClick:et,children:"Send Now"})]})]})]})]})]})]})]}),e.jsx(Tt,{})]})},Tt=()=>{const{toast:r}=Ae(),l=Ke(),[n,N]=o.useState(""),[z,S]=o.useState(!1),{data:v=[],isLoading:p}=V({queryKey:["banned-domains"],queryFn:gt}),x=async()=>{const c=n.trim().toLowerCase().replace(/^https?:\/\//,"").replace(/^www\./,"").replace(/\/.*$/,"");if(!c||!c.includes(".")){r({title:"Enter a valid domain (e.g. example.com)",variant:"destructive"});return}if(v.some(g=>g.domain===c)){r({title:"Domain already banned",variant:"destructive"});return}S(!0);try{const g=ht(c),{error:T}=await m.from("banned_domains").insert({domain:c,brand_keyword:g});if(T)throw T;
80r({title:`Banned ${c}`,description:`Keyword "${g}" will be blocked everywhere.`}),N(""),l.invalidateQueries({queryKey:["banned-domains"]})}catch(g){r({title:"Failed",description:g.message,variant:"destructive"})}finally{S(!1)}},h=async c=>{const{error:g}=await m.from("banned_domains").delete().eq("id",c);if(g){r({title:"Failed to remove",variant:"destructive"});return}l.invalidateQueries({queryKey:["banned-domains"]}),r({title:"Domain unbanned"})};return e.jsxs(Z,{className:"mt-6",children:[e.jsxs("div",{className:"p-4 border-b border-border flex items-center gap-2",children:[e.jsx(kt,{className:"w-4 h-4 text-destructive"}),e.jsx("h3",{className:"text-sm font-semibold text-foreground",children:"Banned Domains"}),e.jsxs("span",{className:"text-xs text-muted-foreground ml-auto",children:[v.length," banned"]})]}),e.jsxs("div",{className:"p-4 space-y-3",children:[e.jsx("p",{className:"text-xs text-muted-foreground",children:"Banned domains block the domain as a website URL and the brand name (shown below) in titles, descriptions, and display names. Individual words remain allowed â only the compound brand keyword is blocked."}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(X,{value:n,onChange:c=>N(c.target.value),placeholder:"e.g. competitor.com",className:"flex-1",onKeyDown:c=>c.key==="Enter"&&x()}),e.jsxs(f,{onClick:x,disabled:z,size:"sm",children:[z?e.jsx(_,{className:"w-4 h-4 animate-spin"}):e.jsx(ot,{className:"w-4 h-4"}),"Ban"]})]}),p?e.jsx("div",{className:"flex justify-center py-4",children:e.jsx(_,{className:"w-4 h-4 animate-spin text-muted-foreground"})}):v.length===0?e.jsx("p",{className:"text-xs text-muted-foreground text-center py-4",children:"No banned domains yet."}):e.jsx("div",{className:"space-y-1",children:v.map(c=>e.jsxs("div",{className:"flex items-center gap-3 py-2 px-3 rounded-lg bg-secondary/30",children:[e.jsx("span",{className:"text-sm font-medium text-foreground",children:c.domain}),e.jsxs(M,{variant:"outline",className:"text-[11px]",children:['blocks "',c.brand_keyword,'"']}),e.jsx("button",{onClick:()=>h(c.id),className:"ml-auto text-muted-foreground hover:text-destructive transition-colors",children:e.jsx(Ie,{className:"w-3.5 h-3.5"})})]},c.id))})]})]})};export{bs as default};
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.