From 8e06c9fbb792d553cd8788127643ed8ccb2d733a Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 5 May 2026 17:21:42 -0500 Subject: [PATCH] Add ability for users to send error reports (#10427) --- package.json | 1 + src/components/SendErrorReportDialog.tsx | 108 +++++++++++++++++++++++ src/logger/reporting/sendErrorReport.ts | 30 +++++++ src/screens/Settings/AboutSettings.tsx | 13 +++ yarn.lock | 5 ++ 5 files changed, 157 insertions(+) create mode 100644 src/components/SendErrorReportDialog.tsx create mode 100644 src/logger/reporting/sendErrorReport.ts diff --git a/package.json b/package.json index bf2895883b..2e331998a8 100644 --- a/package.json +++ b/package.json @@ -235,6 +235,7 @@ "react-responsive": "^10.0.1", "react-textarea-autosize": "^8.5.3", "setimmediate": "^1.0.5", + "slugify": "^1.6.9", "sonner": "^2.0.7", "sonner-native": "^0.21.0", "tippy.js": "^6.3.7", diff --git a/src/components/SendErrorReportDialog.tsx b/src/components/SendErrorReportDialog.tsx new file mode 100644 index 0000000000..a677f54e03 --- /dev/null +++ b/src/components/SendErrorReportDialog.tsx @@ -0,0 +1,108 @@ +import {useState} from 'react' +import {View} from 'react-native' +import {useLingui} from '@lingui/react/macro' +import {useMutation} from '@tanstack/react-query' + +import {logger} from '#/logger' +import {sendErrorReport} from '#/logger/reporting/sendErrorReport' +import {useSession} from '#/state/session' +import {atoms as a, web} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import * as TextField from '#/components/forms/TextField' +import * as Toast from '#/components/Toast' +import {Text} from '#/components/Typography' + +export function SendErrorReportDialog({ + control, +}: { + control: Dialog.DialogControlProps +}) { + return ( + + + + + + ) +} + +function SendErrorReportDialogInner() { + const {t: l} = useLingui() + const control = Dialog.useDialogContext() + const {currentAccount} = useSession() + const [title, setTitle] = useState('') + const [description, setDescription] = useState('') + + const {mutate: onSubmit, isPending} = useMutation({ + mutationFn: async () => { + sendErrorReport({ + title, + description, + handle: currentAccount?.handle ?? '', + }) + }, + onSuccess: () => { + control.close(() => { + Toast.show(l`Report sent`) + }) + }, + onError: error => { + logger.error('Error sending user report', {safeMessage: error}) + Toast.show(l`Failed to send report`, {type: 'error'}) + }, + }) + + const canSubmit = title.trim().length > 0 && !isPending + + return ( + + + + {l`Send error report`} + + + + {l`Title`} + + setTitle(value.slice(0, 100))} + /> + + + + {l`Description`} + setDescription(value.slice(0, 1000))} + /> + + + + + + + + + ) +} diff --git a/src/logger/reporting/sendErrorReport.ts b/src/logger/reporting/sendErrorReport.ts new file mode 100644 index 0000000000..e0159c1703 --- /dev/null +++ b/src/logger/reporting/sendErrorReport.ts @@ -0,0 +1,30 @@ +import slugify from 'slugify' + +import {getEntries} from '#/logger/logDump' +import {Sentry} from '#/logger/sentry/lib' + +export function sendErrorReport({ + title, + description, + handle, +}: { + title: string + description: string + handle: string +}) { + const name = slugify(title, {lower: true, strict: true}).slice(0, 32) + Sentry.withScope(scope => { + scope.addAttachment({ + filename: name + '.json', + data: JSON.stringify(getEntries()), + contentType: 'application/json', + // mimetype: 'application/json', // need to update Sentry + }) + scope.setExtras({ + title, + description, + handle, + }) + scope.captureMessage(`[USER REPORT] ${handle} ${name}`, 'error') + }) +} diff --git a/src/screens/Settings/AboutSettings.tsx b/src/screens/Settings/AboutSettings.tsx index c605a48f0b..9164f5ce0f 100644 --- a/src/screens/Settings/AboutSettings.tsx +++ b/src/screens/Settings/AboutSettings.tsx @@ -13,12 +13,15 @@ import {type CommonNavigatorParams} from '#/lib/routes/types' import * as SettingsList from '#/screens/Settings/components/SettingsList' import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom' import {BroomSparkle_Stroke2_Corner2_Rounded as BroomSparkleIcon} from '#/components/icons/BroomSparkle' +import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble' import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines' import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe' import {Newspaper_Stroke2_Corner2_Rounded as NewspaperIcon} from '#/components/icons/Newspaper' import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench' import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' +import * as Prompt from '#/components/Prompt' +import {SendErrorReportDialog} from '#/components/SendErrorReportDialog' import * as Toast from '#/components/Toast' import {getDeviceId} from '#/analytics/identifiers' import * as env from '#/env' @@ -32,6 +35,7 @@ export function AboutSettingsScreen({}: Props) { const {_, i18n} = useLingui() const [devModeEnabled, setDevModeEnabled] = useDevMode() const [demoModeEnabled, setDemoModeEnabled] = useDemoMode() + const sendErrorReportControl = Prompt.usePromptControl() const {mutate: onClearImageCache, isPending: isClearingImageCache} = useMutation({ @@ -109,6 +113,14 @@ export function AboutSettingsScreen({}: Props) { System log + sendErrorReportControl.open()} + label={_(msg`Send error report`)}> + + + Send error report + + {IS_NATIVE && ( onClearImageCache()} @@ -182,6 +194,7 @@ export function AboutSettingsScreen({}: Props) { )} + ) } diff --git a/yarn.lock b/yarn.lock index 86ff68e803..609a88acec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15119,6 +15119,11 @@ slugify@^1.3.4, slugify@^1.6.6: resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b" integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== +slugify@^1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.9.tgz#610957dea21e56b65e3a153215ef7b265715c8e8" + integrity sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg== + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"