@fatagnus/convex-feedback 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -318,6 +318,56 @@ import { BugReportButton } from '@fatagnus/convex-feedback/react';
318
318
  | `onSuccess` | `function` | - | Success callback |
319
319
  | `onError` | `function` | - | Error callback |
320
320
 
321
+ ### ErrorBugReportButton
322
+
323
+ A self-contained bug report button designed for error pages. Pre-fills error details and allows users to submit bug reports directly from error boundaries or error pages.
324
+
325
+ ```tsx
326
+ import { ErrorBugReportButton } from '@fatagnus/convex-feedback/react';
327
+ import { api } from './convex/_generated/api';
328
+
329
+ function ErrorPage({ error }: { error: Error }) {
330
+ return (
331
+ <div>
332
+ <h1>Something went wrong</h1>
333
+ <p>{error.message}</p>
334
+
335
+ <ErrorBugReportButton
336
+ error={error}
337
+ reporterType="staff"
338
+ reporterId={user?.id}
339
+ reporterEmail={user?.email}
340
+ reporterName={user?.name}
341
+ bugReportApi={{
342
+ create: api.feedback.bugReports.create,
343
+ }}
344
+ variant="outline"
345
+ color="red"
346
+ onSuccess={() => console.log('Bug report submitted')}
347
+ onError={(err) => console.error('Failed to submit:', err)}
348
+ />
349
+ </div>
350
+ );
351
+ }
352
+ ```
353
+
354
+ **Props:**
355
+
356
+ | Prop | Type | Default | Description |
357
+ |------|------|---------|-------------|
358
+ | `error` | `Error` | - | The error to report (required) |
359
+ | `reporterType` | `'staff' \| 'customer'` | `'staff'` | Type of reporter |
360
+ | `reporterId` | `string` | `'error-page'` | Unique reporter identifier |
361
+ | `reporterEmail` | `string` | `'unknown@example.com'` | Reporter's email |
362
+ | `reporterName` | `string` | `'Error Page Reporter'` | Reporter's display name |
363
+ | `bugReportApi` | `object` | - | Convex API reference with `create` mutation (required) |
364
+ | `variant` | `'filled' \| 'outline' \| 'light' \| 'subtle'` | `'outline'` | Button variant |
365
+ | `color` | `string` | `'red'` | Button color |
366
+ | `onSuccess` | `function` | - | Success callback |
367
+ | `onError` | `function` | - | Error callback |
368
+
369
+ **Note:** Unlike `BugReportButton`, this component only requires the `bugReportApi.create` mutation (no `generateUploadUrl` needed since error pages typically don't include screenshots).
370
+
321
371
  ### useBugReportContext
322
372
 
323
373
  Hook to access the bug report context for error capture.
@@ -0,0 +1,57 @@
1
+ import type { FunctionReference } from 'convex/server';
2
+ /**
3
+ * Props for the ErrorBugReportButton component
4
+ */
5
+ export interface ErrorBugReportButtonProps {
6
+ /** The error to report */
7
+ error: Error;
8
+ /** Reporter type: 'staff' for internal users, 'customer' for external users */
9
+ reporterType?: 'staff' | 'customer';
10
+ /** Unique identifier for the reporter (defaults to 'error-page') */
11
+ reporterId?: string;
12
+ /** Reporter's email address (defaults to 'unknown@example.com') */
13
+ reporterEmail?: string;
14
+ /** Reporter's display name (defaults to 'Error Page Reporter') */
15
+ reporterName?: string;
16
+ /** Convex API reference for bug reports */
17
+ bugReportApi: {
18
+ create: FunctionReference<'mutation'>;
19
+ };
20
+ /** Button variant (default: 'outline') */
21
+ variant?: 'filled' | 'outline' | 'light' | 'subtle';
22
+ /** Button color (default: 'red') */
23
+ color?: string;
24
+ /** Callback when submission succeeds */
25
+ onSuccess?: () => void;
26
+ /** Callback when submission fails */
27
+ onError?: (error: Error) => void;
28
+ }
29
+ /**
30
+ * A self-contained bug report button designed for error pages.
31
+ * Pre-fills the error details and allows users to submit bug reports
32
+ * directly from error pages.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * import { ErrorBugReportButton } from '@fatagnus/convex-feedback/react';
37
+ * import { api } from './convex/_generated/api';
38
+ *
39
+ * function ErrorPage({ error }: { error: Error }) {
40
+ * return (
41
+ * <div>
42
+ * <h1>Something went wrong</h1>
43
+ * <ErrorBugReportButton
44
+ * error={error}
45
+ * reporterType="staff"
46
+ * bugReportApi={{
47
+ * create: api.feedback.bugReports.create,
48
+ * }}
49
+ * />
50
+ * </div>
51
+ * );
52
+ * }
53
+ * ```
54
+ */
55
+ export declare function ErrorBugReportButton({ error, reporterType, reporterId, reporterEmail, reporterName, bugReportApi, variant, color, onSuccess, onError, }: ErrorBugReportButtonProps): import("react/jsx-runtime").JSX.Element;
56
+ export default ErrorBugReportButton;
57
+ //# sourceMappingURL=ErrorBugReportButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBugReportButton.d.ts","sourceRoot":"","sources":["../../src/react/ErrorBugReportButton.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAKvD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,0BAA0B;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACpC,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,YAAY,EAAE;QACZ,MAAM,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;KACvC,CAAC;IACF,0CAA0C;IAC1C,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,qCAAqC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,YAAsB,EACtB,UAAyB,EACzB,aAAqC,EACrC,YAAoC,EACpC,YAAY,EACZ,OAAmB,EACnB,KAAa,EACb,SAAS,EACT,OAAO,GACR,EAAE,yBAAyB,2CA+M3B;AAED,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useCallback } from 'react';
3
+ import { Button, Modal, TextInput, Textarea, Select, Stack, Text, Group, Paper, Badge, LoadingOverlay, } from '@mantine/core';
4
+ import { useForm } from '@mantine/form';
5
+ import { useDisclosure } from '@mantine/hooks';
6
+ import { notifications } from '@mantine/notifications';
7
+ import * as TablerIcons from '@tabler/icons-react';
8
+ import { useMutation } from 'convex/react';
9
+ const IconBug = TablerIcons.IconBug;
10
+ const IconCheck = TablerIcons.IconCheck;
11
+ /**
12
+ * A self-contained bug report button designed for error pages.
13
+ * Pre-fills the error details and allows users to submit bug reports
14
+ * directly from error pages.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * import { ErrorBugReportButton } from '@fatagnus/convex-feedback/react';
19
+ * import { api } from './convex/_generated/api';
20
+ *
21
+ * function ErrorPage({ error }: { error: Error }) {
22
+ * return (
23
+ * <div>
24
+ * <h1>Something went wrong</h1>
25
+ * <ErrorBugReportButton
26
+ * error={error}
27
+ * reporterType="staff"
28
+ * bugReportApi={{
29
+ * create: api.feedback.bugReports.create,
30
+ * }}
31
+ * />
32
+ * </div>
33
+ * );
34
+ * }
35
+ * ```
36
+ */
37
+ export function ErrorBugReportButton({ error, reporterType = 'staff', reporterId = 'error-page', reporterEmail = 'unknown@example.com', reporterName = 'Error Page Reporter', bugReportApi, variant = 'outline', color = 'red', onSuccess, onError, }) {
38
+ const [opened, { open, close }] = useDisclosure(false);
39
+ const [isSubmitting, setIsSubmitting] = useState(false);
40
+ const createReport = useMutation(bugReportApi.create);
41
+ const form = useForm({
42
+ initialValues: {
43
+ title: `Error: ${error?.message?.slice(0, 50) || 'Unknown error'}${(error?.message?.length || 0) > 50 ? '...' : ''}`,
44
+ description: '',
45
+ severity: 'high',
46
+ },
47
+ validate: {
48
+ title: (value) => value.trim().length < 5 ? 'Title must be at least 5 characters' : null,
49
+ description: (value) => value.trim().length < 10
50
+ ? 'Please provide more details (at least 10 characters)'
51
+ : null,
52
+ },
53
+ });
54
+ const getBrowserInfo = useCallback(() => {
55
+ return {
56
+ userAgent: navigator.userAgent,
57
+ platform: navigator.platform,
58
+ language: navigator.language,
59
+ cookiesEnabled: navigator.cookieEnabled,
60
+ onLine: navigator.onLine,
61
+ screenWidth: window.screen.width,
62
+ screenHeight: window.screen.height,
63
+ colorDepth: window.screen.colorDepth,
64
+ pixelRatio: window.devicePixelRatio,
65
+ };
66
+ }, []);
67
+ const getErrorDetails = useCallback(() => {
68
+ const parts = [
69
+ '--- Error Details ---',
70
+ `Message: ${error?.message || 'Unknown error'}`,
71
+ ];
72
+ if (error?.stack) {
73
+ parts.push('', 'Stack Trace:', error.stack);
74
+ }
75
+ return parts.join('\n');
76
+ }, [error]);
77
+ const handleSubmit = async (values) => {
78
+ setIsSubmitting(true);
79
+ try {
80
+ // Build the full description with error details
81
+ const fullDescription = [
82
+ values.description,
83
+ '',
84
+ getErrorDetails(),
85
+ ].join('\n');
86
+ // Create the report
87
+ await createReport({
88
+ title: values.title,
89
+ description: fullDescription,
90
+ severity: values.severity,
91
+ reporterType,
92
+ reporterId,
93
+ reporterEmail,
94
+ reporterName,
95
+ url: window.location.href,
96
+ route: window.location.pathname,
97
+ browserInfo: JSON.stringify(getBrowserInfo()),
98
+ viewportWidth: window.innerWidth,
99
+ viewportHeight: window.innerHeight,
100
+ networkState: navigator.onLine ? 'online' : 'offline',
101
+ });
102
+ notifications.show({
103
+ title: 'Bug report submitted',
104
+ message: 'Thank you for reporting this error! We will investigate.',
105
+ color: 'green',
106
+ icon: _jsx(IconCheck, { size: 16 }),
107
+ });
108
+ onSuccess?.();
109
+ form.reset();
110
+ close();
111
+ }
112
+ catch (submitError) {
113
+ console.error('Failed to submit bug report:', submitError);
114
+ notifications.show({
115
+ title: 'Submission failed',
116
+ message: 'Could not submit bug report. Please try again.',
117
+ color: 'red',
118
+ });
119
+ onError?.(submitError instanceof Error ? submitError : new Error(String(submitError)));
120
+ }
121
+ finally {
122
+ setIsSubmitting(false);
123
+ }
124
+ };
125
+ const handleOpen = () => {
126
+ // Reset form with current error info
127
+ form.setValues({
128
+ title: `Error: ${error?.message?.slice(0, 50) || 'Unknown error'}${(error?.message?.length || 0) > 50 ? '...' : ''}`,
129
+ description: '',
130
+ severity: 'high',
131
+ });
132
+ open();
133
+ };
134
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { "data-testid": "error-report-bug-button", leftSection: _jsx(IconBug, { size: 16 }), onClick: handleOpen, variant: variant, color: color, children: "Report Bug" }), _jsxs(Modal, { opened: opened, onClose: close, title: _jsxs(Group, { gap: "xs", children: [_jsx(IconBug, { size: 20 }), _jsx(Text, { fw: 600, children: "Report This Error" })] }), size: "md", children: [_jsx(LoadingOverlay, { visible: isSubmitting }), _jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: _jsxs(Stack, { gap: "md", children: [_jsx(TextInput, { label: "Title", placeholder: "Brief summary of the issue", required: true, ...form.getInputProps('title') }), _jsx(Textarea, { label: "What were you doing when this error occurred?", placeholder: "Please describe what you were trying to do, what you expected to happen, and any other relevant context.", required: true, minRows: 3, ...form.getInputProps('description') }), _jsx(Select, { label: "Severity", placeholder: "How severe is this issue?", data: [
135
+ { value: 'low', label: 'Low - Minor inconvenience' },
136
+ { value: 'medium', label: 'Medium - Affects workflow' },
137
+ { value: 'high', label: 'High - Major functionality broken' },
138
+ { value: 'critical', label: 'Critical - System unusable' },
139
+ ], ...form.getInputProps('severity') }), _jsx(Paper, { withBorder: true, p: "xs", radius: "md", bg: "var(--mantine-color-red-light)", children: _jsxs(Stack, { gap: "xs", children: [_jsx(Group, { gap: "xs", children: _jsx(Badge, { size: "xs", color: "red", children: "Error Details (auto-included)" }) }), _jsx(Text, { size: "xs", c: "dimmed", style: { fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }, children: error?.message || 'Unknown error' })] }) }), _jsx(Paper, { withBorder: true, p: "xs", radius: "md", children: _jsxs(Group, { gap: "xs", children: [_jsx(Badge, { size: "xs", variant: "outline", color: "gray", children: "Auto-captured" }), _jsx(Text, { size: "xs", c: "dimmed", children: "Browser info, URL, viewport size, and full stack trace will be included." })] }) }), _jsxs(Group, { justify: "flex-end", mt: "md", children: [_jsx(Button, { variant: "subtle", onClick: close, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", color: "red", loading: isSubmitting, children: "Submit Report" })] })] }) })] })] }));
140
+ }
141
+ export default ErrorBugReportButton;
142
+ //# sourceMappingURL=ErrorBugReportButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorBugReportButton.js","sourceRoot":"","sources":["../../src/react/ErrorBugReportButton.tsx"],"names":[],"mappings":"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"}
@@ -33,4 +33,5 @@
33
33
  */
34
34
  export { BugReportProvider, useBugReportContext, type BugReportProviderProps, type BugReportContextValue, type ConsoleError, } from './BugReportContext';
35
35
  export { BugReportButton, type BugReportButtonProps, } from './BugReportButton';
36
+ export { ErrorBugReportButton, type ErrorBugReportButtonProps, } from './ErrorBugReportButton';
36
37
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,oBAAoB,EACpB,KAAK,yBAAyB,GAC/B,MAAM,wBAAwB,CAAC"}
@@ -33,4 +33,5 @@
33
33
  */
34
34
  export { BugReportProvider, useBugReportContext, } from './BugReportContext';
35
35
  export { BugReportButton, } from './BugReportButton';
36
+ export { ErrorBugReportButton, } from './ErrorBugReportButton';
36
37
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GAIpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,GAEhB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GAIpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,oBAAoB,GAErB,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fatagnus/convex-feedback",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Bug reports and feedback collection component for Convex applications with AI analysis and email notifications",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -0,0 +1,311 @@
1
+ import { useState, useCallback } from 'react';
2
+ import {
3
+ Button,
4
+ Modal,
5
+ TextInput,
6
+ Textarea,
7
+ Select,
8
+ Stack,
9
+ Text,
10
+ Group,
11
+ Paper,
12
+ Badge,
13
+ LoadingOverlay,
14
+ } from '@mantine/core';
15
+ import { useForm } from '@mantine/form';
16
+ import { useDisclosure } from '@mantine/hooks';
17
+ import { notifications } from '@mantine/notifications';
18
+ import * as TablerIcons from '@tabler/icons-react';
19
+ import { useMutation } from 'convex/react';
20
+ import type { FunctionReference } from 'convex/server';
21
+
22
+ const IconBug = TablerIcons.IconBug as React.FC<{ size?: number }>;
23
+ const IconCheck = TablerIcons.IconCheck as React.FC<{ size?: number }>;
24
+
25
+ /**
26
+ * Props for the ErrorBugReportButton component
27
+ */
28
+ export interface ErrorBugReportButtonProps {
29
+ /** The error to report */
30
+ error: Error;
31
+ /** Reporter type: 'staff' for internal users, 'customer' for external users */
32
+ reporterType?: 'staff' | 'customer';
33
+ /** Unique identifier for the reporter (defaults to 'error-page') */
34
+ reporterId?: string;
35
+ /** Reporter's email address (defaults to 'unknown@example.com') */
36
+ reporterEmail?: string;
37
+ /** Reporter's display name (defaults to 'Error Page Reporter') */
38
+ reporterName?: string;
39
+ /** Convex API reference for bug reports */
40
+ bugReportApi: {
41
+ create: FunctionReference<'mutation'>;
42
+ };
43
+ /** Button variant (default: 'outline') */
44
+ variant?: 'filled' | 'outline' | 'light' | 'subtle';
45
+ /** Button color (default: 'red') */
46
+ color?: string;
47
+ /** Callback when submission succeeds */
48
+ onSuccess?: () => void;
49
+ /** Callback when submission fails */
50
+ onError?: (error: Error) => void;
51
+ }
52
+
53
+ interface BrowserInfo {
54
+ userAgent: string;
55
+ platform: string;
56
+ language: string;
57
+ cookiesEnabled: boolean;
58
+ onLine: boolean;
59
+ screenWidth: number;
60
+ screenHeight: number;
61
+ colorDepth: number;
62
+ pixelRatio: number;
63
+ }
64
+
65
+ /**
66
+ * A self-contained bug report button designed for error pages.
67
+ * Pre-fills the error details and allows users to submit bug reports
68
+ * directly from error pages.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * import { ErrorBugReportButton } from '@fatagnus/convex-feedback/react';
73
+ * import { api } from './convex/_generated/api';
74
+ *
75
+ * function ErrorPage({ error }: { error: Error }) {
76
+ * return (
77
+ * <div>
78
+ * <h1>Something went wrong</h1>
79
+ * <ErrorBugReportButton
80
+ * error={error}
81
+ * reporterType="staff"
82
+ * bugReportApi={{
83
+ * create: api.feedback.bugReports.create,
84
+ * }}
85
+ * />
86
+ * </div>
87
+ * );
88
+ * }
89
+ * ```
90
+ */
91
+ export function ErrorBugReportButton({
92
+ error,
93
+ reporterType = 'staff',
94
+ reporterId = 'error-page',
95
+ reporterEmail = 'unknown@example.com',
96
+ reporterName = 'Error Page Reporter',
97
+ bugReportApi,
98
+ variant = 'outline',
99
+ color = 'red',
100
+ onSuccess,
101
+ onError,
102
+ }: ErrorBugReportButtonProps) {
103
+ const [opened, { open, close }] = useDisclosure(false);
104
+ const [isSubmitting, setIsSubmitting] = useState(false);
105
+
106
+ const createReport = useMutation(bugReportApi.create);
107
+
108
+ const form = useForm({
109
+ initialValues: {
110
+ title: `Error: ${error?.message?.slice(0, 50) || 'Unknown error'}${(error?.message?.length || 0) > 50 ? '...' : ''}`,
111
+ description: '',
112
+ severity: 'high' as 'low' | 'medium' | 'high' | 'critical',
113
+ },
114
+ validate: {
115
+ title: (value) =>
116
+ value.trim().length < 5 ? 'Title must be at least 5 characters' : null,
117
+ description: (value) =>
118
+ value.trim().length < 10
119
+ ? 'Please provide more details (at least 10 characters)'
120
+ : null,
121
+ },
122
+ });
123
+
124
+ const getBrowserInfo = useCallback((): BrowserInfo => {
125
+ return {
126
+ userAgent: navigator.userAgent,
127
+ platform: navigator.platform,
128
+ language: navigator.language,
129
+ cookiesEnabled: navigator.cookieEnabled,
130
+ onLine: navigator.onLine,
131
+ screenWidth: window.screen.width,
132
+ screenHeight: window.screen.height,
133
+ colorDepth: window.screen.colorDepth,
134
+ pixelRatio: window.devicePixelRatio,
135
+ };
136
+ }, []);
137
+
138
+ const getErrorDetails = useCallback((): string => {
139
+ const parts = [
140
+ '--- Error Details ---',
141
+ `Message: ${error?.message || 'Unknown error'}`,
142
+ ];
143
+
144
+ if (error?.stack) {
145
+ parts.push('', 'Stack Trace:', error.stack);
146
+ }
147
+
148
+ return parts.join('\n');
149
+ }, [error]);
150
+
151
+ const handleSubmit = async (values: typeof form.values) => {
152
+ setIsSubmitting(true);
153
+ try {
154
+ // Build the full description with error details
155
+ const fullDescription = [
156
+ values.description,
157
+ '',
158
+ getErrorDetails(),
159
+ ].join('\n');
160
+
161
+ // Create the report
162
+ await createReport({
163
+ title: values.title,
164
+ description: fullDescription,
165
+ severity: values.severity,
166
+ reporterType,
167
+ reporterId,
168
+ reporterEmail,
169
+ reporterName,
170
+ url: window.location.href,
171
+ route: window.location.pathname,
172
+ browserInfo: JSON.stringify(getBrowserInfo()),
173
+ viewportWidth: window.innerWidth,
174
+ viewportHeight: window.innerHeight,
175
+ networkState: navigator.onLine ? 'online' : 'offline',
176
+ });
177
+
178
+ notifications.show({
179
+ title: 'Bug report submitted',
180
+ message: 'Thank you for reporting this error! We will investigate.',
181
+ color: 'green',
182
+ icon: <IconCheck size={16} />,
183
+ });
184
+
185
+ onSuccess?.();
186
+
187
+ form.reset();
188
+ close();
189
+ } catch (submitError) {
190
+ console.error('Failed to submit bug report:', submitError);
191
+ notifications.show({
192
+ title: 'Submission failed',
193
+ message: 'Could not submit bug report. Please try again.',
194
+ color: 'red',
195
+ });
196
+ onError?.(submitError instanceof Error ? submitError : new Error(String(submitError)));
197
+ } finally {
198
+ setIsSubmitting(false);
199
+ }
200
+ };
201
+
202
+ const handleOpen = () => {
203
+ // Reset form with current error info
204
+ form.setValues({
205
+ title: `Error: ${error?.message?.slice(0, 50) || 'Unknown error'}${(error?.message?.length || 0) > 50 ? '...' : ''}`,
206
+ description: '',
207
+ severity: 'high',
208
+ });
209
+ open();
210
+ };
211
+
212
+ return (
213
+ <>
214
+ <Button
215
+ data-testid="error-report-bug-button"
216
+ leftSection={<IconBug size={16} />}
217
+ onClick={handleOpen}
218
+ variant={variant}
219
+ color={color}
220
+ >
221
+ Report Bug
222
+ </Button>
223
+
224
+ <Modal
225
+ opened={opened}
226
+ onClose={close}
227
+ title={
228
+ <Group gap="xs">
229
+ <IconBug size={20} />
230
+ <Text fw={600}>Report This Error</Text>
231
+ </Group>
232
+ }
233
+ size="md"
234
+ >
235
+ <LoadingOverlay visible={isSubmitting} />
236
+ <form onSubmit={form.onSubmit(handleSubmit)}>
237
+ <Stack gap="md">
238
+ <TextInput
239
+ label="Title"
240
+ placeholder="Brief summary of the issue"
241
+ required
242
+ {...form.getInputProps('title')}
243
+ />
244
+
245
+ <Textarea
246
+ label="What were you doing when this error occurred?"
247
+ placeholder="Please describe what you were trying to do, what you expected to happen, and any other relevant context."
248
+ required
249
+ minRows={3}
250
+ {...form.getInputProps('description')}
251
+ />
252
+
253
+ <Select
254
+ label="Severity"
255
+ placeholder="How severe is this issue?"
256
+ data={[
257
+ { value: 'low', label: 'Low - Minor inconvenience' },
258
+ { value: 'medium', label: 'Medium - Affects workflow' },
259
+ { value: 'high', label: 'High - Major functionality broken' },
260
+ { value: 'critical', label: 'Critical - System unusable' },
261
+ ]}
262
+ {...form.getInputProps('severity')}
263
+ />
264
+
265
+ {/* Error Details Preview */}
266
+ <Paper withBorder p="xs" radius="md" bg="var(--mantine-color-red-light)">
267
+ <Stack gap="xs">
268
+ <Group gap="xs">
269
+ <Badge size="xs" color="red">
270
+ Error Details (auto-included)
271
+ </Badge>
272
+ </Group>
273
+ <Text
274
+ size="xs"
275
+ c="dimmed"
276
+ style={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}
277
+ >
278
+ {error?.message || 'Unknown error'}
279
+ </Text>
280
+ </Stack>
281
+ </Paper>
282
+
283
+ {/* Auto-captured Info Badge */}
284
+ <Paper withBorder p="xs" radius="md">
285
+ <Group gap="xs">
286
+ <Badge size="xs" variant="outline" color="gray">
287
+ Auto-captured
288
+ </Badge>
289
+ <Text size="xs" c="dimmed">
290
+ Browser info, URL, viewport size, and full stack trace will be included.
291
+ </Text>
292
+ </Group>
293
+ </Paper>
294
+
295
+ {/* Submit Buttons */}
296
+ <Group justify="flex-end" mt="md">
297
+ <Button variant="subtle" onClick={close} disabled={isSubmitting}>
298
+ Cancel
299
+ </Button>
300
+ <Button type="submit" color="red" loading={isSubmitting}>
301
+ Submit Report
302
+ </Button>
303
+ </Group>
304
+ </Stack>
305
+ </form>
306
+ </Modal>
307
+ </>
308
+ );
309
+ }
310
+
311
+ export default ErrorBugReportButton;
@@ -44,3 +44,8 @@ export {
44
44
  BugReportButton,
45
45
  type BugReportButtonProps,
46
46
  } from './BugReportButton';
47
+
48
+ export {
49
+ ErrorBugReportButton,
50
+ type ErrorBugReportButtonProps,
51
+ } from './ErrorBugReportButton';