doc: add sentry stuff section to md docs
This commit is contained in:
		@ -98,8 +98,10 @@ This guide details the comprehensive changes implemented to prepare the SWA Prac
 | 
			
		||||
  - Modified the `evaluateWithGemini` function to accept the `ai` instance as a parameter, ensuring it uses the dynamically created `GoogleGenAI` instance.
 | 
			
		||||
  - Added a check within `handleSubmit` in `ExerciseSheet` to verify the `ai` instance and its `models.generateContent` method before making API calls, providing a user-friendly error message if the API key is missing or invalid.
 | 
			
		||||
- **Template Literal Escaping Fixes**:
 | 
			
		||||
  - Crucially, fixed syntax errors within the `systemInstruction` and `contents` template literals in `evaluateWithGemini`. The original code had issues with unescaped backticks (``` ` ```) and newlines (`\n`) when they were part of the string content itself, leading to build failures.
 | 
			
		||||
  - **Specific Fix**: All literal backticks within the template strings (e.g., for code blocks like ``` ```c ```) were escaped as``` \`\`\` ```. All literal newlines (`\n`) within the template strings were escaped as `\\n`. This ensures they are interpreted as literal characters within the string rather than JavaScript syntax.
 | 
			
		||||
  - Crucially, fixed syntax errors within the `systemInstruction` and `contents` template literals in `evaluateWithGemini`. The original code had issues with unescaped backticks (``` ` ```) and newlines (`
 | 
			
		||||
`) when they were part of the string content itself, leading to build failures.
 | 
			
		||||
  - **Specific Fix**: All literal backticks within the template strings (e.g., for code blocks like ``` ```c ```) were escaped as``` \`\`\` ```. All literal newlines (`
 | 
			
		||||
`) within the template strings were escaped as `\\n`. This ensures they are interpreted as literal characters within the string rather than JavaScript syntax.
 | 
			
		||||
 | 
			
		||||
**Step-by-step Actions**:
 | 
			
		||||
 | 
			
		||||
@ -525,3 +527,215 @@ CMD [ "serve", "-s", "dist", "-l", "8000" ]
 | 
			
		||||
This enhancement makes the application more suitable for software architecture exercises that require diagram creation and visualization.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### 6. Optional: Implementing Sentry User Feedback
 | 
			
		||||
 | 
			
		||||
**Objective**: To provide a simple and effective way for users to provide feedback, report issues, or suggest new ideas directly from within the application.
 | 
			
		||||
 | 
			
		||||
**Previous State**: The application had no built-in feedback mechanism.
 | 
			
		||||
 | 
			
		||||
**Changes Made**:
 | 
			
		||||
 | 
			
		||||
- **Sentry SDK Update**: Replaced `@sentry/browser` with `@sentry/react` to leverage React-specific features.
 | 
			
		||||
- **Sentry Initialization**: Configured the Sentry SDK in `index.tsx` to include the `feedbackIntegration`, enabling the user feedback features.
 | 
			
		||||
- **Custom Feedback Dialog**:
 | 
			
		||||
  - Instead of using the default Sentry feedback widget, a custom feedback dialog was implemented in `App.tsx` for a more integrated user experience.
 | 
			
		||||
  - A "Feedback" button with a `ChatBubbleLeftRightIcon` was added to the top-right corner of the application.
 | 
			
		||||
  - Clicking the button opens a `Dialog` (modal) from `@headlessui/react`.
 | 
			
		||||
  - The modal contains a textarea for the user to enter their feedback, with German placeholder text.
 | 
			
		||||
  - When the user submits the feedback, `Sentry.captureFeedback` is called to send the feedback to Sentry.
 | 
			
		||||
 | 
			
		||||
**Step-by-step Actions**:
 | 
			
		||||
 | 
			
		||||
1.  Remove `@sentry/browser` and add `@sentry/react` to `package.json`.
 | 
			
		||||
