Client Portal Williams Brunson Solutions, LLC
Logged in as Louise McClendon
Recent Invoices
Active Matters
NameTypeEmailPhoneMattersBalance DueActions
MatterClientTypeStatusOpenedNotesActions
Payment Link: Add your Stripe or PayPal payment URL in Settings — it will appear on every invoice automatically.
Invoice #ClientMatterDateDueAmountStatusActions

Payment Methods

How it works: Add your payment links below. They will appear on all printed invoices and emails so clients can pay directly.

Payment History

InvoiceClientAmountDate Paid

Firm Information

Change Portal Password

Invoice Settings

Data Management

Export or clear all portal data stored in this browser.

`); w.document.close(); w.print(); } function renderInvoices(){ const invoices=(DB.get('invoices')||[]).slice().reverse(); const tbody=document.getElementById('invoicesBody'); if(!invoices.length){tbody.innerHTML='

No invoices yet

Create your first invoice above
';return;} tbody.innerHTML=invoices.map(i=>` ${i.num} ${getClientName(i.clientId)} ${getMatterName(i.matterId)} ${fmtDate(i.date)} ${fmtDate(i.due)} ${fmt$(i.total)} ${statusBadge(i.status)}
${i.status!=='Paid'?``:''}
`).join(''); } function updateBadge(){ const n=(DB.get('invoices')||[]).filter(i=>i.status!=='Paid').length; document.getElementById('unpaidBadge').textContent=n; document.getElementById('unpaidBadge').style.display=n?'':'none'; } // ═══════════════════════════════════════════ // PAYMENTS // ═══════════════════════════════════════════ function openPayMethodModal(){ openModal('payMethodModal'); } function savePayMethod(){ const name=document.getElementById('pm-name').value.trim(); const link=document.getElementById('pm-link').value.trim(); if(!name||!link){alert('Name and link/instructions are required.');return;} const methods=DB.get('payMethods')||[]; methods.push({id:uid(),name,link,notes:document.getElementById('pm-notes').value}); DB.set('payMethods',methods); closeModal('payMethodModal'); ['pm-name','pm-link','pm-notes'].forEach(id=>document.getElementById(id).value=''); renderPayments(); } function deletePayMethod(id){ DB.set('payMethods',(DB.get('payMethods')||[]).filter(x=>x.id!==id)); renderPayments(); } function renderPayments(){ const invoices=DB.get('invoices')||[]; const paid=invoices.filter(i=>i.status==='Paid'); const unpaid=invoices.filter(i=>i.status!=='Paid'); const totalPaid=paid.reduce((s,i)=>s+(i.total||0),0); const totalDue=unpaid.reduce((s,i)=>s+(i.total||0),0); const totalBilled=invoices.reduce((s,i)=>s+(i.total||0),0); document.getElementById('payStats').innerHTML=`
Total Collected
${fmt$(totalPaid)}
Outstanding
${fmt$(totalDue)}
Total Billed
${fmt$(totalBilled)}
`; const methods=DB.get('payMethods')||[]; document.getElementById('payMethodsList').innerHTML=methods.length?methods.map(p=>`
${p.name}
${p.link} ${p.notes?'
'+p.notes+'':''}
`).join(''):'

No payment methods added yet.

'; document.getElementById('payHistory').innerHTML=paid.length?paid.slice().reverse().map(i=>` ${i.num} ${getClientName(i.clientId)} ${fmt$(i.total)} ${fmtDate(i.paidDate)} `).join(''):'

No payments yet

'; } // ═══════════════════════════════════════════ // SETTINGS // ═══════════════════════════════════════════ function renderSettings(){ const firm=DB.get('firm')||{}; document.getElementById('set-firmName').value=firm.name||''; document.getElementById('set-attorney').value=firm.attorney||''; document.getElementById('set-phone').value=firm.phone||''; document.getElementById('set-email').value=firm.email||''; document.getElementById('set-address').value=firm.address||''; document.getElementById('set-website').value=firm.website||''; document.getElementById('set-disclaimer').value=firm.disclaimer||''; document.getElementById('set-terms').value=firm.terms||'Due on Receipt'; document.getElementById('set-tax').value=firm.tax||0; document.getElementById('set-prefix').value=firm.prefix||'WBS-'; } function saveFirmSettings(){ const firm=DB.get('firm')||{}; firm.name=document.getElementById('set-firmName').value; firm.attorney=document.getElementById('set-attorney').value; firm.phone=document.getElementById('set-phone').value; firm.email=document.getElementById('set-email').value; firm.address=document.getElementById('set-address').value; firm.website=document.getElementById('set-website').value; firm.disclaimer=document.getElementById('set-disclaimer').value; DB.set('firm',firm); alert('Firm information saved.'); } function saveInvSettings(){ const firm=DB.get('firm')||{}; firm.terms=document.getElementById('set-terms').value; firm.tax=parseFloat(document.getElementById('set-tax').value)||0; firm.prefix=document.getElementById('set-prefix').value; DB.set('firm',firm); alert('Invoice settings saved.'); } function exportData(){ const data={clients:DB.get('clients'),matters:DB.get('matters'),invoices:DB.get('invoices'),firm:DB.get('firm'),payMethods:DB.get('payMethods')}; const blob=new Blob([JSON.stringify(data,null,2)],{type:'application/json'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='WBS-portal-backup-'+today()+'.json';a.click(); } function clearData(){ if(!confirm('This will permanently delete ALL portal data (clients, matters, invoices). This cannot be undone. Are you sure?')) return; if(!confirm('FINAL WARNING: Delete everything?')) return; ['clients','matters','invoices','payMethods','invoiceCounter'].forEach(k=>localStorage.removeItem('wbs_'+k)); renderAll(); } // ═══════════════════════════════════════════ // HELPERS // ═══════════════════════════════════════════ function getClientName(id){ return (DB.get('clients')||[]).find(c=>c.id===id)?.name||'Unknown Client'; } function getMatterName(id){ if(!id) return '—'; return (DB.get('matters')||[]).find(m=>m.id===id)?.name||'—'; } function statusBadge(s){ const map={Paid:'badge-green',Unpaid:'badge-red',Active:'badge-green',Pending:'badge-gold','On Hold':'badge-gold',Closed:'badge-gray'}; return `${s}`; } function openModal(id){ document.getElementById(id).classList.add('open'); document.body.style.overflow='hidden'; } function closeModal(id){ document.getElementById(id).classList.remove('open'); document.body.style.overflow=''; } document.querySelectorAll('.modal-overlay').forEach(o=>o.addEventListener('click',e=>{if(e.target===o)closeModal(o.id)})); document.addEventListener('keydown',e=>{ if(e.key==='Escape') document.querySelectorAll('.modal-overlay.open').forEach(m=>closeModal(m.id)); }); document.getElementById('loginPass').addEventListener('keydown',e=>{ if(e.key==='Enter') doLogin(); });