PageSourceSearch

https://testivo.tech/assets/admin.prospects.index-tnfR8xNH.js

js testivo.tech collected 2026-09-24 15:13:41 UTC 8,409 bytes, 1 lines download raw bytes

1import{r as u,j as t}from"./react-RN8z7mqm.js";import{L as b}from"./index-BwX0UczS.js";import{toast as l}from"./sonner-CSA395gJ.js";import{B as d}from"./button-D97T890F.js";import{D as h,b as L,c as Q}from"./dm-draft-wKmj5Zwx.js";import{p as r,a as _}from"./prospects-client-DczTDJ8w.js";import{ae as O,ad as j,a4 as P,C as v,bd as T,aM as z,X as A,bu as E}from"./icons-CFy5kY0-.js";import"./supabase-D7bm-uKx.js";import"./index-BzjFgxN2.js";import"./clsx-B-dksMZM.js";import"./utils-6PQrid_T.js";function I(s){if(s==null)return null;const p=s>=.65?{label:"Strong wall",cls:"bg-emerald-500/10 text-emerald-600 border-emerald-500/30"}:s>=.45?{label:"Decent wall",cls:"bg-amber-500/10 text-amber-600 border-amber-500/30"}:{label:"Weak wall",cls:"bg-muted text-muted-foreground border-border"};return t.jsx("span",{className:`inline-block rounded-full border px-2 py-0.5 text-[11px] font-medium ${p.cls}`,children:p.label})}function R(s){return s.status==="claimed"?{text:"Claimed 🎉",cls:"text-emerald-600 font-medium"}:s.outreach?.dm_sent_at?s.preview?.first_view_at?{text:"DM sent · they looked!",cls:"text-emerald-600"}:{text:"DM sent · waiting",cls:"text-amber-600"}:s.outreach?.followed_at?{text:"Followed · DM next",cls:"text-amber-600"}:{text:"Ready — follow them first",cls:"text-muted-foreground"}}function W({loud:s}){return t.jsxs("a",{href:"/admin/prospects/manual",target:"_blank",rel:"noopener noreferrer",className:`flex items-center gap-2.5 rounded-lg border px-4 py-3 text-sm transition hover:bg-accent ${s?"border-primary/30 bg-primary/5":"bg-card"}`,children:[t.jsx(E,{className:"h-4 w-4 shrink-0 text-primary"}),t.jsx("span",{className:"text-foreground",children:s?"New here? Read the Gift Walls manual first":"Gift Walls manual"}),t.jsx("span",{className:"text-xs text-muted-foreground",children:"— find a creator, build their wall, follow, then DM. Opens in a new tab."}),t.jsx(j,{className:"ml-auto h-3.5 w-3.5 shrink-0 text-muted-foreground"})]})}function te(){const[s,p]=u.useState([]),[f,g]=u.useState(!0),[c,N]=u.useState(0),m=u.useCallback(async()=>{g(!0);const[{data:e},{data:a},{data:n},{data:x}]=await Promise.all([r("prospects").select("*").in("status",["built","queued","sent"]).order("score",{ascending:!1}),r("prospect_quotes").select("*").eq("include",!0).order("quality",{ascending:!1}),r("previews").select("*").is("claimed_at",null),r("outreach_log").select("*")]),i=new Map;for(const o of a??[]){const y=i.get(o.prospect_id)??[];y.push(o),i.set(o.prospect_id,y)}const C=new Map((n??[]).map(o=>[o.prospect_id,o])),$=new Map((x??[]).map(o=>[o.prospect_id,o]));p((e??[]).map(o=>({...o,quoteCount:i.get(o.id)?.length??0,topQuote:i.get(o.id)?.[0]??null,preview:C.get(o.id)??null,outreach:$.get(o.id)??null})));const B=Date.now()-864e5;N((x??[]).filter(o=>o.dm_sent_at&&new Date(o.dm_sent_at).getTime()>B).length),g(!1)},[]);u.useEffect(()=>{m()},[m]);const w=async e=>{const{data:a}=await r("outreach_log").select("id").eq("prospect_id",e).maybeSingle();if(a?.id)return a.id;const{data:n}=await r("outreach_log").insert({prospect_id:e}).select("id").single();return n.id},k=async e=>{if(!e.preview||!e.topQuote)return l.error("This wall has no quotes yet");c>=h&&l.warning(`You've sent ${c} DMs in 24 hours — that's the daily limit. Sending more risks looking like spam.`);const a=L({displayName:e.display_name,handle:e.handle,quoteBody:e.topQuote.body,quoteAuthorHandle:e.topQuote.author_handle,previewUrl:_(e.preview.slug),expiresAt:e.preview.expires_at}),{data:n}=await r("outreach_log").select("dm_body").not("dm_body","is",null).order("dm_sent_at",{ascending:!1}).limit(20),x=Q(a,(n??[]).map(i=>i.dm_body).filter(Boolean));await navigator.clipboard.writeText(a),x.tooTemplated?l.warning("Draft copied — but it reads almost the same as a recent DM. Change the wording before sending, or it looks like a bot."):l.success("DM draft copied. Paste it into X, make it yours, then send.")},D=async e=>{const a=await w(e.id);await r("outreach_log").update({followed_at:new Date().toISOString()}).eq("id",a),l.success(`Noted: you followed @${e.handle}`),m()},S=async e=>{const a=window.prompt("Paste the DM exactly as you sent it (we keep it so future drafts don't repeat it):");
1if(a===null)return;const n=await w(e.id);await Promise.all([r("outreach_log").update({dm_sent_at:new Date().toISOString(),dm_body:a||null}).eq("id",n),r("prospects").update({status:"sent",updated_at:new Date().toISOString()}).eq("id",e.id)]),l.success(`Noted: DM sent to @${e.handle}`),m()},q=async e=>{const a=window.prompt("Why skip this creator? (a short note for future-you)","not a fit");a!==null&&(await r("prospects").update({status:"dead",dead_reason:a,updated_at:new Date().toISOString()}).eq("id",e.id),l.success(`@${e.handle} skipped`),m())},M=c>=h?"text-red-600":c>=10?"text-amber-600":"text-muted-foreground";return t.jsxs("div",{className:"space-y-4",children:[t.jsx(W,{loud:!f&&s.length===0}),t.jsxs("div",{className:"flex items-center justify-between",children:[t.jsx("span",{className:"text-sm text-muted-foreground",children:s.length===0?"No walls waiting":`${s.length} wall${s.length===1?"":"s"} ready · best first`}),t.jsxs("span",{className:`text-xs ${M}`,title:"Sending too many DMs per day looks like spam to X",children:["DMs today: ",c," of ",h," max"]})]}),f?t.jsx("div",{className:"rounded-lg border px-3 py-10 text-center text-sm text-muted-foreground",children:"Loading…"}):s.length===0?t.jsxs("div",{className:"rounded-lg border px-3 py-10 text-center",children:[t.jsx("p",{className:"text-sm text-muted-foreground",children:"Nothing here yet — add your first creator and Testivo will build their wall."}),t.jsx(d,{asChild:!0,size:"sm",className:"mt-4",children:t.jsx(b,{to:"/admin/prospects/ingest",children:"+ Add your first creator"})})]}):t.jsx("div",{className:"space-y-3",children:s.map(e=>{const a=R(e);return t.jsx("div",{className:"rounded-lg border bg-card p-4",children:t.jsxs("div",{className:"flex flex-wrap items-start gap-x-4 gap-y-2",children:[t.jsxs("div",{className:"min-w-40",children:[t.jsxs(b,{to:"/admin/prospects/$id",params:{id:e.id},className:"font-semibold hover:text-primary",children:["@",e.handle]}),t.jsxs("div",{className:"mt-0.5 text-xs text-muted-foreground",children:[e.niche??"no niche"," ·"," ",e.followers_est!=null?`~${e.followers_est.toLocaleString()} followers`:"followers unknown"]}),t.jsxs("div",{className:"mt-1.5 flex items-center gap-2",children:[I(e.score),t.jsxs("span",{className:"text-xs text-muted-foreground",children:[e.quoteCount," praise post",e.quoteCount===1?"":"s"]})]})]}),t.jsxs("div",{className:"min-w-64 flex-1",children:[e.topQuote&&t.jsxs("blockquote",{className:"border-l-2 border-primary/30 pl-3 text-sm text-muted-foreground",children:["“",e.topQuote.body.slice(0,160),e.topQuote.body.length>160?"…":"","”",t.jsxs("span",{className:"ml-1 text-xs",children:["— @",e.topQuote.author_handle]})]}),t.jsx("div",{className:`mt-1.5 text-xs ${a.cls}`,children:a.text}),e.preview&&e.preview.view_count>0&&t.jsxs("div",{className:"mt-0.5 inline-flex items-center gap-1 text-xs text-emerald-600",children:[t.jsx(O,{className:"h-3 w-3"})," their wall was opened ",e.preview.view_count,"×"]})]}),t.jsxs("div",{className:"flex flex-col items-end gap-1.5",children:[t.jsxs("div",{className:"flex flex-wrap justify-end gap-1.5",children:[e.preview&&t.jsx(d,{asChild:!0,size:"sm",variant:"outline",children:t.jsxs("a",{href:_(e.preview.slug),target:"_blank",rel:"noopener noreferrer",children:[t.jsx(j,{className:"mr-1.5 h-3.5 w-3.5"})," See their wall"]})}),t.jsxs(d,{size:"sm",onClick:()=>{k(e)},children:[t.jsx(P,{className:"mr-1.5 h-3.5 w-3.5"})," Copy DM"]})]}),t.jsxs("div",{className:"flex flex-wrap justify-end gap-1.5",children:[t.jsxs(d,{size:"sm",variant:e.outreach?.followed_at?"secondary":"outline",onClick:()=>{D(e)},children:[e.outreach?.followed_at?t.jsx(v,{className:"mr-1.5 h-3.5 w-3.5"}):t.jsx(T,{className:"mr-1.5 h-3.5 w-3.5"}),"I followed them"]}),t.jsxs(d,{size:"sm",variant:e.outreach?.dm_sent_at?"secondary":"outline",onClick:()=>{S(e)},children:[e.outreach?.dm_sent_at?t.jsx(v,{className:"mr-1.5 h-3.5 w-3.5"}):t.jsx(z,{className:"mr-1.5 h-3.5 w-3.5"}),"I sent the DM"]}),t.jsxs(d,{size:"sm",variant:"ghost",title:"Remove from this list",onClick:()=>{q(e)},children:[t.jsx(A,{className:"mr-1 h-3.5 w-3.5"})," Skip"]})]})]})]})},e.id)})})]})}export{te 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.