2.  Update `index.tsx` to import from `@sentry/react` and add the `feedbackIntegration` to the `Sentry.init` call.
 | 
			
		||||
3.  In `App.tsx`, import `ChatBubbleLeftRightIcon` from `@heroicons/react/24/outline` and `* as Sentry from "@sentry/react"`.
 | 
			
		||||
4.  Add state variables for the feedback dialog visibility and the feedback message.
 | 
			
		||||
5.  Add a "Feedback" button next to the "Settings" button.
 | 
			
		||||
6.  Implement the custom feedback `Dialog` component with a textarea and submit/cancel buttons.
 | 
			
		||||
7.  Create a handler function that calls `Sentry.captureFeedback` with the user's message.
 | 
			
		||||
8.  Write the modified content back to the respective files.
 | 
			
		||||
 | 
			
		||||
**Code Changes (package.json)**:
 | 
			
		||||
 | 
			
		||||
```diff
 | 
			
		||||
--- a/package.json
 | 
			
		||||
+++ b/package.json
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
   "dependencies": {
 | 
			
		||||
     "@google/genai": "latest",
 | 
			
		||||
     "@headlessui/react": "^2.2.4",
 | 
			
		||||
-    "@heroicons/react": "^2.2.0",
 | 
			
		||||
-    "@sentry/browser": "^9.36.0",
 | 
			
		||||
+    "@heroicons/react": "^2.2.0",
 | 
			
		||||
+    "@sentry/react": "9.36.0",
 | 
			
		||||
     "diff": "5.2.0",
 | 
			
		||||
     "mermaid": "^11.8.1",
 | 
			
		||||
     "prismjs": "1.29.0",
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Code Changes (index.tsx)**:
 | 
			
		||||
 | 
			
		||||
```diff
 | 
			
		||||
--- a/index.tsx
 | 
			
		||||
+++ b/index.tsx
 | 
			
		||||
@@ -1,8 +1,15 @@
 | 
			
		||||
 import React from 'react';
 | 
			
		||||
 import ReactDOM from 'react-dom/client';
 | 
			
		||||
 import App from './App';
 | 
			
		||||
-import * as Sentry from "@sentry/browser";
 | 
			
		||||
+import * as Sentry from "@sentry/react";
 | 
			
		||||
 
 | 
			
		||||
-Sentry.init({ dsn: "https://2851a11b9f1b4715b389979628da322f@glitchtip.yandrik.dev/3" });
 | 
			
		||||
