Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Aura Admin Dashboard</title> | |
| <style> | |
| :root { | |
| --bg: #0d0d0d; | |
| --surface: #1a1a1a; | |
| --border: #333; | |
| --primary: #CC75FF; | |
| --text: #fff; | |
| --text-sec: #aaa; | |
| --danger: #FF3B30; | |
| --success: #12D8C3; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 40px 20px; | |
| background-color: var(--bg); | |
| color: var(--text); | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| } | |
| .container { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| } | |
| .header { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 30px; | |
| } | |
| .header h1 { | |
| color: var(--primary); | |
| margin: 0; | |
| font-size: 28px; | |
| } | |
| .card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 24px; | |
| margin-bottom: 30px; | |
| } | |
| .card h2 { | |
| margin-top: 0; | |
| margin-bottom: 20px; | |
| font-size: 20px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| color: var(--text-sec); | |
| font-weight: 500; | |
| font-size: 14px; | |
| } | |
| input[type="text"], textarea, input[type="file"] { | |
| width: 100%; | |
| padding: 14px; | |
| background: var(--bg); | |
| color: var(--text); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| box-sizing: border-box; | |
| font-size: 15px; | |
| } | |
| textarea { | |
| resize: vertical; | |
| min-height: 100px; | |
| font-family: inherit; | |
| } | |
| input[type="file"] { | |
| padding: 10px; | |
| color: var(--text-sec); | |
| } | |
| button { | |
| background: var(--primary); | |
| color: #000; | |
| border: none; | |
| padding: 14px 24px; | |
| border-radius: 10px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| font-size: 16px; | |
| transition: opacity 0.2s; | |
| width: 100%; | |
| } | |
| button:hover { | |
| opacity: 0.9; | |
| } | |
| button:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| button.danger { | |
| background: rgba(255, 59, 48, 0.15); | |
| color: var(--danger); | |
| width: auto; | |
| padding: 10px 16px; | |
| font-size: 14px; | |
| } | |
| button.danger:hover { | |
| background: rgba(255, 59, 48, 0.25); | |
| } | |
| button.success { | |
| background: rgba(18, 216, 195, 0.15); | |
| color: var(--success); | |
| width: auto; | |
| padding: 10px 16px; | |
| font-size: 14px; | |
| } | |
| button.success:hover { | |
| background: rgba(18, 216, 195, 0.25); | |
| } | |
| .challenge-row { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 16px; | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| margin-bottom: 12px; | |
| background: var(--bg); | |
| } | |
| .challenge-info { | |
| flex: 1; | |
| margin: 0 20px; | |
| } | |
| .challenge-title { | |
| font-size: 18px; | |
| font-weight: bold; | |
| margin-bottom: 6px; | |
| display: block; | |
| } | |
| .challenge-img { | |
| width: 70px; | |
| height: 70px; | |
| border-radius: 8px; | |
| object-fit: cover; | |
| background: #222; | |
| } | |
| .meta-text { | |
| font-size: 13px; | |
| color: var(--text-sec); | |
| } | |
| .badge { | |
| padding: 4px 8px; | |
| border-radius: 6px; | |
| font-size: 11px; | |
| font-weight: bold; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| margin-right: 10px; | |
| } | |
| .badge.active { | |
| background: rgba(18, 216, 195, 0.2); | |
| color: var(--success); | |
| border: 1px solid rgba(18, 216, 195, 0.3); | |
| } | |
| .badge.archived { | |
| background: rgba(255, 255, 255, 0.05); | |
| color: var(--text-sec); | |
| border: 1px solid var(--border); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>Aura Grade Admin</h1> | |
| </div> | |
| <div class="card"> | |
| <h2>Create New Challenge</h2> | |
| <form id="createForm"> | |
| <div class="form-group"> | |
| <label>Challenge Title</label> | |
| <input type="text" id="title" placeholder="e.g. Rate the Fit" required> | |
| </div> | |
| <div class="form-group"> | |
| <label>Description</label> | |
| <textarea id="description" placeholder="Explain what the sorcerers need to match..." required></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label>Reference Image</label> | |
| <input type="file" id="imageFile" accept="image/*" required> | |
| </div> | |
| <button type="submit" id="submitBtn">Publish Challenge</button> | |
| </form> | |
| </div> | |
| <div class="card"> | |
| <h2>Existing Challenges</h2> | |
| <div id="challengesList"> | |
| <p style="color: var(--text-sec);">Loading challenges...</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Update this to your deployed backend URL in production | |
| // Example: const API_BASE = 'https://api.yourdomain.com/api/aurameasure'; | |
| const API_BASE = 'https://everydaycats-everything.hf.space/api/aurameasure'; | |
| document.getElementById('createForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const btn = document.getElementById('submitBtn'); | |
| btn.disabled = true; | |
| btn.innerText = 'Deliberating with Spirits...'; | |
| try { | |
| const title = document.getElementById('title').value; | |
| const description = document.getElementById('description').value; | |
| const fileInput = document.getElementById('imageFile'); | |
| if (!fileInput.files[0]) throw new Error('Please select an image.'); | |
| // Convert file to raw base64 (backend expects it without the data uri prefix) | |
| const base64String = await new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = () => { | |
| const base64 = reader.result.split(',')[1]; | |
| resolve(base64); | |
| }; | |
| reader.onerror = error => reject(error); | |
| reader.readAsDataURL(fileInput.files[0]); | |
| }); | |
| const res = await fetch(`${API_BASE}/admin/challenges`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ title, description, image_base64: base64String }) | |
| }); | |
| const json = await res.json(); | |
| if (!json.success) throw new Error(json.error || 'Failed to create challenge'); | |
| alert('Challenge created successfully!'); | |
| document.getElementById('createForm').reset(); | |
| fetchChallenges(); | |
| } catch (err) { | |
| alert('Error: ' + err.message); | |
| } finally { | |
| btn.disabled = false; | |
| btn.innerText = 'Publish Challenge'; | |
| } | |
| }); | |
| async function fetchChallenges() { | |
| const list = document.getElementById('challengesList'); | |
| try { | |
| const res = await fetch(`${API_BASE}/admin/challenges`); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| const json = await res.json(); | |
| if (!json.success) throw new Error(json.error); | |
| list.innerHTML = ''; | |
| if (json.data.length === 0) { | |
| list.innerHTML = '<p style="color: var(--text-sec);">No challenges found.</p>'; | |
| return; | |
| } | |
| json.data.forEach(c => { | |
| const isActive = !!c.session_id; | |
| const row = document.createElement('div'); | |
| row.className = 'challenge-row'; | |
| row.innerHTML = ` | |
| <img src="${c.reference_image_url}" class="challenge-img" alt="thumb"> | |
| <div class="challenge-info"> | |
| <span class="challenge-title">${c.title}</span> | |
| <span class="badge ${isActive ? 'active' : 'archived'}">${isActive ? 'Active' : 'Archived'}</span> | |
| <span class="meta-text">Entries: ${c.participant_count || 0}</span> | |
| </div> | |
| <button class="${isActive ? 'danger' : 'success'}" onclick="toggleStatus('${c.id}', '${isActive ? 'archive' : 'activate'}')"> | |
| ${isActive ? 'Archive' : 'Make Active'} | |
| </button> | |
| `; | |
| list.appendChild(row); | |
| }); | |
| } catch (err) { | |
| list.innerHTML = `<p style="color: var(--danger);">Error loading challenges: ${err.message}</p>`; | |
| } | |
| } | |
| async function toggleStatus(id, action) { | |
| if (!confirm(`Are you sure you want to ${action} this challenge?`)) return; | |
| try { | |
| const res = await fetch(`${API_BASE}/admin/challenges/${id}/toggle`, { | |
| method: 'PUT', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ action }) | |
| }); | |
| const json = await res.json(); | |
| if (!json.success) throw new Error(json.error); | |
| fetchChallenges(); | |
| } catch (err) { | |
| alert('Error: ' + err.message); | |
| } | |
| } | |
| // Initialize dashboard | |
| fetchChallenges(); | |
| </script> | |
| </body> | |
| </html> |