Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions examples/my-react-crasher/package.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
{
"dependencies": {
"@bugsplat/react": "^1.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"@bugsplat/react": "file:../../",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@bugsplat/symbol-upload": "^4.1.1",
"@testing-library/react": "^13.3.0",
"@types/jest": "^28.1.8",
"@types/node": "^18.7.14",
"@types/react": "^18.0.18",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.0.1",
"@bugsplat/symbol-upload": "^10.2.4",
"@testing-library/react": "^16.0.0",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^6.0.0",
"env-cmd": "^10.1.0",
"prettier": "^2.7.1",
"typescript": "^4.8.2",
"vite": "^3.0.9",
"vitest": "^0.22.1"
"prettier": "^3.0.0",
"typescript": "^5.9.0",
"vite": "^8.0.0",
"vitest": "^4.0.0"
},
"name": "my-react-crasher",
"private": true,
Expand Down
22 changes: 22 additions & 0 deletions examples/my-react-crasher/src/App/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,25 @@ h2 {
button.error > h4 {
margin-top: 0;
}

.feedback {
padding: 0 24px 24px;
border-top: 2px solid rgb(225, 225, 225);
}

.feedbackBtn {
padding: 10px 24px;
background: #007bff;
color: #fff;
font-size: 0.9rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: 220ms all ease-in-out;
width: auto;
display: inline-flex;
}

.feedbackBtn:hover {
background: #0056b3;
}
75 changes: 72 additions & 3 deletions examples/my-react-crasher/src/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ErrorBoundary, useErrorHandler } from '@bugsplat/react';
import { type ReactNode, useState } from 'react';
import { ErrorBoundary, getBugSplat, useErrorHandler, useFeedback, type BugSplatResponse } from '@bugsplat/react';
import { type ReactNode, useEffect, useState } from 'react';
import logo from './bugsplat-logo.png';
import styles from './App.module.css';
import Fallback from '../Fallback';
import FeedbackDialog, { type FeedbackData } from '../FeedbackDialog';

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

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

interface SubmissionLink {
href: string;
text: string;
}

function App() {
const [error, setError] = useState<Error | null>(null);
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
const [submissionLink, setSubmissionLink] = useState<SubmissionLink | null>(null);
const { postFeedback, response: feedbackResponse } = useFeedback();

const database = getBugSplat()?.database;

function handleSubmissionResponse(response: BugSplatResponse) {
if (!database || response.error) return;
const crashId = response.response.crash_id;
setSubmissionLink({
href: `${BASE_CRASH_URL}?database=${database}&id=${crashId}`,
text: `View submission ${crashId} in database ${database}`,
});
}

useEffect(() => {
if (feedbackResponse) {
handleSubmissionResponse(feedbackResponse);
}
}, [feedbackResponse]);

async function handleFeedbackSubmit(data: FeedbackData) {
setShowFeedbackDialog(false);
const attachments = data.attachments.map((file) => ({
filename: file.name,
data: file,
}));
await postFeedback(data.title, {
description: data.description,
attachments,
});
}

return (
<div className={styles.root}>
Expand All @@ -45,12 +84,29 @@ function App() {
JavaScript or TypeScript.
</p>
<ErrorBoundary
fallback={(props) => <Fallback {...props} />}
fallback={() => (
<Fallback
submissionLink={submissionLink}
loading={!submissionLink}
onReset={() => { setError(null); setSubmissionLink(null); }}
/>
)}
onError={(_error, _componentStack, response) => {
if (response) {
handleSubmissionResponse(response);
}
}}
onReset={() => setError(null)}
resetKeys={[error]}
>
<ThrowOnError error={error} />
</ErrorBoundary>
{submissionLink && !error && (
<Fallback
submissionLink={submissionLink}
onReset={() => setSubmissionLink(null)}
/>
)}
<div className={styles.errors}>
<h2>Errors</h2>
{ERRORS.map((error) => (
Expand All @@ -64,7 +120,20 @@ function App() {
</button>
))}
</div>
<div className={styles.feedback}>
<h2>Feedback</h2>
<p>Let your users send arbitrary feedback and file attachments directly to your BugSplat dashboard.</p>
<button className={styles.feedbackBtn} onClick={() => setShowFeedbackDialog(true)}>
Send Feedback
</button>
</div>
</div>
{showFeedbackDialog && (
<FeedbackDialog
onClose={() => setShowFeedbackDialog(false)}
onSubmit={handleFeedbackSubmit}
/>
)}
</div>
);
}
Expand Down
32 changes: 13 additions & 19 deletions examples/my-react-crasher/src/Fallback/Fallback.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import { FallbackProps, getBugSplat } from '@bugsplat/react';
import styles from './Fallback.module.css';

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

export default function Fallback({
resetErrorBoundary,
response,
submissionLink,
loading,
onReset,
}: FallbackProps) {
const bugSplat = getBugSplat();
const database = bugSplat?.database;

const crashId =
response?.error === null ? response.response.crash_id : undefined;

const link = crashId && database && (
<a
href={`${BASE_CRASH_URL}?database=${database}&id=${crashId}`}
target="_blank"
rel="noreferrer"
>
Crash {crashId} in database {database}
const link = submissionLink && (
<a href={submissionLink.href} target="_blank" rel="noreferrer">
{submissionLink.text}
</a>
);

return (
<div className={styles.root}>
<p>{response ? link : 'loading...'}</p>
<button className={styles.reset} onClick={resetErrorBoundary}>
<p>{loading ? 'loading...' : link}</p>
<button className={styles.reset} onClick={onReset}>
<h2>Reset</h2>
</button>
</div>
Expand Down
152 changes: 152 additions & 0 deletions examples/my-react-crasher/src/FeedbackDialog/FeedbackDialog.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}

.dialog {
background: #fff;
width: 440px;
max-width: 90vw;
box-shadow: 0.3em 0.3em 1em rgba(0, 0, 0, 0.3);
border-radius: 8px;
overflow: hidden;
font-family: sans-serif;
}

.header {
padding: 20px 24px 12px;
text-align: center;
}

.header h2 {
margin: 0;
color: #007bff;
}

.body {
padding: 0 24px 16px;
display: flex;
flex-direction: column;
gap: 6px;
}

.body label {
font-weight: 600;
font-size: 0.85rem;
text-align: left;
margin-top: 4px;
}

.body input[type='text'],
.body textarea {
box-sizing: border-box;
width: 100%;
padding: 10px;
border: 2px solid rgb(225, 225, 225);
border-radius: 0;
font-family: inherit;
font-size: 0.9rem;
transition: 220ms all ease-in-out;
-webkit-appearance: none;
appearance: none;
}

.body input[type='text']:focus,
.body textarea:focus {
outline: none;
border-color: #007bff;
}

.body textarea {
resize: vertical;
}

.fileUpload {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-weight: normal;
margin-top: 0;
}

.fileUpload input[type='file'] {
display: none;
}

.fileBtn {
display: inline-block;
padding: 8px 16px;
background: #007bff;
color: #fff;
font-size: 0.85rem;
font-family: inherit;
cursor: pointer;
transition: 220ms all ease-in-out;
white-space: nowrap;
border-radius: 4px;
}

.fileBtn:hover {
background: #0056b3;
}

.fileName {
font-size: 0.85rem;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.footer {
background: rgb(225, 225, 225);
padding: 16px 24px;
display: flex;
justify-content: flex-end;
gap: 8px;
}

.btn {
padding: 10px 24px;
font-size: 0.9rem;
font-family: inherit;
cursor: pointer;
border: none;
border-radius: 4px;
transition: 220ms all ease-in-out;
width: auto;
display: inline-flex;
align-items: center;
justify-content: center;
}

.cancel {
background: #fff;
color: #333;
border: 2px solid rgb(225, 225, 225);
}

.cancel:hover {
background: rgb(245, 245, 245);
border-color: #ccc;
}

.submit {
background: #007bff;
color: #fff;
}

.submit:hover {
background: #0056b3;
}

.submit:disabled {
background: #99c9ff;
cursor: not-allowed;
}
Loading
Loading