Skip to content

Commit eb6f218

Browse files
authored
feat!: useFeedback hook for user feedback submission (#17)
BREAKING CHANGES: remove additionalFormDataParams in favor of attributes/attachments. Update to React 19.
1 parent e1c6920 commit eb6f218

12 files changed

Lines changed: 9742 additions & 10656 deletions

File tree

examples/my-react-crasher/package.json

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
{
22
"dependencies": {
3-
"@bugsplat/react": "^1.0.1",
4-
"react": "^18.2.0",
5-
"react-dom": "^18.2.0"
3+
"@bugsplat/react": "file:../../",
4+
"react": "^19.0.0",
5+
"react-dom": "^19.0.0"
66
},
77
"devDependencies": {
8-
"@bugsplat/symbol-upload": "^4.1.1",
9-
"@testing-library/react": "^13.3.0",
10-
"@types/jest": "^28.1.8",
11-
"@types/node": "^18.7.14",
12-
"@types/react": "^18.0.18",
13-
"@types/react-dom": "^18.0.6",
14-
"@vitejs/plugin-react": "^2.0.1",
8+
"@bugsplat/symbol-upload": "^10.2.4",
9+
"@testing-library/react": "^16.0.0",
10+
"@types/node": "^22.0.0",
11+
"@types/react": "^19.0.0",
12+
"@types/react-dom": "^19.0.0",
13+
"@vitejs/plugin-react": "^6.0.0",
1514
"env-cmd": "^10.1.0",
16-
"prettier": "^2.7.1",
17-
"typescript": "^4.8.2",
18-
"vite": "^3.0.9",
19-
"vitest": "^0.22.1"
15+
"prettier": "^3.0.0",
16+
"typescript": "^5.9.0",
17+
"vite": "^8.0.0",
18+
"vitest": "^4.0.0"
2019
},
2120
"name": "my-react-crasher",
2221
"private": true,

examples/my-react-crasher/src/App/App.module.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,25 @@ h2 {
7575
button.error > h4 {
7676
margin-top: 0;
7777
}
78+
79+
.feedback {
80+
padding: 0 24px 24px;
81+
border-top: 2px solid rgb(225, 225, 225);
82+
}
83+
84+
.feedbackBtn {
85+
padding: 10px 24px;
86+
background: #007bff;
87+
color: #fff;
88+
font-size: 0.9rem;
89+
border: none;
90+
border-radius: 4px;
91+
cursor: pointer;
92+
transition: 220ms all ease-in-out;
93+
width: auto;
94+
display: inline-flex;
95+
}
96+
97+
.feedbackBtn:hover {
98+
background: #0056b3;
99+
}

examples/my-react-crasher/src/App/App.tsx

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { ErrorBoundary, useErrorHandler } from '@bugsplat/react';
2-
import { type ReactNode, useState } from 'react';
1+
import { ErrorBoundary, getBugSplat, useErrorHandler, useFeedback, type BugSplatResponse } from '@bugsplat/react';
2+
import { type ReactNode, useEffect, useState } from 'react';
33
import logo from './bugsplat-logo.png';
44
import styles from './App.module.css';
55
import Fallback from '../Fallback';
6+
import FeedbackDialog, { type FeedbackData } from '../FeedbackDialog';
67

78
const BUGSPLAT_URL = 'https://www.bugsplat.com/';
89
const DOCS_REACT_URL =
910
'https://docs.bugsplat.com/introduction/getting-started/integrations/web/react';
11+
const BASE_CRASH_URL = 'https://app.bugsplat.com/v2/crash';
1012

1113
function Link({ href, children }: { href?: string; children: ReactNode }) {
1214
return (
@@ -28,8 +30,45 @@ function ThrowOnError(props: { error: Error | null }) {
2830
return null;
2931
}
3032

33+
interface SubmissionLink {
34+
href: string;
35+
text: string;
36+
}
37+
3138
function App() {
3239
const [error, setError] = useState<Error | null>(null);
40+
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
41+
const [submissionLink, setSubmissionLink] = useState<SubmissionLink | null>(null);
42+
const { postFeedback, response: feedbackResponse } = useFeedback();
43+
44+
const database = getBugSplat()?.database;
45+
46+
function handleSubmissionResponse(response: BugSplatResponse) {
47+
if (!database || response.error) return;
48+
const crashId = response.response.crash_id;
49+
setSubmissionLink({
50+
href: `${BASE_CRASH_URL}?database=${database}&id=${crashId}`,
51+
text: `View submission ${crashId} in database ${database}`,
52+
});
53+
}
54+
55+
useEffect(() => {
56+
if (feedbackResponse) {
57+
handleSubmissionResponse(feedbackResponse);
58+
}
59+
}, [feedbackResponse]);
60+
61+
async function handleFeedbackSubmit(data: FeedbackData) {
62+
setShowFeedbackDialog(false);
63+
const attachments = data.attachments.map((file) => ({
64+
filename: file.name,
65+
data: file,
66+
}));
67+
await postFeedback(data.title, {
68+
description: data.description,
69+
attachments,
70+
});
71+
}
3372

3473
return (
3574
<div className={styles.root}>
@@ -45,12 +84,29 @@ function App() {
4584
JavaScript or TypeScript.
4685
</p>
4786
<ErrorBoundary
48-
fallback={(props) => <Fallback {...props} />}
87+
fallback={() => (
88+
<Fallback
89+
submissionLink={submissionLink}
90+
loading={!submissionLink}
91+
onReset={() => { setError(null); setSubmissionLink(null); }}
92+
/>
93+
)}
94+
onError={(_error, _componentStack, response) => {
95+
if (response) {
96+
handleSubmissionResponse(response);
97+
}
98+
}}
4999
onReset={() => setError(null)}
50100
resetKeys={[error]}
51101
>
52102
<ThrowOnError error={error} />
53103
</ErrorBoundary>
104+
{submissionLink && !error && (
105+
<Fallback
106+
submissionLink={submissionLink}
107+
onReset={() => setSubmissionLink(null)}
108+
/>
109+
)}
54110
<div className={styles.errors}>
55111
<h2>Errors</h2>
56112
{ERRORS.map((error) => (
@@ -64,7 +120,20 @@ function App() {
64120
</button>
65121
))}
66122
</div>
123+
<div className={styles.feedback}>
124+
<h2>Feedback</h2>
125+
<p>Let your users send arbitrary feedback and file attachments directly to your BugSplat dashboard.</p>
126+
<button className={styles.feedbackBtn} onClick={() => setShowFeedbackDialog(true)}>
127+
Send Feedback
128+
</button>
129+
</div>
67130
</div>
131+
{showFeedbackDialog && (
132+
<FeedbackDialog
133+
onClose={() => setShowFeedbackDialog(false)}
134+
onSubmit={handleFeedbackSubmit}
135+
/>
136+
)}
68137
</div>
69138
);
70139
}

examples/my-react-crasher/src/Fallback/Fallback.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,26 @@
1-
import { FallbackProps, getBugSplat } from '@bugsplat/react';
21
import styles from './Fallback.module.css';
32

4-
const BASE_CRASH_URL = 'https://app.bugsplat.com/v2/crash';
3+
interface FallbackProps {
4+
submissionLink?: { href: string; text: string } | null;
5+
loading?: boolean;
6+
onReset: () => void;
7+
}
58

69
export default function Fallback({
7-
resetErrorBoundary,
8-
response,
10+
submissionLink,
11+
loading,
12+
onReset,
913
}: FallbackProps) {
10-
const bugSplat = getBugSplat();
11-
const database = bugSplat?.database;
12-
13-
const crashId =
14-
response?.error === null ? response.response.crash_id : undefined;
15-
16-
const link = crashId && database && (
17-
<a
18-
href={`${BASE_CRASH_URL}?database=${database}&id=${crashId}`}
19-
target="_blank"
20-
rel="noreferrer"
21-
>
22-
Crash {crashId} in database {database}
14+
const link = submissionLink && (
15+
<a href={submissionLink.href} target="_blank" rel="noreferrer">
16+
{submissionLink.text}
2317
</a>
2418
);
2519

2620
return (
2721
<div className={styles.root}>
28-
<p>{response ? link : 'loading...'}</p>
29-
<button className={styles.reset} onClick={resetErrorBoundary}>
22+
<p>{loading ? 'loading...' : link}</p>
23+
<button className={styles.reset} onClick={onReset}>
3024
<h2>Reset</h2>
3125
</button>
3226
</div>
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
.overlay {
2+
position: fixed;
3+
inset: 0;
4+
background: rgba(0, 0, 0, 0.4);
5+
display: flex;
6+
align-items: center;
7+
justify-content: center;
8+
z-index: 1000;
9+
}
10+
11+
.dialog {
12+
background: #fff;
13+
width: 440px;
14+
max-width: 90vw;
15+
box-shadow: 0.3em 0.3em 1em rgba(0, 0, 0, 0.3);
16+
border-radius: 8px;
17+
overflow: hidden;
18+
font-family: sans-serif;
19+
}
20+
21+
.header {
22+
padding: 20px 24px 12px;
23+
text-align: center;
24+
}
25+
26+
.header h2 {
27+
margin: 0;
28+
color: #007bff;
29+
}
30+
31+
.body {
32+
padding: 0 24px 16px;
33+
display: flex;
34+
flex-direction: column;
35+
gap: 6px;
36+
}
37+
38+
.body label {
39+
font-weight: 600;
40+
font-size: 0.85rem;
41+
text-align: left;
42+
margin-top: 4px;
43+
}
44+
45+
.body input[type='text'],
46+
.body textarea {
47+
box-sizing: border-box;
48+
width: 100%;
49+
padding: 10px;
50+
border: 2px solid rgb(225, 225, 225);
51+
border-radius: 0;
52+
font-family: inherit;
53+
font-size: 0.9rem;
54+
transition: 220ms all ease-in-out;
55+
-webkit-appearance: none;
56+
appearance: none;
57+
}
58+
59+
.body input[type='text']:focus,
60+
.body textarea:focus {
61+
outline: none;
62+
border-color: #007bff;
63+
}
64+
65+
.body textarea {
66+
resize: vertical;
67+
}
68+
69+
.fileUpload {
70+
display: flex;
71+
align-items: center;
72+
gap: 10px;
73+
cursor: pointer;
74+
font-weight: normal;
75+
margin-top: 0;
76+
}
77+
78+
.fileUpload input[type='file'] {
79+
display: none;
80+
}
81+
82+
.fileBtn {
83+
display: inline-block;
84+
padding: 8px 16px;
85+
background: #007bff;
86+
color: #fff;
87+
font-size: 0.85rem;
88+
font-family: inherit;
89+
cursor: pointer;
90+
transition: 220ms all ease-in-out;
91+
white-space: nowrap;
92+
border-radius: 4px;
93+
}
94+
95+
.fileBtn:hover {
96+
background: #0056b3;
97+
}
98+
99+
.fileName {
100+
font-size: 0.85rem;
101+
color: #666;
102+
overflow: hidden;
103+
text-overflow: ellipsis;
104+
white-space: nowrap;
105+
}
106+
107+
.footer {
108+
background: rgb(225, 225, 225);
109+
padding: 16px 24px;
110+
display: flex;
111+
justify-content: flex-end;
112+
gap: 8px;
113+
}
114+
115+
.btn {
116+
padding: 10px 24px;
117+
font-size: 0.9rem;
118+
font-family: inherit;
119+
cursor: pointer;
120+
border: none;
121+
border-radius: 4px;
122+
transition: 220ms all ease-in-out;
123+
width: auto;
124+
display: inline-flex;
125+
align-items: center;
126+
justify-content: center;
127+
}
128+
129+
.cancel {
130+
background: #fff;
131+
color: #333;
132+
border: 2px solid rgb(225, 225, 225);
133+
}
134+
135+
.cancel:hover {
136+
background: rgb(245, 245, 245);
137+
border-color: #ccc;
138+
}
139+
140+
.submit {
141+
background: #007bff;
142+
color: #fff;
143+
}
144+
145+
.submit:hover {
146+
background: #0056b3;
147+
}
148+
149+
.submit:disabled {
150+
background: #99c9ff;
151+
cursor: not-allowed;
152+
}

0 commit comments

Comments
 (0)