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": {
|
"feedback": {
|
||||||
"title": "Send 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 {
|
return {
|
||||||
reportId: reportId ?? newReportId(),
|
reportId: reportId ?? newReportId(),
|
||||||
occurredAt: new Date().toISOString(),
|
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),
|
error: describeError(error, errorInfo),
|
||||||
runtime: describeRuntime(),
|
runtime: describeRuntime(),
|
||||||
app: context,
|
app: context,
|
||||||
@@ -137,7 +140,11 @@ export const formatErrorReport = report => {
|
|||||||
`Report ID: ${report.reportId}`,
|
`Report ID: ${report.reportId}`,
|
||||||
`Time: ${report.occurredAt}`,
|
`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
|
error.status
|
||||||
? `HTTP: ${error.status} ${error.statusText ?? ''}`.trim()
|
? `HTTP: ${error.status} ${error.statusText ?? ''}`.trim()
|
||||||
: null,
|
: null,
|
||||||
@@ -210,11 +217,14 @@ export const formatErrorReport = report => {
|
|||||||
* leaves infrastructure they control, and they see it before it is published.
|
* leaves infrastructure they control, and they see it before it is published.
|
||||||
*/
|
*/
|
||||||
export const buildErrorIssueUrl = ({ description, report }) => {
|
export const buildErrorIssueUrl = ({ description, report }) => {
|
||||||
const title = `[crash] ${
|
const isBug = report.kind === 'bug'
|
||||||
report.error.message?.slice(0, 80) ||
|
const title = isBug
|
||||||
report.error.name ||
|
? `[bug] ${description?.trim().slice(0, 80) || 'Reported from the app'}`
|
||||||
'Unexpected error'
|
: `[crash] ${
|
||||||
}`
|
report.error.message?.slice(0, 80) ||
|
||||||
|
report.error.name ||
|
||||||
|
'Unexpected error'
|
||||||
|
}`
|
||||||
const body = [
|
const body = [
|
||||||
'### What happened',
|
'### What happened',
|
||||||
description?.trim() || '_no description provided_',
|
description?.trim() || '_no description provided_',
|
||||||
@@ -241,7 +251,7 @@ export const submitErrorReport = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
source: 'donetick-app',
|
source: 'donetick-app',
|
||||||
kind: 'error-report',
|
kind: report.kind === 'bug' ? 'bug-report' : 'error-report',
|
||||||
reportId: report.reportId,
|
reportId: report.reportId,
|
||||||
description: description?.trim() || null,
|
description: description?.trim() || null,
|
||||||
contactEmail: contactEmail?.trim() || null,
|
contactEmail: contactEmail?.trim() || null,
|
||||||
|
|||||||
@@ -100,9 +100,13 @@ const IconHalo = ({ color = 'primary', icon }) => (
|
|||||||
* user, everything else gathered automatically. The diagnostics are shown
|
* user, everything else gathered automatically. The diagnostics are shown
|
||||||
* before sending rather than after — people are more willing to send a report
|
* 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.
|
* 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 ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
|
||||||
const { ResponsiveModal } = useResponsiveModal()
|
const { ResponsiveModal } = useResponsiveModal()
|
||||||
|
const isBugReport = !error
|
||||||
|
|
||||||
const [report, setReport] = useState(null)
|
const [report, setReport] = useState(null)
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState('')
|
||||||
@@ -160,7 +164,10 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
|
|||||||
{step === STEP.FORM && (
|
{step === STEP.FORM && (
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Box sx={{ ...enter(0) }}>
|
<Box sx={{ ...enter(0) }}>
|
||||||
<IconHalo icon={<BugReportRounded />} color='danger' />
|
<IconHalo
|
||||||
|
icon={<BugReportRounded />}
|
||||||
|
color={isBugReport ? 'warning' : 'danger'}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ textAlign: 'center', ...enter(50) }}>
|
<Box sx={{ textAlign: 'center', ...enter(50) }}>
|
||||||
@@ -168,26 +175,33 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
|
|||||||
level='h4'
|
level='h4'
|
||||||
sx={{ fontWeight: 700, letterSpacing: '-0.01em' }}
|
sx={{ fontWeight: 700, letterSpacing: '-0.01em' }}
|
||||||
>
|
>
|
||||||
Report this problem
|
{isBugReport ? 'Report a bug' : 'Report this problem'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
level='body-sm'
|
level='body-sm'
|
||||||
sx={{ color: 'text.secondary', mt: 0.5, textWrap: 'pretty' }}
|
sx={{ color: 'text.secondary', mt: 0.5, textWrap: 'pretty' }}
|
||||||
>
|
>
|
||||||
A sentence about what you were doing turns this into something we
|
{isBugReport
|
||||||
can actually fix.
|
? '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>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<FormControl sx={{ ...enter(100) }}>
|
<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
|
<Textarea
|
||||||
minRows={3}
|
minRows={3}
|
||||||
maxRows={6}
|
maxRows={6}
|
||||||
autoFocus
|
autoFocus
|
||||||
value={description}
|
value={description}
|
||||||
onChange={e => setDescription(e.target.value)}
|
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>
|
</FormControl>
|
||||||
|
|
||||||
@@ -279,7 +293,9 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
|
|||||||
size='lg'
|
size='lg'
|
||||||
fullWidth
|
fullWidth
|
||||||
loading={submitting}
|
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}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
Send report
|
Send report
|
||||||
@@ -293,7 +309,7 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
|
|||||||
underline='hover'
|
underline='hover'
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Not now
|
{isBugReport ? 'Cancel' : 'Not now'}
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AccountCircle,
|
AccountCircle,
|
||||||
Api,
|
Api,
|
||||||
|
BugReport,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Circle,
|
Circle,
|
||||||
Code,
|
Code,
|
||||||
@@ -35,9 +36,11 @@ import {
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { useUserProfile } from '../../queries/UserQueries'
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import { isPlusAccount } from '../../utils/Helpers'
|
import { isPlusAccount } from '../../utils/Helpers'
|
||||||
import { isParentUser } from '../../utils/UserHelpers'
|
import { isParentUser } from '../../utils/UserHelpers'
|
||||||
|
import ErrorReportModal from '../Modals/ErrorReportModal'
|
||||||
import FeedbackModal from '../Modals/FeedbackModal'
|
import FeedbackModal from '../Modals/FeedbackModal'
|
||||||
|
|
||||||
const SettingsOverview = () => {
|
const SettingsOverview = () => {
|
||||||
@@ -45,6 +48,7 @@ const SettingsOverview = () => {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { data: userProfile } = useUserProfile()
|
const { data: userProfile } = useUserProfile()
|
||||||
const [feedbackOpen, setFeedbackOpen] = useState(false)
|
const [feedbackOpen, setFeedbackOpen] = useState(false)
|
||||||
|
const [bugReportOpen, setBugReportOpen] = useState(false)
|
||||||
|
|
||||||
const settingsCards = [
|
const settingsCards = [
|
||||||
{
|
{
|
||||||
@@ -133,6 +137,13 @@ const SettingsOverview = () => {
|
|||||||
icon: <Feedback />,
|
icon: <Feedback />,
|
||||||
onSelect: () => setFeedbackOpen(true),
|
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 => {
|
const handleCardClick = setting => {
|
||||||
@@ -387,6 +398,13 @@ const SettingsOverview = () => {
|
|||||||
open={feedbackOpen}
|
open={feedbackOpen}
|
||||||
onClose={() => setFeedbackOpen(false)}
|
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>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user