Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/TEMPLATES/tf_admin_company.html
Diff
/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/TEMPLATES/tf_admin_company.html
added on local at 2026-07-11 18:33:05
Added
+231
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 9f6a44e066ea
to 9f6a44e066ea
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | <div class="crumbs"><a href="/admin_companies.cgi">All companies</a><span class="sep">/</span>$company_name</div> | |
| 2 | ||
| 3 | <div class="page-header"> | |
| 4 | <div> | |
| 5 | <h1>$company_name</h1> | |
| 6 | <p class="subtitle"><code>$company_slug</code> · plan <strong>$plan_tier</strong> · status <strong>$status</strong> · seats <strong>$seat_count</strong>[if:$trial_ends_at] · trial ends <span class="ts" data-ts="$trial_ends_epoch" data-fmt="datetime">$trial_ends_at</span>[/if]</p> | |
| 7 | </div> | |
| 8 | <div class="flex-end"> | |
| 9 | <a href="/admin_companies.cgi" class="btn btn-ghost">← All companies</a> | |
| 10 | </div> | |
| 11 | </div> | |
| 12 | ||
| 13 | [if:$has_flash]<div class="flash-msg">$flash</div>[/if] | |
| 14 | ||
| 15 | <style> | |
| 16 | .au-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-bottom: 18px; } | |
| 17 | .au-stat { padding: 12px 14px; background: var(--col-bg-3, rgba(255,255,255,0.04)); border: 1px solid var(--col-border); border-radius: 9px; } | |
| 18 | .au-stat .n { font-size: 22px; font-weight: 800; color: var(--col-text); font-family: var(--font-mono, monospace); line-height: 1.1; } | |
| 19 | .au-stat .lb { font-size: 10px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--col-text-dim); margin-top: 4px; } | |
| 20 | .au-av { width: 28px; height: 28px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; font-size: 11px; flex-shrink: 0; } | |
| 21 | .au-clickable { cursor: pointer; transition: background 0.12s; } | |
| 22 | .au-clickable:hover { background: rgba(255,255,255,0.04); } | |
| 23 | </style> | |
| 24 | ||
| 25 | <div class="card mb-3" style="padding:20px"> | |
| 26 | <h3 style="margin-top:0">Company stats</h3> | |
| 27 | <div class="au-stat-grid"> | |
| 28 | <div class="au-stat"><div class="n">$users_total</div><div class="lb">Total users</div></div> | |
| 29 | <div class="au-stat"><div class="n">$users_active</div><div class="lb">Active</div></div> | |
| 30 | <div class="au-stat"><div class="n">$users_pending</div><div class="lb">Pending verify</div></div> | |
| 31 | <div class="au-stat"><div class="n">$users_susp</div><div class="lb">Suspended</div></div> | |
| 32 | <div class="au-stat"><div class="n">$proj_n</div><div class="lb">Projects</div></div> | |
| 33 | <div class="au-stat"><div class="n">$task_n</div><div class="lb">Tasks</div></div> | |
| 34 | <div class="au-stat"><div class="n">$$earn_total_disp</div><div class="lb">Lifetime revenue</div></div> | |
| 35 | <div class="au-stat"><div class="n">$$earn_30_disp</div><div class="lb">Revenue (30d)</div></div> | |
| 36 | </div> | |
| 37 | </div> | |
| 38 | ||
| 39 | <div class="card mb-3" style="padding:0"> | |
| 40 | <div class="card-header" style="padding:14px 20px"><h3 style="margin:0">Users in this company · $users_total total</h3></div> | |
| 41 | [if:$has_co_users] | |
| 42 | <table class="table" style="margin:0"> | |
| 43 | <thead><tr><th>Member</th><th>Email</th><th>Role</th><th>Status</th><th>2FA</th><th>Last login</th></tr></thead> | |
| 44 | <tbody> | |
| 45 | [loop:@co_users] | |
| 46 | <tr class="au-clickable" onclick="window.location='/admin_user.cgi?u=$loop1.id'"> | |
| 47 | <td><div style="display:flex;align-items:center;gap:10px"><div class="au-av" style="background:$loop1.avatar_color">$loop1.initials</div><div>$loop1.display_name_h [if:$loop1.is_super_admin]<span class="badge danger">SUPER</span>[/if]</div></div></td> | |
| 48 | <td>$loop1.email_h</td> | |
| 49 | <td><span class="badge">$loop1.role_label</span></td> | |
| 50 | <td><span class="badge">$loop1.account_status</span></td> | |
| 51 | <td>[if:$loop1.two_factor_enabled]On[/if][if:!$loop1.two_factor_enabled]Off[/if]</td> | |
| 52 | <td style="font-size:12px;color:var(--col-text-dim)"><span class="ts" data-ts="$loop1.last_login_epoch" data-fmt="datetime">$loop1.last_login_disp</span></td> | |
| 53 | </tr> | |
| 54 | [/loop] | |
| 55 | </tbody> | |
| 56 | </table> | |
| 57 | [/if] | |
| 58 | [if:!$has_co_users]<div style="padding:18px 20px;color:var(--col-text-dim);font-size:13px">No users in this company yet.</div>[/if] | |
| 59 | </div> | |
| 60 | ||
| 61 | [if:$has_invoices] | |
| 62 | <div class="card mb-3" style="padding:0"> | |
| 63 | <div class="card-header" style="padding:14px 20px"><h3 style="margin:0">Recent invoices · $earn_count paid lifetime</h3></div> | |
| 64 | <table class="table" style="margin:0"> | |
| 65 | <thead><tr><th>Invoice #</th><th>Status</th><th>Paid</th><th>Due</th><th>Paid at</th><th>Created</th></tr></thead> | |
| 66 | <tbody> | |
| 67 | [loop:@recent_invoices] | |
| 68 | <tr> | |
| 69 | <td><code>$loop1.invoice_number</code></td> | |
| 70 | <td><span class="badge">$loop1.status</span></td> | |
| 71 | <td style="font-weight:600">$loop1.amount_disp</td> | |
| 72 | <td>$loop1.due_disp</td> | |
| 73 | <td style="font-size:12px;color:var(--col-text-dim)"><span class="ts" data-ts="$loop1.paid_epoch" data-fmt="datetime">$loop1.paid_at</span></td> | |
| 74 | <td style="font-size:12px;color:var(--col-text-dim)"><span class="ts" data-ts="$loop1.created_epoch" data-fmt="datetime">$loop1.created_at</span></td> | |
| 75 | </tr> | |
| 76 | [/loop] | |
| 77 | </tbody> | |
| 78 | </table> | |
| 79 | </div> | |
| 80 | [/if] | |
| 81 | ||
| 82 | ||
| 83 | <div class="row" style="gap:18px;flex-wrap:wrap"> | |
| 84 | <!-- Credit balance + grant --> | |
| 85 | <div class="col" style="min-width:340px;flex:1"> | |
| 86 | <div class="card mb-3" style="padding:20px"> | |
| 87 | <h3 style="margin-top:0">Account credit</h3> | |
| 88 | <div class="text-muted" style="font-size:13px;margin-bottom:14px">Credits offset the customer's next invoice. Positive numbers grant credit; negative numbers adjust away credit (e.g. for reversal).</div> | |
| 89 | <div style="font-size:28px;font-weight:800;margin-bottom:14px">$$current_balance_disp <span class="text-muted" style="font-size:13px;font-weight:400">current balance</span></div> | |
| 90 | <form method="POST" action="/admin_company.cgi"> | |
| 91 | <input type="hidden" name="id" value="$cid"> | |
| 92 | <input type="hidden" name="act" value="grant_credit"> | |
| 93 | <div class="row" style="gap:10px"> | |
| 94 | <div class="col" style="flex:0 0 140px"><div class="field"><label>Amount ($)</label><input class="input" name="amount_dollars" placeholder="25.00" required></div></div> | |
| 95 | <div class="col"><div class="field"><label>Reason (shown internally)</label><input class="input" name="reason" placeholder="Goodwill credit for outage on 2026-06-12"></div></div> | |
| 96 | </div> | |
| 97 | <div style="display:flex;justify-content:flex-end;margin-top:12px"><button type="submit" class="btn btn-primary">Apply credit</button></div> | |
| 98 | </form> | |
| 99 | </div> | |
| 100 | ||
| 101 | [if:$has_ledger] | |
| 102 | <div class="card" style="padding:0"> | |
| 103 | <div class="card-header"><h3 style="margin:0">Credit ledger (last 20)</h3></div> | |
| 104 | <table class="table" style="margin:0"> | |
| 105 | <thead><tr><th>When</th><th>Kind</th><th>Amount</th><th>Balance</th><th>Reason</th><th>By</th></tr></thead> | |
| 106 | <tbody> | |
| 107 | [loop:@ledger] | |
| 108 | <tr> | |
| 109 | <td style="font-size:12px"><span class="ts" data-ts="$loop1.created_epoch" data-fmt="datetime">$loop1.created_at</span></td> | |
| 110 | <td><span class="badge[if:$loop1.is_credit] success[/if][if:!$loop1.is_credit] warn[/if]">$loop1.kind</span></td> | |
| 111 | <td style="font-weight:600">$loop1.amount_disp</td> | |
| 112 | <td>$loop1.balance_disp</td> | |
| 113 | <td style="font-size:12px;color:var(--col-text-muted)">$loop1.reason</td> | |
| 114 | <td style="font-size:12px;color:var(--col-text-muted)">$loop1.by_name</td> | |
| 115 | </tr> | |
| 116 | [/loop] | |
| 117 | </tbody> | |
| 118 | </table> | |
| 119 | </div> | |
| 120 | [/if] | |
| 121 | [if:!$has_ledger]<p class="text-muted" style="padding:14px 20px;background:rgba(255,255,255,.02);border-radius:8px;font-size:13px">No credits yet.</p>[/if] | |
| 122 | </div> | |
| 123 | ||
| 124 | <!-- Tier override + locked price --> | |
| 125 | <div class="col" style="min-width:340px;flex:1"> | |
| 126 | ||
| 127 | <!-- Tier override --> | |
| 128 | <div class="card mb-3" style="padding:20px"> | |
| 129 | <h3 style="margin-top:0">Push to a higher tier free</h3> | |
| 130 | <div class="text-muted" style="font-size:13px;margin-bottom:14px">Temporarily grant access to a higher plan tier without billing. Reverts automatically when the expiry date passes.</div> | |
| 131 | [if:$tier_override_active] | |
| 132 | <div style="padding:12px;background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.4);border-radius:8px;margin-bottom:14px"> | |
| 133 | <strong style="color:#4ade80">Active:</strong> $plan_tier_override | |
| 134 | [if:$tier_override_expires] · expires <span class="ts" data-ts="$tier_override_expires_epoch" data-fmt="datetime">$tier_override_expires</span>[/if] | |
| 135 | [if:$tier_override_reason]<div class="text-muted" style="font-size:12px;margin-top:4px">$tier_override_reason</div>[/if] | |
| 136 | </div> | |
| 137 | <form method="POST" action="/admin_company.cgi" style="display:inline" onsubmit="return confirm('Clear tier override?')"> | |
| 138 | <input type="hidden" name="id" value="$cid"> | |
| 139 | <input type="hidden" name="act" value="clear_tier_override"> | |
| 140 | <button type="submit" class="btn btn-ghost btn-sm">Clear override</button> | |
| 141 | </form> | |
| 142 | [/if] | |
| 143 | [if:!$tier_override_active] | |
| 144 | <form method="POST" action="/admin_company.cgi"> | |
| 145 | <input type="hidden" name="id" value="$cid"> | |
| 146 | <input type="hidden" name="act" value="set_tier_override"> | |
| 147 | <div class="row" style="gap:10px"> | |
| 148 | <div class="col"><div class="field"><label>Push to tier</label> | |
| 149 | <select class="select" name="override_tier" required> | |
| 150 | <option value="">— pick —</option> | |
| 151 | <option value="starter">Starter</option> | |
| 152 | <option value="pro">Pro</option> | |
| 153 | <option value="business">Business</option> | |
| 154 | <option value="enterprise">Enterprise</option> | |
| 155 | </select> | |
| 156 | </div></div> | |
| 157 | <div class="col"><div class="field"><label>Expires (optional)</label><input class="input" type="date" name="expires_at"></div></div> | |
| 158 | </div> | |
| 159 | <div class="field" style="margin-top:10px"><label>Reason (internal)</label><input class="input" name="reason" placeholder="One-month trial extension"></div> | |
| 160 | <div style="display:flex;justify-content:flex-end;margin-top:12px"><button type="submit" class="btn btn-primary">Apply override</button></div> | |
| 161 | </form> | |
| 162 | [/if] | |
| 163 | </div> | |
| 164 | ||
| 165 | <!-- Locked price --> | |
| 166 | <div class="card" style="padding:20px"> | |
| 167 | <h3 style="margin-top:0">Custom price lock</h3> | |
| 168 | <div class="text-muted" style="font-size:13px;margin-bottom:14px">Override the standard per-seat price for this customer. Use for negotiated rates, time-limited intro pricing, or first-100-customers grandfathered rates. Leave expiry blank to make it permanent.</div> | |
| 169 | [if:$locked_price_active] | |
| 170 | <div style="padding:12px;background:rgba(168,85,247,.10);border:1px solid rgba(168,85,247,.4);border-radius:8px;margin-bottom:14px"> | |
| 171 | <strong style="color:#c4a3f0">Active:</strong> $$locked_price_disp / seat / month | |
| 172 | [if:$locked_price_expires] · expires <span class="ts" data-ts="$locked_price_expires_epoch" data-fmt="datetime">$locked_price_expires</span>[/if] | |
| 173 | [if:$locked_price_reason]<div class="text-muted" style="font-size:12px;margin-top:4px">$locked_price_reason</div>[/if] | |
| 174 | </div> | |
| 175 | <form method="POST" action="/admin_company.cgi" style="display:inline" onsubmit="return confirm('Clear locked price? Customer reverts to standard tier pricing.')"> | |
| 176 | <input type="hidden" name="id" value="$cid"> | |
| 177 | <input type="hidden" name="act" value="clear_locked_price"> | |
| 178 | <button type="submit" class="btn btn-ghost btn-sm">Clear lock</button> | |
| 179 | </form> | |
| 180 | [/if] | |
| 181 | [if:!$locked_price_active] | |
| 182 | <form method="POST" action="/admin_company.cgi"> | |
| 183 | <input type="hidden" name="id" value="$cid"> | |
| 184 | <input type="hidden" name="act" value="set_locked_price"> | |
| 185 | <div class="row" style="gap:10px"> | |
| 186 | <div class="col"><div class="field"><label>Price ($/seat/mo)</label><input class="input" name="price_dollars" placeholder="14.99" required></div></div> | |
| 187 | <div class="col"><div class="field"><label>Expires (optional, blank = permanent)</label><input class="input" type="date" name="expires_at"></div></div> | |
| 188 | </div> | |
| 189 | <div class="field" style="margin-top:10px"><label>Reason (internal)</label><input class="input" name="reason" placeholder="6-month intro rate, then revert to Pro standard"></div> | |
| 190 | <div style="display:flex;justify-content:flex-end;margin-top:12px"><button type="submit" class="btn btn-primary">Lock price</button></div> | |
| 191 | </form> | |
| 192 | [/if] | |
| 193 | </div> | |
| 194 | ||
| 195 | ||
| 196 | <div class="card mb-3" style="padding:20px"> | |
| 197 | <h3 style="margin-top:0">Issue refund (queued — Stripe pending)</h3> | |
| 198 | <div class="text-muted" style="font-size:13px;margin-bottom:14px">Live Stripe refund is gated on activating production keys. This form queues the refund intent + applies a soft credit immediately so the customer sees relief. The worker processes queued intents once Stripe is live.</div> | |
| 199 | <form method="POST" action="/admin_company.cgi"> | |
| 200 | <input type="hidden" name="id" value="$cid"> | |
| 201 | <input type="hidden" name="act" value="queue_refund"> | |
| 202 | <div class="row" style="gap:10px"> | |
| 203 | <div class="col" style="flex:0 0 200px"><div class="field"><label>Invoice ID</label><input class="input" name="invoice_id" placeholder="123" required></div></div> | |
| 204 | <div class="col" style="flex:0 0 140px"><div class="field"><label>Amount ($)</label><input class="input" name="amount_dollars" placeholder="25.00" required></div></div> | |
| 205 | <div class="col"><div class="field"><label>Reason (internal + on customer record)</label><input class="input" name="reason" placeholder="Customer-reported overcharge"></div></div> | |
| 206 | </div> | |
| 207 | <div style="display:flex;justify-content:flex-end;margin-top:12px"><button type="submit" class="btn btn-primary">Queue refund</button></div> | |
| 208 | </form> | |
| 209 | ||
| 210 | [if:$has_refunds] | |
| 211 | <h4 style="margin-top:18px;font-size:13px">Recent refund intents</h4> | |
| 212 | <table class="table" style="margin:0;font-size:12px"> | |
| 213 | <thead><tr><th>ID</th><th>Invoice</th><th>Amount</th><th>Status</th><th>Reason</th><th>When</th></tr></thead> | |
| 214 | <tbody> | |
| 215 | [loop:@refund_intents] | |
| 216 | <tr> | |
| 217 | <td><code>#$loop1.id</code></td> | |
| 218 | <td><code>#$loop1.invoice_id</code></td> | |
| 219 | <td>$loop1.amount_disp</td> | |
| 220 | <td><span class="badge">$loop1.status</span></td> | |
| 221 | <td style="color:var(--col-text-dim)">$loop1.reason</td> | |
| 222 | <td style="color:var(--col-text-dim)"><span class="ts" data-ts="$loop1.requested_epoch" data-fmt="datetime">$loop1.requested_at</span></td> | |
| 223 | </tr> | |
| 224 | [/loop] | |
| 225 | </tbody> | |
| 226 | </table> | |
| 227 | [/if] | |
| 228 | </div> | |
| 229 | ||
| 230 | </div> | |
| 231 | </div> |