diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json
index 085ef01..4edd311 100644
--- a/public/locales/en/settings.json
+++ b/public/locales/en/settings.json
@@ -171,7 +171,11 @@
},
"feedback": {
"title": "Send Feedback",
- "description": "Tell us how Donetick is working for you, report a bug, or request a feature."
+ "description": "Tell us how Donetick is working for you or request a feature."
+ },
+ "bugReport": {
+ "title": "Report a Bug",
+ "description": "Something not working right? Send us the details along with a technical snapshot."
}
}
}
diff --git a/src/service/ErrorReportService.js b/src/service/ErrorReportService.js
index c290c52..1d9689c 100644
--- a/src/service/ErrorReportService.js
+++ b/src/service/ErrorReportService.js
@@ -113,6 +113,9 @@ export const collectErrorReport = async ({ error, errorInfo, reportId }) => {
return {
reportId: reportId ?? newReportId(),
occurredAt: new Date().toISOString(),
+ // No error means the user came here deliberately from settings rather than
+ // off the back of a crash — same diagnostics, different story to tell.
+ kind: error ? 'crash' : 'bug',
error: describeError(error, errorInfo),
runtime: describeRuntime(),
app: context,
@@ -137,7 +140,11 @@ export const formatErrorReport = report => {
`Report ID: ${report.reportId}`,
`Time: ${report.occurredAt}`,
'',
- `Error: ${error.name}${error.message ? `: ${error.message}` : ''}`,
+ // A user-initiated report has no throw behind it; "Error: Unknown" would
+ // only be noise in the panel the user is being asked to read.
+ report.kind === 'bug'
+ ? 'Reported manually (no crash)'
+ : `Error: ${error.name}${error.message ? `: ${error.message}` : ''}`,
error.status
? `HTTP: ${error.status} ${error.statusText ?? ''}`.trim()
: null,
@@ -210,11 +217,14 @@ export const formatErrorReport = report => {
* leaves infrastructure they control, and they see it before it is published.
*/
export const buildErrorIssueUrl = ({ description, report }) => {
- const title = `[crash] ${
- report.error.message?.slice(0, 80) ||
- report.error.name ||
- 'Unexpected error'
- }`
+ const isBug = report.kind === 'bug'
+ const title = isBug
+ ? `[bug] ${description?.trim().slice(0, 80) || 'Reported from the app'}`
+ : `[crash] ${
+ report.error.message?.slice(0, 80) ||
+ report.error.name ||
+ 'Unexpected error'
+ }`
const body = [
'### What happened',
description?.trim() || '_no description provided_',
@@ -241,7 +251,7 @@ export const submitErrorReport = async ({
}) => {
const payload = {
source: 'donetick-app',
- kind: 'error-report',
+ kind: report.kind === 'bug' ? 'bug-report' : 'error-report',
reportId: report.reportId,
description: description?.trim() || null,
contactEmail: contactEmail?.trim() || null,
diff --git a/src/views/Modals/ErrorReportModal.jsx b/src/views/Modals/ErrorReportModal.jsx
index fd3c9a0..be4d41b 100644
--- a/src/views/Modals/ErrorReportModal.jsx
+++ b/src/views/Modals/ErrorReportModal.jsx
@@ -100,9 +100,13 @@ const IconHalo = ({ color = 'primary', icon }) => (
* user, everything else gathered automatically. The diagnostics are shown
* before sending rather than after — people are more willing to send a report
* they can see, and this is the one moment they already distrust the app.
+ *
+ * Also reached deliberately from settings with no error attached, where the
+ * same diagnostics back a bug the user noticed but the app never threw on.
*/
const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
const { ResponsiveModal } = useResponsiveModal()
+ const isBugReport = !error
const [report, setReport] = useState(null)
const [description, setDescription] = useState('')
@@ -160,7 +164,10 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
{step === STEP.FORM && (
- } color='danger' />
+ }
+ color={isBugReport ? 'warning' : 'danger'}
+ />
@@ -168,26 +175,33 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
level='h4'
sx={{ fontWeight: 700, letterSpacing: '-0.01em' }}
>
- Report this problem
+ {isBugReport ? 'Report a bug' : 'Report this problem'}
- A sentence about what you were doing turns this into something we
- can actually fix.
+ {isBugReport
+ ? 'Tell us what went wrong and we’ll attach the technical details for you.'
+ : 'A sentence about what you were doing turns this into something we can actually fix.'}
- What were you doing?
+
+ {isBugReport ? 'What went wrong?' : 'What were you doing?'}
+
@@ -279,7 +293,9 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
size='lg'
fullWidth
loading={submitting}
- disabled={!report}
+ // A crash report stands on its own; a manual one is only the
+ // description, so there's nothing to send without it.
+ disabled={!report || (isBugReport && !description.trim())}
onClick={handleSubmit}
>
Send report
@@ -293,7 +309,7 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
underline='hover'
onClick={onClose}
>
- Not now
+ {isBugReport ? 'Cancel' : 'Not now'}
diff --git a/src/views/Settings/SettingsOverview.jsx b/src/views/Settings/SettingsOverview.jsx
index 3249d3f..378e919 100644
--- a/src/views/Settings/SettingsOverview.jsx
+++ b/src/views/Settings/SettingsOverview.jsx
@@ -1,6 +1,7 @@
import {
AccountCircle,
Api,
+ BugReport,
ChevronRight,
Circle,
Code,
@@ -35,9 +36,11 @@ import {
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
+
import { useUserProfile } from '../../queries/UserQueries'
import { isPlusAccount } from '../../utils/Helpers'
import { isParentUser } from '../../utils/UserHelpers'
+import ErrorReportModal from '../Modals/ErrorReportModal'
import FeedbackModal from '../Modals/FeedbackModal'
const SettingsOverview = () => {
@@ -45,6 +48,7 @@ const SettingsOverview = () => {
const navigate = useNavigate()
const { data: userProfile } = useUserProfile()
const [feedbackOpen, setFeedbackOpen] = useState(false)
+ const [bugReportOpen, setBugReportOpen] = useState(false)
const settingsCards = [
{
@@ -133,6 +137,13 @@ const SettingsOverview = () => {
icon: ,
onSelect: () => setFeedbackOpen(true),
},
+ {
+ id: 'bugreport',
+ title: t('overview.sections.bugReport.title'),
+ description: t('overview.sections.bugReport.description'),
+ icon: ,
+ onSelect: () => setBugReportOpen(true),
+ },
]
const handleCardClick = setting => {
@@ -387,6 +398,13 @@ const SettingsOverview = () => {
open={feedbackOpen}
onClose={() => setFeedbackOpen(false)}
/>
+
+ {/* No error to pass: the report is about something the user saw, not
+ something the app threw, so the modal collects diagnostics only. */}
+ setBugReportOpen(false)}
+ />
)
}