PageSourceSearch

https://app.backstagebaton.com/app/assets/PaymentsPage-CIADDGes.js

js backstagebaton.com collected 2026-10-07 08:49:38 UTC 75,725 bytes, 68 lines download raw bytes

1import{c as U,r as n,j as e,B as w,M as ne,E as J,z as fe,G as C,S as V,p as Ct,K as Le,d as de,T as St,I as Ve,N as et,t as We,h as $t,A as Pt,a as Et,e as Tt}from"./index-_aijvlUq.js";import{P as Ot}from"./PageHeader-DCofr6qo.js";import{a as At}from"./Skeleton-B3Wj6cNd.js";import{I as _}from"./Input-ysTyZFq5.js";import{B as Rt}from"./Badge-DJIi163x.js";import{C as be}from"./ConfirmDialog-CODvETNM.js";import{p as j}from"./payments-BcDSbBQ_.js";import{E as Ne}from"./EmptyState-CPGKooqj.js";import{e as Dt}from"./equipment-BFd-7mSR.js";import{g as It}from"./stockInventory-CSFNe4Se.js";import{M as xe}from"./Menu-CATQC14B.js";import{b as tt,c as st,d as G,e as at,T as nt,a as Y}from"./Table-Yng-lqpt.js";import{d as Mt}from"./exportCsv-CZAUSrIW.js";import{M as Lt}from"./format-BheQoBt9.js";import{r as rt,R as zt}from"./RecipientRoles-Dnrpkk2i.js";import"./downscaleImage-qyGK-a3D.js";function Ft(t){const a=[];if(t.firstTime&&a.push(`${t.firstTime} member${t.firstTime===1?"":"s"} ${t.firstTime===1?"has":"have"} not been emailed about this yet and will get the first notice.`),t.promised&&a.push(`${t.promised} member${t.promised===1?"":"s"} said they were paying by cash or check and ${t.promised===1?"has":"have"} not been recorded yet — they get a softer check-in that acknowledges what they told you.`),t.reminders){const r=t.daysSinceLastReminder===0?"earlier today":t.daysSinceLastReminder===1?"yesterday":`${t.daysSinceLastReminder} days ago`;a.push(`${t.reminders} member${t.reminders===1?"":"s"} still ${t.reminders===1?"owes":"owe"} after being emailed — the most recent went out ${r}. They will get a reminder that assumes a payment may already be on its way.`)}return a.push("Every email includes what they owe for this, a personal pay-online link (no login needed), and your check instructions."),t.noEmail&&a.push(`${t.noEmail} member${t.noEmail===1?" has":"s have"} this charge but no email on file — reach them another way.`),a.push("Anyone who has already paid is not contacted."),a.join(" ")}function _t(t){return t.email?"Send emails":t.show?"Show on home page":"Notify members"}function Ut(t){const a=t.counts||{};return!t.show&&!t.email?"Tick at least one.":t.email?Ft(a)+(t.show&&a.noEmail?" Members with no email on file will still see it on their home page.":""):a.hiddenMembers?`${a.hiddenMembers} member${a.hiddenMembers===1?"":"s"} will see what they owe for this on their home page and their Dues & Payments page. No email is sent.`:"Everyone who owes this can already see it on their home page. No email is sent."}function Bt({product:t,visibility:a,onChanged:r}){const o=U(m=>m.showSuccess),c=U(m=>m.showError),[i,l]=n.useState(null),[f,h]=n.useState(!1),[g,b]=n.useState(null),[p,k]=n.useState(!1);if(!a?.owing)return null;const $=t.id;async function R(m){const T=await j.notifyChargedMembers({dryRun:!0,show:m.show||!m.email,email:m.email,productId:$});l({...m,counts:T})}async function O(){try{try{const m=await j.notifyChargedMembers({dryRun:!0,show:!0,email:!0,productId:$});l({show:!0,email:!0,emailPaused:!1,counts:m})}catch(m){if(m?.code!=="NOTIFICATIONS_PAUSED"&&m?.status!==409)throw m;const T=await j.notifyChargedMembers({dryRun:!0,show:!0,email:!1,productId:$});l({show:!0,email:!1,emailPaused:!0,counts:T})}}catch(m){c(m.message||"Could not check who owes right now.")}}async function A(m){const T={...i,[m]:!i[m]};if(!T.show&&!T.email){l(T);return}try{await R(T)}catch(N){c(N.message||"Could not check who owes right now.")}}async function u(){h(!0);try{const m=await j.notifyChargedMembers({show:i.show,email:i.email,productId:$}),T=[];if(i.email){const N=[];m.firstTime&&N.push(`${m.firstTime} first notice${m.firstTime===1?"":"s"}`),m.reminders&&N.push(`${m.reminders} reminder${m.reminders===1?"":"s"}`),T.push(`Emailed ${m.notified} member${m.notified===1?"":"s"}${N.length?` (${N.join(", ")})`:""}${m.noEmail?`; ${m.noEmail} had no email on file`:""}.`)}m.shown&&T.push(`${m.shown} member${m.shown===1?"":"s"} can now see ${t.name} on their home page.`),o(T.join(" ")||"Done."),l(null),r?.()}catch(m){c(m.message||"Sending failed.")}finally{h(!1)}}async function E(){try{const m=await j.hideCharges({dryRun:!0,productId:$});if(!m.hidden){o(`Members cannot see ${t.name} right now.`);return}b(m)}catch(m){c(m.message||J)}}async function S(){k(!0);try{const m=await j.hideCharges({productId:$});o(`${t.name} is hidden from ${m.hidden} member${m.hidden===1?"":"s"}. Nothing was sent.`),b(null),r?.()}catch(m){c(m.message||J)}finally{k(!1)}}const{owing:y,hiddenMembers:M,shownMembers:I}=a;return e.jsxs("div",{className:"mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm","data-testid":`product-visibility-${$}`,children:[M>0?e.jsx("p",{className:"text-amber-700",children:M===y?`Members can't see this yet · ${y} owe${y===1?"s":""}`:`${M} of ${y} members who owe can't see this yet`}):e.jsx("p",{className:"text-gray-600",children:`Showing on members' home page · ${y} still owe${y===1?"s":""}`}),e.jsx(w,{type:"button",variant:"secondary",size:"sm",onClick:O,children:"Notify members…"}),I>0&&e.jsx(w,{type:"button",variant:"ghost",size:"sm",onClick:E,children:"Hide from members"}),e.jsx(ne,{isOpen:!!i,onClose:()=>l(null),title:`Notify members: ${t.name}`,footer:i&&e.jsxs("div",{className:"flex gap-2",children:[e.jsx(w,{type:"button",variant:"primary",onClick:u,isLoading:f,disabled:!i.show&&!i.email,children:_t(i)}),e.jsx(w,{type:"button",variant:"secondary",onClick:()=>l(null),children:"Cancel"})]}),children:i&&e.jsxs("div",{className:"space-y-4 text-base text-gray-900","data-testid":"notify-dialog",children:[e.jsxs("p",{className:"text-sm text-gray-600",children:[i.counts.owing," member",i.counts.owing===1?" owes":"s owe"," for ",t.name,". Choose how they hear about it."]}),e.jsxs("fieldset",{className:"space-y-1",children:[e.jsx("legend",{className:"sr-only",children:"How members hear"}),e.jsxs("label",{className:"flex items-start gap-3 min-h-[2.75rem]",children:[e.jsx("input",{type:"checkbox",checked:i.show||i.email,disabled:i.email||f,onCh
1ange:()=>A("show"),className:"mt-1 rounded border-gray-300 text-band-600 focus:ring-band-500 h-5 w-5 shrink-0"}),e.jsxs("span",{children:["Show it on their home page, with a Pay now button",i.email&&e.jsx("span",{className:"block text-sm text-gray-500",children:"Anyone you email sees it there too."})]})]}),e.jsxs("label",{className:"flex items-start gap-3 min-h-[2.75rem]",children:[e.jsx("input",{type:"checkbox",checked:i.email,disabled:i.emailPaused||f,onChange:()=>A("email"),className:"mt-1 rounded border-gray-300 text-band-600 focus:ring-band-500 h-5 w-5 shrink-0"}),e.jsxs("span",{children:["Email them what they owe, with a link to pay",i.emailPaused&&e.jsx("span",{className:"block text-sm text-gray-500",children:"Emails to members are paused for the band right now."})]})]})]}),e.jsx("p",{className:"text-sm text-gray-600","data-testid":"notify-summary",children:Ut(i)})]})}),e.jsx(be,{isOpen:!!g,onClose:()=>b(null),onConfirm:S,isLoading:p,title:`Hide ${t.name} from members?`,message:g?`${g.hidden} member${g.hidden===1?"":"s"} will stop seeing what they owe for ${t.name} on their home page, their Dues & Payments page and in the Weekly digest. Nothing is sent, and nothing is removed. Anything already paid stays as it is. Use Notify members… to show it again.`:"",confirmLabel:"Hide charges",confirmVariant:"warning"})]})}const Ht={annual:12,semiannual:6,quarterly:3,monthly:1};function ze(t,a){const r=new Date(t),o=r.getUTCDate();return r.setUTCMonth(r.getUTCMonth()+a),r.getUTCDate()!==o&&r.setUTCDate(0),r}function Oe(t){return t.toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric",timeZone:"UTC"})}function it({period:t,seasonStart:a,today:r=new Date}){const o=Ht[t];if(!o)return null;const[c,i]=(a||"01-01").split("-").map(Number),l=new Date(Date.UTC(r.getUTCFullYear(),r.getUTCMonth(),r.getUTCDate()));let f=new Date(Date.UTC(l.getUTCFullYear(),c-1,i));f>l&&(f=new Date(Date.UTC(l.getUTCFullYear()-1,c-1,i)));let h=f,g=ze(f,o);for(;g<=l;)h=g,g=ze(h,o);const b=g,p=ze(b,o),k=$=>new Date($.getTime()-864e5);return{current:{start:h,end:g,label:`${Oe(h)} – ${Oe(k(g))}`},next:{start:b,end:p,label:`${Oe(b)} – ${Oe(k(p))}`},daysUntilNext:Math.round((b-l)/864e5)}}const ot=t=>!t||t==="01-01";function Be(t,a){if(t==null||t==="")return"";const r=Number(t);return ot(a)?String(r):`${r}-${String((r+1)%100).padStart(2,"0")}`}function lt(t,a){if(!t)return null;const r=Number(t.slice(0,4));return ot(a)||t.slice(5,10)>=a?r:r-1}function Wt(t=[]){const a=t.filter(r=>r.type==="dues");return(a.find(r=>r.active)||a[0])?.seasonStart||"01-01"}function Kt(t=[],{seasonStart:a="01-01",today:r}={}){const o=new Set;for(const i of t){const l=/^(\d{4})/.exec(String(i.periodKey||""));l&&o.add(Number(l[1]))}const c=lt(r,a);return c!=null&&o.add(c),[...o].sort((i,l)=>l-i).map(i=>({value:String(i),label:Be(i,a)}))}const qt=[{value:"dues",label:"Dues (recurring, prorated)"},{value:"fee",label:"Fee (one-time, e.g. trip or uniform fee)"},{value:"good",label:"Item for sale (e.g. polo, method book)"},{value:"donation",label:"Donation fund (member gives any amount)"}],Gt=[{value:"annual",label:"Annual"},{value:"semiannual",label:"Twice a year"},{value:"quarterly",label:"Quarterly"},{value:"monthly",label:"Monthly"}],Yt=[{value:"none",label:"No proration — everyone pays full price"},{value:"monthly",label:"By month joined"},{value:"quarterly",label:"By quarter joined"}],Vt=[{value:"org",label:"Organization absorbs processing fees"},{value:"member",label:"Member covers processing fees"}],Zt={dues:"bg-teal-100 text-teal-700",fee:"bg-amber-100 text-amber-700",good:"bg-indigo-100 text-indigo-700",donation:"bg-rose-100 text-rose-700"},Ze={name:"",type:"dues",priceDollars:"",description:"",feeAbsorption:"org",requiresNote:!1,noteLabel:"",stockGroup:"",period:"annual",prorationRule:"monthly",seasonStart:"01-01"};function Jt(t){return{name:t.name||"",type:t.type,priceDollars:(t.priceCents/100).toFixed(2),description:t.description||"",feeAbsorption:t.feeAbsorption||"org",requiresNote:!!t.requiresNote,noteLabel:t.noteLabel||"",stockGroup:t.stockGroup||"",period:t.period||"annual",prorationRule:t.prorationRule||"none",seasonStart:t.seasonStart||"01-01"}}function Qt(t){const a=parseFloat(t.priceDollars),r=Number.isFinite(a)?Math.round(a*100):0,o={name:t.name.trim(),type:t.type,priceCents:r,description:t.description.trim()||null,feeAbsorption:t.feeAbsorption,requiresNote:t.requiresNote,noteLabel:t.requiresNote&&t.noteLabel.trim()||null,stockGroup:t.type==="good"&&t.stockGroup||null};return t.type==="dues"&&(o.period=t.period,o.prorationRule=t.prorationRule,o.seasonStart=t.seasonStart),o}function Xt({canManage:t,onChargesChanged:a,onProductsLoaded:r,onVisibility:o,refreshKey:c=0}){const i=U(d=>d.showSuccess),l=U(d=>d.showError),[f,h]=n.useState([]),[g,b]=n.useState({}),[p,k]=n.useState([]),$=n.useCallback(()=>{Dt.stockGroups().then(d=>k(Array.isArray(d)?d:[])).catch(fe("stock groups are optional on a product"))},[]);n.useEffect(()=>{$()},[$]);const[R,O]=n.useState(!0),[A,u]=n.useState(!1),[E,S]=n.useState(null),[y,M]=n.useState(Ze),I=p.find(d=>d.name===y.stockGroup)||null,m=I?.costPerUnitCents??null,T=Number.isFinite(parseFloat(y.priceDollars))?Math.round(parseFloat(y.priceDollars)*100):null;let N=null;if(y.type==="good"&&y.stockGroup)if(m==null)N=`No cost recorded for ${I?.name||"this item"} yet — add Cost per unit on it under Equipment and the margin shows here.`;else{const d=C(m),D=I?.unitsPerPack>1?` (a pack of ${I.unitsPerPack} costs ${C(m*I.unitsPerPack)})`:"",te=I?.costMissingCount>0?` · ${I.costMissingCount} ${I.costMissingCount===1?"size has":"sizes have"} no cost recorded`:"",pe=T==null?"":T>=m?` · this price earns ${C(T-m)} each`:` · this price is ${C(m-T)} under cost`;N=`Costs the band ${d} each${D}${te}${pe}.`}const[K,B]=n.useState(!1),[x,L]=n.useState(null),[z,Q]=n.useState(null),[re,X]=n.useState(!0),[H,P]=n.useState(!0),[q,ie]=n.useState(!1),[ke,Z]=n.useState("current"),oe=n.useRef(null);async function me(){try{const d=await j.listProducts();h(d),r?.(d)}catch{l("Couldn't load products. Refresh to try again.")}finally{O(!1)}}n.useEffect(()=>{me()},[]);const le=n.useCallback(()=>{t&&j.chargeVisibility().then(d=>{b(d?.byProduct||{}),o?.(d?.byProduct||{})}).catch(fe("product visibility lines"))},[t,o]);n.useEffect(()=>{le()},[le,c]),n.useEffect(()=>{A&&oe.current?.scrollIntoView({behavior:"smooth",block:"nearest"})},[A]);function Ce(){$(),S(null),M(Ze),u(!0)}function ue(d){$(),S(d),M(Jt(d)),u(!0)}function he(){u(!1),S(null)}const W=d=>D=>{const te=D?.target?D.target.type==="checkbox"?D.target.checked:D.target.value:D;M(pe=>({...pe,[d]:te}))};async function Se(d){if(d.preventDefault(),!y.name.trim()||y.type!=="donation"&&!y.priceDollars)return l("Name and price are required.");B(!0);try{const D=Qt(y);E?(await j.updateProduct(E.id,D),i("Product updated.")):(await j.createProduct(D),i("Product created.")),he(),await me()}catch(D){l(D.message||J)}finally{B(!1)}}async function $e(){try{const d=await j.deleteProduct(x.id);i(d.deactivated?"Product deactivated — its payment history is preserved.":"Product deleted."),L(null),he(),await me()}catch(d){l(d.message||Ct),L(null)}}function F(d){const D=d.type==="dues"?it({period:d.period,seasonStart:d.seasonStart}):null;Z(D&&D.daysUntilNext<=60?"next":"current"),Q(d),j.getSettings().then(te=>{X(te?.digestDuesReminder!==!1),P(te?.weeklyEmailOn!==!1)}).catch(fe("assess dialog: reminder setting"))}async function ce(){ie(!0);try{const d=await j.assessProduct(z.id,z.type==="dues"?{targetPeriod:ke}:{}
1),D=d.periodKey?` for ${d.periodKey}`:"";i(d.created?`Charged ${d.created} member${d.created===1?"":"s"}${D}.${d.skipped?` ${d.skipped} already charged.`:""}`:"No new charges — everyone is already charged."),Q(null),await me(),le(),a?.()}catch(d){l(d.message||"Couldn’t assess this product."),Q(null)}finally{ie(!1)}}return e.jsxs("div",{children:[t&&!A&&e.jsx("div",{className:"flex justify-end mb-3",children:e.jsx(w,{variant:"primary",size:"sm",onClick:Ce,children:"New product"})}),A&&e.jsxs("form",{ref:oe,onSubmit:Se,className:"bg-gray-50 rounded-xl border border-gray-200 p-6 mb-4",children:[e.jsx("h3",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide mb-4",children:E?"Edit Product":"New Product"}),e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-4",children:[e.jsx(_,{label:"Name",value:y.name,onChange:W("name"),placeholder:"Annual Dues",required:!0}),e.jsx(V,{label:"Type",value:y.type,onChange:W("type"),options:qt,disabled:!!E}),e.jsxs("div",{children:[e.jsx(_,{label:y.type==="donation"?"Suggested amount (USD, optional)":"Price (USD)",type:"number",min:"0",step:"0.01",value:y.priceDollars,onChange:W("priceDollars"),placeholder:y.type==="donation"?"25.00":"120.00",required:y.type!=="donation"}),N&&e.jsx("p",{className:"mt-1 text-xs text-gray-600","data-testid":"cost-of-goods",children:N})]}),e.jsx(V,{label:"Processing fees",value:y.feeAbsorption,onChange:W("feeAbsorption"),options:Vt}),y.type==="good"&&e.jsx(V,{label:"Draws from stock",value:y.stockGroup,onChange:W("stockGroup"),hint:"a sale takes one off the shelf",options:[{value:"",label:"Not stocked"},...p.map(d=>({value:d.name,label:`${d.name} (${It(d.rows).inStock} in stock)`}))]}),y.type==="dues"&&e.jsxs(e.Fragment,{children:[e.jsx(V,{label:"Billing period",value:y.period,onChange:W("period"),options:Gt}),e.jsx(V,{label:"Proration for mid-season joins",value:y.prorationRule,onChange:W("prorationRule"),options:Yt}),e.jsx(_,{label:"Season starts (MM-DD)",value:y.seasonStart,onChange:W("seasonStart"),placeholder:"09-01",hint:"e.g. 09-01 for a September-to-August season"})]}),e.jsx("div",{className:"sm:col-span-2",children:e.jsx(_,{label:"Description (optional)",value:y.description,onChange:W("description"),placeholder:"Covers music, venue, and insurance for the season"})}),e.jsxs("div",{className:"sm:col-span-2 flex items-center gap-3 flex-wrap",children:[e.jsxs("label",{className:"flex items-center gap-2 text-sm text-gray-700",children:[e.jsx("input",{type:"checkbox",checked:y.requiresNote,onChange:W("requiresNote"),className:"rounded border-gray-300 text-band-600 focus:ring-band-500"}),"Ask the member a question at purchase"]}),y.requiresNote&&e.jsx("div",{className:"flex-1 min-w-[12rem]",children:e.jsx(_,{value:y.noteLabel,onChange:W("noteLabel"),placeholder:y.type==="good"&&y.stockGroup?'e.g. "Name to embroider"':'e.g. "Uniform size"',"aria-label":"Question label"})}),y.type==="good"&&y.stockGroup&&e.jsx("p",{className:"basis-full text-xs text-gray-500",children:"The size is picked from what is on the shelf when it sells, so there is no need to ask for it here. Use a question only for something else, like a name to embroider."})]})]}),e.jsxs("div",{className:"flex gap-2 pt-4",children:[e.jsx(w,{type:"submit",variant:"primary",size:"md",isLoading:K,children:E?"Save changes":"Create"}),e.jsx(w,{type:"button",variant:"secondary",size:"md",onClick:he,children:"Cancel"}),E&&e.jsx("div",{className:"ml-auto",children:e.jsx(w,{type:"button",variant:"danger",size:"md",onClick:()=>L(E),children:"Delete"})})]})]}),R?e.jsx("p",{className:"text-sm text-gray-400",children:"Loading…"}):f.length===0&&!A?e.jsx(Ne,{title:"No products yet.",description:"Create your first product — annual dues is the usual place to start.",action:t?e.jsx(w,{variant:"primary",size:"md",onClick:Ce,children:"New product"}):null}):e.jsx("ul",{className:"divide-y divide-gray-100",children:f.map(d=>e.jsxs("li",{className:"py-3 flex flex-wrap items-center gap-3",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"font-medium text-gray-900 truncate",children:d.name}),e.jsx("span",{className:`rounded-full px-2 py-0.5 text-xs font-medium ${Zt[d.type]||"bg-gray-100 text-gray-600"}`,children:d.type==="good"?"item":d.type}),!d.active&&e.jsx(Rt,{color:"gray",children:"Inactive"})]}),e.jsxs("p",{className:"text-sm text-gray-500",children:[C(d.priceCents),d.type==="dues"&&` · ${d.peri
1od}${d.prorationRule&&d.prorationRule!=="none"?", prorated":""}`,d.chargeCount>0&&` · ${d.chargeCount} charge${d.chargeCount===1?"":"s"}`]}),t&&(d.type==="dues"||d.type==="fee")&&e.jsx(Bt,{product:d,visibility:g[d.id],onChanged:()=>{le(),a?.()}})]}),t&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[d.active&&(d.type==="dues"||d.type==="fee")&&e.jsx(w,{variant:g[d.id]?.owing>0?"ghost":"secondary",size:"sm",onClick:()=>F(d),children:d.type==="dues"?"Assess dues…":"Charge members"}),e.jsx(w,{variant:"ghost",size:"sm",onClick:()=>ue(d),children:"Edit"})]})]},d.id))}),e.jsx(be,{isOpen:!!x,onClose:()=>L(null),onConfirm:$e,title:"Delete product?",message:x?.chargeCount>0?`"${x?.name}" has payment history, so it will be deactivated instead of deleted — records are preserved.`:`Delete "${x?.name}"? This can't be undone.`,confirmLabel:x?.chargeCount>0?"Deactivate":"Delete"}),e.jsx(be,{isOpen:!!z,onClose:()=>Q(null),onConfirm:ce,isLoading:q,title:z?.type==="dues"?"Assess dues?":"Add this charge for every member?",message:e.jsxs(e.Fragment,{children:[z?.type==="dues"?e.jsx(ts,{product:z,value:ke,onChange:Z}):`Every active musician will owe ${z?C(z.priceCents):""} for "${z?.name}". Members already charged are skipped.`,e.jsx(es,{reminderOn:re&&H})]}),confirmLabel:"Add the charges",confirmVariant:"primary"})]})}function es({reminderOn:t}){return e.jsxs("div",{className:"mt-3 rounded-lg border border-gray-200 bg-gray-50 p-3 text-left text-sm text-gray-700","data-testid":"nothing-sent-note",children:[e.jsx("p",{className:"font-medium text-gray-900",children:"No email or text is sent."}),e.jsx("p",{children:"This only adds what each member owes to their account. Members don't see it yet."}),e.jsx("p",{className:"mt-1",children:"When you are ready, press Notify members… on this product to show it on their home page with a Pay now button, email them, or both."}),t&&e.jsx("p",{className:"mt-1",children:"Once they can see it, members who owe also get a reminder in their weekly email (the Weekly digest). You can turn that off under Weekly email reminder."})]})}function ts({product:t,value:a,onChange:r}){const o=it({period:t.period,seasonStart:t.seasonStart}),c=t.prorationRule&&t.prorationRule!=="none",i=[{key:"next",title:`Upcoming period — ${o?o.next.label:"next season"}`,detail:`Everyone is charged the full ${C(t.priceCents)}.${c?" Members who join after it starts are prorated when you re-assess.":""}`},{key:"current",title:`Current period — ${o?o.current.label:"this season"}`,detail:c?"Members who joined mid-period are prorated by join date.":`Everyone is charged the full ${C(t.priceCents)}.`}];return e.jsxs("div",{className:"space-y-3 text-left",children:[e.jsx("p",{className:"text-sm text-gray-600",children:"Which period is this assessment for?"}),e.jsx("div",{className:"space-y-2",children:i.map(l=>e.jsxs("label",{className:`flex items-start gap-2.5 rounded-lg border p-3 cursor-pointer transition-colors ${a===l.key?"border-band-500 bg-band-50":"border-gray-200 hover:border-gray-300"}`,children:[e.jsx("input",{type:"radio",name:"assess-period",checked:a===l.key,onChange:()=>r(l.key),className:"mt-0.5 border-gray-300 text-band-600 focus:ring-band-500"}),e.jsxs("span",{children:[e.jsx("span",{className:"block text-sm font-medium text-gray-800",children:l.title}),e.jsx("span",{className:"block text-xs text-gray-500 mt-0.5",children:l.detail})]})]},l.key))}),e.jsx("p",{className:"text-xs text-gray-400",children:"Members already charged for the selected period are skipped, so re-running is safe after adding members."})]})}const ss=new Set(["of","the","and","&","de","la","le","du","a","an","for","at"]);function as(t){const a=String(t||"").trim().split(/[\s-]+/).filter(Boolean);if(a.length===0)return"";const r=a.filter(c=>!ss.has(c.toLowerCase())),o=r.length?r:a;return o.length===1?o[0].replace(/[^A-Za-z0-9]/g,"").slice(0,3).toUpperCase():o.map(c=>c[0]).join("").replace(/[^A-Za-z0-9]/g,"").slice(0,5).toUpperCase()}function Fe(t=[]){const a=t.map(r=>typeof r=="string"?r:r?.name).filter(Boolean);return{short:a.map(as).join(", "),full:a.join(", ")}}const ns={owed:"bg-amber-100 text-amber-700",processing:"bg-blue-100 text-blue-700",paid:"bg-green-100 text-green-700",waived:"bg-gray-100 text-gray-500"},rs=[{value:"",label:"All statuses"},{value:"owed",label:"Owed"},{value:"owed_no_promise",label:"Owed — no promise"},{value:"promised_cash",label:"Promised — cash"},{value:"promised_check",label:"Promised — check"},{value:"processing",label:"Processing"},{value:"paid",label:"Paid"},{value:"paid_cash",label:"Paid — cash"},{value:"paid_check",label:"Paid — check"},{value:"paid_card",label:"Paid — card"},{value:"paid_ach",label:"Paid — bank (ACH)"},{value:"waived",label:"Waived"}],is=new Set(["owed_no_promise","promised_cash","promised_check"]),Je=new Set(["paid_cash","paid_check","paid_card","paid_ach"]),os={paid_cash:"cash",paid_check:"check",paid_card:"stripe_card",paid_ach:"stripe_ach"};function Ae({label:t,cents:a,tone:r="text-gray-900"}){return e.jsxs("div",{className:"bg-white rounded-xl border border-gray-200 p-4 text-center",children:[e.jsx("p",{className:`text-xl font-bold ${r}`,children:C(a)}),e.jsx("p",{className:"text-xs text-gray-400 mt-1",children:t})]})}function _e(t){if(!t)return{ok:!1,line:""};const a=t.memberName||"this member",r=He(t.memberName);return t.proxyEmail&&t.proxyMode==="both"&&t.memberEmail?{ok:!0,line:`Sends to ${a} (${t.memberEmail}) and ${t.proxyName||"their proxy"} (${t.proxyEmail}), who gets ${r}’s mail.`}:t.proxyEmail?{ok:!0,line:`Sends to ${t.proxyName||"their proxy"} (${t.proxyEmail}) on ${a}’s behalf`}:t.memberEmail?{ok:!0,line:`Sends to ${t.memberEmail}`}:{ok:!1,line:"No email on file — print a statement instead."}}const He=t=>(t||"").split(" ")[0]||"there";
1function ls({firstName:t,promisedMethod:a,hasDues:r,season:o}){return a==="check"?`Hi ${t}, thanks for letting me know a check is on the way — just a note that it hasn't reached me yet. No rush if it's in the mail.`:a==="cash"?`Hi ${t}, thanks for letting me know that cash is coming — just a note that it hasn't reached me yet. No rush.`:`Hi ${t}, a quick reminder that ${r?"your dues are still open":"there’s still a balance open"}${o?` for ${o}`:""}. Pay online with the button below, or bring a check to rehearsal. Thanks!`}function cs({canManage:t,products:a=[],refreshKey:r=0,onChanged:o}){const c=U(s=>s.showSuccess),i=U(s=>s.showError),[l,f]=n.useState([]),[h,g]=n.useState(null),[b,p]=n.useState(!0),k=n.useRef(!1),[$,R]=n.useState(""),[O,A]=n.useState(""),[u,E]=n.useState(null),[S,y]=n.useState({method:"cash",checkNumber:"",note:"",amountReceived:"",receivedAt:""}),[M,I]=n.useState(!1),[m,T]=n.useState(null),[N,K]=n.useState({amount:"",reason:""}),[B,x]=n.useState(!1),[L,z]=n.useState(null),[Q,re]=n.useState(""),[X,H]=n.useState(!1),[P,q]=n.useState(null),[ie,ke]=n.useState(""),[Z,oe]=n.useState(null),[me,le]=n.useState(!1),[Ce,ue]=n.useState(!1),[he,W]=n.useState(""),[Se,$e]=n.useState(!0),[F,ce]=n.useState(null),[d,D]=n.useState(""),[te,pe]=n.useState(!1),se=n.useCallback(async()=>{k.current||p(!0);try{const s=is.has(O)?"owed":Je.has(O)?"paid":O,v=await j.listCharges({...$?{productId:$}:{},...s?{status:s}:{}});f(v.charges),g(v.totals)}catch{i("Couldn't load charges. Refresh to try again.")}finally{p(!1),k.current=!0}},[$,O,i]);n.useEffect(()=>{se()},[se,r]);function ct(s){E(s),y({method:s.promisedMethod==="check"?"check":"cash",checkNumber:"",note:"",amountReceived:(s.amountCents/100).toFixed(2),receivedAt:Le()})}function dt(s){T(s),K({amount:(s.amountCents/100).toFixed(2),reason:""})}async function mt(s){s.preventDefault(),x(!0);try{const v=Math.round(parseFloat(N.amount)*100);await j.refundPayment(m.paymentId,{amountCents:v,reason:N.reason||null}),c(`Refunded ${C(v)} to ${m.memberName}.`),T(null),await se(),o?.()}catch(v){i(v?.message||J)}finally{x(!1)}}async function ut(s){s.preventDefault(),I(!0);try{const v=parseFloat(S.amountReceived),ee=await j.recordPayment(u.id,{method:S.method,checkNumber:S.method==="check"?S.checkNumber:null,note:S.note||null,...Number.isFinite(v)?{amountReceivedCents:Math.round(v*100)}:{},receivedAt:S.receivedAt||null});c(ee.donation?`Recorded ${S.method} from ${u.memberName} — extra ${C(ee.donation.amountCents)} logged as a donation.`:`Recorded ${S.method} payment from ${u.memberName}.`),E(null),await se(),o?.()}catch(v){i(v.message||"Couldn’t record the payment.")}finally{I(!1)}}async function ht(s){s.preventDefault(),H(!0);try{await j.waiveCharge(L.id,{reason:Q||null}),c(`Waived for ${L.memberName}.`),z(null),re(""),await se(),o?.()}catch(v){i(v.message||"Couldn’t waive the charge.")}finally{H(!1)}}async function pt(s){try{await j.unwaiveCharge(s.id),c(`${s.memberName} owes again.`),await se(),o?.()}catch(v){i(v.message||"Couldn’t unwaive the charge.")}}async function xt(){try{await j.deleteCharge(P.id),c("Charge removed."),q(null),await se(),o?.()}catch(s){i(s.message||"Couldn’t remove the charge."),q(null)}}const Pe=Wt(a),Ie=Kt(l,{seasonStart:Pe,today:Le()}),Ee=s=>{const v=/^\d{4}/.exec(String(s.periodKey||""))?.[0];if(v)return v;const ee=lt(String(s.paidAt||s.createdAt||"").slice(0,10),Pe);return ee!=null?String(ee):null};function Ke(s){Ve(j.statements.printUrl(s.memberId,{season:Ee(s)}))}async function gt(){le(!0);try{const s=await j.statements.email(Z.memberId,{season:Ee(Z)});if(s?.sent)c(`Statement emailed to ${Z.memberName}.`),oe(null);else if(s?.reason==="no_email"){const v=Z.memberName;oe(null),i(`${v} has no email on file — print the statement instead.`)}else i(J)}catch(s){i(s?.message||J)}finally{le(!1)}}const ve=s=>l.filter(v=>v.memberId===s&&v.status==="owed"&&v.productType!=="donation");function yt(s){const v=ve(s.memberId),ee=v.find(Me=>Me.productType==="dues")||null,Ye=ee?Ee(ee):null,Nt=Ye?Be(Ye,Pe):"",kt=v.map(Me=>Me.promisedMethod).find(Boolean)||null;D(ls({firstName:He(s.memberName),promisedMethod:kt,hasDues:!!ee,season:Nt})),ce(s)}const qe=F&&ve(F.memberId).map(s=>s.promisedMethod).find(Boolean)||null;async function ft(){pe(!0);try{const s=await j.remindMember(F.memberId,{message:d});c(`Reminder sent to ${(s?.to||[]).join(" and ")}`),ce(null),await se(),o?.()}catch(s){i(s?.message||J)}finally{pe(!1)}}function bt(){W(Ie[0]?.value||""),$e(!0),ue(!0)}function vt(){Ve(j.statements.everyoneUrl({season:he||void 0,status:Se?"owed":"all"})),ue(!1)}const Te=l.filter(s=>!(O==="owed_no_promise"&&s.promisedMethod||O==="promised_cash"&&s.promisedMethod!=="cash"||O==="promised_check"&&s.promisedMethod!=="check"||Je.has(O)&&s.paidMethod!==os[O]||ie&&!(s.memberName||"").toLowerCase().includes(ie.trim().toLowerCase()))),jt=[{value:"",label:"All products"},...a.map(s=>({value:s.id,label:s.name}))],Ge=$||O||ie;function wt(){Mt(`charges-${Le()}.csv`,["Member","Ensembles","Last Performed","Product","Period","Amount","Status","Paid Method","Promised Method","Waive Reason"],Te.map(s=>[s.memberName,Fe(s.ensembles).full,s.lastPerformedOn||"",s.productName,s.periodKey||"",(s.amountCents/100).toFixed(2),s.status,s.paidMethod==="str
1ipe_card"?"card":s.paidMethod==="stripe_ach"?"ACH":s.paidMethod?s.paidMethod+(s.paidCheckNumber?` #${s.paidCheckNumber}`:""):"",s.promisedMethod||"",s.waivedReason||""]))}return e.jsxs("div",{className:"bg-white rounded-xl border border-gray-200 p-6",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 mb-4",children:[e.jsx("h3",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide",children:"Charges"}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(_,{value:ie,onChange:s=>ke(s.target.value),placeholder:"Search by name...","aria-label":"Search charges by member name",className:"w-44"}),e.jsx(V,{value:$,onChange:s=>R(s.target.value),options:jt,"aria-label":"Filter by product"}),e.jsx(V,{value:O,onChange:s=>A(s.target.value),options:rs,"aria-label":"Filter by status"}),Te.length>0&&e.jsx(w,{variant:"secondary",size:"sm",onClick:wt,children:"Export CSV"}),l.length>0&&e.jsx(w,{variant:"ghost",size:"sm",onClick:bt,children:"Statements for everyone"})]})]}),h&&h.count>0&&e.jsxs("div",{className:"grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4",children:[e.jsx(Ae,{label:"Outstanding",cents:h.owedCents,tone:"text-amber-600"}),e.jsx(Ae,{label:"Processing",cents:h.processingCents,tone:"text-blue-600"}),e.jsx(Ae,{label:"Collected",cents:h.paidCents,tone:"text-green-600"}),e.jsx(Ae,{label:"Waived",cents:h.waivedCents,tone:"text-gray-500"})]}),b?e.jsx("p",{className:"text-sm text-gray-400",children:"Loading…"}):Te.length===0?e.jsx(Ne,{title:Ge?"No charges match these filters.":"No charges yet.",description:Ge?void 0:"Create a product above, then use its Assess dues or Charge members button."}):e.jsx("div",{className:"overflow-x-auto",children:e.jsxs(tt,{children:[e.jsxs(st,{children:[e.jsx(G,{children:"Member"}),e.jsx(G,{children:"Ensembles"}),e.jsx(G,{children:"Last performed"}),e.jsx(G,{children:"Product"}),e.jsx(G,{children:"Period"}),e.jsx(G,{className:"text-right",children:"Amount"}),e.jsx(G,{children:"Status"}),e.jsx(G,{className:"text-right",children:"Actions"})]}),e.jsx(at,{children:Te.map(s=>e.jsxs(nt,{children:[e.jsx(Y,{className:"font-medium text-gray-900",children:s.memberName}),e.jsx(Y,{className:"text-gray-700",title:Fe(s.ensembles).full||void 0,"data-testid":`ensembles-${s.id}`,children:Fe(s.ensembles).short||"—"}),e.jsx(Y,{className:"text-gray-700 tabular-nums","data-testid":`last-performed-${s.id}`,children:s.lastPerformedOn?de(s.lastPerformedOn):"Never"}),e.jsx(Y,{children:s.productName}),e.jsx(Y,{className:"text-gray-500",children:s.periodKey||"—"}),e.jsx(Y,{className:"text-right tabular-nums",children:C(s.amountCents)}),e.jsxs(Y,{children:[e.jsx("span",{className:`rounded-full px-2 py-0.5 text-xs font-medium ${ns[s.status]||"bg-gray-100 text-gray-600"}`,children:s.status}),s.status==="owed"&&s.promisedMethod&&e.jsxs("span",{className:"ml-1.5 text-xs text-gray-400",children:["promised ",s.promisedMethod]}),s.status==="owed"&&s.notifiedAt&&e.jsxs("span",{className:"ml-1.5 text-xs text-gray-400","data-testid":`notified-${s.id}`,children:["emailed ",de(s.notifiedAt)]})]}),e.jsx(Y,{className:"text-right",children:e.jsxs("div",{className:"flex justify-end gap-1",children:[t&&(s.status==="owed"||s.status==="processing")&&e.jsx(w,{variant:"ghost",size:"sm",onClick:()=>ct(s),children:"Record payment"}),t&&s.status==="owed"&&e.jsx(w,{variant:"ghost",size:"sm",onClick:()=>z(s),children:"Waive"}),t&&s.status==="waived"&&e.jsx(w,{variant:"ghost",size:"sm",onClick:()=>pt(s),children:"Unwaive"}),e.jsxs(xe,{align:"right",menuId:`charge-menu-${s.id}`,trigger:e.jsx(w,{variant:"ghost",size:"sm",className:"min-h-[2.75rem] min-w-[2.75rem]","aria-label":`More for ${s.memberName}`,children:"…"}),children:[t&&ve(s.memberId).length>0?e.jsx(xe.Item,{onClick:()=>yt(s),children:"Email reminder…"}):null,t&&s.status==="paid"&&(s.paidMethod==="cash"||s.paidMethod==="check")&&s.paymentId?e.jsx(xe.Item,{onClick:()=>dt(s),children:"Refund…"}):null,t&&s.status!=="paid"?e.jsx(xe.Item,{onClick:()=>q(s),children:"Remove"}):null,e.jsx(xe.Item,{onClick:()=>Ke(s),children:"Print statement"}),t?e.jsx(xe.Item,{onClick:()=>oe(s),children:"Email statement"}):null]})]})})]},s.id))})]})}),e.jsx(ne,{isOpen:!!u,onClose:()=>E(null),title:"Record payment",size:"sm",children:e.jsxs("form",{onSubmit:ut,className:"space-y-4",children:[e.jsxs("p",{className:"text-sm text-gray-600",children:[u?.memberName," · ",u?.productName," ·"," ",e.jsx("span",{className:"font-semibold",children:u?C(u.amountCents):""})]}),e.jsx(V,{label:"Method",value:S.method,onChange:s=>y(v=>({...v,method:s.target.value})),options:[{value:"cash",label:"Cash"},{value:"check",label:"Check"}]}),e.jsx(_,{label:"Received on",type:"date",value:S.receivedAt,onChange:s=>y(v=>({...v,receivedAt:s.target.value})),hint:"Date on the check"}),e.jsx(_,{label:"Amount received (USD)",type:"number",min:"0",step:"0.01",value:S.amountReceived,onChange:s=>y(v=>({...v,amountReceived:s.target.value})),hint:u&&parseFloat(S.amountReceived)*100>u.amountCents?`The extra ${C(Math.round(parseFloat(S.amountReceived)*100)-u.amountCents)} will be recorded as a donation.`:void 0}),S.method==="check"&&e.jsx(_,{label:"Check number (optional)",value:S.checkNumber,onChange:s=>y(v=>({...v,checkNumber:s.target.value})),placeholder:"1042"}),e.jsx(_,{label:"Note (optional)",value:S.note,onChange:s=>y(v=>({...v,note:s.target.value})),placeholder:"Handed to Kevin at rehearsal"}),e.jsxs("div",{className:"flex gap-2 pt-2",children:[e.jsx(w,{type:"submit",variant:"primary",size:"md",isLoading:M,children:"Record payment"}),e.jsx(w,{type:"button",variant:"secondary",size:"md",onClick:()=>E(null),children:"Cancel"})]})]})}),e.jsx(ne,{isOpen:!!m,onClose:()=>T(null),title:"Refund payment",size:"sm",children:e.jsxs("form",{onSubmit:mt,className:"space-y-4",children:[e.jsxs("p",{className:"text-sm text-gray-600",children:[m?.memberName," · ",m?.productName," · paid by ",Lt[m?.paidMethod]||m?.paidMethod,m?.paidCheckNumber?` #${m.paidCheckNumber}`:""," ·"," ",e.jsx("span",{className:"font-semibold",children:m?C(m.amountCents):""})]}),e.jsx(_,{label:"Amount to refund (USD)",type:"number",min:"0.01",step:"0.01",value:N.amount,onChange:s=>K(v=>({...v,amount:s.target.value})),hint:"A full refund puts the charge back to owed; a partial one leaves it paid."}),e.jsx(_,{label:"Reason (optional)",value:N.reason,onChange:s=>K(v=>({...v,reason:s.target.value})),placeholder:"Overpaid, moved away, board vote 7/9",maxLength:200}),e.jsx("p",{className:"text-xs text-gray-500",children:"Card and bank payments are refunded from your Str
1ipe dashboard; the refund shows up here on its own."}),e.jsxs("div",{className:"flex gap-2 pt-2",children:[e.jsx(w,{type:"submit",variant:"primary",size:"md",isLoading:B,disabled:!(parseFloat(N.amount)>0),children:"Refund"}),e.jsx(w,{type:"button",variant:"secondary",size:"md",onClick:()=>T(null),children:"Cancel"})]})]})}),e.jsx(ne,{isOpen:!!L,onClose:()=>z(null),title:"Waive charge",size:"sm",children:e.jsxs("form",{onSubmit:ht,className:"space-y-4",children:[e.jsxs("p",{className:"text-sm text-gray-600",children:["Waive ",L?C(L.amountCents):""," for ",L?.memberName,"? They won't be reminded and it won't count as outstanding."]}),e.jsx(_,{label:"Reason (optional)",value:Q,onChange:s=>re(s.target.value),placeholder:"Financial hardship, board vote 7/9"}),e.jsxs("div",{className:"flex gap-2 pt-2",children:[e.jsx(w,{type:"submit",variant:"primary",size:"md",isLoading:X,children:"Waive"}),e.jsx(w,{type:"button",variant:"secondary",size:"md",onClick:()=>z(null),children:"Cancel"})]})]})}),e.jsx(be,{isOpen:!!Z,onClose:()=>oe(null),onConfirm:gt,isLoading:me,title:"Email statement?",message:Z?`Email ${Z.memberName} their ${Be(Ee(Z)||Ie[0]?.value,Pe)} statement — what they were charged, what they paid, and any balance with their personal pay link. Replies come to you.`:"",confirmLabel:"Send",confirmVariant:"primary"}),e.jsx(ne,{isOpen:!!F,onClose:()=>ce(null),title:F?`Email reminder to ${F.memberName}`:"Email reminder",size:"md",footer:e.jsxs("div",{className:"flex gap-2",children:[_e(F).ok?e.jsx(w,{type:"button",variant:"primary",size:"md",onClick:ft,isLoading:te,children:"Send reminder"}):e.jsx(w,{type:"button",variant:"primary",size:"md",onClick:()=>{Ke(F),ce(null)},children:"Print statement"}),e.jsx(w,{type:"button",variant:"secondary",size:"md",onClick:()=>ce(null),children:"Cancel"})]}),children:F&&e.jsxs("div",{className:"space-y-4",children:[e.jsx("p",{className:`text-sm ${_e(F).ok?"text-gray-600":"text-amber-700"}`,"data-testid":"remind-recipient",children:_e(F).line}),qe&&e.jsx("p",{className:"text-sm text-gray-500 -mt-2","data-testid":"remind-promise",children:qe==="check"?"Promised a check":"Promised cash"}),e.jsx(St,{label:"Your message",rows:5,value:d,onChange:s=>D(s.target.value),maxLength:2e3}),e.jsxs("div",{children:[e.jsx("h3",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2",children:"What rides along"}),e.jsxs("ul",{className:"text-sm text-gray-700 space-y-1",children:[ve(F.memberId).map(s=>e.jsxs("li",{className:"flex justify-between gap-3",children:[e.jsxs("span",{children:[s.productName,s.periodKey?` · ${s.periodKey}`:""]}),e.jsx("span",{className:"tabular-nums",children:C(s.amountCents)})]},s.id)),e.jsxs("li",{className:"flex justify-between gap-3 border-t border-gray-200 pt-1 font-semibold",children:[e.jsx("span",{children:"Total"}),e.jsx("span",{className:"tabular-nums",children:C(ve(F.memberId).reduce((s,v)=>s+v.amountCents,0))})]}),e.jsxs("li",{className:"text-gray-500 pt-1",children:["Pay online button — ",He(F.memberName),"’s own link, no login"]}),e.jsx("li",{className:"text-gray-500",children:"Check instructions"})]})]})]})}),e.jsx(ne,{isOpen:Ce,onClose:()=>ue(!1),title:"Statements for everyone",size:"sm",children:e.jsxs("div",{className:"space-y-4",children:[e.jsx(V,{label:"Season",value:he,onChange:s=>W(s.target.value),options:Ie}),e.jsxs("label",{className:"flex items-center gap-2 text-sm text-gray-700 min-h-[44px] cursor-pointer",children:[e.jsx("input",{type:"checkbox",className:"h-4 w-4 rounded border-gray-300 text-band-600 focus:ring-band-500",checked:Se,onChange:s=>$e(s.target.checked)}),"Only members who still owe"]}),e.jsx("p",{className:"text-xs text-gray-500",children:"One page per member, with their balance and personal pay link. No fees appear on a statement."}),e.jsxs("div",{className:"flex gap-2 pt-2",children:[e.jsx(w,{type:"button",variant:"primary",size:"md",onClick:vt,children:"Print"}),e.jsx(w,{type:"button",variant:"secondary",size:"md",onClick:()=>ue(!1),children:"Cancel"})]})]})}),e.jsx(be,{isOpen:!!P,onClose:()=>q(null),onConfirm:xt,title:"Remove charge?",message:`Remove the ${P?C(P.amountCents):""} charge for ${P?.memberName}
1? Use this for mistaken assessments — it can't be undone.`,confirmLabel:"Remove"})]})}const Qe="payments.item_purchased",ds={fetch:()=>j.fetchPurchaseRecipients(),save:(t,a)=>j.savePurchaseRecipients(t,a)};function ms({canManage:t,refreshKey:a=0,onCount:r,onRecipients:o}){const{showSuccess:c,showError:i}=U(),[l,f]=n.useState(null),[h,g]=n.useState(null),[b,p]=n.useState(!1),[k,$]=n.useState(null),R=et(u=>u.roleLabels)||{},O=n.useCallback(async()=>{try{f(await j.listHandOut())}catch{i(We("items to hand out"))}},[i]);n.useEffect(()=>{O()},[O,a]),n.useEffect(()=>{l&&r?.(l.length)},[l,r]),n.useEffect(()=>{j.fetchPurchaseRecipients().then(u=>$((u.events||[]).find(E=>E.eventKey===Qe)?.roles||[])).catch(fe("who gets the purchase email"))},[]),n.useEffect(()=>{k&&o?.(k)},[k,o]);async function A(u){g(u.chargeId);try{await j.markHandedOver(u.chargeId,!0),f(E=>(E||[]).filter(S=>S.chargeId!==u.chargeId)),c(`${u.item} marked handed over to ${u.memberName}.`,{action:{label:"Undo",fn:()=>j.markHandedOver(u.chargeId,!1).then(()=>f(E=>[...(E||[]).filter(S=>S.chargeId!==u.chargeId),u])).catch(E=>i(E?.message||J))}})}catch(E){i(E?.message||J)}finally{g(null)}}return l?e.jsxs("div",{className:"space-y-3","data-testid":"hand-out-panel",children:[l.length===0?e.jsx(Ne,{size:"compact",title:"Nothing to hand out."}):e.jsx("ul",{className:"divide-y divide-gray-100",children:l.map(u=>e.jsxs("li",{className:"py-3 flex flex-wrap items-center gap-3","data-testid":`hand-out-${u.chargeId}`,children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("p",{className:"text-base text-gray-900",children:[e.jsx("span",{className:"font-medium",children:u.memberName})," · ",u.item]}),e.jsxs("p",{className:"text-sm text-gray-500",children:[[C(u.amountCents),u.method?`paid by ${u.method}`:"paid",u.paidAt?de(u.paidAt):null].filter(Boolean).join(" · "),u.note?` · “${u.note}”`:""]})]}),t&&e.jsx(w,{type:"button",variant:"secondary",size:"sm",isLoading:h===u.chargeId,onClick:()=>A(u),children:"Mark handed over"})]},u.chargeId))}),e.jsxs("div",{className:"border-t border-gray-100 pt-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-x-3 gap-y-1",children:[e.jsxs("p",{className:"text-base text-gray-900","data-testid":"purchase-recipients-line",children:["When a member buys something, an email goes to: ",e.jsx("span",{className:"font-medium",children:k?rt(k,R):"…"})]}),e.jsx(w,{type:"button",variant:"ghost",size:"sm","aria-expanded":b,onClick:()=>p(u=>!u),children:b?"Done":"Change"})]}),b&&e.jsx("div",{className:"mt-2",children:e.jsx(zt,{eventKey:Qe,canManage:t,api:ds,intro:"When a member pays for an item, everyone you tick gets one email saying who bought what, so it gets handed over.",readOnlyNote:"Only people who manage payments can change this.",onChange:$})})]})]}):null}function us(){return new Promise(t=>{if(window.Chart){t(window.Chart);return}const a=document.createElement("script");a.src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js",a.onload=()=>t(window.Chart),document.head.appendChild(a)})}const hs=[{key:"stripe_ach",label:"Bank transfer (ACH)",color:"#0D9488"},{key:"stripe_card",label:"Card",color:"#6366F1"},{key:"check",label:"Check",color:"#D97706"},{key:"cash",label:"Cash",color:"#9333EA"}];function De(t,a){return a?Math.round(t/a*100):0}function je({label:t,value:a,sub:r,meterPct:o=null,tone:c="text-gray-900"}){return e.jsxs("div",{className:"bg-white rounded-xl border border-gray-200 p-4",children:[e.jsx("p",{className:"text-xs text-gray-400",children:t}),e.jsx("p",{className:`text-2xl font-bold mt-1 ${c}`,children:a}),r&&e.jsx("p",{className:"text-xs text-gray-500 mt-0.5",children:r}),o!=null&&e.jsx("div",{className:"mt-2 h-2 rounded-full bg-teal-100 overflow-hidden",children:e.jsx("div",{className:"h-full rounded-full bg-teal-600 transition-all",style:{width:`${Math.min(100,o)}%`}})})]})}function ps({methodBreakdown:t}){const a=n.useRef(null),r=n.useRef(null),o=hs.map(i=>({...i,...t[i.key]||{count:0,amountCents:0}})).filter(i=>i.count>0);if(n.useEffect(()=>{if(!(!a.current||o.length===0))return us().then(i=>
1{r.current&&r.current.destroy(),r.current=new i(a.current,{type:"bar",data:{labels:o.map(l=>l.label),datasets:[{data:o.map(l=>l.amountCents/100),backgroundColor:o.map(l=>l.color),borderRadius:4,barThickness:18}]},options:{indexAxis:"y",responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:!1},tooltip:{callbacks:{label:l=>{const f=o[l.dataIndex];return` ${C(f.amountCents)} · ${f.count} payment${f.count===1?"":"s"}`}}}},scales:{x:{ticks:{callback:l=>`$${l}`},grid:{color:"rgba(0,0,0,0.05)"}},y:{grid:{display:!1}}}}})}),()=>{r.current&&(r.current.destroy(),r.current=null)}},[JSON.stringify(t)]),o.length===0)return e.jsx(Ne,{size:"compact",title:"No payments recorded yet."});const c=o.reduce((i,l)=>i+l.count,0);return e.jsxs("div",{children:[e.jsx("div",{style:{height:`${Math.max(o.length*40,80)}px`},children:e.jsx("canvas",{ref:a})}),e.jsx("div",{className:"flex flex-wrap gap-x-4 gap-y-1 mt-2",children:o.map(i=>e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-gray-600",children:[e.jsx("span",{className:"h-2 w-2 rounded-full",style:{backgroundColor:i.color}}),i.label,": ",i.count," (",De(i.count,c),"%)"]},i.key))})]})}const Xe=600,we=120,ge=6,Ue=t=>Date.UTC(Number(t.slice(0,4)),Number(t.slice(5,7))-1,Number(t.slice(8,10)))/864e5;function xs({timeline:t=[],expectedCents:a=0}){if(!t||t.length<2)return null;const r=t[0],o=t[t.length-1],c=Math.max(a||0,o.cumulativeCents,1),i=Ue(r.weekStart),l=Math.max(Ue(o.weekStart)-i,1),f=R=>ge+(Ue(R.weekStart)-i)/l*(Xe-ge*2),h=R=>we-ge-R/c*(we-ge*2),b=`M${t.map(R=>`${f(R).toFixed(1)},${h(R.cumulativeCents).toFixed(1)}`).join(" L")}`,p=`${b} L${f(o).toFixed(1)},${we-ge} L${f(r).toFixed(1)},${we-ge} Z`,k=`Collected over time: ${C(o.cumulativeCents)} by the week of ${de(o.weekStart)}, starting the week of ${de(r.weekStart)}${a?` (${De(o.cumulativeCents,a)}% of ${C(a)} expected)`:""}.`,$=a>o.cumulativeCents?`${C(a)} expected`:`${C(o.cumulativeCents)} collected`;return e.jsxs("figure",{className:"mt-4","data-testid":"collected-line",children:[e.jsxs("div",{className:"flex items-baseline justify-between text-xs text-gray-500",children:[e.jsxs("span",{children:["Collected over time ",e.jsxs("span",{className:"text-teal-700 font-medium tabular-nums",children:["· ",C(o.cumulativeCents)]})]}),e.jsx("span",{className:"tabular-nums","data-testid":"collected-line-top",children:$})]}),e.jsxs("svg",{role:"img","aria-label":k,viewBox:`0 0 ${Xe} ${we}`,preserveAspectRatio:"none",className:"block w-full h-[120px] mt-1",children:[e.jsx("path",{d:p,className:"fill-teal-600/10"}),e.jsx("path",{d:b,className:"fill-none stroke-teal-600",strokeWidth:"2",vectorEffect:"non-scaling-stroke",strokeLinejoin:"round",strokeLinecap:"round"})]}),e.jsxs("figcaption",{className:"flex justify-between text-xs text-gray-500 mt-1 tabular-nums",children:[e.jsx("span",{children:de(r.weekStart)}),e.jsx("span",{children:de(o.weekStart)})]})]})}const ae=t=>t==null?"—":C(t);function gs({byProduct:t=[]}){return t.length===0?null:e.jsxs("div",{children:[e.jsx("div",{className:"hidden md:block","data-testid":"by-product-table",children:e.jsxs(tt,{children:[e.jsxs(st,{children:[e.jsx(G,{children:"Product"}),e.jsx(G,{className:"text-right",children:"Assessed"}),e.jsx(G,{className:"text-right",children:"Collected"}),e.jsx(G,{className:"text-right",children:"Waived"}),e.jsx(G,{className:"text-right",children:"Outstanding"})]}),e.jsx(at,{children:t.map(a=>{const r=a.type==="donation";return e.jsxs(nt,{className:r?"text-gray-400":"text-gray-700",children:[e.jsxs(Y,{className:"whitespace-normal",children:[a.name,r&&e.jsx("span",{className:"ml-1.5 text-xs",children:"donations · not part of expected"})]}),e.jsx(Y,{className:"text-right tabular-nums",children:ae(a.assessedCents)}),e.jsx(Y,{className:`text-right tabular-nums ${r?"":"text-teal-700 font-medium"}`,children:ae(a.collectedCents)}),e.jsx(Y,{className:"text-right tabular-nums",children:ae(a.waivedCents)}),e.jsx(Y,{className:`text-right tabular-nums ${!r&&a.outstandingCents>0?"text-amber-600 font-medium":""}`,children:ae(a.outstandingCents)})]},a.productId)})})]})}),e.jsx("ul",{className:"md:hidden divide-y divide-gray-100","data-testid":"by-product-list",children:t.map(a=>{const r=a.type==="donation";return e.jsxs("li",{className:`py-2 text-sm ${r?"text-gray-400":"text-gray-700"}`,children:[e.jsxs("p",{className:"font-medium",children:[a.name,r&&e.jsx("span",{className:"ml-1.5 text-xs font-normal",children:"donations"})]}),e.jsxs("dl",{className:"grid grid-cols-2 gap-x-3 gap-y-0.5 mt-1 text-xs",children:[e.jsx("dt",{children:"Assessed"}),e.jsx("dd",{className:"text-right tabular-nums",children:ae(a.assessedCents)}),e.jsx("dt",{children:"Collected"}),e.jsx("dd",{className:"text-right tabular-nums",children:ae(a.collectedCents)}),e.jsx("dt",{children:"Waived"}),e.jsx("dd",{className:"text-right tabular-nums",children:ae(a.waivedCents)}),e.jsx("dt",{children:"Outstanding"}),e.jsx("dd",{className:"text-right tabular-nums",children:ae(a.outstandingCents)})]})]},a.productId)})})]})}function ys({aging:t}){const a=[{key:"fresh",label:"Waiting 0–14 days",color:"#FCD34D"},{key:"aging",label:"15–30 days",color:"#F59E0B"},{key:"overdue",label:"Over 30 days",color:"#B45309"}],r=Math.max(...a.map(c=>t[c.key]?.count||0),1);return a.reduce((c,i)=>c+(t[i.key]?.count||0),0)===0?null:e.jsxs("div",{className:"bg-white rounded-xl border border-gray-200 p-6",children:[e.jsx("h4",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3",children:"Still outstanding"}),e.jsx("div",{className:"space-y-2",children:a.map(c=>{const i=t[c.key]||{count:0,amountCents:0};return e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"w-36 shrink-0 text-xs text-gray-600",children:c.label}),e.jsx("div",{className:"flex-1 h-4 bg-gray-50 rounded overflow-hidden",children:e.jsx("div",{className:"h-full rounded",style:{width:`${i.count/r*100}%`,backgroundColor:c.color}})}),e.jsx("span",{className:"w-28 shrink-0 text-xs text-gray-600 text-right tabular-nums",children:i.count>0?`${i.count} · ${C(i.amountCents)}`:"—"})]},c.key)})}),t.overdue?.count>0&&e.jsxs("p",{className:"text-xs text-amber-700 mt-3",children:[t.overdue.count," member",t.overdue.count===1?" has":"s have"," owed for over a month — a good week for a friendly reminder."]})]})}function fs({products:t=[],refreshKey:a=0}
1){const r=U(N=>N.showError),[o,c]=n.useState(null),[i,l]=n.useState(""),[f,h]=n.useState("");if(n.useEffect(()=>{j.getStats({...i?{productId:i}:{},...f?{season:f}:{}}).then(c).catch(()=>r("Couldn't load payment statistics."))},[i,f,a]),!o)return null;const{totals:g,membersTotal:b,membersPaid:p,methodBreakdown:k,timeline:$,aging:R,byProduct:O=[],seasons:A=[]}=o,u=o.season||"all",E=o.currentSeason||"all";if(!(g.expectedCents>0||g.waivedCents>0||g.donationsCents>0||A.length>1))return null;const y=De(g.paidCents,g.expectedCents),M=De(p,b),I=[{value:"",label:"All products"},...t.map(N=>({value:N.id,label:N.name}))],m=A.map(N=>({value:N.key,label:N.key!=="all"&&N.key===o.currentSeason?`${N.label} (current)`:N.label})),T=A.find(N=>N.key===u)?.label||u;return e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[e.jsxs("h3",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide",children:["Progress ",e.jsxs("span",{className:"normal-case tracking-normal font-normal text-gray-400",children:["· ",T]})]}),e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[m.length>1&&e.jsx(V,{value:u,onChange:N=>h(N.target.value),options:m,active:u!==E,"aria-label":"Season"}),t.length>1&&e.jsx(V,{value:i,onChange:N=>l(N.target.value),options:I,active:!!i,"aria-label":"Filter statistics by product"})]})]}),e.jsxs("div",{className:`grid grid-cols-2 gap-3 ${g.donationsCents>0?"lg:grid-cols-5":"lg:grid-cols-4"}`,children:[e.jsx(je,{label:"Collected",value:C(g.paidCents),sub:`${y}% of ${C(g.expectedCents)} expected`,meterPct:y,tone:"text-teal-700"}),e.jsx(je,{label:"Members paid up",value:`${p} of ${b}`,sub:`${M}% of charged members`,meterPct:M,tone:"text-teal-700"}),e.jsx(je,{label:"Outstanding",value:C(g.owedCents+g.processingCents),sub:g.processingCents>0?`${C(g.processingCents)} processing`:"awaiting payment",tone:"text-amber-600"}),e.jsx(je,{label:"Waived",value:C(g.waivedCents),sub:`${o.counts.waived||0} charge${(o.counts.waived||0)===1?"":"s"}`,tone:"text-gray-500"}),g.donationsCents>0&&e.jsx(je,{label:"Donations",value:C(g.donationsCents),sub:`${o.counts.donations||0} gift${(o.counts.donations||0)===1?"":"s"} — kept separate from dues`,tone:"text-rose-600"})]}),e.jsxs("div",{className:"bg-white rounded-xl border border-gray-200 p-6",children:[e.jsx("h4",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3",children:"By product"}),O.length>0?e.jsx(gs,{byProduct:O}):e.jsx(Ne,{size:"compact",title:"No charges this season."}),e.jsx(xs,{timeline:$,expectedCents:g.expectedCents})]}),e.jsxs("div",{className:"bg-white rounded-xl border border-gray-200 p-6",children:[e.jsx("h4",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3",children:"How members are paying"}),e.jsx(ps,{methodBreakdown:k})]}),e.jsx(ys,{aging:R})]})}function bs({canManage:t,onPrintLabels:a}){const r=U(p=>p.showSuccess),o=U(p=>p.showError),[c,i]=n.useState(null),[l,f]=n.useState({checkPayableTo:"",checkInstructions:""}),[h,g]=n.useState(!1);if(n.useEffect(()=>{j.getSettings().then(p=>{i(p),f({checkPayableTo:p.checkPayableTo||"",checkInstructions:p.checkInstructions||""})}).catch(fe("check-instructions panel is auxiliary; the page still works"))},[]),!c)return null;async function b(p){p.preventDefault(),g(!0);try{const k=await j.updateSettings({checkPayableTo:l.checkPayableTo.trim()||null,checkInstructions:l.checkInstructions.trim()||null});i(k),r("Check & cash instructions saved.")}catch(k){o(k.message||J)}finally{g(!1)}}return e.jsxs("div",{children:[e.jsxs("div",{className:"flex flex-wrap items-start justify-between gap-3 mb-4",children:[e.jsx("p",{className:"text-sm text-gray-500",children:"Shown to members who choose to pay by check or cash — everywhere they see the option."}),a&&e.jsx(w,{type:"button",variant:"secondary",size:"sm",onClick:a,children:"Print envelope labels"})]}),t?e.jsxs("form",{onSubmit:b,className:"grid grid-cols-1 sm:grid-cols-2 gap-4",children:[e.jsx(_,{label:"Make checks payable to",value:l.checkPayableTo,onChange:p=>f(k=>({...k,checkPayableTo:p.target.value})),placeholder:c.effectivePayableTo||"Your organization’s legal name",hint:l.checkPayableTo?void 0:`Blank uses your organization name: “${c.effectivePayableTo}”`}),e.jsx(_,{label:"How to get it to you",value:l.checkInstructions,onChange:p=>f(k=>({...k,checkInstructions:p.target.value})),placeholder:"Hand to Kevin at rehearsal, or mail to PO Box 123, Denver CO 80202"}),e.jsx("div",{className:"sm:col-span-2",children:e.jsx(w,{type:"submit",variant:"primary",size:"md",isLoading:h,children:"Save changes"})})]}):e.jsxs("p",{className:"text-sm text-gray-700",children:["Checks payable to ",e.jsx("span",{className:"font-medium",children:c.effectivePayableTo}),c.checkInstructions?` — ${c.checkInstructions}`:""]})]})}function vs({canManage:t=!1}){const{showSuccess:a,showError:r}=U(),[o,c]=n.useState(null),[i,l]=n.useState(!0),[f,h]=n.useState(!1);if(n.useEffect(()=>{let b=!0;return j.getSettings().then(p=>{b&&(c(p?.digestDuesReminder!==!1),l(p?.weeklyEmailOn!==!1))}).catch(()=>{b&&r(We("the weekly email reminder setting"))}),()=>{b=!1}},[]),o===null)return null;async function g(){const b=!o;c(b),h(!0);try{await j.updateSettings({digestDuesReminder:b}),a(b?i?"Members who owe will see a reminder in their weekly email.":"Saved. The reminder will start once your band sends a weekly email.":"The weekly email will no longer remind members who owe.")}catch(p){c(!b),r(p?.message||J)}finally{h(!1)}}return e.jsx("div",{className:"space-y-1","data-testid":"dues-reminder-setting",children:e.jsxs("label",{className:"flex items-start gap-3 min-h-[2.75rem] text-base text-gray-900",children:[e.jsx("input",{type:"checkbox",checked:o,disabled:!t||f,onCh
1ange:g,className:"mt-1 rounded border-gray-300 text-band-600 focus:ring-band-500 h-5 w-5 shrink-0"}),e.jsxs("span",{children:["Remind members who owe in the weekly email",e.jsx("span",{className:"block text-sm text-gray-600",children:"Members who owe see a short note in the weekly email (the Weekly digest, set up under Communications) with what they owe and a link to pay. Adding charges never sends an email by itself."}),!i&&e.jsx("span",{className:"block text-sm text-gray-600",children:"Your band doesn’t send a weekly digest yet, so this does nothing for now."})]})]})})}function js({isOpen:t,onClose:a,products:r=[]}){const o=$t(p=>p.currentOrg?.name)||"Your Organization",c=n.useMemo(()=>r.filter(p=>p.type!=="donation").map(p=>({value:p.id,label:p.name})),[r]),[i,l]=n.useState(""),h=r.find(p=>p.id===(i||c[0]?.value))?.name||"Dues";function g(){window.print()}const b=e.jsxs("div",{className:"env-label",children:[e.jsx("div",{className:"env-org",children:o}),e.jsx("div",{className:"env-product",children:h}),e.jsxs("div",{className:"env-line",children:[e.jsx("span",{className:"env-field",children:"Name:"}),e.jsx("span",{className:"env-rule"})]}),e.jsxs("div",{className:"env-line",children:[e.jsx("span",{className:"env-field",children:"Amount:"}),e.jsx("span",{className:"env-dollar",children:"$"}),e.jsx("span",{className:"env-rule short"})]}),e.jsxs("div",{className:"env-checks",children:[e.jsxs("span",{className:"env-check",children:[e.jsx("span",{className:"env-box"})," Cash"]}),e.jsxs("span",{className:"env-check",children:[e.jsx("span",{className:"env-box"})," Check"]}),e.jsxs("span",{className:"env-check",children:[e.jsx("span",{className:"env-box"})," Other: ",e.jsx("span",{className:"env-rule tiny"})]})]}),e.jsxs("div",{className:"env-line",children:[e.jsx("span",{className:"env-field",children:"Date received:"}),e.jsx("span",{className:"env-rule short"})]}),e.jsx("div",{className:"env-brand",children:"Powered by Backstage Baton · backstagebaton.com"})]});return e.jsxs(e.Fragment,{children:[e.jsxs(ne,{isOpen:t,onClose:a,title:"Print Envelope Labels",children:[e.jsxs("p",{className:"text-sm text-gray-600 mb-4",children:["Prints a sheet of 6 collection labels on ",e.jsx("strong",{children:"Avery 5164"})," (3⅓″ × 4″) label stock. Affix one to each envelope as board members collect payments — name, amount, method, and date travel with the money to the treasurer. Print as many sheets as you need."]}),e.jsx("div",{className:"mb-4",children:e.jsx(V,{label:"Product on the label",value:i||c[0]?.value||"",onChange:p=>l(p.target.value),options:c.length?c:[{value:"",label:"Dues"}]})}),e.jsxs("div",{className:"flex gap-2 pt-2",children:[e.jsx(w,{type:"button",variant:"primary",onClick:g,children:"Print sheet"}),e.jsx(w,{type:"button",variant:"ghost",onClick:a,children:"Close"})]})]}),t&&Pt.createPortal(e.jsx("div",{id:"envelope-labels-print","aria-hidden":"true",children:Array.from({length:6},(p,k)=>e.jsx("div",{className:"env-cell",style:{top:`${.5+Math.floor(k/2)*3.3333}in`,left:`${.15625+k%2*4.1875}in`},children:b},k))}),document.body),e.jsx("style",{children:`
2        #envelope-labels-print { display: none; }
3        @media print {
4          @page { size: letter; margin: 0; }
5          /* The sheet is portaled to <body> as a sibling of #root — hide
6             EVERYTHING else entirely (display:none removes layout height,
7             so no phantom page 2; visibility tricks left height behind). */
8          body > *:not(#envelope-labels-print) { display: none !important; }
9          html, body { margin: 0; padding: 0; }
10          #envelope-labels-print {
11            display: block;
12            position: absolute;
13            left: 0; top: 0;
14            width: 8.5in; height: 11in;
15            overflow: hidden;
16          }
17          .env-cell {
18            position: absolute;
19            width: 4in; height: 3.3333in;
20            padding: 0.28in 0.3in;
21            box-sizing: border-box;
22            overflow: hidden;
23          }
24          .env-label {
25            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
26            color: #1a1a1a;
27            height: 100%;
28            display: flex;
29            flex-direction: column;
30            justify-content: space-between;
31          }
32          .env-org {
33            font-size: 9pt; font-weight: 600; letter-spacing: 0.06em;
34            text-transform: uppercase; color: #2e6b8a;
35          }
36          .env-product {
37            font-size: 17pt; font-weight: 700; color: #1a1a1a;
38            margin: 1pt 0 4pt;
39          }
40          .env-line {
41            display: flex; align-items: flex-end; gap: 5pt;
42            font-size: 10.5pt; margin: 0 0 2pt;
43          }
44          .env-field { white-space: nowrap; color: #374151; }
45          .env-dollar { font-size: 11pt; color: #374151; }
46          .env-rule {
47            flex: 1; border-bottom: 1pt solid #6b7280; height: 13pt;
48          }
49          .env-rule.short { max-width: 1.6in; }
50          .env-rule.tiny { display: inline-block; width: 0.7in; height: 11pt; border-bottom: 1pt solid #6b7280; vertical-align: bottom; }
51          .env-checks {
52            display: flex; gap: 12pt; align-items: center;
53            font-size: 10.5pt; color: #374151; margin: 3pt 0;
54          }
55          .env-check { display: flex; align-items: center; gap: 4pt; white-space: nowrap; }
56          .env-box {
57            display: inline-block; width: 11pt; height: 11pt;
58            border: 1.25pt solid #374151; border-radius: 2pt;
59          }
60          .env-brand {
61            font-size: 6.5pt; color: #9ca3af;
62            text-align: right; letter-spacing: 0.03em;
63            border-top: 0.75pt solid #e5e7eb; padding-top: 2pt; margin-top: 2pt;
64          }
65        }
66      `})]})}function ws({canManage:t,refreshKey:a=0}){const r=U(x=>x.showSuccess),o=U(x=>x.showError),[c,i]=n.useState(null),[l,f]=n.useState(""),[h,g]=n.useState(null),[b,p]=n.useState(!1),[k,$]=n.useState(""),[R,O]=n.useState(!1),[A,u]=n.useState(null),[E,S]=n.useState(!1),[y,M]=n.useState(!1);if(n.useEffect(()=>{j.getDonationStatementStatus().then(x=>{i(x),$(x.ein||""),x.years?.length&&!l&&f(String(x.years[0]))}).catch(fe("donation statements panel is auxiliary; the page still works"))},[a]),n.useEffect(()=>{if(!l||!c?.attested){g(null);return}p(!0),j.getDonationStatements(Number(l)).then(g).catch(()=>o("Couldn't load donation statements.")).finally(()=>p(!1))},[l,c?.attested]),!c)return null;if(!c.years?.length&&!c.attested)return e.jsx("p",{className:"text-sm text-gray-500",children:"Once donations are recorded, year-end donor statements appear here."});async function I(){O(!0);try{const x=await j.attestTaxExempt({ein:k.trim()||null});i(x),r("Attestation recorded — donor letters unlocked.")}catch(x){o(x.message||"Couldn’t save the attestation.")}finally{O(!1)}}async function m(){M(!0);try{const x=await j.sendDonationStatements(Number(l));r(`Emailed ${x.sent} statement${x.sent===1?"":"s"}.`+(x.noEmail.length?` ${x.noEmail.length} donor${x.noEmail.length===1?" has":"s have"} no email — use Print All for them.`:"")),S(!1)}catch(x){o(x.message||"Couldn’t send the statements."),S(!1)}finally{M(!1)}}function T(){const x=window.open("","_blank");if(!x)return o("Allow pop-ups to print statements.");x.document.write(`<!doctype html><html><head><title>${h.year} Donation Statements — ${h.org.name}</title>
67      <style>body{font-family:Georgia,serif;max-width:640px;margin:0 auto;padding:40px 24px}.stmt{page-break-after:always;padding-bottom:40px}h2{font-size:18px;color:#163d55}</style>
68      </head><body>${h.statements.map(L=>`<div class="stmt"><h2>${h.org.name} — ${h.year} Donation Statement</h2>${L.html}</div>`).join("")}</body></html>`),x.document.close(),x.print()}const N=(c.years||[]).map(x=>({value:String(x),label:String(x)})),K=h?.statements.filter(x=>x.emailAddress).length||0,B=(h?.statements.length||0)-K;return e.jsxs("div",{children:[e.jsx("p",{className:"text-sm text-gray-500 mb-4",children:"Year-end acknowledgment letters your donors need for their taxes (required for gifts of $250+)."}),c.attested?e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(V,{value:l,onChange:x=>f(x.target.value),options:N,"aria-label":"Statement year"}),h&&t&&h.statements.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs(w,{variant:"primary",size:"sm",onClick:()=>S(!0),children:["Email Statements (",K,")"]}),e.jsxs(w,{variant:"secondary",size:"sm",onClick:T,children:["Print All (",h.statements.length,")"]})]})]}),b?e.jsx("p",{className:"text-sm text-gray-400",children:"Loading…"}):h&&h.statements.length===0?e.jsxs("p",{className:"text-sm text-gray-500",children:["No donations recorded for ",l,"."]}):h?e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"text-sm text-gray-600",children:[h.statements.length," donor",h.statements.length===1?"":"s"," ·"," ",e.jsx("span",{className:"font-semibold",children:C(h.totalCents)})," in ",l," donations",B>0&&e.jsxs("span",{className:"text-amber-600",children:[" · ",B," without email (print for them)"]})]}),e.jsx("ul",{className:"divide-y divide-gray-100",children:h.statements.map(x=>e.jsxs("li",{className:"py-2 flex items-center justify-between gap-3 text-sm",children:[e.jsxs("span",{className:"text-gray-700",children:[x.name,!x.emailAddress&&e.jsx("span",{className:"ml-1.5 text-xs text-amber-600",children:"no email"})]}),e.jsxs("span",{className:"flex items-center gap-3",children:[e.jsx("span",{className:"text-gray-500 tabular-nums",children:C(x.totalCents)}),e.jsx(w,{variant:"ghost",size:"sm",onClick:()=>u(x),children:"Preview"})]})]},x.memberId))})]}):null]}):t?e.jsxs("div",{className:"bg-amber-50 border border-amber-200 rounded-lg p-4",children:[e.jsx("p",{className:"text-sm text-amber-800 mb-3",children:"Before sending donor letters, confirm your organization is recognized as tax-exempt under Section 501(c)(3). Letters from non-exempt organizations must not imply deductibility."}),e.jsxs("div",{className:"flex flex-wrap items-end gap-3",children:[c.ein?e.jsxs("p",{className:"text-sm text-amber-800",children:["Using the EIN on file: ",e.jsx("span",{className:"font-semibold",children:c.ein})]}):e.jsx("div",{className:"w-44",children:e.jsx(_,{label:"EIN (optional)",value:k,onChange:x=>$(x.target.value),placeholder:"12-3456789"})}),e.jsx(w,{variant:"primary",size:"md",onClick:I,isLoading:R,children:"Confirm we're a 501(c)(3)"})]})]}):e.jsx("p",{className:"text-sm text-gray-500",children:"A treasurer or president needs to confirm the organization's 501(c)(3) status first."}),e.jsx(ne,{isOpen:!!A,onClose:()=>u(null),title:A?`Statement — ${A.name}`:"",size:"lg",children:A&&e.jsx("div",{className:"max-h-[60vh] overflow-y-auto border border-gray-200 rounded-lg p-6 bg-gray-50",children:e.jsx("div",{dangerouslySetInnerHTML:{__html:A.html}})})}),e.jsx(be,{isOpen:E,onClose:()=>S(!1),onConfirm:m,isLoading:y,title:`Email ${l} donation statements?`,message:`${K} donor${K===1?"":"s"} will receive their statement by email.${B>0?` ${B} without email won't — print for them afterward.`:""}`,confirmLabel:"Send Statements",confirmVariant:"primary"})]})}function ye({id:t,title:a,summary:r,defaultOpen:o=!1,children:c}){const i=`bb-collapse-${t}`,l=n.useRef(!1),[f,h]=n.useState(()=>{try{const b=localStorage.getItem(i);if(b!=null)return l.current=!0,b==="1"}catch{}return o}
68);n.useEffect(()=>{l.current||h(o)},[o]);function g(){l.current=!0,h(b=>{try{localStorage.setItem(i,b?"0":"1")}catch{}return!b})}return e.jsxs("div",{className:"bg-white rounded-xl border border-gray-200",children:[e.jsxs("button",{type:"button",onClick:g,"aria-expanded":f,className:"w-full flex items-center justify-between gap-3 px-6 py-4 text-left",children:[e.jsx("span",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide",children:a}),e.jsxs("span",{className:"flex items-center gap-3 min-w-0",children:[!f&&r&&e.jsx("span",{className:"text-sm text-gray-500 truncate",children:r}),e.jsx("svg",{className:`h-4 w-4 shrink-0 text-gray-400 transition-transform ${f?"rotate-180":""}`,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:"2",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 9l-7 7-7-7"})})]})]}),f&&e.jsx("div",{className:"px-6 pb-6",children:c})]})}function Ns(t,a){if(!t.length)return"None yet";const r=`${t.length} product${t.length===1?"":"s"}`,o=t.filter(c=>a[c.id]?.hiddenMembers>0).length;return o?`${r} · ${o} not shown to members yet`:r}function Re({ok:t,label:a}){return e.jsxs("span",{className:`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${t?"bg-green-100 text-green-700":"bg-gray-100 text-gray-500"}`,children:[e.jsx("span",{className:`h-1.5 w-1.5 rounded-full ${t?"bg-green-500":"bg-gray-400"}`}),a]})}function _s(){const{canFully:t}=Et(),a=t("payments:manage"),r=U(P=>P.showError),o=U(P=>P.showSuccess),[c,i]=Tt(),[l,f]=n.useState(null),[h,g]=n.useState(!0),[b,p]=n.useState(!1),[k,$]=n.useState(!1),[R,O]=n.useState([]),[A,u]=n.useState({}),[E,S]=n.useState(0),[y,M]=n.useState(null),I=et(P=>P.roleLabels)||{},[m,T]=n.useState(!1),[N,K]=n.useState(0),[B,x]=n.useState(0),L=n.useCallback(async()=>{try{const P=await j.getAccount();f(P)}catch{r(We("payment account status"),{retryFn:L})}finally{g(!1)}},[r]);n.useEffect(()=>{if(c.get("onboarding")&&a){i({},{replace:!0}),g(!0),j.refreshAccountStatus().then(q=>{f(q),q?.chargesEnabled&&o("Stripe account connected — your organization can now accept payments.")}).catch(()=>L()).finally(()=>g(!1));return}L()},[]);async function z(){p(!0);try{const P=await j.createOnboardingLink();window.location.assign(P.url)}catch(P){r(P.message||"Couldn’t start Stripe onboarding. Try again."),p(!1)}}async function Q(){$(!0);try{const P=await j.refreshAccountStatus();f(P),K(q=>q+1),x(q=>q+1),o("Payments refreshed.")}catch(P){r(P.message||"Couldn’t refresh.")}finally{$(!1)}}const re=l?.connected,X=l?.chargesEnabled,H=l?.statusDetail||{mode:X?"ok":"act",headline:X?"Connected — payments enabled":"Setup isn't finished yet",explanation:"Stripe still needs some information before your organization can accept payments.",canAct:!X};return n.useEffect(()=>{h||window.location.hash!=="#items-to-hand-out"||document.getElementById("items-to-hand-out")?.scrollIntoView({behavior:"smooth",block:"start"})},[h]),e.jsxs("div",{className:"px-4 md:px-0",children:[e.jsx(Ot,{title:"Payments",subtitle:"Collect dues and other payments directly to your organization's bank account.",actions:re&&a?e.jsx(w,{variant:"secondary",size:"md",onClick:Q,isLoading:k,children:"Refresh"}):null}),h?e.jsx(At,{}):re?e.jsxs(ye,{id:"payments-stripe-status",title:"Stripe Account Status",defaultOpen:!X,summary:H.headline,children:[e.jsxs("div",{className:"flex flex-wrap gap-2 mb-4",children:[e.jsx(Re,{ok:l.detailsSubmitted,label:"Details submitted"}),e.jsx(Re,{ok:l.chargesEnabled,label:"Charges enabled"}),e.jsx(Re,{ok:l.payoutsEnabled,label:"Payouts enabled"}),e.jsx(Re,{ok:l.achEnabled,label:"Bank transfers (ACH)"})]}),X?e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-sm text-gray-600",children:"Your organization is ready to accept payments. Payments settle to your own Stripe account and pay out to your bank on Stripe's standard schedule."}),e.jsxs("p",{className:"text-sm text-gray-600",children:["Bank account, business details, refunds, and payout history are managed in your organization's own Stripe dashboard — log in with the email you used during setup."," ",e.jsx("a",{href:"https://dashboard.stripe.com",target:"_blank",rel:"noopener noreferrer",className:"font-medium text-band-600 hover:text-band-700 underline",children:"Open Stripe Dashboard"})]})]}):e.jsxs("div",{className:H.mode==="wait"?"bg-blue-50 border border-blue-200 rounded-lg p-3 text-sm text-blue-800":H.mode==="blocked"?"bg-red-50 border border-red-200 rounded-lg p-3 text-sm text-red-700":"bg-amber-50 border border-amber-200 rounded-lg p-3 text-sm text-amber-800",children:[e.jsx("p",{className:"font-medium mb-1",children:H.headline}),e.jsxs("p",{children:[H.explanation,H.canAct&&a&&" Continue below to pick up where you left off."]}),H.mode==="wait"&&e.jsx("p",{className:"mt-2",children:"Nothing is needed from you — we'll email you the moment it clears."}),H.canAct&&a&&e.jsx("div",{className:"mt-3",children:e.jsx(w,{variant:"primary",size:"sm",onClick:z,isLoading:b,children:"Continue Stripe Setup"})}),H.mode==="blocked"&&e.jsx("p",{className:"mt-2",children:e.jsx("a",{href:"https://support.stripe.com",target:"_blank",rel:"noopener noreferrer",className:"font-medium underline",children:"Contact Stripe Support"})})]})]}):e.jsx("div",{className:"bg-white rounded-xl border border-gray-200 p-8",children:e.jsxs("div",{className:"max-w-2xl",children:[e.jsx("h3",{className:"text-sm font-semibold text-gray-500 uppercase tracking-wide mb-4",children:"Get Set Up to Collect Payments"}),e.jsxs("p",{className:"text-gray-700 mb-3",children:["Connect your organization's own Stripe account to collect dues, uniform fees, and other payments. Money goes ",e.jsx("span",{className:"font-semibold",children:"directly to your organization's bank account"})," — Backstage Baton never holds your funds."]}),e.jsxs("ul",{className:"text-sm text-gray-600 space-y-2 mb-6 list-disc pl-5",children:[e.jsx("li",{children:"One-time setup on Stripe's secure site (~10 minutes) — you'll need your organization's EIN and bank account details."}),e.jsx("li",{children:"Members can pay by bank transfer (0.8% fee, capped at $5) or card (2.9% + 30¢) — including Apple Pay and Google Pay, which appear automatically at checkout at the card rate. Bank transfer is the budget-friendly option we encourage."}),e.jsx("li",{children:"Cash and check still work — record them here and everything lands in one who's-paid report."})]}),a?e.jsx(w,{variant:"primary",size:"md",onClick:z,isLoading:b,children:"Connect with Stripe"}):e.jsx("p",{className:"text-sm text-gray-500",children:"A treasurer or president can connect your organization's Stripe account."})]})}
68),!h&&e.jsxs("div",{className:"space-y-4 mt-4",children:[e.jsx(ye,{id:"payments-products",title:"Products",defaultOpen:R.length===0||Object.values(A).some(P=>P?.hiddenMembers>0),summary:Ns(R,A),children:e.jsx(Xt,{canManage:a,refreshKey:B,onVisibility:u,onProductsLoaded:O,onChargesChanged:()=>{K(P=>P+1),x(P=>P+1)}})}),e.jsx("div",{id:"items-to-hand-out",children:e.jsx(ye,{id:"payments-hand-out",title:"Items to hand out",defaultOpen:E>0,summary:`${E>0?`${E} waiting`:"Nothing waiting"}${y?` · emails go to ${rt(y,I)}`:""}`,children:e.jsx(ms,{canManage:a,refreshKey:B,onCount:S,onRecipients:M})})}),e.jsx(ye,{id:"payments-check-instructions",title:"Check & Cash Instructions",summary:"Who to pay and how to deliver",children:e.jsx(bs,{canManage:a,onPrintLabels:a?()=>T(!0):void 0})}),e.jsx(ye,{id:"payments-dues-reminder",title:"Weekly email reminder",summary:"Remind members who owe",children:e.jsx(vs,{canManage:a})}),e.jsx(js,{isOpen:m,onClose:()=>T(!1),products:R}),e.jsx(ye,{id:"payments-donation-statements",title:"Donation Statements",summary:"Year-end donor tax letters",children:e.jsx(ws,{canManage:a,refreshKey:B})}),e.jsx(fs,{products:R,refreshKey:B}),e.jsx(cs,{canManage:a,products:R,refreshKey:N,onChanged:()=>x(P=>P+1)})]})]})}export{_s as PaymentsPage};

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.