diff --git a/app/api/routes/bug_reports.py b/app/api/routes/bug_reports.py
index 6522cef..45e403c 100644
--- a/app/api/routes/bug_reports.py
+++ b/app/api/routes/bug_reports.py
@@ -12,7 +12,7 @@ router = APIRouter(prefix="/bug-reports", tags=["bug-reports"])
class BugReportCreate(BaseModel):
- description: str = Field(min_length=1, max_length=5000)
+ description: str = Field(min_length=1, max_length=2000)
contact: str | None = Field(default=None, max_length=256)
@field_validator("description")
diff --git a/app/static/admin.js b/app/static/admin.js
index 1307746..68b52b5 100644
--- a/app/static/admin.js
+++ b/app/static/admin.js
@@ -346,7 +346,7 @@ async function loadAuditLog() {
}
}
-const BUG_REPORT_STATUS_LABELS = { open: 'Da leggere', read: 'Letta', resolved: 'Risolta' };
+const BUG_REPORT_STATUS_LABELS = { open: 'Da leggere', read: 'Presa in carico', resolved: 'Risolta' };
function bugReportBadge(status) {
return `${escapeHtml(BUG_REPORT_STATUS_LABELS[status] || status)}`;
@@ -366,7 +366,7 @@ async function loadBugReports() {
${bugReportBadge(r.status)}
- ${r.status === 'open' ? `` : ''}
+ ${r.status === 'open' ? `` : ''}
${r.status !== 'resolved' ? `` : ''}
${r.status !== 'open' ? `` : ''}
diff --git a/app/static/i18n.js b/app/static/i18n.js
index 0e95c11..5d7d173 100644
--- a/app/static/i18n.js
+++ b/app/static/i18n.js
@@ -27,9 +27,9 @@ const TRANSLATIONS = {
'bugReport.myReportsHint': 'Only reports sent from this account, with the status set by the admin team.',
'bugReport.myReportsEmpty': "You haven't sent any reports yet.",
'bugReport.statusOpen': 'Not read yet',
- 'bugReport.statusRead': 'Read',
+ 'bugReport.statusRead': 'Acknowledged',
'bugReport.statusResolved': 'Resolved',
- 'bugReport.backLink': '← Back to home',
+ 'bugReport.backLink': 'Back to home',
'nav.logoutTitle': 'Log out',
'nav.logoutAria': 'Log out of your account',
'nav.deposit': 'Deposit',
@@ -190,9 +190,9 @@ const TRANSLATIONS = {
'bugReport.myReportsHint': "Solo le segnalazioni inviate da questo account, con lo stato aggiornato dall'amministrazione.",
'bugReport.myReportsEmpty': 'Non hai ancora inviato segnalazioni.',
'bugReport.statusOpen': 'Da leggere',
- 'bugReport.statusRead': 'Letta',
+ 'bugReport.statusRead': 'Presa in carico',
'bugReport.statusResolved': 'Risolta',
- 'bugReport.backLink': '← Torna alla home',
+ 'bugReport.backLink': 'Torna alla home',
'nav.logoutTitle': 'Esci',
'nav.logoutAria': "Esci dall'account",
'nav.deposit': 'Deposito',
@@ -350,9 +350,9 @@ const TRANSLATIONS = {
'bugReport.myReportsHint': 'Solo los informes enviados desde esta cuenta, con el estado actualizado por el equipo de administración.',
'bugReport.myReportsEmpty': 'Todavía no has enviado ningún informe.',
'bugReport.statusOpen': 'Sin leer',
- 'bugReport.statusRead': 'Leído',
+ 'bugReport.statusRead': 'En curso',
'bugReport.statusResolved': 'Resuelto',
- 'bugReport.backLink': '← Volver al inicio',
+ 'bugReport.backLink': 'Volver al inicio',
'nav.logoutTitle': 'Salir',
'nav.logoutAria': 'Cerrar sesión',
'nav.deposit': 'Depósito',
@@ -510,9 +510,9 @@ const TRANSLATIONS = {
'bugReport.myReportsHint': "Seulement les signalements envoyés depuis ce compte, avec le statut mis à jour par l'équipe d'administration.",
'bugReport.myReportsEmpty': "Vous n'avez encore envoyé aucun signalement.",
'bugReport.statusOpen': 'Non lu',
- 'bugReport.statusRead': 'Lu',
+ 'bugReport.statusRead': 'Prise en charge',
'bugReport.statusResolved': 'Résolu',
- 'bugReport.backLink': "← Retour à l'accueil",
+ 'bugReport.backLink': "Retour à l'accueil",
'nav.logoutTitle': 'Se déconnecter',
'nav.logoutAria': 'Se déconnecter du compte',
'nav.deposit': 'Dépôt',
@@ -670,9 +670,9 @@ const TRANSLATIONS = {
'bugReport.myReportsHint': 'Nur Meldungen, die von diesem Konto gesendet wurden, mit dem vom Admin-Team aktualisierten Status.',
'bugReport.myReportsEmpty': 'Du hast noch keine Meldungen gesendet.',
'bugReport.statusOpen': 'Ungelesen',
- 'bugReport.statusRead': 'Gelesen',
+ 'bugReport.statusRead': 'In Bearbeitung',
'bugReport.statusResolved': 'Gelöst',
- 'bugReport.backLink': '← Zurück zur Startseite',
+ 'bugReport.backLink': 'Zurück zur Startseite',
'nav.logoutTitle': 'Abmelden',
'nav.logoutAria': 'Vom Konto abmelden',
'nav.deposit': 'Einzahlung',
@@ -830,9 +830,9 @@ const TRANSLATIONS = {
'bugReport.myReportsHint': 'Только сообщения, отправленные с этого аккаунта, со статусом, обновлённым администрацией.',
'bugReport.myReportsEmpty': 'Вы ещё не отправляли сообщений.',
'bugReport.statusOpen': 'Не прочитано',
- 'bugReport.statusRead': 'Прочитано',
+ 'bugReport.statusRead': 'В обработке',
'bugReport.statusResolved': 'Решено',
- 'bugReport.backLink': '← Назад на главную',
+ 'bugReport.backLink': 'Назад на главную',
'nav.logoutTitle': 'Выйти',
'nav.logoutAria': 'Выйти из аккаунта',
'nav.deposit': 'Депозит',
@@ -990,9 +990,9 @@ const TRANSLATIONS = {
'bugReport.myReportsHint': '仅显示此账户发送的反馈,状态由管理团队更新。',
'bugReport.myReportsEmpty': '您还没有发送过反馈。',
'bugReport.statusOpen': '待处理',
- 'bugReport.statusRead': '已读',
+ 'bugReport.statusRead': '处理中',
'bugReport.statusResolved': '已解决',
- 'bugReport.backLink': '← 返回首页',
+ 'bugReport.backLink': '返回首页',
'nav.logoutTitle': '退出登录',
'nav.logoutAria': '退出账户',
'nav.deposit': '存款',
diff --git a/app/static/report-bug.html b/app/static/report-bug.html
index 38b96e7..4b4de4b 100644
--- a/app/static/report-bug.html
+++ b/app/static/report-bug.html
@@ -4,31 +4,53 @@
Report a bug
+
-
-
-
+
+
+
- Report a bug
- Found a problem? Describe it below — your report goes straight to the admin panel.
-
- Please write your bug report in English, regardless of the language you're browsing in — this helps us handle it faster.
-
+
+
+
+ Report a bug
+ Found a problem? Describe it below — your report goes straight to the admin panel.
+
+
-
- Your reports
- Only reports sent from this account, with the status set by the admin team.
-
+
+ Your reports
+
+ Only reports sent from this account, with the status set by the admin team.
+
+
- ← Back to home
@@ -62,6 +85,11 @@
return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
}
+ function updateCharCount() {
+ const field = document.getElementById('bug-description');
+ document.getElementById('char-count').textContent = field.value.length + ' / ' + field.maxLength;
+ }
+
async function submitBugReport() {
const btn = document.getElementById('bug-submit-btn');
const description = document.getElementById('bug-description').value.trim();
@@ -88,6 +116,7 @@
if (!res.ok) throw new Error(data.detail?.message || data.detail || res.statusText);
document.getElementById('bug-description').value = '';
document.getElementById('bug-contact').value = '';
+ updateCharCount();
toast(t('bugReport.successToast'), 'success');
loadMyBugReports();
} catch (e) {
@@ -101,29 +130,29 @@
async function loadMyBugReports() {
const token = localStorage.getItem('plm_token');
- const card = document.getElementById('my-reports-card');
+ const section = document.getElementById('my-reports-section');
if (!token) {
- card.classList.add('hidden');
+ section.classList.add('hidden');
return;
}
try {
const res = await fetch('/bug-reports/mine', { headers: { Authorization: 'Bearer ' + token } });
if (!res.ok) {
- card.classList.add('hidden');
+ section.classList.add('hidden');
return;
}
const reports = await res.json();
- card.classList.remove('hidden');
+ section.classList.remove('hidden');
const list = document.getElementById('my-reports-list');
list.innerHTML = reports.map((r) => `
-
+
${escapeHtml(t('bugReport.status' + r.status.charAt(0).toUpperCase() + r.status.slice(1)))}
- ${escapeHtml(r.description)}
- ${new Date(r.created_at).toLocaleString(currentDateLocale())}
+ ${escapeHtml(r.description)}
+ ${new Date(r.created_at).toLocaleDateString(currentDateLocale(), { day: 'numeric', month: 'short', year: 'numeric' })}
- `).join('') || ` ${escapeHtml(t('bugReport.myReportsEmpty'))} `;
+ `).join('') || ` ${escapeHtml(t('bugReport.myReportsEmpty'))} `;
} catch (e) {
- card.classList.add('hidden');
+ section.classList.add('hidden');
}
}
@@ -133,6 +162,7 @@
loadMyBugReports();
}
+ updateCharCount();
loadMyBugReports();
diff --git a/app/static/style.css b/app/static/style.css
index 0a7f111..08937c5 100644
--- a/app/static/style.css
+++ b/app/static/style.css
@@ -254,28 +254,92 @@ button.link:hover, a.link:hover { filter: none; color: var(--color-foreground);
.toast.error { background: var(--color-destructive-bg); color: var(--color-destructive); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
-.lang-bar { display: flex; justify-content: flex-end; margin-bottom: 4px; }
+/* --- /report-bug: a standalone page (no logged-in navbar), so it gets its
+ own slim top bar rather than the app's bottom tab bar / sticky header. --- */
+.app-shell-bugreport { padding-bottom: 32px; }
-.english-notice {
- background: #FEF3C7; color: #92400E; border: 1px solid #F59E0B;
- border-radius: var(--radius-sm); padding: 10px 12px; font-size: 0.85rem; font-weight: 500;
+.bugreport-topbar {
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
+ padding: 4px 0 20px; margin-bottom: 20px; border-bottom: 1px solid var(--color-border);
+}
+.bugreport-topbar .brand {
+ display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 1rem;
+ letter-spacing: -0.01em; color: var(--color-foreground); text-decoration: none;
+}
+.bugreport-topbar .brand-mark { width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; display: block; }
+.bugreport-topbar-right { display: flex; align-items: center; gap: 12px; }
+
+/* Pill button, same idiom as .trust-pill / .chain-status-pill elsewhere on the
+ site: a bordered chip rather than a bare text link, so "go back" reads as an
+ actual control instead of fading into the surrounding copy. */
+.back-home-btn {
+ display: inline-flex; align-items: center; gap: 6px;
+ font-size: 0.8rem; font-weight: 500; color: var(--color-muted-foreground);
+ background: var(--color-surface); border: 1px solid var(--color-border);
+ padding: 6px 12px 6px 10px; border-radius: 999px; text-decoration: none;
+ transition: color 150ms, border-color 150ms, background 150ms;
+}
+.back-home-btn .icon { width: 15px; height: 15px; }
+.back-home-btn:hover {
+ color: var(--color-foreground); background: var(--color-surface-inset);
+ border-color: color-mix(in srgb, var(--color-ring) 40%, var(--color-border));
+}
+.back-home-btn:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }
+
+.bugreport-hero { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 20px; }
+.bugreport-hero-icon {
+ width: 44px; height: 44px; flex-shrink: 0; border-radius: 999px;
+ background: color-mix(in srgb, var(--color-primary) 14%, transparent);
+ color: var(--color-primary);
+ display: flex; align-items: center; justify-content: center;
+}
+.bugreport-hero-icon .icon { width: 22px; height: 22px; }
+.bugreport-hero h1 { font-size: 1.3rem; font-weight: 700; margin: 2px 0 4px; text-wrap: balance; }
+.bugreport-hero p { color: var(--color-muted-foreground); font-size: 0.9rem; margin: 0; max-width: 46ch; }
+
+/* Info callout, anchored inside the form card right above the field it
+ applies to — not a warning (that's what the amber status badges below are
+ for), so it gets the accent hue instead, keeping the two meanings visually
+ distinct. */
+.field-note {
+ display: flex; align-items: flex-start; gap: 10px;
+ background: color-mix(in srgb, var(--color-accent) 10%, transparent);
+ border: 1px solid color-mix(in srgb, var(--color-accent) 28%, transparent);
+ color: color-mix(in srgb, var(--color-accent) 75%, var(--color-foreground));
+ border-radius: var(--radius-sm); padding: 10px 12px; font-size: 0.82rem; line-height: 1.4;
+ margin-bottom: 16px;
+}
+.field-note .icon { width: 16px; height: 16px; margin-top: 1px; flex-shrink: 0; }
+
+.char-count {
+ font-variant-numeric: tabular-nums; text-align: right;
+ font-size: 0.75rem; color: var(--color-muted-foreground); margin-top: 4px;
}
.badge {
display: inline-block; font-size: 0.72rem; font-weight: 600; padding: 2px 8px;
border-radius: 999px; background: var(--color-background); border: 1px solid var(--color-border);
+ flex-shrink: 0;
}
-.badge.bug-status-open { background: #FEF3C7; color: #92400E; border-color: #F59E0B; }
+.badge.bug-status-open { background: color-mix(in srgb, var(--color-primary) 16%, transparent); color: #92400E; border-color: color-mix(in srgb, var(--color-primary) 55%, transparent); }
.badge.bug-status-read { background: var(--color-background); color: var(--color-muted-foreground); }
.badge.bug-status-resolved { background: var(--color-success-bg); color: var(--color-success); border-color: var(--color-success); }
-.my-report-row {
+.report-row {
display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
- padding: 10px 0; border-bottom: 1px solid var(--color-border);
+ padding: 12px 0; border-bottom: 1px solid var(--color-border);
}
-.my-report-row:last-child { border-bottom: none; }
-.my-report-desc { flex: 1 1 200px; font-size: 0.9rem; }
-.my-report-date { font-size: 0.8rem; color: var(--color-muted-foreground); white-space: nowrap; }
+.report-row:first-child { padding-top: 0; }
+.report-row:last-child { padding-bottom: 0; border-bottom: none; }
+.report-row-desc {
+ flex: 1 1 200px; font-size: 0.88rem;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+.report-row-date {
+ font-size: 0.78rem; color: var(--color-muted-foreground); white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+}
+.report-empty { margin: 0; }
/* --- landing hero (shown only when logged out) --- */
body {
|