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' ;
33import logo from './bugsplat-logo.png' ;
44import styles from './App.module.css' ;
55import Fallback from '../Fallback' ;
6+ import FeedbackDialog , { type FeedbackData } from '../FeedbackDialog' ;
67
78const BUGSPLAT_URL = 'https://www.bugsplat.com/' ;
89const 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
1113function 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+
3138function 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}
0 commit comments