Download api/templates/shared/bug_report.html from mwask/anicove: direct link, hf CLI and curl.
- Browser
- Download file 26.6 kB
-
https://huggingface.co/spaces/mwask/anicove/resolve/main/api/templates/shared/bug_report.html
- Command line
-
hf download hf://spaces/mwask/anicove/api/templates/shared/bug_report.html
-
curl -L -o bug_report.html https://huggingface.co/spaces/mwask/anicove/resolve/main/api/templates/shared/bug_report.html
26.6 kB
| {% extends "shared/base.html" %} | |
| {% block title %}Report an Issue - AniCove{% endblock %} | |
| {% block meta_description %}Report bugs, issues, or request features on AniCove.{% endblock %} | |
| {% block extra_css %} | |
| <style> | |
| :root { | |
| --bug-bg: #0a0a0a; | |
| --bug-surface: #121212; | |
| --bug-border: #1e1e1e; | |
| --bug-text: #e0e0e0; | |
| --bug-muted: #777; | |
| --bug-accent: #3b82f6; | |
| --bug-accent-glow: rgba(59,130,246,0.25); | |
| --bug-success: #22c55e; | |
| --bug-warning: #f59e0b; | |
| --bug-error: #ef4444; | |
| } | |
| .bug-page { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 32px 20px; | |
| } | |
| .bug-header { | |
| margin-bottom: 32px; | |
| } | |
| .bug-header-icon { | |
| width: 48px; | |
| height: 48px; | |
| border-radius: 14px; | |
| background: rgba(239, 68, 68, 0.15); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 16px; | |
| color: #ef4444; | |
| } | |
| .bug-header h1 { | |
| font-size: 1.6rem; | |
| font-weight: 700; | |
| margin: 0 0 6px 0; | |
| color: #fff; | |
| } | |
| .bug-header p { | |
| font-size: 0.9rem; | |
| color: var(--bug-muted); | |
| margin: 0; | |
| line-height: 1.5; | |
| } | |
| /* Form */ | |
| .bug-form { | |
| background: var(--bug-surface); | |
| border: 1px solid var(--bug-border); | |
| border-radius: 12px; | |
| padding: 24px; | |
| margin-bottom: 28px; | |
| } | |
| .bug-form-group { | |
| margin-bottom: 18px; | |
| } | |
| .bug-form-label { | |
| display: block; | |
| font-size: 0.82rem; | |
| font-weight: 600; | |
| color: #bbb; | |
| margin-bottom: 6px; | |
| } | |
| .bug-form-label .required { | |
| color: #ef4444; | |
| margin-left: 2px; | |
| } | |
| .bug-form-input, | |
| .bug-form-textarea { | |
| width: 100%; | |
| padding: 10px 14px; | |
| background: rgba(0,0,0,0.3); | |
| border: 1px solid var(--bug-border); | |
| border-radius: 8px; | |
| color: #fff; | |
| font-size: 0.9rem; | |
| transition: border-color 0.2s, box-shadow 0.2s; | |
| font-family: inherit; | |
| box-sizing: border-box; | |
| } | |
| .bug-form-input:focus, | |
| .bug-form-textarea:focus { | |
| outline: none; | |
| border-color: var(--bug-accent); | |
| box-shadow: 0 0 0 3px var(--bug-accent-glow); | |
| } | |
| .bug-form-textarea { | |
| min-height: 120px; | |
| resize: vertical; | |
| } | |
| .bug-form-hint { | |
| font-size: 0.75rem; | |
| color: var(--bug-muted); | |
| margin-top: 4px; | |
| } | |
| /* Category pills */ | |
| .bug-category-group { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| } | |
| .bug-category-pill { | |
| padding: 8px 18px; | |
| border-radius: 999px; | |
| border: 1px solid var(--bug-border); | |
| background: rgba(0,0,0,0.2); | |
| color: #999; | |
| font-size: 0.82rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| font-family: inherit; | |
| } | |
| .bug-category-pill:hover { | |
| border-color: rgba(59,130,246,0.3); | |
| color: #fff; | |
| } | |
| .bug-category-pill.active { | |
| border-color: var(--bug-accent); | |
| background: rgba(59,130,246,0.15); | |
| color: var(--bug-accent); | |
| } | |
| .bug-category-pill[data-category="bug"].active { | |
| border-color: #ef4444; | |
| background: rgba(239,68,68,0.15); | |
| color: #ef4444; | |
| } | |
| .bug-category-pill[data-category="feature"].active { | |
| border-color: #22c55e; | |
| background: rgba(34,197,94,0.15); | |
| color: #22c55e; | |
| } | |
| .bug-category-pill[data-category="other"].active { | |
| border-color: #f59e0b; | |
| background: rgba(245,158,11,0.15); | |
| color: #f59e0b; | |
| } | |
| /* Submit button */ | |
| .bug-submit-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 12px 28px; | |
| background: var(--bug-accent); | |
| color: #fff; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: background 0.2s, transform 0.15s, box-shadow 0.2s; | |
| font-family: inherit; | |
| } | |
| .bug-submit-btn:hover { | |
| background: #2563eb; | |
| transform: translateY(-1px); | |
| box-shadow: 0 4px 15px var(--bug-accent-glow); | |
| } | |
| .bug-submit-btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* Success message */ | |
| .bug-success { | |
| display: none; | |
| background: rgba(34,197,94,0.1); | |
| border: 1px solid rgba(34,197,94,0.25); | |
| border-radius: 10px; | |
| padding: 16px 20px; | |
| margin-bottom: 20px; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .bug-success.visible { | |
| display: flex; | |
| } | |
| .bug-success-icon { | |
| color: var(--bug-success); | |
| flex-shrink: 0; | |
| } | |
| .bug-success-text { | |
| color: #bbb; | |
| font-size: 0.9rem; | |
| line-height: 1.5; | |
| } | |
| .bug-success-text strong { | |
| color: #fff; | |
| } | |
| /* Error */ | |
| .bug-error { | |
| display: none; | |
| background: rgba(239,68,68,0.1); | |
| border: 1px solid rgba(239,68,68,0.25); | |
| border-radius: 8px; | |
| padding: 10px 14px; | |
| margin-bottom: 14px; | |
| color: #fca5a5; | |
| font-size: 0.85rem; | |
| } | |
| .bug-error.visible { | |
| display: block; | |
| } | |
| /* My reports section */ | |
| .bug-my-reports { | |
| background: var(--bug-surface); | |
| border: 1px solid var(--bug-border); | |
| border-radius: 12px; | |
| overflow: hidden; | |
| } | |
| .bug-my-reports-header { | |
| padding: 16px 20px; | |
| border-bottom: 1px solid var(--bug-border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .bug-my-reports-header h2 { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| margin: 0; | |
| color: #fff; | |
| } | |
| .bug-my-reports-count { | |
| font-size: 0.8rem; | |
| color: var(--bug-muted); | |
| background: rgba(255,255,255,0.05); | |
| padding: 2px 10px; | |
| border-radius: 999px; | |
| } | |
| .bug-report-item { | |
| border-bottom: 1px solid rgba(255,255,255,0.04); | |
| transition: background 0.15s; | |
| } | |
| .bug-report-item:last-child { | |
| border-bottom: none; | |
| } | |
| .bug-report-summary { | |
| padding: 16px 20px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 10px; | |
| transition: background 0.15s; | |
| } | |
| .bug-report-summary:hover { | |
| background: rgba(255,255,255,0.03); | |
| } | |
| .bug-report-expand-icon { | |
| flex-shrink: 0; | |
| color: #555; | |
| transition: transform 0.2s; | |
| margin-top: 4px; | |
| } | |
| .bug-report-item.expanded .bug-report-expand-icon { | |
| transform: rotate(90deg); | |
| } | |
| .bug-report-summary-content { | |
| flex: 1; | |
| min-width: 0; | |
| } | |
| .bug-report-title { | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| color: #ddd; | |
| margin-bottom: 4px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .bug-report-body-preview { | |
| font-size: 0.82rem; | |
| color: var(--bug-muted); | |
| line-height: 1.5; | |
| margin-bottom: 6px; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 2; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .bug-report-meta { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| flex-wrap: wrap; | |
| } | |
| .bug-status-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| font-size: 0.7rem; | |
| font-weight: 600; | |
| padding: 2px 10px; | |
| border-radius: 999px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.04em; | |
| } | |
| .bug-status-badge.open { | |
| background: rgba(239,68,68,0.12); | |
| color: #ef4444; | |
| } | |
| .bug-status-badge.in_progress { | |
| background: rgba(59,130,246,0.12); | |
| color: var(--bug-accent); | |
| } | |
| .bug-status-badge.resolved { | |
| background: rgba(34,197,94,0.12); | |
| color: var(--bug-success); | |
| } | |
| .bug-status-badge.closed { | |
| background: rgba(255,255,255,0.05); | |
| color: var(--bug-muted); | |
| } | |
| .bug-report-date { | |
| font-size: 0.75rem; | |
| color: #555; | |
| } | |
| .bug-report-reply-count { | |
| font-size: 0.75rem; | |
| color: var(--bug-muted); | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .bug-report-reply-count svg { | |
| width: 12px; | |
| height: 12px; | |
| } | |
| /* Expanded report detail */ | |
| .bug-report-detail { | |
| display: none; | |
| padding: 0 20px 16px; | |
| } | |
| .bug-report-item.expanded .bug-report-detail { | |
| display: block; | |
| } | |
| .bug-report-full-body { | |
| background: rgba(0,0,0,0.2); | |
| border: 1px solid rgba(255,255,255,0.04); | |
| border-radius: 8px; | |
| padding: 12px 14px; | |
| font-size: 0.85rem; | |
| color: #bbb; | |
| line-height: 1.6; | |
| white-space: pre-wrap; | |
| margin-bottom: 12px; | |
| } | |
| /* Conversation thread */ | |
| .bug-thread { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| margin-bottom: 12px; | |
| } | |
| .bug-thread-message { | |
| display: flex; | |
| gap: 10px; | |
| align-items: flex-start; | |
| } | |
| .bug-thread-message.admin { | |
| flex-direction: row-reverse; | |
| } | |
| .bug-thread-avatar { | |
| width: 28px; | |
| height: 28px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| background: #222; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.7rem; | |
| font-weight: 700; | |
| color: #fff; | |
| } | |
| .bug-thread-avatar.admin-avatar { | |
| background: var(--bug-accent); | |
| } | |
| .bug-thread-avatar.user-avatar { | |
| background: #555; | |
| } | |
| .bug-thread-bubble { | |
| max-width: 80%; | |
| padding: 10px 14px; | |
| border-radius: 12px; | |
| font-size: 0.83rem; | |
| line-height: 1.5; | |
| position: relative; | |
| } | |
| .bug-thread-message.user .bug-thread-bubble { | |
| background: rgba(255,255,255,0.06); | |
| border: 1px solid rgba(255,255,255,0.06); | |
| color: #ddd; | |
| border-bottom-left-radius: 4px; | |
| } | |
| .bug-thread-message.admin .bug-thread-bubble { | |
| background: rgba(59,130,246,0.1); | |
| border: 1px solid rgba(59,130,246,0.15); | |
| color: #cbd5e1; | |
| border-bottom-right-radius: 4px; | |
| text-align: right; | |
| } | |
| .bug-thread-bubble .bubble-meta { | |
| font-size: 0.65rem; | |
| color: #555; | |
| margin-top: 4px; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .bug-thread-message.admin .bubble-meta { | |
| justify-content: flex-end; | |
| } | |
| .bug-thread-badge { | |
| font-size: 0.6rem; | |
| font-weight: 600; | |
| padding: 1px 6px; | |
| border-radius: 4px; | |
| text-transform: uppercase; | |
| background: rgba(59,130,246,0.15); | |
| color: var(--bug-accent); | |
| } | |
| .bug-thread-badge.broadcast { | |
| background: rgba(245,158,11,0.15); | |
| color: #f59e0b; | |
| } | |
| /* User reply form in expanded report */ | |
| .bug-thread-reply { | |
| display: flex; | |
| gap: 8px; | |
| align-items: flex-end; | |
| } | |
| .bug-thread-reply-input { | |
| flex: 1; | |
| padding: 8px 12px; | |
| background: rgba(0,0,0,0.3); | |
| border: 1px solid var(--bug-border); | |
| border-radius: 8px; | |
| color: #fff; | |
| font-size: 0.83rem; | |
| font-family: inherit; | |
| resize: vertical; | |
| min-height: 36px; | |
| } | |
| .bug-thread-reply-input:focus { | |
| outline: none; | |
| border-color: var(--bug-accent); | |
| } | |
| .bug-thread-reply-btn { | |
| padding: 8px 14px; | |
| background: var(--bug-accent); | |
| color: #fff; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 0.82rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| font-family: inherit; | |
| white-space: nowrap; | |
| transition: background 0.2s; | |
| } | |
| .bug-thread-reply-btn:hover { | |
| background: #2563eb; | |
| } | |
| .bug-report-empty { | |
| padding: 40px 20px; | |
| text-align: center; | |
| color: var(--bug-muted); | |
| font-size: 0.85rem; | |
| } | |
| .bug-report-empty-icon { | |
| font-size: 2rem; | |
| margin-bottom: 8px; | |
| opacity: 0.5; | |
| } | |
| .bug-loading { | |
| padding: 40px; | |
| text-align: center; | |
| color: var(--bug-muted); | |
| } | |
| .bug-spinner { | |
| display: inline-block; | |
| width: 24px; | |
| height: 24px; | |
| border: 2px solid rgba(255,255,255,0.1); | |
| border-top-color: var(--bug-accent); | |
| border-radius: 50%; | |
| animation: bugSpin 0.6s linear infinite; | |
| } | |
| @keyframes bugSpin { | |
| to { transform: rotate(360deg); } | |
| } | |
| @media (max-width: 600px) { | |
| .bug-page { | |
| padding: 20px 14px; | |
| } | |
| .bug-form { | |
| padding: 18px; | |
| } | |
| } | |
| </style> | |
| {% endblock %} | |
| {% block content %} | |
| <div class="bug-page"> | |
| <div class="bug-header"> | |
| <div class="bug-header-icon"> | |
| <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path> | |
| <line x1="4" y1="22" x2="4" y2="15"></line> | |
| </svg> | |
| </div> | |
| <h1>Report an Issue</h1> | |
| <p>Found a bug, something not working, or have a feature idea? Let us know and we'll look into it.</p> | |
| </div> | |
| <!-- Success Message --> | |
| <div class="bug-success" id="bug-success"> | |
| <div class="bug-success-icon"> | |
| <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path> | |
| <polyline points="22 4 12 14.01 9 11.01"></polyline> | |
| </svg> | |
| </div> | |
| <div class="bug-success-text"> | |
| <strong>Report submitted!</strong> Thank you for your feedback. We'll review it and get back to you if needed. | |
| </div> | |
| </div> | |
| <!-- Error Message --> | |
| <div class="bug-error" id="bug-error"></div> | |
| {% if session.get('username') %} | |
| <!-- Submission Form --> | |
| <form class="bug-form" id="bug-report-form"> | |
| <div class="bug-form-group"> | |
| <label class="bug-form-label"> | |
| Category <span class="required">*</span> | |
| </label> | |
| <div class="bug-category-group" id="bug-category-group"> | |
| <button type="button" class="bug-category-pill active" data-category="bug" onclick="selectCategory(this)"> | |
| 🐛 Bug Report | |
| </button> | |
| <button type="button" class="bug-category-pill" data-category="feature" onclick="selectCategory(this)"> | |
| 💡 Feature Request | |
| </button> | |
| <button type="button" class="bug-category-pill" data-category="other" onclick="selectCategory(this)"> | |
| 📝 Other | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bug-form-group"> | |
| <label class="bug-form-label" for="bug-title"> | |
| Title <span class="required">*</span> | |
| </label> | |
| <input type="text" id="bug-title" class="bug-form-input" placeholder="Brief summary of the issue..." maxlength="200" required> | |
| <div class="bug-form-hint">Be specific — this helps us find and fix it faster.</div> | |
| </div> | |
| <div class="bug-form-group"> | |
| <label class="bug-form-label" for="bug-body"> | |
| Description <span class="required">*</span> | |
| </label> | |
| <textarea id="bug-body" class="bug-form-textarea" placeholder="Describe what happened, steps to reproduce, what you expected to happen..." maxlength="5000" required></textarea> | |
| <div class="bug-form-hint">Include any relevant details like error messages, browser, or device info.</div> | |
| </div> | |
| <div class="bug-form-group"> | |
| <label class="bug-form-label" for="bug-page-url"> | |
| Page URL <span style="color:#555;">(optional)</span> | |
| </label> | |
| <input type="text" id="bug-page-url" class="bug-form-input" placeholder="e.g. /watch/example-ep-1" maxlength="500"> | |
| <div class="bug-form-hint">Which page were you on when you encountered this issue?</div> | |
| </div> | |
| <button type="submit" class="bug-submit-btn" id="bug-submit-btn"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path> | |
| <polyline points="22 4 12 14.01 9 11.01"></polyline> | |
| </svg> | |
| Submit Report | |
| </button> | |
| </form> | |
| <!-- My Previous Reports --> | |
| <div class="bug-my-reports" id="bug-my-reports"> | |
| <div class="bug-my-reports-header"> | |
| <h2>My Reports</h2> | |
| <span class="bug-my-reports-count" id="bug-reports-count">0</span> | |
| </div> | |
| <div id="bug-reports-list"> | |
| <div class="bug-loading"> | |
| <div class="bug-spinner"></div> | |
| </div> | |
| </div> | |
| </div> | |
| {% else %} | |
| <div class="bug-form" style="text-align:center; padding: 40px 20px;"> | |
| <p style="color: var(--bug-muted); margin-bottom: 16px;">Please sign in to submit a bug report.</p> | |
| <button class="bug-submit-btn" onclick="openLoginModal()"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/> | |
| <polyline points="10 17 15 12 10 7"/> | |
| <line x1="15" y1="12" x2="3" y2="12"/> | |
| </svg> | |
| Sign In | |
| </button> | |
| </div> | |
| {% endif %} | |
| </div> | |
| {% endblock %} | |
| {% block extra_js %} | |
| <script> | |
| function selectCategory(btn) { | |
| document.querySelectorAll('.bug-category-pill').forEach(p => p.classList.remove('active')); | |
| btn.classList.add('active'); | |
| } | |
| {% if session.get('username') %} | |
| // Load user's reports | |
| async function loadMyReports() { | |
| const listEl = document.getElementById('bug-reports-list'); | |
| try { | |
| const res = await fetch('/api/bug-reports/my'); | |
| const data = await res.json(); | |
| if (!data.success) { | |
| listEl.innerHTML = '<div class="bug-report-empty">Could not load reports.</div>'; | |
| return; | |
| } | |
| document.getElementById('bug-reports-count').textContent = data.total; | |
| if (data.reports.length === 0) { | |
| listEl.innerHTML = '<div class="bug-report-empty"><div class="bug-report-empty-icon">📭</div>No reports yet. Submit one above!</div>'; | |
| return; | |
| } | |
| listEl.innerHTML = data.reports.map(r => { | |
| const statusLabel = r.status === 'in_progress' ? 'In Progress' : r.status.charAt(0).toUpperCase() + r.status.slice(1); | |
| const date = r.created_at ? new Date(r.created_at).toLocaleDateString() : ''; | |
| // Build conversation thread | |
| let threadHtml = ''; | |
| const allMessages = []; | |
| // Admin replies | |
| (r.admin_replies || []).forEach(rep => { | |
| allMessages.push({ ...rep, type: 'admin' }); | |
| }); | |
| // User replies | |
| (r.user_replies || []).forEach(rep => { | |
| allMessages.push({ ...rep, type: 'user' }); | |
| }); | |
| // Sort by created_at | |
| allMessages.sort((a, b) => (a.created_at || '').localeCompare(b.created_at || '')); | |
| if (allMessages.length > 0) { | |
| threadHtml = '<div class="bug-thread">' + allMessages.map(msg => { | |
| const msgDate = msg.created_at ? new Date(msg.created_at).toLocaleString() : ''; | |
| if (msg.type === 'admin') { | |
| return ` | |
| <div class="bug-thread-message admin"> | |
| <div class="bug-thread-avatar admin-avatar">A</div> | |
| <div class="bug-thread-bubble"> | |
| <div>${msg.body}</div> | |
| <div class="bubble-meta"> | |
| <span>${msgDate}</span> | |
| ${msg.is_broadcast ? '<span class="bug-thread-badge broadcast">Broadcast</span>' : '<span class="bug-thread-badge">Admin</span>'} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } else { | |
| return ` | |
| <div class="bug-thread-message user"> | |
| <div class="bug-thread-avatar user-avatar">U</div> | |
| <div class="bug-thread-bubble"> | |
| <div>${msg.body}</div> | |
| <div class="bubble-meta"> | |
| <span>${msgDate}</span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| }).join('') + '</div>'; | |
| } | |
| return ` | |
| <div class="bug-report-item" id="report-${r._id}"> | |
| <div class="bug-report-summary" onclick="toggleReport('${r._id}')"> | |
| <svg class="bug-report-expand-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <polyline points="9 18 15 12 9 6"></polyline> | |
| </svg> | |
| <div class="bug-report-summary-content"> | |
| <div class="bug-report-title"> | |
| ${r.title} | |
| <span class="bug-status-badge ${r.status}">${statusLabel}</span> | |
| </div> | |
| <div class="bug-report-body-preview">${r.body}</div> | |
| <div class="bug-report-meta"> | |
| <span class="bug-report-date">${date}</span> | |
| ${r.reply_count > 0 ? ` | |
| <span class="bug-report-reply-count"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/> | |
| </svg> | |
| ${r.reply_count} ${r.reply_count === 1 ? 'reply' : 'replies'} | |
| </span> | |
| ` : ''} | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bug-report-detail"> | |
| <div class="bug-report-full-body">${r.body}</div> | |
| ${threadHtml} | |
| <div class="bug-thread-reply"> | |
| <textarea class="bug-thread-reply-input" id="user-reply-input-${r._id}" placeholder="Reply to admin..." rows="1"></textarea> | |
| <button class="bug-thread-reply-btn" onclick="sendUserReply('${r._id}')">Send</button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| }).join(''); | |
| } catch (e) { | |
| listEl.innerHTML = '<div class="bug-report-empty">Failed to load reports.</div>'; | |
| } | |
| } | |
| // Submit report | |
| document.getElementById('bug-report-form').addEventListener('submit', async function(e) { | |
| e.preventDefault(); | |
| const btn = document.getElementById('bug-submit-btn'); | |
| const errorEl = document.getElementById('bug-error'); | |
| const successEl = document.getElementById('bug-success'); | |
| errorEl.classList.remove('visible'); | |
| successEl.classList.remove('visible'); | |
| const activeCategory = document.querySelector('.bug-category-pill.active'); | |
| const category = activeCategory ? activeCategory.dataset.category : 'bug'; | |
| const title = document.getElementById('bug-title').value.trim(); | |
| const body = document.getElementById('bug-body').value.trim(); | |
| const pageUrl = document.getElementById('bug-page-url').value.trim(); | |
| if (!title || title.length < 3) { | |
| errorEl.textContent = 'Title must be at least 3 characters.'; | |
| errorEl.classList.add('visible'); | |
| return; | |
| } | |
| if (!body || body.length < 10) { | |
| errorEl.textContent = 'Description must be at least 10 characters.'; | |
| errorEl.classList.add('visible'); | |
| return; | |
| } | |
| btn.disabled = true; | |
| btn.textContent = 'Submitting...'; | |
| try { | |
| const res = await fetch('/api/bug-reports', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ title, body, category, page_url: pageUrl }), | |
| }); | |
| const data = await res.json(); | |
| if (data.success) { | |
| document.getElementById('bug-report-form').reset(); | |
| // Re-set default category | |
| document.querySelectorAll('.bug-category-pill').forEach(p => p.classList.remove('active')); | |
| document.querySelector('.bug-category-pill[data-category="bug"]').classList.add('active'); | |
| successEl.classList.add('visible'); | |
| loadMyReports(); | |
| } else { | |
| errorEl.textContent = data.message || 'Failed to submit report.'; | |
| errorEl.classList.add('visible'); | |
| } | |
| } catch (e) { | |
| errorEl.textContent = 'Network error. Please try again.'; | |
| errorEl.classList.add('visible'); | |
| } finally { | |
| btn.disabled = false; | |
| btn.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> Submit Report`; | |
| } | |
| }); | |
| function toggleReport(id) { | |
| const item = document.getElementById('report-' + id); | |
| if (item) { | |
| item.classList.toggle('expanded'); | |
| } | |
| } | |
| async function sendUserReply(reportId) { | |
| const input = document.getElementById('user-reply-input-' + reportId); | |
| const body = input.value.trim(); | |
| if (!body) return; | |
| const btn = input.nextElementSibling; | |
| btn.disabled = true; | |
| btn.textContent = 'Sending...'; | |
| try { | |
| const res = await fetch('/api/bug-reports/' + reportId + '/user-reply', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ body }), | |
| }); | |
| const data = await res.json(); | |
| if (data.success) { | |
| input.value = ''; | |
| loadMyReports(); | |
| } else { | |
| alert(data.message || 'Failed to send reply.'); | |
| } | |
| } catch (e) { | |
| alert('Network error. Please try again.'); | |
| } finally { | |
| btn.disabled = false; | |
| btn.textContent = 'Send'; | |
| } | |
| } | |
| loadMyReports(); | |
| {% endif %} | |
| </script> | |
| {% endblock %} | |