The bug report form previously shipped as plain Italian only. It now shares i18n.js with / (same TRANSLATIONS table, new bugReport.* keys in all 7 languages, own language switcher since the page has no navbar to hang one off), so a non-Italian speaker can read the form and their own report history in their language. The description field itself still has to reach the admin panel in English (operator-facing, untranslated by design), so the page states that explicitly via a standing banner (bugReport.englishNotice) — translated into every language rather than left in English, so the instruction to write in English is itself understandable to whoever's reading it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
140 lines
5.5 KiB
HTML
140 lines
5.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title data-i18n="bugReport.pageTitle">Report a bug</title>
|
|
<link rel="stylesheet" href="/style.css">
|
|
</head>
|
|
<body>
|
|
<div class="app-shell">
|
|
<div class="lang-bar">
|
|
<select id="lang-switcher" class="lang-switcher" onchange="setLanguage(this.value)" aria-label="Language">
|
|
<option value="en">English</option>
|
|
<option value="it">Italiano</option>
|
|
<option value="es">Español</option>
|
|
<option value="fr">Français</option>
|
|
<option value="de">Deutsch</option>
|
|
<option value="ru">Русский</option>
|
|
<option value="zh">中文</option>
|
|
</select>
|
|
</div>
|
|
|
|
<h1 data-i18n="bugReport.heading">Report a bug</h1>
|
|
<p data-i18n="bugReport.intro">Found a problem? Describe it below — your report goes straight to the admin panel.</p>
|
|
<p class="english-notice" data-i18n="bugReport.englishNotice">
|
|
Please write your bug report in English, regardless of the language you're browsing in — this helps us handle it faster.
|
|
</p>
|
|
|
|
<div class="card" id="report-form">
|
|
<label for="bug-description" data-i18n="bugReport.descriptionLabel">What happened?</label>
|
|
<textarea id="bug-description" rows="6" maxlength="5000" data-i18n-placeholder="bugReport.descriptionPlaceholder"></textarea>
|
|
|
|
<label for="bug-contact" data-i18n="bugReport.contactLabel">Contact (optional)</label>
|
|
<input id="bug-contact" type="text" maxlength="256" data-i18n-placeholder="bugReport.contactPlaceholder">
|
|
|
|
<button onclick="submitBugReport()" id="bug-submit-btn" data-i18n="bugReport.submitBtn">Send report</button>
|
|
</div>
|
|
|
|
<div class="card hidden" id="my-reports-card">
|
|
<h2 data-i18n="bugReport.myReportsTitle">Your reports</h2>
|
|
<p class="hint" data-i18n="bugReport.myReportsHint">Only reports sent from this account, with the status set by the admin team.</p>
|
|
<div id="my-reports-list"></div>
|
|
</div>
|
|
|
|
<p><a class="link" href="/" data-i18n="bugReport.backLink">← Back to home</a></p>
|
|
</div>
|
|
|
|
<div id="toast-container" aria-live="polite"></div>
|
|
|
|
<script src="/i18n.js"></script>
|
|
<script>
|
|
function toast(message, type) {
|
|
const container = document.getElementById('toast-container');
|
|
const el = document.createElement('div');
|
|
el.className = 'toast ' + type;
|
|
el.textContent = message;
|
|
container.appendChild(el);
|
|
setTimeout(() => el.remove(), 4000);
|
|
}
|
|
|
|
function escapeHtml(s) {
|
|
return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
}
|
|
|
|
async function submitBugReport() {
|
|
const btn = document.getElementById('bug-submit-btn');
|
|
const description = document.getElementById('bug-description').value.trim();
|
|
const contact = document.getElementById('bug-contact').value.trim();
|
|
if (!description) {
|
|
toast(t('bugReport.blankError'), 'error');
|
|
return;
|
|
}
|
|
|
|
const headers = { 'Content-Type': 'application/json' };
|
|
const token = localStorage.getItem('plm_token');
|
|
if (token) headers['Authorization'] = 'Bearer ' + token;
|
|
|
|
btn.disabled = true;
|
|
const original = btn.textContent;
|
|
btn.textContent = t('bugReport.submitting');
|
|
try {
|
|
const res = await fetch('/bug-reports', {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({ description, contact: contact || null }),
|
|
});
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) throw new Error(data.detail?.message || data.detail || res.statusText);
|
|
document.getElementById('bug-description').value = '';
|
|
document.getElementById('bug-contact').value = '';
|
|
toast(t('bugReport.successToast'), 'success');
|
|
loadMyBugReports();
|
|
} catch (e) {
|
|
toast(t('bugReport.errorPrefix') + e.message, 'error');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = original;
|
|
applyStaticTranslations(btn);
|
|
}
|
|
}
|
|
|
|
async function loadMyBugReports() {
|
|
const token = localStorage.getItem('plm_token');
|
|
const card = document.getElementById('my-reports-card');
|
|
if (!token) {
|
|
card.classList.add('hidden');
|
|
return;
|
|
}
|
|
try {
|
|
const res = await fetch('/bug-reports/mine', { headers: { Authorization: 'Bearer ' + token } });
|
|
if (!res.ok) {
|
|
card.classList.add('hidden');
|
|
return;
|
|
}
|
|
const reports = await res.json();
|
|
card.classList.remove('hidden');
|
|
const list = document.getElementById('my-reports-list');
|
|
list.innerHTML = reports.map((r) => `
|
|
<div class="my-report-row">
|
|
<span class="badge bug-status-${escapeHtml(r.status)}">${escapeHtml(t('bugReport.status' + r.status.charAt(0).toUpperCase() + r.status.slice(1)))}</span>
|
|
<span class="my-report-desc">${escapeHtml(r.description)}</span>
|
|
<span class="my-report-date">${new Date(r.created_at).toLocaleString(currentDateLocale())}</span>
|
|
</div>
|
|
`).join('') || `<p class="hint">${escapeHtml(t('bugReport.myReportsEmpty'))}</p>`;
|
|
} catch (e) {
|
|
card.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
// Re-renders server-rendered content (my reports list) on a language switch,
|
|
// the same split app.js uses between data-i18n (static markup) and t() (data).
|
|
function onLanguageChange() {
|
|
loadMyBugReports();
|
|
}
|
|
|
|
loadMyBugReports();
|
|
</script>
|
|
</body>
|
|
</html>
|