← back to IWasCute
public/admin.html
247 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin — I was Cute</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#1a1714;--bg2:#221f1a;--bg3:#2a2620;--cream:#f5ede0;--amber:#c4763a;--amber2:#d4894e;--rose:#d4827a;--green:#34d399;--text:#f5ede0;--text2:#b8ad9e;--text3:#8a7f72;--sans:'DM Sans',sans-serif;--serif:'Playfair Display',serif;--radius:10px}
body{font-family:var(--sans);background:var(--bg);color:var(--text);min-height:100vh}
.admin-header{padding:20px 32px;border-bottom:1px solid rgba(245,237,224,.08);display:flex;align-items:center;justify-content:space-between}
.admin-header h1{font-family:var(--serif);font-size:22px}
.admin-header h1 em{color:var(--amber2);font-style:italic}
.admin-header .badge{font-size:12px;background:var(--amber);color:#fff;padding:4px 12px;border-radius:99px;font-weight:600}
.stats-bar{display:flex;gap:16px;padding:20px 32px;flex-wrap:wrap}
.stat-card{background:var(--bg2);border:1px solid rgba(245,237,224,.06);border-radius:var(--radius);padding:16px 20px;flex:1;min-width:140px}
.stat-card .stat-num{font-size:28px;font-weight:700;font-family:var(--serif)}
.stat-card .stat-label{font-size:12px;color:var(--text3);margin-top:2px;text-transform:uppercase;letter-spacing:.08em}
.stat-card.pending .stat-num{color:var(--amber2)}
.stat-card.approved .stat-num{color:var(--green)}
.stat-card.rejected .stat-num{color:var(--rose)}
.queue-header{padding:20px 32px 12px;display:flex;align-items:center;justify-content:space-between}
.queue-header h2{font-size:16px;font-weight:600}
.queue-header .filter-pills{display:flex;gap:6px}
.queue-header .pill{background:var(--bg3);border:1px solid rgba(245,237,224,.08);color:var(--text2);padding:6px 14px;border-radius:99px;font-size:12px;cursor:pointer;font-family:var(--sans);border:none}
.queue-header .pill.active{background:var(--amber);color:#fff}
.queue{padding:0 32px 32px}
.queue-item{display:flex;gap:16px;padding:16px;background:var(--bg2);border:1px solid rgba(245,237,224,.06);border-radius:var(--radius);margin-bottom:10px;align-items:center;transition:border-color .2s}
.queue-item:hover{border-color:var(--amber)}
.queue-item .qi-img{width:80px;height:80px;border-radius:6px;object-fit:cover;background:var(--bg3);flex-shrink:0}
.queue-item .qi-info{flex:1;min-width:0}
.queue-item .qi-title{font-weight:600;font-size:14px;margin-bottom:2px}
.queue-item .qi-meta{font-size:12px;color:var(--text3)}
.queue-item .qi-meta span{margin-right:12px}
.queue-item .qi-desc{font-size:12px;color:var(--text2);margin-top:4px;font-style:italic;line-height:1.4}
.queue-item .qi-actions{display:flex;gap:8px;flex-shrink:0}
.qi-btn{padding:8px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;border:none;font-family:var(--sans);transition:all .2s}
.qi-btn.approve{background:var(--green);color:#000}
.qi-btn.approve:hover{filter:brightness(1.1);transform:translateY(-1px)}
.qi-btn.reject{background:var(--rose);color:#fff}
.qi-btn.reject:hover{filter:brightness(1.1);transform:translateY(-1px)}
.qi-btn.view{background:var(--bg3);color:var(--text2);border:1px solid rgba(245,237,224,.1)}
.qi-btn.view:hover{border-color:var(--amber)}
.qi-status{padding:4px 10px;border-radius:4px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}
.qi-status.pending{background:rgba(196,118,58,.15);color:var(--amber2)}
.qi-status.approved{background:rgba(52,211,153,.15);color:var(--green)}
.qi-status.rejected{background:rgba(212,130,122,.15);color:var(--rose)}
.empty-queue{text-align:center;padding:60px 20px;color:var(--text3);font-size:14px}
.empty-queue .eq-icon{font-size:48px;margin-bottom:12px;opacity:.4}
/* Demo banner */
.demo-banner{background:linear-gradient(135deg,rgba(196,118,58,.1),rgba(212,130,122,.1));border:1px solid rgba(196,118,58,.2);border-radius:var(--radius);padding:16px 20px;margin:0 32px 0;font-size:13px;color:var(--amber2);display:flex;align-items:center;gap:12px}
.demo-banner strong{color:var(--cream)}
</style>
</head>
<body>
<div class="admin-header">
<h1>I was <em>Cute</em> — Admin</h1>
<span class="badge">Admin Panel</span>
</div>
<div class="stats-bar" id="stats-bar">
<div class="stat-card"><div class="stat-num" id="stat-total">-</div><div class="stat-label">Total Submissions</div></div>
<div class="stat-card pending"><div class="stat-num" id="stat-pending">-</div><div class="stat-label">Pending Review</div></div>
<div class="stat-card approved"><div class="stat-num" id="stat-approved">-</div><div class="stat-label">Approved</div></div>
<div class="stat-card rejected"><div class="stat-num" id="stat-rejected">-</div><div class="stat-label">Rejected</div></div>
</div>
<div class="demo-banner" id="demo-banner" style="margin-top:16px">
<span style="font-size:20px">💡</span>
<span><strong>Demo mode</strong> — Submit photos from the main app to see them appear here. <a href="/" style="color:var(--amber);text-decoration:underline">Go to main app</a></span>
</div>
<div class="queue-header">
<h2>Submission Queue</h2>
<div class="filter-pills">
<button class="pill active" data-filter="all">All</button>
<button class="pill" data-filter="pending">Pending</button>
<button class="pill" data-filter="approved">Approved</button>
<button class="pill" data-filter="rejected">Rejected</button>
</div>
</div>
<div class="queue" id="queue"></div>
<script>
var AUTH = 'Basic ' + btoa('admin:DWSecure2024!');
var currentFilter = 'all';
document.querySelectorAll('.pill').forEach(function(p) {
p.addEventListener('click', function() {
document.querySelectorAll('.pill').forEach(function(x) { x.classList.remove('active'); });
p.classList.add('active');
currentFilter = p.dataset.filter;
renderQueue();
});
});
var allSubmissions = [];
function loadSubmissions() {
fetch('/api/admin/submissions', { headers: { Authorization: AUTH } })
.then(function(r) { return r.json(); })
.then(function(data) {
allSubmissions = data.submissions || [];
updateStats();
renderQueue();
if (allSubmissions.length > 0) document.getElementById('demo-banner').style.display = 'none';
})
.catch(function(e) { console.error('Load error:', e); });
}
function updateStats() {
var total = allSubmissions.length;
var pending = allSubmissions.filter(function(s) { return s.status === 'pending'; }).length;
var approved = allSubmissions.filter(function(s) { return s.status === 'approved'; }).length;
var rejected = allSubmissions.filter(function(s) { return s.status === 'rejected'; }).length;
document.getElementById('stat-total').textContent = total;
document.getElementById('stat-pending').textContent = pending;
document.getElementById('stat-approved').textContent = approved;
document.getElementById('stat-rejected').textContent = rejected;
}
function renderQueue() {
var queue = document.getElementById('queue');
queue.textContent = '';
var filtered = currentFilter === 'all' ? allSubmissions : allSubmissions.filter(function(s) { return s.status === currentFilter; });
if (filtered.length === 0) {
var empty = document.createElement('div');
empty.className = 'empty-queue';
var icon = document.createElement('div');
icon.className = 'eq-icon';
icon.textContent = '\uD83D\uDCED';
empty.appendChild(icon);
var msg = document.createElement('p');
msg.textContent = currentFilter === 'all' ? 'No submissions yet. Photos submitted from the main app will appear here.' : 'No ' + currentFilter + ' submissions.';
empty.appendChild(msg);
queue.appendChild(empty);
return;
}
filtered.forEach(function(sub) {
var item = document.createElement('div');
item.className = 'queue-item';
// Thumbnail placeholder
var img = document.createElement('div');
img.className = 'qi-img';
img.style.background = 'linear-gradient(135deg, var(--amber), var(--bg3))';
img.style.display = 'flex';
img.style.alignItems = 'center';
img.style.justifyContent = 'center';
img.style.fontSize = '24px';
img.textContent = '\uD83D\uDDBC';
item.appendChild(img);
// Info
var info = document.createElement('div');
info.className = 'qi-info';
var titleRow = document.createElement('div');
titleRow.style.cssText = 'display:flex;align-items:center;gap:8px';
var title = document.createElement('span');
title.className = 'qi-title';
title.textContent = sub.title || 'Untitled Photo';
titleRow.appendChild(title);
var status = document.createElement('span');
status.className = 'qi-status ' + sub.status;
status.textContent = sub.status;
titleRow.appendChild(status);
info.appendChild(titleRow);
var meta = document.createElement('div');
meta.className = 'qi-meta';
var catSpan = document.createElement('span');
catSpan.textContent = sub.category || 'Uncategorized';
meta.appendChild(catSpan);
var yearSpan = document.createElement('span');
yearSpan.textContent = sub.year ? 'Year: ' + sub.year : '';
meta.appendChild(yearSpan);
var ageSpan = document.createElement('span');
ageSpan.textContent = sub.age ? 'Age: ' + sub.age : '';
meta.appendChild(ageSpan);
var dateSpan = document.createElement('span');
dateSpan.textContent = new Date(sub.submittedAt).toLocaleString();
meta.appendChild(dateSpan);
info.appendChild(meta);
if (sub.description) {
var desc = document.createElement('div');
desc.className = 'qi-desc';
desc.textContent = '"' + sub.description.substring(0, 120) + (sub.description.length > 120 ? '...' : '') + '"';
info.appendChild(desc);
}
item.appendChild(info);
// Actions
var actions = document.createElement('div');
actions.className = 'qi-actions';
if (sub.status === 'pending') {
var approveBtn = document.createElement('button');
approveBtn.className = 'qi-btn approve';
approveBtn.textContent = 'Approve';
approveBtn.addEventListener('click', function() { reviewAction(sub.id, 'approve'); });
actions.appendChild(approveBtn);
var rejectBtn = document.createElement('button');
rejectBtn.className = 'qi-btn reject';
rejectBtn.textContent = 'Reject';
rejectBtn.addEventListener('click', function() { reviewAction(sub.id, 'reject'); });
actions.appendChild(rejectBtn);
}
item.appendChild(actions);
queue.appendChild(item);
});
}
function reviewAction(id, action) {
fetch('/api/admin/review', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: AUTH },
body: JSON.stringify({ id: id, action: action })
})
.then(function(r) { return r.json(); })
.then(function() { loadSubmissions(); })
.catch(function(e) { console.error('Review error:', e); });
}
// Poll every 5s for new submissions
loadSubmissions();
setInterval(loadSubmissions, 5000);
</script>
</body>
</html>