← back to Interiordesignershowroom
public/js/adminbar.js
78 lines
// Front-end admin "see-more" bar. Shown ONLY when the server has set the
// `ids_admin_ui` hint cookie (after a successful /admin Basic-auth). This cookie
// is a UI hint, NOT a security boundary — every mutation below still POSTs to the
// Basic-auth-gated /admin route, so a forged cookie reveals only the low-value
// metadata the storefront already carries and can change nothing.
(function () {
function cookie(n) { var m = document.cookie.match('(?:^|; )' + n + '=([^;]*)'); return m ? m[1] : ''; }
if (cookie('ids_admin_ui') !== '1') return;
var KEY = 'ids_adminview';
var on = localStorage.getItem(KEY) === '1';
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
var bar = document.createElement('div');
bar.className = 'admin-bar';
bar.innerHTML =
'<span class="ab-tag">🔧 Admin</span>'
+ '<label class="ab-toggle"><input type="checkbox" id="ab-on"' + (on ? ' checked' : '') + '><span>Admin view</span></label>'
+ '<a class="ab-link" href="/admin">Dashboard ↗</a>'
+ '<a class="ab-link" href="/admin/affiliates">Affiliates ↗</a>'
+ '<button type="button" class="ab-exit" id="ab-exit" title="Leave admin view on this device">Exit</button>';
document.body.appendChild(bar);
apply(on);
document.getElementById('ab-on').addEventListener('change', function (e) {
on = e.target.checked; localStorage.setItem(KEY, on ? '1' : '0'); apply(on);
});
document.getElementById('ab-exit').addEventListener('click', function () {
document.cookie = 'ids_admin_ui=; Max-Age=0; path=/';
document.body.classList.remove('admin-view');
Array.prototype.forEach.call(document.querySelectorAll('.admin-chip'), function (c) { c.remove(); });
bar.remove();
});
function apply(show) {
document.body.classList.toggle('admin-view', show);
if (show) decorate();
}
// Build a per-card admin chip from the data-* the server already emits — no extra
// request. Idempotent: skips a card that already has one.
function decorate() {
Array.prototype.forEach.call(document.querySelectorAll('.card[data-id]'), function (card) {
if (card.querySelector('.admin-chip')) return;
var id = card.getAttribute('data-id');
var net = card.getAttribute('data-network') || '';
var adv = card.getAttribute('data-advertiser') || '';
var feat = card.getAttribute('data-featured') === '1';
var inStock = card.getAttribute('data-instock') !== '0';
var chip = document.createElement('div');
chip.className = 'admin-chip';
chip.innerHTML =
'<div class="ac-src">' + esc(net) + (adv ? ' · ' + esc(adv) : '') + ' <span class="ac-id">#' + esc(id) + '</span></div>'
+ '<div class="ac-flags">' + (inStock ? '' : '<span class="ac-out">out of stock</span> ')
+ '<button type="button" class="ac-feat' + (feat ? ' is-on' : '') + '" data-id="' + esc(id) + '">' + (feat ? '★ Featured' : '☆ Feature') + '</button></div>';
card.appendChild(chip);
var btn = chip.querySelector('.ac-feat');
btn.addEventListener('click', function () {
btn.disabled = true;
// Browser auto-attaches the cached /admin Basic-auth creds; same-origin POST
// satisfies the admin CSRF guard.
fetch('/admin/products/' + id + '/featured', {
method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: ''
}).then(function (r) {
if (!r.ok) throw new Error(r.status);
var nowOn = !btn.classList.contains('is-on');
btn.classList.toggle('is-on', nowOn);
btn.textContent = nowOn ? '★ Featured' : '☆ Feature';
card.setAttribute('data-featured', nowOn ? '1' : '0');
}).catch(function () {
alert('Feature toggle failed — make sure you are signed in to /admin first.');
}).finally(function () { btn.disabled = false; });
});
});
}
})();