Skip to main content

max / makenotwork

4.8 KB · 106 lines History Blame Raw
1 function togglePromoFields(purpose) {
2 var discountFields = document.getElementById('discount-fields');
3 var trialFields = document.getElementById('trial-fields');
4 var codeInput = document.getElementById('pc-code');
5 if (purpose === 'discount') {
6 discountFields.classList.remove('is-hidden');
7 trialFields.classList.remove('is-visible');
8 codeInput.required = true;
9 codeInput.placeholder = 'e.g. LAUNCH50';
10 } else if (purpose === 'free_trial') {
11 discountFields.classList.add('is-hidden');
12 trialFields.classList.add('is-visible');
13 codeInput.required = true;
14 codeInput.placeholder = 'e.g. TRIAL14';
15 } else {
16 discountFields.classList.add('is-hidden');
17 trialFields.classList.remove('is-visible');
18 codeInput.required = false;
19 codeInput.placeholder = 'Auto-generated';
20 }
21 }
22
23 /// Show a modal listing redemptions of a single promo code. Fetched on
24 /// demand so the page-render path doesn't pay for it. Guest checkouts
25 /// surface as the guest's email; missing item titles render as "—".
26 function showPromoRedemptions(codeId, codeLabel) {
27 var existing = document.getElementById('promo-redemptions-modal');
28 if (existing) existing.remove();
29
30 var overlay = document.createElement('div');
31 overlay.id = 'promo-redemptions-modal';
32 overlay.className = 'modal-overlay';
33 overlay.style.display = 'flex';
34 overlay.onclick = function (e) { if (e.target === overlay) overlay.remove(); };
35
36 var content = document.createElement('div');
37 content.className = 'modal-content';
38 content.style.maxWidth = '640px';
39 content.style.padding = '2rem';
40 content.innerHTML =
41 '<div class="modal-header" style="margin-bottom: 1rem;">'
42 + '<h2>Redemptions: <code>' + escapeHtml(codeLabel) + '</code></h2>'
43 + '<button type="button" class="modal-close" aria-label="Dismiss">&times;</button>'
44 + '</div>'
45 + '<div id="promo-redemptions-body" class="muted">Loading…</div>';
46 // Wire the close button via JS, not an inline onclick — the app CSP has no
47 // script-src 'unsafe-inline', so a generated inline handler is dead (Run 20).
48 var closeBtn = content.querySelector('.modal-close');
49 if (closeBtn) closeBtn.onclick = function () { overlay.remove(); };
50 overlay.appendChild(content);
51 document.body.appendChild(overlay);
52
53 fetch('/api/promo-codes/' + codeId + '/redemptions', { credentials: 'same-origin' })
54 .then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
55 .then(function (data) {
56 var body = document.getElementById('promo-redemptions-body');
57 if (!body) return;
58 var rows = (data && data.redemptions) || [];
59 if (rows.length === 0) {
60 body.textContent = 'No redemptions yet.';
61 return;
62 }
63 var html = '<table class="data-table" style="width:100%; font-size: 0.9rem;">'
64 + '<thead><tr><th>When</th><th>Who</th><th>Item</th><th>Paid</th></tr></thead><tbody>';
65 for (var i = 0; i < rows.length; i++) {
66 var r = rows[i];
67 var when = new Date(r.redeemed_at).toLocaleDateString();
68 var who = r.username
69 ? '@' + escapeHtml(r.username)
70 : (r.guest_email ? escapeHtml(r.guest_email) + ' (guest)' : '');
71 var item = r.item_title ? escapeHtml(r.item_title) : '';
72 var paid = r.amount_cents === 0 ? 'Free' : '$' + (r.amount_cents / 100).toFixed(2);
73 html += '<tr><td>' + when + '</td><td>' + who + '</td><td>' + item + '</td><td>' + paid + '</td></tr>';
74 }
75 html += '</tbody></table>';
76 if (rows.length === 500) {
77 html += '<p class="muted" style="margin-top: 0.75rem;">Showing the 500 most recent redemptions.</p>';
78 }
79 body.innerHTML = html;
80 })
81 .catch(function () {
82 var body = document.getElementById('promo-redemptions-body');
83 if (body) body.textContent = 'Could not load redemptions.';
84 });
85 }
86
87
88 /// Switch between preset trial lengths and a custom number input. The
89 /// hidden #pc-trial-days carries the actual submitted value so we can keep
90 /// the server-side contract (a single `trial_days` field) untouched.
91 function onTrialPresetChange() {
92 var preset = document.getElementById('pc-trial-preset');
93 var hidden = document.getElementById('pc-trial-days');
94 var custom = document.getElementById('pc-trial-days-custom');
95 if (preset.value === 'custom') {
96 custom.hidden = false;
97 custom.required = true;
98 custom.focus();
99 hidden.value = custom.value || '';
100 } else {
101 custom.hidden = true;
102 custom.required = false;
103 hidden.value = preset.value;
104 }
105 }
106