PageSourceSearch

https://faithprompt.ai/assets/admin_.qa-CgRRc7YC.js

js faithprompt.ai collected 2026-10-05 14:02:45 UTC 9,039 bytes, 1 lines download raw bytes

1import{r as u,j as e,u as L,L as w}from"./index-F1TD4xr2.js";import{c as T,s as k,S as g,B as o}from"./SiteLayout-CFTNJqLL.js";import{S as E,B as b}from"./badge-DkYliox0.js";import{L as _}from"./loader-circle-BcHrH9LM.js";import{c as v}from"./createLucideIcon-4gHsqMwm.js";import{C as N}from"./circle-check-CncO6VTn.js";const z=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]],y=v("circle-x",z);const D=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],B=v("circle",D);const F=[["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"}]],O=v("rotate-ccw",F),C=u.forwardRef(({className:i,...a},r)=>e.jsx("textarea",{className:T("flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",i),ref:r,...a}));C.displayName="Textarea";const h=[{id:"refund",title:"Refund handling",description:"Refunding a transaction in Paddle marks the order as refunded and disables the download link.",steps:["In Paddle sandbox dashboard, refund a recent test transaction.","Wait ~30 seconds for the webhook.",'Refresh /admin — order should show "Refunded".',"Click the download link from that order — should show the expired/invalid page."]},{id:"admin-extend",title:"Admin: Extend",description:"The Extend button pushes the expiry out 7 days and issues a new token.",steps:["Go to /admin and pick a non-refunded order.","Click Extend.","Verify the expiry date jumps ~7 days into the future."]},{id:"admin-resend",title:"Admin: Resend",description:"The Resend button issues a new download link and emails it to the buyer.",steps:["In /admin, click Resend on a non-refunded order.","Confirm a fresh email arrives in the buyer inbox.","Verify the link in the email opens the PDF."]},{id:"admin-revoke",title:"Admin: Revoke",description:"The Revoke button immediately disables the download link.",steps:["In /admin, click Revoke on a test order.","Confirm the expiry sets to now and the order shows revoked.","Click the download link — should return the expired page."]},{id:"unsubscribe",title:"Unsubscribe + suppression",description:"Clicking the unsubscribe link in an email should suppress future sends to that address.",steps:["Open a recent delivery email and click the unsubscribe link in the footer.","Confirm the unsubscribe on the page.","Go to /resend and submit the same email.","You should see the generic success message but no email should arrive."]},{id:"rate-limit",title:"Resend rate limit",description:"Submitting /resend twice within 60 seconds should silently rate-limit the second request.",steps:["Go to /resend and submit your buyer email.","Immediately submit again (within 60 seconds).","Both should show the success message; only one email should be delivered."]},{id:"webhook-replay",title:"Webhook idempotency",description:"Replaying a transaction.completed webhook should not create a duplicate order or send a duplicate email.",steps:["In Paddle sandbox → Developer Tools → Notifications, find a recent transaction.completed.","Click Replay.","Refresh /admin — the order count for that transaction should not increase.","No duplicate email should arrive in the buyer inbox."]},{id:"link-expiry",title:"Link expiry",description:'An expired download token should return the "expired" page instead of the PDF.',steps:["Use the Revoke action on a test order (sets expiry to now).","Click the download link from the original email.",'Confirm you see the "Link expired" page (HTTP 410), not the PDF.']}],S="faithprompt-qa-checklist-v1";function l(){const i={};for(const a of h)i[a.id]={status:"pending",notes:"",updatedAt:null};return i}function q(){if(typeof window>"u")return l();try{const i=window.localStorage.getItem(S);if(!i)return l();const a=JSON.parse(i),r=l();for(const c of h)a[c.id]&&(r[c.id]=a[c.id]);return r}catch{return l()}}function G({status:i}){return i==="pass"?e.jsxs(b,{className:"bg-green-600 hover:bg-green-700 text-white",children:[e.jsx(N,{className:"w-3 h-3 mr-1"})," Pass"]}):i==="fail"?e.jsxs(b,{variant:"destructive",children:[e.jsx(y,{className:"w-3 h-3 mr-1"})," Fail"]}):e.jsxs(b,{variant:"secondary",children:[e.jsx(B,{className:"w-3 h-3 mr-1"})," Pending"]})}function W(){const i=L(),[a,r]=u.useState(!0),[c,R]=u.useState(!1),[d,j]=u.useState(()=>l());u.useEffect(()=>{let s=!0;return(async()=>{const{data:t}=await k.auth.getSession();if(!t.session){i({to:"/auth"});return}const{data:n}=await k.from("user_roles").select("role").eq("user_id",t.session.user.id),m=(n||[]).some(I=>I.role==="admin");
1s&&(R(m),r(!1),m&&j(q()))})(),()=>{s=!1}},[i]);function x(s){j(s);try{window.localStorage.setItem(S,JSON.stringify(s))}catch{}}function p(s,t){const n={...d,[s]:{...d[s],status:t,updatedAt:new Date().toISOString()}};x(n)}function A(s,t){const n={...d,[s]:{...d[s],notes:t,updatedAt:new Date().toISOString()}};x(n)}function P(){confirm("Reset all QA checklist progress?")&&x(l())}const f=h.reduce((s,t)=>(s[d[t.id]?.status||"pending"]++,s),{pending:0,pass:0,fail:0});return a?e.jsx(g,{children:e.jsx("div",{className:"flex items-center justify-center min-h-[40vh]",children:e.jsx(_,{className:"w-6 h-6 animate-spin text-muted-foreground"})})}):c?e.jsx(g,{children:e.jsxs("div",{className:"max-w-4xl mx-auto py-8 px-4",children:[e.jsxs("div",{className:"flex items-start justify-between mb-6 gap-4 flex-wrap",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-3xl font-semibold",children:"QA Checklist"}),e.jsx("p",{className:"text-muted-foreground mt-1",children:"Track end-to-end test results for the purchase + delivery flow. Progress is saved in this browser."})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(o,{variant:"outline",size:"sm",asChild:!0,children:e.jsx(w,{to:"/admin",children:"Back to Admin"})}),e.jsxs(o,{variant:"ghost",size:"sm",onClick:P,children:[e.jsx(O,{className:"w-4 h-4 mr-1"}),"Reset"]})]})]}),e.jsxs("div",{className:"grid grid-cols-3 gap-3 mb-6",children:[e.jsxs("div",{className:"rounded-lg border bg-card p-4",children:[e.jsx("div",{className:"text-xs text-muted-foreground uppercase tracking-wide",children:"Pending"}),e.jsx("div",{className:"text-2xl font-semibold mt-1",children:f.pending})]}),e.jsxs("div",{className:"rounded-lg border bg-card p-4",children:[e.jsx("div",{className:"text-xs text-muted-foreground uppercase tracking-wide",children:"Passed"}),e.jsx("div",{className:"text-2xl font-semibold mt-1 text-green-600",children:f.pass})]}),e.jsxs("div",{className:"rounded-lg border bg-card p-4",children:[e.jsx("div",{className:"text-xs text-muted-foreground uppercase tracking-wide",children:"Failed"}),e.jsx("div",{className:"text-2xl font-semibold mt-1 text-destructive",children:f.fail})]})]}),e.jsx("div",{className:"space-y-4",children:h.map(s=>{const t=d[s.id]||{status:"pending",notes:"",updatedAt:null};return e.jsxs("div",{className:"rounded-lg border bg-card p-5",children:[e.jsxs("div",{className:"flex items-start justify-between gap-4 mb-2 flex-wrap",children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsx("h2",{className:"text-lg font-semibold",children:s.title}),e.jsx(G,{status:t.status})]}),e.jsx("p",{className:"text-sm text-muted-foreground mt-1",children:s.description})]}),e.jsxs("div",{className:"flex gap-2",children:[e.jsxs(o,{size:"sm",variant:t.status==="pass"?"default":"outline",className:t.status==="pass"?"bg-green-600 hover:bg-green-700 text-white":"",onClick:()=>p(s.id,"pass"),children:[e.jsx(N,{className:"w-4 h-4 mr-1"}),"Pass"]}),e.jsxs(o,{size:"sm",variant:t.status==="fail"?"destructive":"outline",onClick:()=>p(s.id,"fail"),children:[e.jsx(y,{className:"w-4 h-4 mr-1"}),"Fail"]}),t.status!=="pending"&&e.jsx(o,{size:"sm",variant:"ghost",onClick:()=>p(s.id,"pending"),children:"Clear"})]})]}),e.jsx("ol",{className:"list-decimal list-inside text-sm space-y-1 mt-3 text-muted-foreground",children:s.steps.map((n,m)=>e.jsx("li",{children:n},m))}),e.jsxs("div",{className:"mt-4",children:[e.jsx(C,{placeholder:"Notes (optional) — paste error messages, transaction IDs, etc.",value:t.notes,onChange:n=>
1A(s.id,n.target.value),className:"min-h-[60px]"}),t.updatedAt&&e.jsxs("div",{className:"text-xs text-muted-foreground mt-1",children:["Last updated: ",new Date(t.updatedAt).toLocaleString()]})]})]},s.id)})})]})}):e.jsx(g,{children:e.jsxs("div",{className:"max-w-md mx-auto text-center py-16",children:[e.jsx(E,{className:"w-12 h-12 mx-auto text-muted-foreground mb-4"}),e.jsx("h1",{className:"text-2xl font-semibold mb-2",children:"Not authorized"}),e.jsx("p",{className:"text-muted-foreground mb-6",children:"You need admin access to view this page."}),e.jsx(o,{asChild:!0,children:e.jsx(w,{to:"/",children:"Go home"})})]})})}export{W 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.