PageSourceSearch

https://cutplan.ai/assets/AdminEmailConsent-Ba1gHsYv.js

js cutplan.ai collected 2026-10-05 11:57:59 UTC 19,693 bytes, 2 lines download raw bytes

1import{r as p,j as e}from"./vendor-react-CHzuPTbR.js";import{A as T,b as w,c as W}from"./AdminAsync-DCZS4Vi1.js";import{a as R}from"./adminDateUtils-DiRx7F2l.js";import{dR as A}from"./index-asBHl4od.js";import{n as C,ba as E,a0 as I,bm as $,u as B,X as q}from"./vendor-icons-BZ8ojHLX.js";import"./vendor-sentry-11AWytiw.js";import"./preload-helper-ckwbz45p.js";import"./i18n-MHWIGCW0.js";const j=async(t,i)=>{const s=localStorage.getItem("cutplan.token"),x=await fetch(t,{...i,headers:{"Content-Type":"application/json",...s?{Authorization:`Bearer ${s}`}:{}}});return x.ok?x.json():null},o={fontFamily:"monospace",fontSize:12},S={background:"var(--bg-surface)",border:"1px solid var(--border)",borderRadius:12,padding:18},f=t=>t?R(t):null,z=t=>{const i=t==="explicit"?"#10b981":t==="declined"||t==="withdrawn"?"#ef4444":"var(--text-muted)";return e.jsx("span",{style:{...o,color:i,fontWeight:600},children:t})};function _(){const[t,i]=p.useState(""),[s,x]=p.useState(null),[d,y]=p.useState(null),[m,l]=p.useState(!1),[a,n]=p.useState(!1),[c,g]=p.useState(null),v=async r=>{if(r==null||r.preventDefault(),!t.includes("@"))return;n(!0),x(null),y(null),l(!1),g(null);const u=await j(`/api/admin/email-consent/lookup?email=${encodeURIComponent(t.trim())}`);if(u===null){g(new Error("The lookup request failed.")),n(!1);return}if(x(u),!u.found){const h=await j(`/api/admin/email-consent/suppression-only?email=${encodeURIComponent(t.trim())}`);h===null?l(!0):y(h.suppression??null)}n(!1)};return e.jsxs("div",{style:{...S,marginBottom:16},children:[e.jsxs("form",{onSubmit:v,style:{display:"flex",gap:8},children:[e.jsxs("div",{style:{position:"relative",flex:1},children:[e.jsx(I,{size:14,style:{position:"absolute",left:10,top:11,color:"var(--text-muted)"}}),e.jsx("input",{value:t,onChange:r=>i(r.target.value),type:"email",placeholder:"Did this person opt out? — search by email address",style:{width:"100%",padding:"8px 12px 8px 30px",borderRadius:8,border:"1px solid var(--border)",background:"var(--bg-page)",color:"var(--text-primary)",fontSize:13.5}})]}),e.jsx("button",{type:"submit",disabled:a||!t.includes("@"),style:{padding:"8px 16px",borderRadius:8,border:"none",background:"var(--brand)",color:"var(--brand-text)",fontSize:13,fontWeight:600,cursor:"pointer",opacity:a?.6:1},children:a?e.jsx(C,{size:14,style:{animation:"spin 1s linear infinite"}}):"Look up"})]}),!!c&&e.jsx("div",{style:{marginTop:16},children:e.jsx(T,{error:c,what:"this lookup",onRetry:()=>void v()})}),s&&!s.found&&e.jsxs("div",{style:{marginTop:16},children:[e.jsxs("div",{style:{fontSize:14,color:"var(--text-primary)"},children:["No account with ",e.jsx("code",{children:s.email}),"."]}),e.jsx("div",{style:{fontSize:13,color:m||d?"#f59e0b":"var(--text-secondary)",marginTop:6,lineHeight:1.6},children:m?'Could not check whether this address is suppressed — treat suppression as unknown, not "not suppressed".':d?e.jsxs(e.Fragment,{children:["But the address ",e.jsx("strong",{children:"is suppressed"})," — ",e.jsx("code",{children:d.reason})," via ",d.source,", recorded ",f(d.created_at),". We are correctly refusing to email it."]}):"The address is also not suppressed, so there is no record of them at all — never signed up, and never asked us to stop."})]}),s&&s.found&&e.jsxs("div",{style:{marginTop:18,display:"grid",gap:16},children:[e.jsxs("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit,minmax(min(260px,100%),1fr))",gap:14},children:[e.jsxs("div",{children:[e.jsx("div",{style:{...o,fontSize:10.5,letterSpacing:".1em",textTransform:"uppercase",color:"var(--text-muted)"},children:"Consent"}),e.jsx("div",{style:{fontSize:15,marginTop:3},children:z(s.consent.state)}),e.jsx("div",{style:{fontSize:12,color:"var(--text-muted)",marginTop:3},children:s.consent.state==="unknown"?'never asked — not "said no". They predate consent capture, or have not returned since.':s.consent.since?`since ${f(s.consent.since)}`:"no timestamp recorded"})]}),e.jsxs("div",{children:[e.jsx("div",{style:{...o,fontSize:10.5,letterSpacing:".1em",textTransform:"uppercase",color:"var(--text-muted)"},children:"Suppression"}),s.suppression?e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:{fontSize:15,marginTop:3},children:[e.jsx("span",{style:{...o,color:"#ef4444",fontWeight:600},children:s.suppression.reason}),e.jsxs("span",{style:{...o,color:"var(--text-muted)"},children:[" via ",s.suppression.source]})]}),e.jsxs("div",{style:{fontSize:12,color:"var(--text-muted)",marginTop:3,lineHeight:1.6},children:["recorded ",f(s.suppression.recordedAt)," ·"," ","acted ",s.suppression.actedAt?f(s.suppression.actedAt):e.jsx("span",{style:{color:"#f59e0b"},children:"unknown — the Zoho export did not preserve it"}),e.jsx("br",{}),"blocks marketing: ",String(s.suppression.blocksMarketing)," · blocks transactional: ",String(s.suppression.blocksTransactional)]})]}):e.jsx("div",{style:{fontSize:14,color:"var(--text-secondary)",marginTop:4},children:"Not suppressed — no row for this address."})]})]}),s.consent.history.length>0&&e.jsxs("div",{children:[e.jsx("div",{style:{...o,fontSize:10.5,letterSpacing:".1em",textTransform:"uppercase",color:"var(--text-muted)",marginBottom:6},children:"Consent history"}),s.consent.history.map((r,u)=>
1e.jsxs("div",{style:{borderInlineStart:"2px solid var(--border)",paddingInlineStart:10,paddingBottom:10},children:[e.jsxs("div",{style:{fontSize:13},children:[z(r.state)," ",e.jsxs("span",{style:{...o,color:"var(--text-muted)"},children:["via ",r.method," · ",f(r.at)]}),r.policyVersion&&e.jsxs("span",{style:{...o,color:"var(--text-muted)"},children:[" · policy ",r.policyVersion]})]}),e.jsx("div",{style:{fontSize:12.5,color:"var(--text-secondary)",marginTop:3,fontStyle:r.textShown?"normal":"italic"},children:r.textShown?`“${r.textShown}”`:"the exact wording was not recorded for this row — it predates text_shown, which is different from an empty string"})]},u))]}),s.sequences.length>0&&e.jsxs("div",{children:[e.jsx("div",{style:{...o,fontSize:10.5,letterSpacing:".1em",textTransform:"uppercase",color:"var(--text-muted)",marginBottom:6},children:"In sequences"}),s.sequences.map(r=>e.jsxs("div",{style:{...o,fontSize:12.5,color:"var(--text-secondary)",padding:"2px 0"},children:[r.key," ",e.jsx("span",{style:{color:"var(--text-muted)"},children:r.position}),r.step?` · next: ${r.step}`:" · complete",!r.enabled&&e.jsx("span",{style:{color:"var(--text-muted)"},children:" · sequence off"})]},r.key))]}),e.jsxs("div",{children:[e.jsxs("div",{style:{...o,fontSize:10.5,letterSpacing:".1em",textTransform:"uppercase",color:"var(--text-muted)",marginBottom:6},children:["Sent (",s.sends.length,")"]}),s.sends.length===0?e.jsx("div",{style:{fontSize:13,color:"var(--text-secondary)"},children:"Nothing has been sent to this address."}):s.sends.slice(0,40).map((r,u)=>e.jsxs("div",{style:{...o,fontSize:12.5,display:"flex",gap:10,flexWrap:"wrap",padding:"2px 0"},children:[e.jsx("span",{style:{color:"var(--text-muted)",minWidth:108},children:f(r.at)}),e.jsx("span",{style:{color:"var(--text-primary)"},children:r.emailId}),e.jsx("span",{style:{color:r.status==="bounced"||r.status==="complained"?"#ef4444":"var(--text-secondary)"},children:r.status??"no status"}),r.era==="zoho"&&e.jsx("span",{style:{color:"#f59e0b"},children:"Zoho-era tag — delivery unknown"}),r.reason&&e.jsx("span",{style:{color:"#ef4444"},children:r.reason})]},u)),s.disarmed.length>0&&e.jsxs("div",{style:{...o,fontSize:12,color:"var(--text-muted)",marginTop:8},children:["+ ",s.disarmed.length," disarm rows, written by the cutover to exclude this person from sequences Zoho had already sent. Not sends."]})]})]})]})}function F({rows:t,error:i,onChanged:s}){const[x,d]=p.useState(null),y=async()=>{var g,v,r;const l=(g=prompt("Email to suppress?"))==null?void 0:g.trim();if(!l)return;const a=(v=prompt("Reason: unsubscribe_marketing | hard_bounce | complaint | manual"))==null?void 0:v.trim(),n=(r=prompt("Where did this request arrive from? (min 10 chars — this is the record)"))==null?void 0:r.trim();if(!n||n.length<10){d("Suppression note must be at least 10 characters — describe where this request arrived from.");return}const c=await j("/api/admin/suppression",{method:"POST",body:JSON.stringify({email:l,reason:a,note:n})});d(c!=null&&c.ok?`Suppressed ${l}`:`Failed: ${(c==null?void 0:c.error)??"rejected"}`),s()},m=async l=>{var c;const a=(c=prompt(`Who asked for ${l} to be resubscribed? (min 10 chars)`))==null?void 0:c.trim();if(!a||a.length<10){a&&d("Resubscribe note must be at least 10 characters.");return}const n=await j(`/api/admin/suppression/${encodeURIComponent(l)}?note=${encodeURIComponent(a)}`,{method:"DELETE"});d(n!=null&&n.ok?`Removed ${l}`:"Not removed"),s()};return e.jsxs("div",{style:{...S,marginBottom:16},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10,marginBottom:12},children:[e.jsx($,{size:15,style:{color:"var(--text-secondary)"}}),e.jsxs("h2",{style:{fontSize:14.5,color:"var(--text-primary)",margin:0},children:["Asked us to stop ",t&&e.jsxs("span",{style:{...o,color:"var(--text-muted)",fontWeight:400},children:["(",t.length,")"]})]}),e.jsxs("button",{onClick:()=>void y(),style:{marginInlineStart:"auto",display:"flex",alignItems:"center",gap:5,padding:"5px 11px",borderRadius:8,border:"1px solid var(--border)",background:"none",color:"var(--text-secondary)",fontSize:12.5,cursor:"pointer"},children:[e.jsx(B,{size:13})," Add"]})]}),x&&e.jsx("div",{style:{fontSize:12.5,color:"var(--text-secondary)",marginBottom:10},children:x}),e.jsx(w,{loading:t===null&&!i,error:i,what:"the suppression list",onRetry:s,isEmpty:t!==null&&t.length===0,empty:e.jsx("div",{style:{fontSize:13,color:"var(--text-secondary)"},children:"Nobody is suppressed."}),children:e.jsx("div",{style:{maxHeight:340,overflowY:"auto"}
1,children:(t??[]).map(l=>e.jsxs("div",{style:{display:"flex",gap:10,alignItems:"center",flexWrap:"wrap",padding:"5px 0",borderTop:"1px solid var(--border)"},children:[e.jsx("span",{style:{...o,fontSize:12.5,color:"var(--text-primary)",flex:1,minWidth:190},children:l.email}),e.jsx("span",{style:{...o,fontSize:12,color:"#ef4444"},children:l.reason}),e.jsx("span",{style:{...o,fontSize:12,color:"var(--text-muted)"},children:l.source}),e.jsx("span",{style:{...o,fontSize:12,color:"var(--text-muted)"},children:f(l.created_at)}),e.jsx("button",{onClick:()=>void m(l.email),"aria-label":`Remove ${l.email}`,style:{background:"none",border:"none",cursor:"pointer",color:"var(--text-muted)",padding:2},children:e.jsx(q,{size:13})})]},l.email))})})]})}function N({f:t}){return e.jsxs("div",{style:S,children:[e.jsx("h2",{style:{fontSize:14.5,color:"var(--text-primary)",margin:"0 0 12px"},children:"Consent rate"}),e.jsxs("div",{style:{display:"flex",flexWrap:"wrap",gap:24,alignItems:"flex-end"},children:[e.jsxs("div",{children:[e.jsx("div",{style:{...o,fontSize:24,fontWeight:600,color:"var(--text-primary)",fontVariantNumeric:"tabular-nums"},children:t.totalAsked===0?"—":`${Math.round(100*t.totalTicked/t.totalAsked)}%`}),e.jsxs("div",{style:{...o,fontSize:11,color:"var(--text-muted)"},children:[t.totalTicked," of ",t.totalAsked," asked"]})]}),e.jsx("div",{style:{fontSize:12.5,color:"var(--text-secondary)",maxWidth:430,lineHeight:1.6},children:t.totalAsked===0?'Nobody has been asked yet. This is 0 of 0 — a broken instrument reads the same as a real result, so treat it as "no data", not "no interest".':e.jsxs(e.Fragment,{children:["Really between ",e.jsxs("strong",{children:[Math.round(100*t.ci.low),"%"]})," and ",e.jsxs("strong",{children:[Math.round(100*t.ci.high),"%"]}),"."," ",t.asksUntilDecidable>0?e.jsxs(e.Fragment,{children:["~",e.jsx("strong",{children:t.asksUntilDecidable})," more asks before 30% and 50% can be told apart."]}):"Enough asks to distinguish 30% from 50%."]})}),e.jsxs("div",{style:{marginInlineStart:"auto",textAlign:"end"},children:[e.jsxs("div",{style:{...o,fontSize:14,color:t.returningPromptPercent<100?"#f59e0b":"var(--text-secondary)"},children:[t.returningPromptPercent,"%"]}),e.jsx("div",{style:{...o,fontSize:11,color:"var(--text-muted)"},children:"of returning users prompted"})]})]}),e.jsx("div",{style:{marginTop:14,display:"flex",flexWrap:"wrap",gap:8},children:Object.entries(t.states).map(([i,s])=>e.jsxs("span",{style:{...o,fontSize:12,padding:"3px 9px",borderRadius:20,background:"var(--bg-page)",border:"1px solid var(--border)",color:i==="explicit"?"#10b981":i==="unknown"?"var(--text-muted)":"var(--text-secondary)"},children:[i,": ",s]},i))}),t.byMethod.map(i=>e.jsxs("div",{style:{display:"grid",gridTemplateColumns:"160px 54px 1fr",gap:10,alignItems:"center",padding:"3px 0",marginTop:4},children:[e.jsx("code",{style:{...o,color:"var(--text-primary)"},children:i.method}),e.jsx("span",{style:{...o,fontSize:13,textAlign:"end",fontVariantNumeric:"tabular-nums"},children:i.tickRate===null?"—":`${Math.round(100*i.tickRate)}%`}),e.jsxs("span",{style:{fontSize:12,color:"var(--text-muted)"},children:[i.ticked," ticked · ",i.declined," declined · ",i.asked," asked"]})]},i.method)),e.jsxs("div",{style:{marginTop:12,paddingTop:10,borderTop:"1px solid var(--border)",fontSize:12.5,color:"var(--text-secondary)"},children:["returning prompt — shown ",e.jsx("strong",{children:t.prompt.shown})," · decided ",e.jsx("strong",{children:t.prompt.decided})," · dismissed ",e.jsx("strong",{children:t.prompt.dismissed}),e.jsxs("span",{style:{color:t.prompt.dismissRate!==null&&t.prompt.dismissRate>.6?"#f59e0b":"var(--text-muted)"},children:[" — ",t.prompt.dismissRate===null?"never shown; this is 0 of 0, not indifference":`${Math.round(100*t.prompt.dismissRate)}% dismissed without deciding${t.prompt.dismissRate>.6?" — the moment is wrong, not the copy":""}`]})]})]})}function O(){const{data:t,error:i,loading:s,reload:x}=W(()=>A(void 0),[]),[d,y]=p.useState(!1),m=(t==null?void 0:t.records)??[],l=d?m:m.slice(0,10),a=t==null?void 0:t.summary;return e.jsxs("div",{id:"cookie-consent",style:{...S,marginBottom:16},children:[e.jsx("h2",{style:{fontSize:14.5,color:"var(--text-primary)",margin:"0 0 4px"},children:"Cookie consent decisions, last 30 days"}),e.jsx("p",{style:{fontSize:12.5,color:"var(--text-muted)",margin:"0 0 10px"},children:"What the cookie banner recorded. A grant is the first decision, an update a later change, a revoke a withdrawal."}),e.jsxs(w,{loading:s,error:i,what:"cookie consent records",onRetry:x,isEmpty:m.length===0,empty:e.jsx("p",{style:{fontSize:13,color:"var(--text-muted)",margin:0},children:"No decisions recorded in the last 30 days."}),children:[a&&e.jsxs("div",{style:{display:"flex",gap:18,flexWrap:"wrap",marginBottom:10,fontSize:12.5},children:[e.jsxs("span",{children:[e.jsx("b",{style:{color:"var(--text-primary)"},children:a.total})," ",e.jsx("span",{style:{color:"var(--text-muted)"},children:"decisions"})]}),e.jsxs("span",{children:[e.jsx("b",{style:{color:"var(--text-primary)"},children:a.unique_users})," ",e.jsx("span",{style:{color:"var(--text-muted)"},children:"people"})]}),e.jsxs("span",{children:[e.jsx("b",{style:{color:"#10b981"},children:a.grants})," ",e.jsx("span",{style:{color:"var(--text-muted)"},children:"grants"})]}),e.jsxs("span",{children:[e.jsx("b",{style:{color:"var(--text-primary)"},children:a.updates})," ",e.jsx("span",{style:{color:"var(--text-muted)"},children:"updates"})]}),e.jsxs("span",{children:[e.jsx("b",{style:{color:"#ef4444"},children:a.revokes})," ",e.jsx("span",{style:{color:"var(--text-muted)"},children:"revokes"})]}),e.jsxs("span",{children:[e.jsxs("b",{style:{color:"var(--text-primary)"},children:[a.total>0?Math.round(a.marketing_accepted/a.total*100):0,"%"]})," ",e.jsx("span",{style:{color:"var(--text-muted)"},children:"of decisions include marketing"})]})]}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2},children:l.map(n=>{const c=n.action==="grant"?"#10b981":n.action==="revoke"?"#ef4444":"var(--text-secondary)";return e.jsxs("div",{style:{...o,fontSize:12.5,display:"flex",gap:10,flexWrap:"wrap",padding:"3px 0",borderBottom:"1px solid var(--border)"},children:[e.jsx("span",{style:{color:"var(--text-muted)",minWidth:120},children:R(n.created_at)}),e.jsx("span",{style:{color:c,fontWeight:600,minWidth:52},children:n.action}),e.jsx("span",{style:{color:"var(--text-primary)",minWidth:160,overflow:"hidden",textOverflow:"ellipsis"},children:n.email||(n.visitor_id?`anonymous ${n.visitor_id.slice(0,8)}…`:"—")}),e.jsx("span",{style:{color:"var(--text-muted)"},children:Array.isArray(n.categories)?n.categories.join(", "):String(n.categories)})]},n.id)})}),m.length>10&&e.jsx("button",{onClick:()=>y(n=>!n),style:{background:"none",border:"none",padding:"6px 0 0",font:"inherit",fontSize:12,color:"var(--brand)",cursor:"pointer",textDecoration:"underline"},children:d?"Show the latest 10":`Show all ${m.length}`})]})]})}function G(){const[t,i]=p.useState(null),[s,x]=p.useState(null),[d,y]=p.useState(null),[m,l]=p.useState(null),[a,n]=p.useState(null),[c,g]=p.useState(null),[v,r]=p.useState(!1),u=p.useCallback(async()=>{r(!0);const[h,k,b]=await Promise.all([j("/api/admin/suppression?search="),j("/api/admin/email-engine/consent-funnel"),j("/api/admin/email-marketing/collisions")]);h===null?x(new Error("Could not load the suppression list.")):(i(h.rows),x(null)),k===null?l(new Error("Could not load the consent funnel.")):(y(k),l(null)),b===null?g(new Error("Could not load recent collisions.")):(n(b.collisions),g(null)),r(!1)},[]);return p.useEffect(()=>{u()},[u]),e.jsxs("div",{style:{maxWidth:980},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:12,marginBottom:6},children:[e.jsx("h1",{style:{color:"var(--text-primary)",fontSize:20,margin:0},children:"Who agreed, and who asked us to stop"}),e.jsxs("button",{onClick:()=>void u(),disabled:v,style:{marginInlineStart:"auto",display:"flex",alignItems:"center",gap:6,padding:"6px 12px",background:"var(--bg-surface)",border:"1px solid var(--border)",borderRadius:8,color:"var(--text-secondary)",fontSize:13,cursor:"pointer"},children:[v?e.jsx(C,{size:14,style:{animation:"spin 1s linear infinite"}}):e.jsx(E,{size:14})," Refresh"]})]}),e.jsx("p",{style:{fontSize:13,color:"var(--text-secondary)",margin:"0 0 18px",maxWidth:660,lineHeight:1.6},children:"One person at a time, first — that is the question support actually receives."}),e.jsx(_,{}),e.jsx(F,{rows:t,error:s,onChanged:()=>void u()}),!!c&&e.jsx("div",{style:{marginBottom:16},children:e.jsx(T,{error:c,what:"the frequency-collision list",onRetry:()=>void u()})}),a&&a.length>0&&e.jsxs("div",{style:{...S,marginBottom:16},children:[e.jsxs("h2",{style:{fontSize:14.5,color:"var(--text-primary)",margin:"0 0 4px"},children:["Received more than the budget allows (",a.length,")"]}),e.jsx("p",{style:{fontSize:12.5,color:"var(--text-muted)",margin:"0 0 10px"},children:"The frequency budget is 1/day, 3/week, 8/month. A row here is a person who got more, which is the complaint before it arrives."}),a.slice(0,20).map((h,k)=>{var b;return e.jsxs("div",{style:{...o,fontSize:12.5,display:"flex",gap:10,flexWrap:"wrap",padding:"2px 0"},children:[e.jsx("span",{style:{color:"var(--text-muted)",minWidth:92},children:(b=h.day)==null?void 0:b.slice(0,10)}),e.jsx("span",{style:{color:"var(--text-primary)"},children:h.email}),e.jsxs("span",{style:{color:"#f59e0b"},children:[h.email_count," emails"]}),e.jsx("span",{style:{color:"var(--text-muted)"},children:(h.email_ids??[]).join(", ")})]},k)})]}),(d||!!m)&&e.jsx(w,{loading:!1,error:m,what:"the consent funnel",onRetry:()=>void u(),children:d&&e.jsx(N,{f:d})}),e.jsx("div",{style:{height:16}}),e.jsx(O,{})]})}
1export{G as AdminEmailConsent,G as default};
2//# sourceMappingURL=AdminEmailConsent-Ba1gHsYv.js.map

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.