PageSourceSearch

https://todo.is/assets/AdminDashboard-BBsTKmvy.js

js todo.is collected 2026-10-07 01:32:06 UTC 10,420 bytes, 6 lines download raw bytes

1import{r as m,j as e,L as S}from"./react-vendor-DwPZuQ7h.js";import{u as D,i as P,a as _}from"./api-_3fDfxiX.js";import{C as g,E as U,P as B,B as k,N as E,a as h,S as j,b as T,d as N,e as O,f as I,u as H,g as W,I as $,M as z,F as q,T as Z,h as Q}from"./ui-xGr0oIbu.js";import{n,m as b,p as R,t as A,o as V}from"./format-D2EcCOkk.js";import{U as Y}from"./UserDrawer-G-TZ0svl.js";import{R as G}from"./refresh-cw-B_ri4qwG.js";import{c as J,L,az as K,T as X}from"./index-CxzsQ6aB.js";import"./search-CSWmN1Df.js";import"./chevron-left-DUImjSPl.js";import"./pencil-C84dZy9s.js";import"./rotate-cw-C42T4QQS.js";import"./square-BDCWAkgW.js";import"./i18n-vendor-7j51uk6n.js";/**
2 * @license lucide-react v0.563.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 ee=[["path",{d:"M20 18v-2a4 4 0 0 0-4-4H4",key:"5vmcpk"}],["path",{d:"m9 17-5-5 5-5",key:"nvlc11"}]],se=J("reply",ee),F=t=>new Date(`${t}T00:00:00Z`).toLocaleDateString("en-US",{month:"short",day:"numeric",timeZone:"UTC"});function je(){const{data:t,error:v,loading:r,refreshing:o,reload:x}=D("/api/admin/overview"),[i,c]=m.useState(null),[p,u]=m.useState(!1),a=t?.kpis,y=m.useMemo(()=>(t?.series||[]).map(s=>({label:F(s.date),value:s.signups})),[t]),f=m.useMemo(()=>(t?.series||[]).map(s=>({label:F(s.date),value:s.revenue})),[t]),w=(t?.plans||[]).reduce((s,d)=>s+d.count,0);return v&&!t?e.jsx(g,{children:e.jsx(U,{error:v,onRetry:x})}):e.jsxs("div",{className:"space-y-4",children:[e.jsx(B,{title:"Overview",description:"Last 30 days",actions:e.jsx(k,{icon:e.jsx(G,{size:13,className:o?"animate-spin":""}),onClick:x,disabled:o,children:"Refresh"})}),a&&a.legacyCreem>0&&e.jsx(E,{tone:"amber",title:`${a.legacyCreem} legacy Creem subscriber${a.legacyCreem===1?"":"s"} awaiting migration`,action:e.jsx(S,{to:"/admin/billing",children:e.jsx(k,{size:"sm",children:"Manage"})}),children:"These users must re-subscribe on Whop. Pause and notify them from Billing."}),e.jsxs("div",{className:"grid grid-cols-2 lg:grid-cols-4 gap-3",children:[e.jsx(h,{loading:r,label:"MRR (Whop)",value:b(a?.mrr,"USD",{compact:!0}),hint:`${n(a?.whopSubscribers)} active memberships`}),e.jsx(h,{loading:r,label:"Revenue · 30d",value:b(a?.revenue30d,"USD",{compact:!0}),delta:a?R(a.revenue30d,a.revenuePrev30d):null,hint:`${n(a?.orders30d)} completed orders`}),e.jsx(h,{loading:r,label:"Paid users",value:n(a?.paidUsers),hint:`of ${n(a?.totalUsers)} users`}),e.jsx(h,{loading:r,label:"Legacy Creem",value:n(a?.legacyCreem),hint:"awaiting Whop migration",highlight:a&&a.legacyCreem>0?"amber":void 0}),e.jsx(h,{loading:r,label:"New signups · 30d",value:n(a?.new30d),delta:a?R(a.new30d,a.newPrev30d):null,hint:`${n(a?.newToday)} today · ${n(a?.new7d)} this week`}),e.jsx(h,{loading:r,label:"Agents running",value:n(a?.runningAgents),hint:`of ${n(a?.totalAgents)} agents`}),e.jsx(h,{loading:r,label:"API spend · 30d",value:b(a?.apiSpend30d,"USD",{compact:!0}),hint:`${b(a?.apiSpendToday)} today`}),e.jsx(h,{loading:r,label:"Total users",value:n(a?.totalUsers),hint:`${n(a?.new7d)} joined this week`})]}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-3",children:[e.jsx(g,{title:"Signups",subtitle:"Daily, last 30 days",children:r?e.jsx(j,{className:"h-[122px]"}):e.jsx(T,{data:y,color:"#38bdf8"})}),e.jsx(g,{title:"Revenue",subtitle:"Completed orders, last 30 days",children:r?e.jsx(j,{className:"h-[122px]"}):e.jsx(T,{data:f,color:"#d4ff3a",format:s=>b(s)})})]}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-3",children:[e.jsx(g,{title:"Recent orders",className:"lg:col-span-2",flush:!0,actions:e.jsx(S,{to:"/admin/billing",className:"text-[12px] text-[var(--text-muted)] hover:text-[var(--text-primary)]",children:"All orders"}),children:r?e.jsx("div",{className:"p-4 space-y-3",children:Array.from({length:5}).map((s,d)=>e.jsx(j,{className:"h-8"},d))}):t?.recentOrders.length?e.jsx("ul",{className:"divide-y divide-[var(--border-subtle)]",children:t.recentOrders.map(s=>e.jsx("li",{children:e.jsxs("button",{type:"button",disabled:!s.user,onClick:()=>s.user&&c(s.user.id),className:"w-full flex items-center gap-3 px-4 py-2.5 text-left hover:bg-[var(--bg-elevated)] transition-colors",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("p",{className:"text-[12px] text-[var(--text-primary)] truncate",children:s.user?.email||"Deleted user"}),e.jsxs("p",{className:"text-[11px] text-[var(--text-muted)] truncate",children:[s.type==="credit_topup"?"Credit top-up":s.planName||s.type," · ",s.provider," · ",A(s.createdAt)]})]}),e.jsx(O,{tone:V(s.status),children:s.status}),e.jsx("span",{className:"w-16 text-right text-[12px] font-medium tabular-nums text-[var(--text-primary)]",children:b(s.amount,s.currency)})]})},s.id))}):e.jsx(N,{compact:!0,title:"No orders yet"})}),e.jsx(g,{title:"Plans",subtitle:"Users per plan",children:r?e.jsx("div",{className:"space-y-3",children:Array.from({length:4}).map((s,d)=>e.jsx(j,{className:"h-5"},d))}):t?.plans.length?e.jsx("ul",{className:"space-y-2.5",children:t.plans.map(s=>e.jsxs("li",{children:[e.jsxs("div",{className:"flex justify-between text-[12px] mb-1",children:[e.jsx("span",{className:"text-[var(--text-secondary)]",children:s.plan}),e.jsx("span",{className:"tabular-nums text-[var(--text-primary)]",children:n(s.count)})]}),e.jsx("div",{className:"h-1.5 rounded-full bg-zinc-500/15 overflow-hidden",children:e.jsx("div",{className:"h-full rounded-full bg-[var(--brand)]",style:{width:`${w?s.count/w*100:0}%`,opacity:s.plan==="Free"?.4:1}})})]},s.plan))}):e.jsx(N,{compact:!0,title:"No subscriptions yet"})})]}),e.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-3",children:[e.jsx(g,{title:"Recent signups",flush:!0,actions:e.jsx(S,{to:"/admin/users",className:"text-[12px] text-[var(--text-muted)] hover:text-[var(--text-primary)]",children:"All users"}),children:r?e.jsx("div",{className:"p-4 space-y-3",children:Array.from({length:5}).map((s,d)=>e.jsx(j,{className:"h-8"},d))}):t?.recentSignups.length?e.jsx("ul",{className:"divide-y divide-[var(--border-subtle)]",children:t.recentSignups.map(s=>e.jsx("li",{children:e.jsxs("button",{type:"button",onClick:()=>c(s.id),className:"w-full flex items-center gap-3 px-4 py-2.5 text-left hover:bg-[var(--bg-elevated)] transition-colors",children:[e.jsx(I,{name:s.full_name,email:s.email,size:26}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("p",{className:"text-[12px] text-[var(--text-primary)] truncate",children:s.email}),e.jsxs("p",{className:"text-[11px] text-[var(--text-muted)] truncate",children:[s.acquisition_source||"Direct",s.country?` · ${s.country}`:""]})]}),e.jsx("span",{className:"text-[11px] text-[var(--text-muted)] whitespace-nowrap",children:A(s.createdAt)})]})},s.id))}):e.jsx(N,{compact:!0,title:"No users yet"})}),e.jsx(g,{title:"Feedback & bugs",flush:!0,actions:t&&t.feedbackTotal>0?e.jsxs("button",{type:"button",onClick:()=>u(!0),className:"text-[12px] text-[var(--text-muted)] hover:text-[var(--text-primary)]",children:["View all (",n(t.feedbackTotal),")"]}):void 0,children:r?e.jsx("div",{className:"p-4 space-y-3",children:Array.from({length:4}).map((s,d)=>e.jsx(j,{className:"h-10"},d))}):t?.recentFeedback.length?e.jsx(M,{items:t.recentFeedback,onChanged:x}):e.jsx(N,{compact:!0,icon:e.jsx(L,{size:18}),title:"No feedback yet"})})]}),e.jsx(Y,{userId:i,onClose:()=>c(null),onChanged:()=>P("/api/admin/overview")}),e.jsx(ae,{open:p,onClose:()=>u(!1),onChanged:x})]})}function M({items:t,onChanged:v}){const r=_(),o=H(),x=W(),[i,c]=m.useState(null),[p,u]=m.useState(""),[a,y]=m.useState(!1),[f,w]=m.useState(new Set),s=async l=>{if(await x({title:"Delete feedback?",message:`From ${l.email}. This cannot be undone.`,confirmLabel:"Delete",danger:!0}))try{await r(`/api/admin/feedback/${l.id}`,{method:"DELETE"}),w(C=>new Set(C).add(l.id)),o("Feedback deleted"),P("/api/admin/feedback"),v()}catch(C){o(C.message,"error")}},d=async()=>
6{if(!(!i||!p.trim())){y(!0);try{await r(`/api/admin/feedback/${i.id}/reply`,{method:"POST",json:{message:p}}),o(`Reply sent to ${i.email}`),c(null),u("")}catch(l){o(l.message,"error")}finally{y(!1)}}};return e.jsxs(e.Fragment,{children:[e.jsx("ul",{className:"divide-y divide-[var(--border-subtle)]",children:t.filter(l=>!f.has(l.id)).map(l=>e.jsx("li",{className:"px-4 py-2.5 group",children:e.jsxs("div",{className:"flex items-start gap-2.5",children:[l.type==="bug"?e.jsx(K,{size:14,className:"text-red-400 mt-0.5 shrink-0"}):e.jsx(L,{size:14,className:"text-sky-400 mt-0.5 shrink-0"}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("p",{className:"text-[12px] text-[var(--text-primary)] line-clamp-3 whitespace-pre-wrap break-words",children:l.message}),e.jsxs("p",{className:"text-[11px] text-[var(--text-muted)] mt-0.5 truncate",children:[l.email," · ",A(l.createdAt)]})]}),e.jsxs("div",{className:"flex shrink-0",children:[e.jsx($,{label:"Reply",onClick:()=>{c(l),u("")},children:e.jsx(se,{size:13})}),e.jsx($,{label:"Delete",tone:"danger",onClick:()=>s(l),children:e.jsx(X,{size:13})})]})]})},l.id))}),e.jsxs(z,{open:!!i,onClose:()=>c(null),title:`Reply to ${i?.email||""}`,footer:e.jsxs(e.Fragment,{children:[e.jsx(k,{variant:"ghost",onClick:()=>c(null),children:"Cancel"}),e.jsx(k,{variant:"primary",loading:a,disabled:!p.trim(),onClick:d,children:"Send reply"})]}),children:[e.jsx("div",{className:"rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-deep)] px-3 py-2 text-[12px] text-[var(--text-muted)] mb-3 max-h-32 overflow-y-auto whitespace-pre-wrap",children:i?.message}),e.jsx(q,{label:"Your reply",hint:"Sent by email from [email protected]",children:e.jsx(Z,{rows:5,value:p,onChange:l=>u(l.target.value),autoFocus:!0})})]})]})}function ae({open:t,onClose:v,onChanged:r}){const[o,x]=m.useState(1),{data:i,error:c,loading:p,refreshing:u,reload:a}=D(t?`/api/admin/feedback?page=${o}&limit=20`:null);return e.jsx(z,{open:t,onClose:v,title:"Feedback & bug reports",width:640,children:e.jsx("div",{className:"-m-4",children:p?e.jsx("div",{className:"p-4 space-y-3",children:Array.from({length:6}).map((y,f)=>e.jsx(j,{className:"h-10"},f))}):c?e.jsx(U,{error:c,onRetry:a,compact:!0}):i?.items.length?e.jsxs(e.Fragment,{children:[e.jsx(M,{items:i.items,onChanged:()=>{a(),r()}}),e.jsx(Q,{page:i.page,totalPages:i.totalPages,total:i.total,onChange:x,busy:u})]}):e.jsx(N,{compact:!0,title:"No feedback"})})})}export{je 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.