Enhance error reporting functionality: add bug report option in settings, update error report modal, and refine feedback descriptions
This commit is contained in:
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,7 +217,10 @@ export const formatErrorReport = report => {
|
||||
* leaves infrastructure they control, and they see it before it is published.
|
||||
*/
|
||||
export const buildErrorIssueUrl = ({ description, report }) => {
|
||||
const title = `[crash] ${
|
||||
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'
|
||||
@@ -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,
|
||||
|
||||
@@ -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 && (
|
||||
<Stack spacing={2}>
|
||||
<Box sx={{ ...enter(0) }}>
|
||||
<IconHalo icon={<BugReportRounded />} color='danger' />
|
||||
<IconHalo
|
||||
icon={<BugReportRounded />}
|
||||
color={isBugReport ? 'warning' : 'danger'}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ textAlign: 'center', ...enter(50) }}>
|
||||
@@ -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'}
|
||||
</Typography>
|
||||
<Typography
|
||||
level='body-sm'
|
||||
sx={{ color: 'text.secondary', mt: 0.5, textWrap: 'pretty' }}
|
||||
>
|
||||
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.'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<FormControl sx={{ ...enter(100) }}>
|
||||
<FormLabel sx={{ fontWeight: 600 }}>What were you doing?</FormLabel>
|
||||
<FormLabel sx={{ fontWeight: 600 }}>
|
||||
{isBugReport ? 'What went wrong?' : 'What were you doing?'}
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
minRows={3}
|
||||
maxRows={6}
|
||||
autoFocus
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
placeholder='e.g. I tapped a chore in My Chores and the screen went blank'
|
||||
placeholder={
|
||||
isBugReport
|
||||
? 'e.g. Completing a chore from the list doesn’t update the due date'
|
||||
: 'e.g. I tapped a chore in My Chores and the screen went blank'
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -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'}
|
||||
</Link>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -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: <Feedback />,
|
||||
onSelect: () => setFeedbackOpen(true),
|
||||
},
|
||||
{
|
||||
id: 'bugreport',
|
||||
title: t('overview.sections.bugReport.title'),
|
||||
description: t('overview.sections.bugReport.description'),
|
||||
icon: <BugReport />,
|
||||
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. */}
|
||||
<ErrorReportModal
|
||||
open={bugReportOpen}
|
||||
onClose={() => setBugReportOpen(false)}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user