feat: sentry bug reports and integration (glitchtip)

This commit is contained in:
2025-07-09 15:45:35 +02:00
parent cb069a45be
commit 99cd4098da
4 changed files with 152 additions and 2 deletions

60
App.tsx
View File

@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { Dialog } from "@headlessui/react"; import { Dialog } from "@headlessui/react";
import { Cog6ToothIcon } from "@heroicons/react/24/outline"; import { Cog6ToothIcon, ChatBubbleLeftRightIcon } from "@heroicons/react/24/outline";
import * as Sentry from "@sentry/react";
import { Exercise, CheckResult, ExercisePartType } from './types'; import { Exercise, CheckResult, ExercisePartType } from './types';
import { swaExercises1 } from './data/swa_exercises.1'; import { swaExercises1 } from './data/swa_exercises.1';
import { swaExercises2 } from './data/swa_exercises.2'; import { swaExercises2 } from './data/swa_exercises.2';
@ -539,6 +540,16 @@ const allSwaExercises: Exercise[] = [
export default function App() { export default function App() {
const [exercises, setExercises] = useState<Exercise[]>([]); const [exercises, setExercises] = useState<Exercise[]>([]);
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const [showFeedback, setShowFeedback] = useState(false);
const [feedbackMessage, setFeedbackMessage] = useState('');
const handleFeedbackSubmit = () => {
Sentry.captureFeedback({
message: feedbackMessage,
});
setFeedbackMessage('');
setShowFeedback(false);
};
// --- API KEY STATE --- // --- API KEY STATE ---
const [apiKey, setApiKey] = useState<string>(''); const [apiKey, setApiKey] = useState<string>('');
@ -592,6 +603,14 @@ export default function App() {
return ( return (
<div className="min-h-screen flex flex-col p-4 md:p-8"> <div className="min-h-screen flex flex-col p-4 md:p-8">
{/* Feedback Button */}
<button
className="fixed top-4 right-16 z-50 bg-white rounded-full p-2 shadow hover:bg-slate-100 transition"
aria-label="Feedback"
onClick={() => setShowFeedback(true)}
>
<ChatBubbleLeftRightIcon className="w-7 h-7 text-slate-600" />
</button>
{/* Settings Button */} {/* Settings Button */}
<button <button
className="fixed top-4 right-4 z-50 bg-white rounded-full p-2 shadow hover:bg-slate-100 transition" className="fixed top-4 right-4 z-50 bg-white rounded-full p-2 shadow hover:bg-slate-100 transition"
@ -642,6 +661,45 @@ export default function App() {
</Dialog.Panel> </Dialog.Panel>
</div> </div>
</Dialog> </Dialog>
{/* Feedback Modal */}
<Dialog open={showFeedback} onClose={() => setShowFeedback(false)} className="relative z-50">
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel className="bg-white rounded-lg shadow-xl p-8 max-w-md w-full">
<Dialog.Title className="font-bold text-lg mb-4 flex items-center gap-2">
<ChatBubbleLeftRightIcon className="w-6 h-6 text-blue-600" />
Feedback?
</Dialog.Title>
<div className="mb-4">
<p>
Hi :)<br/>Hast du Feedback, Aufgabenideen, oder gab es einen Bug? Sag mir gern bescheid, und ich schau, was ich tun kann - yandrik
</p>
<textarea
className="w-full border rounded px-3 py-2 text-slate-800"
value={feedbackMessage}
onChange={e => setFeedbackMessage(e.target.value)}
placeholder="Ich finde, dass..."
rows={5}
/>
</div>
<div className="flex justify-end gap-2">
<button
className="px-4 py-2 rounded bg-slate-200 text-slate-700 hover:bg-slate-300"
onClick={() => setShowFeedback(false)}
>
Abbrechen
</button>
<button
className="px-4 py-2 rounded bg-blue-600 text-white font-bold hover:bg-blue-700"
onClick={handleFeedbackSubmit}
disabled={!feedbackMessage.trim()}
>
Senden
</button>
</div>
</Dialog.Panel>
</div>
</Dialog>
<header className="mb-6 flex-shrink-0"> <header className="mb-6 flex-shrink-0">
<h1 className="text-3xl font-serif font-bold text-slate-800">SWA Trainer</h1> <h1 className="text-3xl font-serif font-bold text-slate-800">SWA Trainer</h1>
<p className="text-slate-500">Interaktive Übungen zur Vorbereitung auf die SWA-Klausur</p> <p className="text-slate-500">Interaktive Übungen zur Vorbereitung auf die SWA-Klausur</p>

View File

@ -2,6 +2,17 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from './App'; import App from './App';
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: "https://2851a11b9f1b4715b389979628da322f@glitchtip.yandrik.dev/3",
integrations: [
Sentry.feedbackIntegration({
// Additional SDK configuration goes in here, for example:
colorScheme: "system",
}),
],
});
const rootElement = document.getElementById('root'); const rootElement = document.getElementById('root');
if (!rootElement) { if (!rootElement) {

View File

@ -12,6 +12,7 @@
"@google/genai": "latest", "@google/genai": "latest",
"@headlessui/react": "^2.2.4", "@headlessui/react": "^2.2.4",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"@sentry/react": "^9.36.0",
"diff": "5.2.0", "diff": "5.2.0",
"mermaid": "^11.8.1", "mermaid": "^11.8.1",
"prismjs": "1.29.0", "prismjs": "1.29.0",

80
pnpm-lock.yaml generated
View File

@ -17,6 +17,9 @@ importers:
'@heroicons/react': '@heroicons/react':
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0(react@19.1.0) version: 2.2.0(react@19.1.0)
'@sentry/react':
specifier: ^9.36.0
version: 9.36.0(react@19.1.0)
diff: diff:
specifier: 5.2.0 specifier: 5.2.0
version: 5.2.0 version: 5.2.0
@ -434,6 +437,36 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@sentry-internal/browser-utils@9.36.0':
resolution: {integrity: sha512-4cGT7c4kr4DMRlcnErgWiL/uLwHvD8A52w6ffJkT/Bj7olSUjFCo8RKIEsDiJqk8No3yfoS6dRbudMZUIFuDTA==}
engines: {node: '>=18'}
'@sentry-internal/feedback@9.36.0':
resolution: {integrity: sha512-0FNKlP2/CWbitAqtLKVPuSqMs8h7jMJm812wSA0gPpCBVMoX+mWEKQh3pYHYekHVfI0muieX9CWYlup0McXaJA==}
engines: {node: '>=18'}
'@sentry-internal/replay-canvas@9.36.0':
resolution: {integrity: sha512-/RrYKvQ80gzjEJoHWMFXqYPlQS8ovEUWG2KeSJQdFtvL+/M/864jfxTA21hgHJC/5GbnOq4V541C1P5YVXwg6w==}
engines: {node: '>=18'}
'@sentry-internal/replay@9.36.0':
resolution: {integrity: sha512-BKxDz4r7bC23d9+zx3a0qkjWLa4zgE/8tXSfcSVYwhczEzTNXYOPxIodYm2IzgVgN9NwUE1m9Cf+/xlKKQLyFQ==}
engines: {node: '>=18'}
'@sentry/browser@9.36.0':
resolution: {integrity: sha512-DCn6VoJCWMjZm5sOfD2+2EgEVdr+H/8Hqs080ruWZo1MZzIUmF1+qnG7AtYeIlm93OydU0PLjvYeWo0zttgGvg==}
engines: {node: '>=18'}
'@sentry/core@9.36.0':
resolution: {integrity: sha512-LU6EmsXPxi8QFkrx0fCqhXicsJA6uUWCD0VrxePZzs+Xs0SgVNDxOgRELVrZa4LPomQJBR5wmm3Duozp9JkHcQ==}
engines: {node: '>=18'}
'@sentry/react@9.36.0':
resolution: {integrity: sha512-SCl3jxZ5fEw+r+7Eo95NgI4cNs7r8Z2ppv0/iyK6qQE/CCTJfBDgnJf6Af/NupO2V/tkfdEJcsb+QNLAu91qvA==}
engines: {node: '>=18'}
peerDependencies:
react: ^16.14.0 || 17.x || 18.x || 19.x
'@swc/helpers@0.5.17': '@swc/helpers@0.5.17':
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
@ -919,6 +952,9 @@ packages:
hast-util-whitespace@3.0.0: hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
html-url-attributes@3.0.1: html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
@ -1209,6 +1245,9 @@ packages:
peerDependencies: peerDependencies:
react: ^19.1.0 react: ^19.1.0
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-markdown@9.1.0: react-markdown@9.1.0:
resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==} resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==}
peerDependencies: peerDependencies:
@ -1727,6 +1766,41 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.44.2': '@rollup/rollup-win32-x64-msvc@4.44.2':
optional: true optional: true
'@sentry-internal/browser-utils@9.36.0':
dependencies:
'@sentry/core': 9.36.0
'@sentry-internal/feedback@9.36.0':
dependencies:
'@sentry/core': 9.36.0
'@sentry-internal/replay-canvas@9.36.0':
dependencies:
'@sentry-internal/replay': 9.36.0
'@sentry/core': 9.36.0
'@sentry-internal/replay@9.36.0':
dependencies:
'@sentry-internal/browser-utils': 9.36.0
'@sentry/core': 9.36.0
'@sentry/browser@9.36.0':
dependencies:
'@sentry-internal/browser-utils': 9.36.0
'@sentry-internal/feedback': 9.36.0
'@sentry-internal/replay': 9.36.0
'@sentry-internal/replay-canvas': 9.36.0
'@sentry/core': 9.36.0
'@sentry/core@9.36.0': {}
'@sentry/react@9.36.0(react@19.1.0)':
dependencies:
'@sentry/browser': 9.36.0
'@sentry/core': 9.36.0
hoist-non-react-statics: 3.3.2
react: 19.1.0
'@swc/helpers@0.5.17': '@swc/helpers@0.5.17':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@ -2291,6 +2365,10 @@ snapshots:
dependencies: dependencies:
'@types/hast': 3.0.4 '@types/hast': 3.0.4
hoist-non-react-statics@3.3.2:
dependencies:
react-is: 16.13.1
html-url-attributes@3.0.1: {} html-url-attributes@3.0.1: {}
https-proxy-agent@7.0.6: https-proxy-agent@7.0.6:
@ -2814,6 +2892,8 @@ snapshots:
react: 19.1.0 react: 19.1.0
scheduler: 0.26.0 scheduler: 0.26.0
react-is@16.13.1: {}
react-markdown@9.1.0(@types/react@19.1.8)(react@19.1.0): react-markdown@9.1.0(@types/react@19.1.8)(react@19.1.0):
dependencies: dependencies:
'@types/hast': 3.0.4 '@types/hast': 3.0.4