PageSourceSearch

https://jellyfishnews.web.app/assets/Subscribers-BEJTgX3i.js

js jellyfishnews.web.app collected 2026-10-03 12:27:27 UTC 8,474 bytes, 4 lines download raw bytes

1import{r as l,h as _,q as k,e as C,g as m,i as q,Z as M,w as P,j as t,n as D,p as A,t as b,G as W,H as G}from"./index-BKHXjXpd.js";const H=W(G,"resendNewsletterToSubscriber",{timeout:12e4}),Z=["all","active","unsubscribed","blocked"];function x(n,o){switch(o){case"active":return n.status==="active"&&!n.doNotEmail;case"unsubscribed":return n.status==="unsubscribed"&&!n.doNotEmail;case"blocked":return n.doNotEmail===!0;default:return!0}}function J(n){return(n instanceof Date?n:n.toDate()).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"})}function K(n){return n.doNotEmail?"bg-red-900/50 text-red-300 border border-red-700/50":n.status==="active"?"bg-green-900/50 text-green-400":n.status==="complained"?"bg-orange-900/50 text-orange-300":n.status==="bounced"?"bg-yellow-900/40 text-yellow-300":"bg-gray-800 text-gray-500"}function Q(n){return n.doNotEmail?"Blocked":n.status}function Y(){const[n,o]=l.useState([]),[g,y]=l.useState(!0),[d,$]=l.useState(""),[h,R]=l.useState("all"),[L,p]=l.useState(0),[I,u]=l.useState(null),[N,j]=l.useState(null),[T,v]=l.useState(new Set);l.useEffect(()=>{F()},[]);async function F(){y(!0);try{const[e,i]=await Promise.all([_(k(C(m,"subscribers"),q("subscribedAt","desc"))),M(k(C(m,"subscribers"),P("status","==","active")))]),a=e.docs.map(s=>({id:s.id,...s.data()}));o(a),p(i.data().count)}finally{y(!1)}}async function S(e,i){if(e.doNotEmail&&i==="active"){alert("This address is on the Do-Not-Email list. Clear that flag first.");return}u(e.id);try{await D(A(m,"subscribers",e.id),{status:i,...i==="unsubscribed"?{unsubscribedAt:b()}:{unsubscribedAt:null,subscribedAt:b()}}),o(a=>a.map(s=>s.id===e.id?{...s,status:i,unsubscribedAt:i==="unsubscribed"?new Date:void 0}:s)),p(a=>i==="active"?a+1:Math.max(0,a-1))}catch(a){alert(`Failed to update status: ${(a==null?void 0:a.message)||a}`)}finally{u(null)}}async function U(e,i){let a="";if(i){const s=prompt(`Add ${e.email} to the Do-Not-Email list?
2
3They will be marked unsubscribed and any future re-subscribe attempts will silently fail. Optional reason (admin notes only):`,"");if(s===null)return;a=s.trim()}else if(!confirm(`Remove ${e.email} from the Do-Not-Email list?`))return;u(e.id);try{await D(A(m,"subscribers",e.id),{doNotEmail:i,doNotEmailSetAt:i?b():null,doNotEmailReason:i?a:null,...i?{status:"unsubscribed",unsubscribedAt:b()}:{}}),o(s=>s.map(r=>r.id===e.id?{...r,doNotEmail:i,doNotEmailReason:i?a:void 0,...i?{status:"unsubscribed"}:{}}:r)),i&&e.status==="active"&&p(s=>Math.max(0,s-1))}catch(s){alert(`Failed to update Do-Not-Email flag: ${(s==null?void 0:s.message)||s}`)}finally{u(null)}}async function O(e){var i;if(confirm(`Re-send today's newsletter to ${e.email}?`)){j(e.id);try{const a=await H({subscriberId:e.id});if(!((i=a.data)!=null&&i.success)){alert("Send failed: server returned no success.");return}a.data.postCount===0&&alert("Sent — but there are no posts in the last 24h, so the newsletter would have been empty."),v(s=>{const r=new Set(s);return r.add(e.id),r}),setTimeout(()=>{v(s=>{const r=new Set(s);return r.delete(e.id),r})},4e3)}catch(a){alert(`Send failed: ${(a==null?void 0:a.message)||a}`)}finally{j(null)}}}function B(){const e=n.filter(c=>c.status==="active"&&!c.doNotEmail),i=["email,subscribed_at,source"].concat(e.map(c=>{const z=c.subscribedAt instanceof Date?c.subscribedAt:c.subscribedAt.toDate();return`${c.email},${z.toISOString()},${c.source}`})).join(`
4`),a=new Blob([i],{type:"text/csv"}),s=URL.createObjectURL(a),r=document.createElement("a");r.href=s,r.download=`jn-subscribers-${new Date().toISOString().split("T")[0]}.csv`,r.click(),URL.revokeObjectURL(s)}const f=n.filter(e=>d&&!e.email.toLowerCase().includes(d.toLowerCase())?!1:x(e,h)),w={all:n.length,active:n.filter(e=>x(e,"active")).length,unsubscribed:n.filter(e=>x(e,"unsubscribed")).length,blocked:n.filter(e=>x(e,"blocked")).length},E=w.blocked;return t.jsxs("div",{children:[t.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6",children:[t.jsxs("div",{children:[t.jsx("h1",{className:"text-2xl font-bold text-gray-100",children:"Subscribers"}),t.jsxs("p",{className:"text-sm text-gray-400 mt-0.5",children:[t.jsx("span",{className:"text-jelly-400 font-semibold",children:L})," active",E>0&&t.jsxs(t.Fragment,{children:[" ","·"," ",t.jsx("span",{className:"text-red-400 font-semibold",children:E})," blocked"]})]})]}),t.jsx("button",{onClick:B,className:"jelly-btn-outline inline-flex items-center gap-2 text-sm px-4 py-2",children:"Export active CSV"})]}),t.jsxs("div",{className:"flex flex-wrap items-center gap-3 mb-4",children:[t.jsx("div",{className:"flex gap-1 bg-ocean-800/60 rounded-lg p-1",children:Z.map(e=>t.jsxs("button",{type:"button",onClick:()=>R(e),className:`px-3 py-1.5 text-xs font-semibold rounded-md transition-colors capitalize inline-flex items-center gap-1.5 ${h===e?"bg-jelly-500/20 text-jelly-300":"text-gray-400 hover:text-gray-200"}`,children:[e,!g&&t.jsx("span",{className:`px-1.5 rounded-full text-[10px] font-bold tabular-nums ${h===e?"bg-jelly-500/30 text-jelly-200":"bg-ocean-700 text-gray-400"}
4`,children:w[e].toLocaleString()})]},e))}),t.jsx("input",{type:"text",placeholder:"Search by email…",className:"input max-w-sm",value:d,onChange:e=>$(e.target.value)})]}),g?t.jsx("div",{className:"space-y-2",children:Array.from({length:6}).map((e,i)=>t.jsx("div",{className:"h-14 bg-ocean-800 rounded-lg animate-pulse"},i))}):f.length===0?t.jsx("div",{className:"text-center py-16 text-gray-400",children:d?"No subscribers match your search.":"No subscribers in this view."}):t.jsx("div",{className:"card overflow-hidden",children:t.jsx("div",{className:"overflow-x-auto",children:t.jsxs("table",{className:"w-full text-sm",children:[t.jsx("thead",{children:t.jsxs("tr",{className:"border-b border-ocean-600 text-left",children:[t.jsx("th",{className:"px-4 py-3 font-medium text-gray-400",children:"Email"}),t.jsx("th",{className:"px-4 py-3 font-medium text-gray-400",children:"Status"}),t.jsx("th",{className:"px-4 py-3 font-medium text-gray-400 hidden md:table-cell",children:"Source"}),t.jsx("th",{className:"px-4 py-3 font-medium text-gray-400 hidden md:table-cell",children:"Subscribed"}),t.jsx("th",{className:"px-4 py-3 font-medium text-gray-400 text-right",children:"Actions"})]})}),t.jsx("tbody",{children:f.map((e,i)=>{const a=I===e.id,s=!!e.doNotEmail;return t.jsxs("tr",{className:`border-b border-ocean-600/50 hover:bg-ocean-700/30 transition-colors ${i===f.length-1?"border-b-0":""}`,children:[t.jsxs("td",{className:"px-4 py-3 text-gray-200",children:[t.jsx("div",{children:e.email}),s&&e.doNotEmailReason&&t.jsx("div",{className:"text-xs text-red-400/80 mt-0.5 italic",children:e.doNotEmailReason})]}),t.jsx("td",{className:"px-4 py-3",children:t.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium capitalize ${K(e)}`,children:Q(e)})}),t.jsx("td",{className:"px-4 py-3 text-gray-400 capitalize hidden md:table-cell",children:e.source}),t.jsx("td",{className:"px-4 py-3 text-gray-400 hidden md:table-cell",children:J(e.subscribedAt)}),t.jsx("td",{className:"px-4 py-3 text-right",children:t.jsxs("div",{className:"inline-flex items-center gap-2 justify-end flex-wrap",children:[e.status==="active"&&!s&&t.jsx("button",{type:"button",disabled:N===e.id||a,onClick:()=>O(e),className:"px-2.5 py-1 text-xs bg-jelly-500/20 text-jelly-300 hover:bg-jelly-500/30 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:N===e.id?"Sending…":T.has(e.id)?"Sent ✓":"Send Newsletter"}),e.status==="active"?t.jsx("button",{type:"button",disabled:a,onClick:()=>S(e,"unsubscribed"),className:"px-2.5 py-1 text-xs bg-ocean-700 text-gray-200 hover:bg-ocean-600 rounded-md transition-colors disabled:opacity-50",children:"Deactivate"}):t.jsx("button",{type:"button",disabled:a||s,onClick:()=>S(e,"active"),title:s?"Clear Do-Not-Email first":"",className:"px-2.5 py-1 text-xs bg-ocean-700 text-jelly-300 hover:bg-ocean-600 rounded-md transition-colors disabled:opacity-30 disabled:cursor-not-allowed",children:"Reactivate"}),t.jsxs("label",{className:"inline-flex items-center gap-1.5 text-xs text-gray-300 cursor-pointer select-none",children:[t.jsx("input",{type:"checkbox",checked:s,disabled:a,onChange:r=>U(e,r.target.checked),className:"w-3.5 h-3.5 rounded border-ocean-600 bg-ocean-900 text-red-500 focus:ring-red-500"}),"Do not email"]})]})})]},e.id)})})]})})})]})}export{Y 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.