From 5f4c29bd5aa8941834102cedfa1dd95bf6e3bdd5 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sat, 16 May 2026 13:25:51 +0000 Subject: [PATCH] feat: in-app bug report form (replaces GitHub link) --- api/index.php | 84 ++++++++++++++++++++++++++ assets/css/style.css | 30 ++++++++++ assets/js/app.js | 137 +++++++++++++++++++++++++++++++++---------- index.html | 3 +- translations/de.json | 14 ++++- translations/en.json | 14 ++++- translations/it.json | 14 ++++- 7 files changed, 259 insertions(+), 37 deletions(-) diff --git a/api/index.php b/api/index.php index c12f641..9599167 100644 --- a/api/index.php +++ b/api/index.php @@ -438,6 +438,10 @@ try { reportError(); break; + case 'report_bug': + reportBugManual(); + break; + case 'check_update': checkUpdate(); break; @@ -6897,6 +6901,86 @@ function reportError(): void { echo json_encode(['ok' => true]); } +/** + * POST /api/?action=report_bug + * + * Manual bug/feature/question report submitted by the user via the in-app form. + * Creates a GitHub issue directly with the provided title and description. + * + * Expected JSON body: + * type string 'bug'|'feature'|'question' + * title string Issue title (required, max 150 chars) + * description string Main description (required, max 3000 chars) + * steps string? Steps to reproduce (optional, max 2000 chars) + * lang string? UI language the user is running + * url string? Page URL + * user_agent string? Navigator UA + * version string? App version + */ +function reportBugManual(): void { + $input = json_decode(file_get_contents('php://input'), true) ?: []; + + $allowedTypes = ['bug', 'feature', 'question']; + $type = in_array($input['type'] ?? '', $allowedTypes, true) ? $input['type'] : 'bug'; + $title = substr(trim($input['title'] ?? ''), 0, 150); + $desc = substr(trim($input['description'] ?? ''), 0, 3000); + $steps = substr(trim($input['steps'] ?? ''), 0, 2000); + $ua = substr(trim($input['user_agent'] ?? ($_SERVER['HTTP_USER_AGENT'] ?? '')), 0, 300); + $url = substr(trim($input['url'] ?? ''), 0, 300); + $ver = substr(trim($input['version'] ?? ''), 0, 50); + $lang = preg_replace('/[^a-z\-]/', '', strtolower($input['lang'] ?? 'it')); + + if (empty($title) || empty($desc)) { + echo json_encode(['ok' => false, 'error' => 'title and description required']); + return; + } + + $token = _ghToken(); + if (!$token) { + // No GitHub token configured — log locally and return ok so the UX is not broken + _appendErrorLog('pwa', 'manual_report', $title, $desc, $url, $ua, ['type' => $type, 'version' => $ver, 'lang' => $lang]); + echo json_encode(['ok' => true, 'issue' => null]); + return; + } + + // Labels: always 'user-report' + type-specific label + $labelMap = [ + 'bug' => ['bug', 'user-report'], + 'feature' => ['enhancement', 'user-report'], + 'question' => ['question', 'user-report'], + ]; + $labels = $labelMap[$type]; + + $typeEmoji = ['bug' => '🐛', 'feature' => '💡', 'question' => '❓'][$type]; + $ts = date('Y-m-d H:i:s T'); + + $body = "## {$typeEmoji} User Report\n\n"; + $body .= "**Description:**\n{$desc}\n\n"; + if ($steps) { + $body .= "**Steps to reproduce:**\n{$steps}\n\n"; + } + $body .= "---\n"; + $body .= "**Version:** `{$ver}` \n"; + $body .= "**Language:** `{$lang}` \n"; + if ($url) $body .= "**URL:** `{$url}` \n"; + if ($ua) $body .= "**User-Agent:** `{$ua}` \n"; + $body .= "**Reported at:** {$ts}\n\n"; + $body .= "_This issue was submitted via the in-app bug report form._"; + + $res = _githubRequest($token, 'POST', + 'https://api.github.com/repos/' . GH_REPO . '/issues', + ['title' => $title, 'body' => $body, 'labels' => $labels] + ); + + $issueNum = $res['body']['number'] ?? null; + $issueUrl = $res['body']['html_url'] ?? null; + if ($issueNum) { + echo json_encode(['ok' => true, 'issue' => $issueNum, 'url' => $issueUrl]); + } else { + echo json_encode(['ok' => false, 'error' => 'github_api_error']); + } +} + /** * Append to data/error_reports.log (local safety net, max 500 KB) */ diff --git a/assets/css/style.css b/assets/css/style.css index f6dcbcd..bd61ef5 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -3103,6 +3103,36 @@ body.server-offline .bottom-nav { justify-content: center; } +/* ── Bug report form ── */ +.bug-type-pills { + display: flex; + gap: 8px; + flex-wrap: wrap; +} +.bug-type-pill { + flex: 1; + min-width: 80px; + padding: 7px 10px; + border: 1.5px solid #cbd5e1; + border-radius: 20px; + background: #fff; + color: #475569; + font-size: 0.88rem; + cursor: pointer; + transition: border-color 0.15s, background 0.15s, color 0.15s; + -webkit-tap-highlight-color: transparent; +} +.bug-type-pill.active { + border-color: var(--primary, #2d5016); + background: var(--primary, #2d5016); + color: #fff; + font-weight: 600; +} +.bug-type-pill:not(.active):hover { + border-color: var(--primary, #2d5016); + color: var(--primary, #2d5016); +} + .modal-detail { display: flex; flex-direction: column; diff --git a/assets/js/app.js b/assets/js/app.js index 0ef85b3..8b2f3e7 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -2100,51 +2100,124 @@ async function _loadAboutSection() { * Manually triggered bug report from the About section in Settings. * Collects basic info and submits via the existing report_error endpoint. */ -async function reportBugManual() { - const btn = document.getElementById('btn-report-bug'); - const statusEl = document.getElementById('report-bug-status'); - if (!btn || !statusEl) return; +function reportBugManual() { + const mc = document.getElementById('modal-content'); + if (!mc) return; - btn.disabled = true; + mc.innerHTML = ` + +
+
+
+ + + +
+
+
+ + +
+
+ + +
+
+ + +
+

+ ${t('about.report_auto_info').replace('{version}', _loadedVersion || '—').replace('{lang}', _currentLang || '—')} +

+
+ + +
+ +
+ `; + document.getElementById('modal-overlay').style.display = 'flex'; + + // Pill click: switch type, show/hide steps field + mc.querySelectorAll('.bug-type-pill').forEach(pill => { + pill.addEventListener('click', () => { + mc.querySelectorAll('.bug-type-pill').forEach(p => p.classList.remove('active')); + pill.classList.add('active'); + const stepsGroup = document.getElementById('bug-form-steps-group'); + if (stepsGroup) stepsGroup.style.display = (pill.dataset.btype === 'bug') ? '' : 'none'; + }); + }); +} + +async function _submitBugReport() { + const submitBtn = document.getElementById('bug-form-submit'); + const statusEl = document.getElementById('bug-form-status'); + const titleEl = document.getElementById('bug-form-title'); + const descEl = document.getElementById('bug-form-desc'); + const stepsEl = document.getElementById('bug-form-steps'); + const activePill = document.querySelector('#modal-content .bug-type-pill.active'); + + const title = titleEl?.value.trim() || ''; + const desc = descEl?.value.trim() || ''; + const steps = stepsEl?.value.trim() || ''; + const type = activePill?.dataset.btype || 'bug'; + + // Inline validation + if (!title) { + titleEl.style.borderColor = '#dc2626'; + titleEl.focus(); + return; + } + if (!desc) { + descEl.style.borderColor = '#dc2626'; + descEl.focus(); + return; + } + + submitBtn.disabled = true; statusEl.style.display = ''; + statusEl.style.background = '#f1f5f9'; statusEl.style.color = '#64748b'; statusEl.textContent = t('about.report_bug_sending'); - const manifest = await fetch('manifest.json?_=' + Date.now()).then(r => r.json()).catch(() => ({})); - try { - const res = await fetch(API_BASE + '?action=report_error', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - source: 'pwa', - type: 'manual_report', - message: 'Manual bug report submitted from Settings → About', - stack: '', - url: location.href, - user_agent: navigator.userAgent, - version: manifest.version || '', - context: { - lang: _currentLang, - online: navigator.onLine, - version_guard_bypass: true, - } - }) + const res = await api('report_bug', null, 'POST', { + type, + title, + description: desc, + steps, + user_agent: navigator.userAgent, + url: location.href, + version: _loadedVersion || '', + lang: _currentLang || 'it', }); - const json = await res.json(); - if (json.ok) { + + if (res.ok) { + statusEl.style.background = '#dcfce7'; statusEl.style.color = '#15803d'; - statusEl.textContent = t('about.report_bug_sent'); - // Open GitHub issues so user can add details - setTimeout(() => window.open('https://github.com/dadaloop82/EverShelf/issues', '_blank', 'noopener'), 800); + const issueRef = res.issue ? ` (#${res.issue})` : ''; + statusEl.textContent = t('about.report_bug_sent') + issueRef; + submitBtn.style.display = 'none'; + setTimeout(() => closeModal(), 3500); } else { - throw new Error(json.error || 'error'); + throw new Error(res.error || 'error'); } } catch(e) { + statusEl.style.background = '#fee2e2'; statusEl.style.color = '#dc2626'; statusEl.textContent = t('about.report_bug_error'); - } finally { - btn.disabled = false; + submitBtn.disabled = false; } } diff --git a/index.html b/index.html index e655d7a..f05f609 100644 --- a/index.html +++ b/index.html @@ -1344,7 +1344,7 @@ -

Qualcosa non funziona? Apri una segnalazione su GitHub.

+

Qualcosa non funziona? Inviaci una segnalazione direttamente dall'app.

GitHub
- diff --git a/translations/de.json b/translations/de.json index 6a5abf4..a20fc9d 100644 --- a/translations/de.json +++ b/translations/de.json @@ -1097,7 +1097,19 @@ "title": "Über", "version": "Version", "report_bug": "Fehler melden", - "report_bug_hint": "Etwas funktioniert nicht? Öffne ein Issue auf GitHub.", + "report_bug_hint": "Etwas funktioniert nicht? Sende uns direkt aus der App eine Meldung.", + "report_bug_modal_title": "Fehler melden", + "report_type_bug": "Fehler", + "report_type_feature": "Funktion", + "report_type_question": "Frage", + "report_field_title": "Titel", + "report_field_title_ph": "Kurze Beschreibung des Problems", + "report_field_desc": "Beschreibung", + "report_field_desc_ph": "Problem detailliert beschreiben…", + "report_field_steps": "Schritte zum Reproduzieren (optional)", + "report_field_steps_ph": "1. Gehe zu…\n2. Tippe auf…\n3. Fehler erscheint…", + "report_auto_info": "Automatisch beigefügt: Version {version}, Sprache {lang}.", + "report_send_btn": "Bericht senden", "report_bug_sending": "Wird gesendet…", "report_bug_sent": "Bericht gesendet — danke!", "report_bug_error": "Bericht konnte nicht gesendet werden. Verbindung prüfen.", diff --git a/translations/en.json b/translations/en.json index 3e3afb3..5ee9b25 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1097,7 +1097,19 @@ "title": "About", "version": "Version", "report_bug": "Report a Bug", - "report_bug_hint": "Something not working? Open an issue on GitHub.", + "report_bug_hint": "Something not working? Send us a report directly from the app.", + "report_bug_modal_title": "Report a Bug", + "report_type_bug": "Bug", + "report_type_feature": "Feature", + "report_type_question": "Question", + "report_field_title": "Title", + "report_field_title_ph": "Brief description of the issue", + "report_field_desc": "Description", + "report_field_desc_ph": "Describe the issue in detail…", + "report_field_steps": "Steps to reproduce (optional)", + "report_field_steps_ph": "1. Go to…\n2. Tap…\n3. See the error…", + "report_auto_info": "Automatically attached: version {version}, language {lang}.", + "report_send_btn": "Send report", "report_bug_sending": "Sending…", "report_bug_sent": "Report sent — thank you!", "report_bug_error": "Could not send the report. Check your connection.", diff --git a/translations/it.json b/translations/it.json index db8d4f8..199bc3e 100644 --- a/translations/it.json +++ b/translations/it.json @@ -1097,7 +1097,19 @@ "title": "Informazioni", "version": "Versione", "report_bug": "Segnala un problema", - "report_bug_hint": "Qualcosa non funziona? Apri una segnalazione su GitHub.", + "report_bug_hint": "Qualcosa non funziona? Inviaci una segnalazione direttamente dall'app.", + "report_bug_modal_title": "Segnala un problema", + "report_type_bug": "Bug", + "report_type_feature": "Funzionalità", + "report_type_question": "Domanda", + "report_field_title": "Titolo", + "report_field_title_ph": "Breve descrizione del problema", + "report_field_desc": "Descrizione", + "report_field_desc_ph": "Descrivi il problema in dettaglio…", + "report_field_steps": "Passi per riprodurlo (opzionale)", + "report_field_steps_ph": "1. Vai su…\n2. Tocca…\n3. Vedi l'errore…", + "report_auto_info": "Saranno allegati automaticamente: versione {version}, lingua {lang}.", + "report_send_btn": "Invia segnalazione", "report_bug_sending": "Invio in corso…", "report_bug_sent": "Segnalazione inviata — grazie!", "report_bug_error": "Impossibile inviare la segnalazione. Controlla la connessione.",