Diff -- /var/www/vhosts/webstls.com/httpdocs/TEMPLATES/webstls_customers.html
Diff

/var/www/vhosts/webstls.com/httpdocs/TEMPLATES/webstls_customers.html

added on WebSTLs (webstls.com) at 2026-07-01 22:26:59

Added
+315
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 693d3648142a
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<!-- =====================================================================
2 Seller-side Customers page: list of buyers + per-buyer detail.
3 One template handles both views, switched by $is_list_view /
4 $is_detail_view from customers.cgi.
5===================================================================== -->
6<style>
7 .cu-flash { padding: 10px 14px; border-radius: 8px; margin-bottom: 14px; font-size: 13px; }
8 .cu-flash.ok { background: rgba(34,197,94,0.15); border: 1px solid rgba(34,197,94,0.40); color: #4ade80; }
9 .cu-flash.danger { background: rgba(239,68,68,0.15); border: 1px solid rgba(239,68,68,0.40); color: #f87171; }
10
11 .cu-card {
12 background: var(--col-surface-1);
13 border: 1px solid var(--col-border);
14 border-radius: 12px;
15 padding: 18px;
16 margin-bottom: 14px;
17 }
18 .cu-card-title {
19 font-size: 10px; font-weight: 700;
20 letter-spacing: 1.5px; text-transform: uppercase;
21 color: var(--col-text-3);
22 margin-bottom: 12px;
23 }
24
25 .cu-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
26 .cu-table th { text-align: left; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--col-text-3); padding: 9px 10px; font-weight: 700; border-bottom: 1px solid var(--col-border); }
27 .cu-table td { padding: 11px 10px; border-bottom: 1px solid var(--col-border); color: var(--col-text-2); }
28 .cu-table tr:last-child td { border-bottom: none; }
29 .cu-table tr.cu-clickable { cursor: pointer; }
30 .cu-table tr.cu-clickable:hover td { background: var(--col-surface-2); }
31 .cu-table .mono { font-family: var(--font-mono, monospace); color: var(--col-text); font-size: 12px; }
32 .cu-table .num { text-align: right; font-family: var(--font-mono, monospace); }
33
34 .cu-pill { font-size: 9px; font-weight: 700; padding: 3px 8px; border-radius: 6px; letter-spacing: 1px; text-transform: uppercase; }
35 .cu-pill.ok { background: rgba(34,197,94,0.18); color: #4ade80; }
36 .cu-pill.warn { background: rgba(245,158,11,0.20); color: #fbbf24; }
37 .cu-pill.zero { background: var(--col-surface-2); color: var(--col-text-3); }
38 .cu-pill.neg { background: rgba(239,68,68,0.20); color: #f87171; }
39
40 .cu-bal {
41 display: flex; align-items: baseline; gap: 14px;
42 padding: 18px 22px;
43 background: var(--col-surface-2);
44 border: 1px solid var(--col-border);
45 border-radius: 12px;
46 }
47 .cu-bal .amt { font-size: 30px; font-weight: 800; font-family: var(--font-mono, monospace); color: var(--col-text); }
48 .cu-bal .amt.pos { color: #4ade80; }
49 .cu-bal .amt.neg { color: #f87171; }
50 .cu-bal .lbl { font-size: 10px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--col-text-3); }
51
52 .cu-detail-grid {
53 display: grid;
54 grid-template-columns: 320px 1fr;
55 gap: 16px;
56 margin-top: 14px;
57 }
58 \@media (max-width: 1000px) { .cu-detail-grid { grid-template-columns: 1fr; } }
59
60 .cu-meta-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; font-size: 13px; border-bottom: 1px solid var(--col-border); }
61 .cu-meta-row:last-child { border-bottom: none; }
62 .cu-meta-row .lbl { color: var(--col-text-3); }
63 .cu-meta-row .val { color: var(--col-text); font-weight: 600; text-align: right; word-break: break-word; }
64
65 .cu-grant-btn {
66 width: 100%;
67 background: var(--grad-brand);
68 border: 0;
69 color: #fff;
70 padding: 11px 14px;
71 border-radius: 9px;
72 cursor: pointer;
73 font-size: 13px;
74 font-weight: 700;
75 margin-top: 10px;
76 }
77 .cu-grant-btn:hover { filter: brightness(1.08); }
78</style>
79
80[if:$has_flash]
81<div class="cu-flash $flash_kind">$flash_msg</div>
82[/if]
83
84[if:$is_list_view]
85<div class="page-head">
86 <div>
87 <span class="page-eyebrow"><span class="dot"></span> Studio &middot; Customers</span>
88 <h1 class="page-title">Customers</h1>
89 <p class="page-subtitle">Every buyer who has ordered from your storefronts, with their current store-credit balance.</p>
90 </div>
91</div>
92
93<div class="cu-card">
94 <div class="cu-card-title">All buyers</div>
95 [if:$has_rows]
96 <table class="cu-table">
97 <thead>
98 <tr>
99 <th>Buyer</th>
100 <th>Storefront</th>
101 <th class="num">Orders</th>
102 <th class="num">Lifetime spend</th>
103 <th class="num">Store credit</th>
104 <th>Last order</th>
105 </tr>
106 </thead>
107 <tbody>
108 [loop:@rows]
109 <tr class="cu-clickable" data-href="/customers.cgi?$loop1.detail_qs">
110 <td>
111 [if:$loop1.display_name]<strong>$loop1.display_name</strong><br>[/if]
112 <span class="mono">$loop1.buyer_email</span>
113 [if:!$loop1.has_account] <span class="cu-pill warn" title="Guest checkout - no buyer account">Guest</span>[/if]
114 </td>
115 <td>$loop1.store_name</td>
116 <td class="num">$loop1.order_count</td>
117 <td class="num mono">$loop1.spent_dollars</td>
118 <td class="num">
119 [if:$loop1.has_credit]<span class="cu-pill ok mono">$loop1.balance_dollars</span>[/if]
120 [if:$loop1.no_credit]<span class="cu-pill zero mono">$loop1.balance_dollars</span>[/if]
121 [if:$loop1.has_debit]<span class="cu-pill neg mono">$loop1.balance_dollars</span>[/if]
122 </td>
123 <td class="mono">$loop1.last_order_at</td>
124 </tr>
125 [/loop]
126 </tbody>
127 </table>
128 <script>
129 // Whole-row click navigation. Avoids putting an anchor in every cell.
130 document.querySelectorAll('.cu-clickable').forEach(function (tr) {
131 tr.addEventListener('click', function () {
132 var h = tr.getAttribute('data-href');
133 if (h) window.location.href = h;
134 });
135 });
136 </script>
137 [/if]
138 [if:$no_rows]
139 <div style="text-align:center;padding:40px 18px;color:var(--col-text-3)">
140 <div style="font-size:14px;font-weight:600;color:var(--col-text);margin-bottom:6px">No customers yet</div>
141 <div style="font-size:12px">As soon as your first buyer places an order they will appear here.</div>
142 </div>
143 [/if]
144</div>
145[/if]
146
147[if:$is_detail_view]
148<div class="page-head">
149 <div>
150 <span class="page-eyebrow"><span class="dot"></span> Customers &middot; $store_name</span>
151 <h1 class="page-title">$buyer_name</h1>
152 <p class="page-subtitle">$buyer_email[if:$buyer_no_account] &middot; guest checkout (no account)[/if]</p>
153 </div>
154 <div class="page-actions">
155 <a href="/customers.cgi" class="btn btn-secondary">
156 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
157 Back to customers
158 </a>
159 </div>
160</div>
161
162<div class="cu-detail-grid">
163 <!-- LEFT: profile + credit -->
164 <div>
165 <div class="cu-card">
166 <div class="cu-card-title">Buyer</div>
167 <div class="cu-meta-row"><span class="lbl">Email</span><span class="val">$buyer_email</span></div>
168 [if:$buyer_has_account]
169 <div class="cu-meta-row"><span class="lbl">Display name</span><span class="val">$buyer_name</span></div>
170 <div class="cu-meta-row"><span class="lbl">Joined</span><span class="val">$buyer_joined</span></div>
171 <div class="cu-meta-row"><span class="lbl">Last login</span><span class="val">$buyer_last_login</span></div>
172 [/if]
173 [if:$buyer_no_account]
174 <div class="cu-meta-row"><span class="lbl">Account</span><span class="val">Guest checkout</span></div>
175 [/if]
176 <div class="cu-meta-row"><span class="lbl">Orders</span><span class="val">$buyer_order_count</span></div>
177 <div class="cu-meta-row"><span class="lbl">Lifetime spend</span><span class="val">$buyer_lifetime</span></div>
178 </div>
179
180 <div class="cu-card">
181 <div class="cu-card-title">Store credit balance</div>
182 <div class="cu-bal">
183 <span class="amt[if:$has_credit] pos[/if][if:$has_debit] neg[/if]">$balance_dollars</span>
184 <span class="lbl">applied to next order</span>
185 </div>
186 [if:$can_grant]
187 <form id="cu-grant-form" method="POST" action="/customer_action.cgi">
188 <input type="hidden" name="act" value="grant_credit">
189 <input type="hidden" name="storefront_id" value="$store_id">
190 [if:$buyer_has_account]<input type="hidden" name="buyer_account_id" value="$buyer_id">[/if]
191 <input type="hidden" name="buyer_email" value="$buyer_email">
192 <input type="hidden" name="amount_cents" value="">
193 <input type="hidden" name="note" value="">
194 <input type="hidden" name="return_to" value="">
195 <button type="button" class="cu-grant-btn js-cu-grant">Grant credit to this buyer</button>
196 </form>
197 [/if]
198 [if:!$can_grant]
199 <div style="font-size:11px;color:var(--col-text-3);margin-top:10px">You do not have the &quot;Issue refunds&quot; permission required to grant credit. Ask your storefront owner to enable it for your role.</div>
200 [/if]
201 </div>
202 </div>
203
204 <!-- RIGHT: orders + credit history -->
205 <div>
206 <div class="cu-card">
207 <div class="cu-card-title">Recent orders</div>
208 [if:$has_orders]
209 <table class="cu-table">
210 <thead><tr><th>Order</th><th>Status</th><th class="num">Total</th><th>Credit used</th><th>Placed</th></tr></thead>
211 <tbody>
212 [loop:@orders]
213 <tr>
214 <td class="mono">#$loop1.id</td>
215 <td>
216 [if:$loop1.is_paid]<span class="cu-pill ok">$loop1.status_label</span>[/if]
217 [if:$loop1.is_refunded]<span class="cu-pill neg">$loop1.status_label</span>[/if]
218 [if:!$loop1.is_paid][if:!$loop1.is_refunded]$loop1.status_label[/if][/if]
219 </td>
220 <td class="num mono">$loop1.total_dollars</td>
221 <td class="mono">
222 [if:$loop1.has_credit_used]$loop1.credit_used_dollars[/if]
223 [if:!$loop1.has_credit_used]&mdash;[/if]
224 </td>
225 <td class="mono">$loop1.placed_at</td>
226 </tr>
227 [/loop]
228 </tbody>
229 </table>
230 [/if]
231 [if:$no_orders]
232 <div style="color:var(--col-text-3);font-size:12px;padding:6px 0">No orders from this buyer yet.</div>
233 [/if]
234 </div>
235
236 <div class="cu-card">
237 <div class="cu-card-title">Credit history</div>
238 [if:$has_history]
239 <table class="cu-table">
240 <thead><tr><th class="num">Amount</th><th>Reason</th><th>Order</th><th>Note</th><th>When</th></tr></thead>
241 <tbody>
242 [loop:@history]
243 <tr>
244 <td class="num mono" style="color:[if:$loop1.is_positive]#4ade80[/if][if:$loop1.is_negative]#f87171[/if]">$loop1.amount_dollars</td>
245 <td>$loop1.reason_label</td>
246 <td class="mono">[if:$loop1.has_related]#$loop1.related_order_id[/if][if:!$loop1.has_related]&mdash;[/if]</td>
247 <td>$loop1.note</td>
248 <td class="mono">$loop1.created_at</td>
249 </tr>
250 [/loop]
251 </tbody>
252 </table>
253 [/if]
254 [if:$no_history]
255 <div style="color:var(--col-text-3);font-size:12px;padding:6px 0">No credit activity yet.</div>
256 [/if]
257 </div>
258 </div>
259</div>
260
261[if:$can_grant]
262<script>
263// Grant-credit flow: amount prompt -> note prompt -> confirm -> submit.
264document.addEventListener('click', function (e) {
265 var btn = e.target.closest('.js-cu-grant');
266 if (!btn) return;
267 e.preventDefault();
268 var form = document.getElementById('cu-grant-form');
269 if (!form) return;
270 appPrompt({
271 title: 'Grant credit',
272 message: 'Enter the amount in cents. Example: 500 for \$5.00.',
273 placeholder: '500',
274 input_type: 'number',
275 label: 'Next',
276 required: true,
277 validate: function (v) {
278 if (!/^[0-9]+\$/.test(v)) return 'Whole-cent integer required.';
279 if (parseInt(v, 10) <= 0) return 'Must be greater than zero.';
280 return '';
281 }
282 }).then(function (amt) {
283 if (amt === null) return;
284 appPrompt({
285 title: 'Note for the buyer',
286 message: 'Shown to the buyer on their credit history. Keep it short and clear.',
287 placeholder: 'Sorry for the wait!',
288 default_value: 'Store credit',
289 label: 'Next',
290 required: true
291 }).then(function (note) {
292 if (note === null) return;
293 var dollars = (parseInt(amt, 10) / 100).toFixed(2);
294 appConfirm({
295 title: 'Grant \$' + dollars + ' credit?',
296 message: 'Goes into the buyer ledger immediately and applies on their next order.',
297 confirm_label: 'Grant credit',
298 confirm_style: 'success'
299 }).then(function (ok) {
300 if (!ok) return;
301 form.querySelector('input[name="amount_cents"]').value = amt;
302 form.querySelector('input[name="note"]').value = note;
303 form.querySelector('input[name="return_to"]').value = window.location.pathname + window.location.search;
304 form.submit();
305 });
306 });
307 });
308});
309</script>
310[/if]
311[/if]
312
313<!-- Trailing non-conditional content: MODS::Template returns the eval's
314 final value, so a template that ends with [/if] silently renders empty. -->
315<div style="height:1px"></div>
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help