+Sentry.init({
 | 
			
		||||
+  dsn: "https://2851a11b9f1b4715b389979628da322f@glitchtip.yandrik.dev/3",
 | 
			
		||||
+  integrations: [
 | 
			
		||||
+    Sentry.feedbackIntegration({
 | 
			
		||||
+      colorScheme: "system",
 | 
			
		||||
+    }),
 | 
			
		||||
+  ],
 | 
			
		||||
+});
 | 
			
		||||
 
 | 
			
		||||
 const rootElement = document.getElementById('root');
 | 
			
		||||
 if (!rootElement) {
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Code Changes (App.tsx)**:
 | 
			
		||||
 | 
			
		||||
```diff
 | 
			
		||||
--- a/App.tsx
 | 
			
		||||
+++ b/App.tsx
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
 import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
 | 
			
		||||
 import { Dialog } from "@headlessui/react";
 | 
			
		||||
-import { Cog6ToothIcon } from "@heroicons/react/24/outline";
 | 
			
		||||
+import { Cog6ToothIcon, ChatBubbleLeftRightIcon } from "@heroicons/react/24/outline";
 | 
			
		||||
 import { Exercise, CheckResult, ExercisePartType } from './types';
 | 
			
		||||
+import * as Sentry from "@sentry/react";
 | 
			
		||||
 import { swaExercises1 } from './data/swa_exercises.1';
 | 
			
		||||
 import { swaExercises2 } from './data/swa_exercises.2';
 | 
			
		||||
 import { swaExercises3 } from './data/swa_exercises.3';
 | 
			
		||||
@@ -564,6 +565,18 @@
 | 
			
		||||
     const [exercises, setExercises] = useState<Exercise[]>([]);
 | 
			
		||||
     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 ---
 | 
			
		||||
     const [apiKey, setApiKey] = useState<string>('');
 | 
			
		||||
     const [showSettings, setShowSettings] = useState(false);
 | 
			
		||||
@@ -604,12 +617,60 @@
 | 
			
		||||
 
 | 
			
		||||
     return (
 | 
			
		||||
         <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 */}
 | 
			
		||||
             <button
 | 
			
		||||
                 className="fixed top-4 right-4 z-50 bg-white rounded-full p-2 shadow hover:bg-slate-100 transition"
 | 
			
		||||
                 aria-label="API Key Settings"
 | 
			
		||||
                 onClick={() => setShowSettings(true)}
 | 
			
		||||
             >
 | 
			
		||||
                 <Cog6ToothIcon className="w-7 h-7 text-slate-600" />
 | 
			
		||||
             </button>
 | 
			
		||||
             {/* Settings Modal */}
 | 
			
		||||
             <Dialog open={showSettings} onClose={() => setShowSettings(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">
 | 
			
		||||
+                            <Cog6ToothIcon className="w-6 h-6 text-blue-600" />
 | 
			
		||||
+                            Gemini API Key
 | 
			
		||||
+                        </Dialog.Title>
 | 
			
		||||
+                        <div className="mb-4">
 | 
			
		||||
+                            <input
 | 
			
		||||
+                                type="text"
 | 
			
		||||
+                                className="w-full border rounded px-3 py-2 text-slate-800"
 | 
			
		||||
+                                value={inputKey}
 | 
			
		||||
+                                onChange={e => setInputKey(e.target.value)}
 | 
			
		||||
+                                placeholder="Enter Gemini API Key"
 | 
			
		||||
+                            />
 | 
			
		||||
+                        </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={() => setShowSettings(false)}
 | 
			
		||||
+                            >
 | 
			
		||||
+                                Cancel
 | 
			
		||||
+                            </button>
 | 
			
		||||
+                            <button
 | 
			
		||||
+                                className="px-4 py-2 rounded bg-blue-600 text-white font-bold hover:bg-blue-700"
 | 
			
		||||
+                                onClick={handleSaveKey}
 | 
			
		||||
+                                disabled={!inputKey.trim()}
 | 
			
		||||
+                            >
 | 
			
		||||
+                                Save
 | 
			
		||||
+                            </button>
 | 
			
		||||
+                        </div>
 | 
			
		||||
+                        <div className="mt-4 text-xs text-slate-500">
 | 
			
		||||
+                            Your API key is stored in your browser only.
 | 
			
		||||
+                        </div>
 | 
			
		||||
+                        <div className="mt-2 text-xs text-slate-500">
 | 
			
		||||
+                            Get an API key at <a href="https://aistudio.google.com/" target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">https://aistudio.google.com/</a> - this app uses gemini-2.5-flash, which is free for 500 requests a day (as of 9.7.2025)
 | 
			
		||||
+                        </div>
 | 
			
		||||
+                    </Dialog.Panel>
 | 
			
		||||
+                </div>
 | 
			
		||||
+            </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, Aufgabenideen, oder was auch immer
 | 
			
		||||
+                        </Dialog.Title>
 | 
			
		||||
+                        <div className="mb-4">
 | 
			
		||||
+                            <textarea
 | 
			
		||||
+                                className="w-full border rounded px-3 py-2 text-slate-800"
 | 
			
		||||
+                                value={feedbackMessage}
 | 
			
		||||
+                                onChange={e => setFeedbackMessage(e.target.value)}
 | 
			
		||||
+                                placeholder="Was kann ich besser machen?"
 | 
			
		||||
+                                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">
 | 
			
		||||
                 <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>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
		Reference in New Issue
	
	Block a user