react-visual-feedback 1.3.7 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +461 -278
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +2 -21
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2 -21
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
# React Visual Feedback
|
|
2
2
|
|
|
3
|
-
A powerful, visual feedback collection tool for React applications with an integrated dashboard for managing user feedback.
|
|
3
|
+
A powerful, visual feedback collection tool for React applications with screen recording, session replay, and an integrated dashboard for managing user feedback.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
### Feedback Collection
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- ⏰ Smart time formatting (e.g., "2 hours ago", "3 days ago")
|
|
8
|
+
- **Visual Element Selection** - Click any element with hover highlighting
|
|
9
|
+
- **Screenshot Capture** - Automatic pixel-perfect screenshot with CSS rendering
|
|
10
|
+
- **Screen Recording** - Record screen with audio and capture console/network logs
|
|
11
|
+
- **Canvas Drawing** - Annotate screenshots with drawings and highlights
|
|
12
|
+
- **React Component Detection** - Automatically detects React component names and source files
|
|
13
|
+
- **Keyboard Shortcuts** - `Alt+Q` to activate, `Esc` to cancel
|
|
14
|
+
|
|
15
|
+
### Session Replay
|
|
16
|
+
- **Video Playback** - Watch recorded user sessions
|
|
17
|
+
- **Console Logs** - See console.log, errors, warnings synced with video
|
|
18
|
+
- **Network Requests** - Track API calls and responses
|
|
19
|
+
- **Expandable Logs Panel** - Slide-out panel on the right side (customizable)
|
|
20
|
+
|
|
21
|
+
### Dashboard
|
|
22
|
+
- **Professional UI** - Clean 700px slide-out panel
|
|
23
|
+
- **Developer Mode** - Full technical details (source file, component stack, viewport)
|
|
24
|
+
- **User Mode** - Simplified view for end users
|
|
25
|
+
- **8 Status Options** - New, Open, In Progress, Under Review, On Hold, Resolved, Closed, Won't Fix
|
|
26
|
+
- **Status Callbacks** - Sync with your database on status changes
|
|
27
|
+
|
|
28
|
+
### Theming
|
|
29
|
+
- **Light/Dark Mode** - Full theme support
|
|
30
|
+
- **styled-components** - No external CSS required
|
|
32
31
|
|
|
33
32
|
## Installation
|
|
34
33
|
|
|
@@ -36,25 +35,22 @@ A powerful, visual feedback collection tool for React applications with an integ
|
|
|
36
35
|
npm install react-visual-feedback
|
|
37
36
|
```
|
|
38
37
|
|
|
39
|
-
**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
import 'react-visual-feedback/dist/index.css';
|
|
38
|
+
**Peer Dependencies:**
|
|
39
|
+
```bash
|
|
40
|
+
npm install react react-dom styled-components
|
|
43
41
|
```
|
|
44
42
|
|
|
45
43
|
## Quick Start
|
|
46
44
|
|
|
47
|
-
### Basic Usage
|
|
45
|
+
### Basic Usage
|
|
48
46
|
|
|
49
47
|
```jsx
|
|
50
48
|
import React from 'react';
|
|
51
49
|
import { FeedbackProvider } from 'react-visual-feedback';
|
|
52
|
-
import 'react-visual-feedback/dist/index.css';
|
|
53
50
|
|
|
54
51
|
function App() {
|
|
55
52
|
const handleFeedbackSubmit = async (feedbackData) => {
|
|
56
53
|
console.log('Feedback received:', feedbackData);
|
|
57
|
-
// Send to your backend
|
|
58
54
|
await fetch('/api/feedback', {
|
|
59
55
|
method: 'POST',
|
|
60
56
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -68,27 +64,23 @@ function App() {
|
|
|
68
64
|
</FeedbackProvider>
|
|
69
65
|
);
|
|
70
66
|
}
|
|
71
|
-
|
|
72
|
-
export default App;
|
|
73
67
|
```
|
|
74
68
|
|
|
75
|
-
### With Dashboard
|
|
69
|
+
### With Dashboard & Screen Recording
|
|
76
70
|
|
|
77
71
|
```jsx
|
|
78
72
|
import React from 'react';
|
|
79
73
|
import { FeedbackProvider, useFeedback } from 'react-visual-feedback';
|
|
80
|
-
import 'react-visual-feedback/dist/index.css';
|
|
81
74
|
|
|
82
75
|
function FeedbackButtons() {
|
|
83
|
-
const { isActive, setIsActive, setIsDashboardOpen } = useFeedback();
|
|
76
|
+
const { isActive, setIsActive, setIsDashboardOpen, startRecording } = useFeedback();
|
|
84
77
|
|
|
85
78
|
return (
|
|
86
79
|
<div style={{ position: 'fixed', bottom: 20, right: 20, display: 'flex', gap: 10 }}>
|
|
87
|
-
<button onClick={() => setIsDashboardOpen(true)}>
|
|
88
|
-
|
|
89
|
-
</button>
|
|
80
|
+
<button onClick={() => setIsDashboardOpen(true)}>Dashboard</button>
|
|
81
|
+
<button onClick={startRecording}>Record Screen</button>
|
|
90
82
|
<button onClick={() => setIsActive(!isActive)}>
|
|
91
|
-
{isActive ? '
|
|
83
|
+
{isActive ? 'Cancel' : 'Report Issue'}
|
|
92
84
|
</button>
|
|
93
85
|
</div>
|
|
94
86
|
);
|
|
@@ -96,6 +88,7 @@ function FeedbackButtons() {
|
|
|
96
88
|
|
|
97
89
|
function App() {
|
|
98
90
|
const handleFeedbackSubmit = async (feedbackData) => {
|
|
91
|
+
// feedbackData contains: feedback, screenshot, video, eventLogs, elementInfo, etc.
|
|
99
92
|
await fetch('/api/feedback', {
|
|
100
93
|
method: 'POST',
|
|
101
94
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -104,7 +97,6 @@ function App() {
|
|
|
104
97
|
};
|
|
105
98
|
|
|
106
99
|
const handleStatusChange = async ({ id, status, comment }) => {
|
|
107
|
-
// Update your database with status and developer comment
|
|
108
100
|
await fetch(`/api/feedback/${id}/status`, {
|
|
109
101
|
method: 'PATCH',
|
|
110
102
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -117,286 +109,477 @@ function App() {
|
|
|
117
109
|
onSubmit={handleFeedbackSubmit}
|
|
118
110
|
onStatusChange={handleStatusChange}
|
|
119
111
|
dashboard={true}
|
|
120
|
-
isDeveloper={true}
|
|
112
|
+
isDeveloper={true}
|
|
121
113
|
userName="John Doe"
|
|
122
114
|
userEmail="john@example.com"
|
|
115
|
+
mode="light"
|
|
123
116
|
>
|
|
124
117
|
<YourApp />
|
|
125
118
|
<FeedbackButtons />
|
|
126
119
|
</FeedbackProvider>
|
|
127
120
|
);
|
|
128
121
|
}
|
|
129
|
-
|
|
130
|
-
export default App;
|
|
131
122
|
```
|
|
132
123
|
|
|
133
|
-
### With Update Notifications
|
|
134
|
-
|
|
135
|
-
```jsx
|
|
136
|
-
import React, { useState } from 'react';
|
|
137
|
-
import {
|
|
138
|
-
FeedbackProvider,
|
|
139
|
-
FeedbackUpdatesNotification
|
|
140
|
-
} from 'react-visual-feedback';
|
|
141
|
-
import 'react-visual-feedback/dist/index.css';
|
|
142
|
-
|
|
143
|
-
function App() {
|
|
144
|
-
const [showNotifications, setShowNotifications] = useState(false);
|
|
145
|
-
const [updates, setUpdates] = useState([
|
|
146
|
-
{
|
|
147
|
-
id: '1',
|
|
148
|
-
title: 'Button not working on mobile',
|
|
149
|
-
feedback: 'The submit button is not clickable',
|
|
150
|
-
status: 'resolved',
|
|
151
|
-
responseMessage: 'Fixed! The button now works on all devices.',
|
|
152
|
-
resolvedBy: 'John Developer',
|
|
153
|
-
updatedAt: '2025-11-02T14:30:00Z'
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
id: '2',
|
|
157
|
-
title: 'Add dark mode',
|
|
158
|
-
feedback: 'Would be great to have dark mode',
|
|
159
|
-
status: 'inProgress',
|
|
160
|
-
responseMessage: 'Working on it! Should be ready next week.',
|
|
161
|
-
assignedTo: 'Sarah Designer',
|
|
162
|
-
estimatedResolutionDate: '2025-11-10T00:00:00Z',
|
|
163
|
-
updatedAt: '2025-11-01T11:00:00Z'
|
|
164
|
-
}
|
|
165
|
-
]);
|
|
166
|
-
|
|
167
|
-
const handleDismissUpdate = (updateId) => {
|
|
168
|
-
setUpdates(prev => prev.filter(u => u.id !== updateId));
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const handleDismissAll = () => {
|
|
172
|
-
setUpdates([]);
|
|
173
|
-
setShowNotifications(false);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<FeedbackProvider onSubmit={handleFeedbackSubmit}>
|
|
178
|
-
<YourApp />
|
|
179
|
-
|
|
180
|
-
<button onClick={() => setShowNotifications(true)}>
|
|
181
|
-
🔔 Updates ({updates.length})
|
|
182
|
-
</button>
|
|
183
|
-
|
|
184
|
-
<FeedbackUpdatesNotification
|
|
185
|
-
isOpen={showNotifications}
|
|
186
|
-
onClose={() => setShowNotifications(false)}
|
|
187
|
-
updates={updates}
|
|
188
|
-
onDismissUpdate={handleDismissUpdate}
|
|
189
|
-
onDismissAll={handleDismissAll}
|
|
190
|
-
/>
|
|
191
|
-
</FeedbackProvider>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export default App;
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Status Options
|
|
199
|
-
|
|
200
|
-
The dashboard includes 7 professional status options:
|
|
201
|
-
|
|
202
|
-
| Status | Color | Description |
|
|
203
|
-
|--------|-------|-------------|
|
|
204
|
-
| **Reported** 🔴 | Red | Initial feedback submission |
|
|
205
|
-
| **Opened** 🟠 | Amber | Acknowledged and under review |
|
|
206
|
-
| **In Progress** 🔵 | Blue | Actively being worked on |
|
|
207
|
-
| **Resolved** 🟢 | Green | Fixed and ready |
|
|
208
|
-
| **Released** 🟣 | Purple | Deployed to production |
|
|
209
|
-
| **Blocked** 🔴 | Red | Waiting on dependencies |
|
|
210
|
-
| **Won't Fix** ⚪ | Gray | Not planned for implementation |
|
|
211
|
-
|
|
212
124
|
## API Reference
|
|
213
125
|
|
|
214
126
|
### FeedbackProvider Props
|
|
215
127
|
|
|
216
|
-
| Prop | Type |
|
|
217
|
-
|
|
218
|
-
| `onSubmit` | `(
|
|
219
|
-
| `onStatusChange` | `({
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
128
|
+
| Prop | Type | Default | Description |
|
|
129
|
+
|------|------|---------|-------------|
|
|
130
|
+
| `onSubmit` | `(data) => Promise` | required | Callback when feedback is submitted |
|
|
131
|
+
| `onStatusChange` | `({id, status, comment}) => void` | - | Callback when status changes |
|
|
132
|
+
| `dashboard` | `boolean` | `false` | Enable dashboard feature |
|
|
133
|
+
| `dashboardData` | `Array` | - | Custom data (uses localStorage if undefined) |
|
|
134
|
+
| `isDeveloper` | `boolean` | `false` | Enable developer mode |
|
|
135
|
+
| `isUser` | `boolean` | `true` | Enable user mode |
|
|
136
|
+
| `userName` | `string` | `'Anonymous'` | User name |
|
|
137
|
+
| `userEmail` | `string` | `null` | User email |
|
|
138
|
+
| `mode` | `'light' \| 'dark'` | `'light'` | Theme mode |
|
|
139
|
+
| `isActive` | `boolean` | - | Controlled active state |
|
|
140
|
+
| `onActiveChange` | `(active) => void` | - | Callback for controlled mode |
|
|
228
141
|
|
|
229
142
|
### useFeedback Hook
|
|
230
143
|
|
|
231
144
|
```jsx
|
|
232
|
-
const {
|
|
145
|
+
const {
|
|
146
|
+
isActive, // boolean - feedback mode active
|
|
147
|
+
setIsActive, // (active: boolean) => void
|
|
148
|
+
setIsDashboardOpen, // (open: boolean) => void
|
|
149
|
+
startRecording // () => void - start screen recording
|
|
150
|
+
} = useFeedback();
|
|
233
151
|
```
|
|
234
152
|
|
|
235
|
-
|
|
236
|
-
- `isActive`: `boolean` - Whether feedback mode is active
|
|
237
|
-
- `setIsActive`: `(active: boolean) => void` - Activate/deactivate feedback mode
|
|
238
|
-
- `setIsDashboardOpen`: `(open: boolean) => void` - Open/close dashboard
|
|
153
|
+
### SessionReplay Props (for custom implementations)
|
|
239
154
|
|
|
240
|
-
|
|
155
|
+
```jsx
|
|
156
|
+
import { SessionReplay } from 'react-visual-feedback';
|
|
157
|
+
|
|
158
|
+
<SessionReplay
|
|
159
|
+
videoSrc={videoDataUrl} // Video source (data URL, blob URL, or http URL)
|
|
160
|
+
eventLogs={logs} // Array of log objects with timestamp
|
|
161
|
+
mode="light" // Theme mode
|
|
162
|
+
showLogsButton={true} // Show/hide logs toggle button
|
|
163
|
+
logsPanelWidth="320px" // Width of logs panel
|
|
164
|
+
defaultLogsOpen={false} // Start with logs panel open
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Data Structures
|
|
241
169
|
|
|
242
|
-
|
|
243
|
-
|------|------|----------|---------|-------------|
|
|
244
|
-
| `isOpen` | `boolean` | Yes | - | Controls notification visibility |
|
|
245
|
-
| `onClose` | `() => void` | Yes | - | Callback when notification is closed |
|
|
246
|
-
| `updates` | `Array` | Yes | - | Array of feedback updates to display |
|
|
247
|
-
| `onDismissUpdate` | `(id: string) => void` | No | - | Callback when a single update is dismissed |
|
|
248
|
-
| `onDismissAll` | `() => void` | No | - | Callback when all updates are dismissed |
|
|
170
|
+
### Feedback Data (submitted via onSubmit)
|
|
249
171
|
|
|
250
|
-
**Update Object Structure:**
|
|
251
172
|
```typescript
|
|
252
|
-
{
|
|
253
|
-
id: string
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
173
|
+
interface FeedbackData {
|
|
174
|
+
id: string; // UUID
|
|
175
|
+
feedback: string; // User's feedback text
|
|
176
|
+
type: 'bug' | 'feature' | 'improvement' | 'question' | 'other';
|
|
177
|
+
userName: string;
|
|
178
|
+
userEmail: string | null;
|
|
179
|
+
status: string; // 'new', 'open', 'inProgress', etc.
|
|
180
|
+
timestamp: string; // ISO 8601
|
|
181
|
+
url: string; // Page URL
|
|
182
|
+
userAgent: string;
|
|
183
|
+
viewport: {
|
|
184
|
+
width: number;
|
|
185
|
+
height: number;
|
|
186
|
+
};
|
|
265
187
|
|
|
266
|
-
|
|
188
|
+
// Screenshot (for element selection)
|
|
189
|
+
screenshot?: string; // Base64 PNG data URL
|
|
190
|
+
|
|
191
|
+
// Video (for screen recording)
|
|
192
|
+
video?: string; // Base64 webm data URL
|
|
193
|
+
eventLogs?: EventLog[]; // Console/network logs
|
|
194
|
+
|
|
195
|
+
// Element info (for element selection)
|
|
196
|
+
elementInfo?: {
|
|
197
|
+
tagName: string;
|
|
198
|
+
id: string;
|
|
199
|
+
className: string;
|
|
200
|
+
selector: string;
|
|
201
|
+
text: string;
|
|
202
|
+
position: { x: number; y: number; width: number; height: number };
|
|
203
|
+
styles: { backgroundColor: string; color: string; fontSize: string };
|
|
204
|
+
sourceFile?: string; // React source file path
|
|
205
|
+
lineNumber?: number;
|
|
206
|
+
columnNumber?: number;
|
|
207
|
+
componentStack?: string[]; // React component hierarchy
|
|
208
|
+
};
|
|
209
|
+
}
|
|
267
210
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
userEmail: string | null,
|
|
274
|
-
status: 'reported' | 'opened' | 'inProgress' | 'resolved' | 'released' | 'blocked' | 'wontFix',
|
|
275
|
-
timestamp: string, // ISO 8601 format
|
|
276
|
-
url: string,
|
|
277
|
-
elementInfo: {
|
|
278
|
-
tagName: string,
|
|
279
|
-
id: string,
|
|
280
|
-
className: string,
|
|
281
|
-
selector: string,
|
|
282
|
-
text: string,
|
|
283
|
-
position: { x: number, y: number, width: number, height: number },
|
|
284
|
-
styles: { backgroundColor: string, color: string, fontSize: string, fontFamily: string }
|
|
285
|
-
},
|
|
286
|
-
screenshot: string, // Base64 encoded PNG
|
|
287
|
-
viewport: { width: number, height: number },
|
|
288
|
-
userAgent: string
|
|
211
|
+
interface EventLog {
|
|
212
|
+
timestamp: number; // Milliseconds from recording start
|
|
213
|
+
type: 'log' | 'warn' | 'error' | 'info' | 'network';
|
|
214
|
+
message: string;
|
|
215
|
+
data?: any;
|
|
289
216
|
}
|
|
290
217
|
```
|
|
291
218
|
|
|
292
|
-
##
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
219
|
+
## Database Schema (SQL)
|
|
220
|
+
|
|
221
|
+
### PostgreSQL
|
|
222
|
+
|
|
223
|
+
```sql
|
|
224
|
+
-- Enable UUID extension
|
|
225
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
226
|
+
|
|
227
|
+
-- Main feedback table
|
|
228
|
+
CREATE TABLE feedback (
|
|
229
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
230
|
+
feedback TEXT NOT NULL,
|
|
231
|
+
type VARCHAR(20) DEFAULT 'bug' CHECK (type IN ('bug', 'feature', 'improvement', 'question', 'other')),
|
|
232
|
+
status VARCHAR(20) DEFAULT 'new' CHECK (status IN ('new', 'open', 'inProgress', 'underReview', 'onHold', 'resolved', 'closed', 'wontFix')),
|
|
233
|
+
|
|
234
|
+
-- User info
|
|
235
|
+
user_name VARCHAR(255) DEFAULT 'Anonymous',
|
|
236
|
+
user_email VARCHAR(255),
|
|
237
|
+
|
|
238
|
+
-- Page context
|
|
239
|
+
url TEXT,
|
|
240
|
+
user_agent TEXT,
|
|
241
|
+
viewport_width INTEGER,
|
|
242
|
+
viewport_height INTEGER,
|
|
243
|
+
|
|
244
|
+
-- Timestamps
|
|
245
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
246
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
-- Screenshots table (separate for large data)
|
|
250
|
+
CREATE TABLE feedback_screenshots (
|
|
251
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
252
|
+
feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
|
|
253
|
+
screenshot TEXT NOT NULL, -- Base64 encoded PNG
|
|
254
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
-- Videos table (for screen recordings)
|
|
258
|
+
CREATE TABLE feedback_videos (
|
|
259
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
260
|
+
feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
|
|
261
|
+
video_data TEXT, -- Base64 encoded webm (for small videos)
|
|
262
|
+
video_url TEXT, -- URL to cloud storage (for large videos)
|
|
263
|
+
duration_ms INTEGER,
|
|
264
|
+
file_size_bytes BIGINT,
|
|
265
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
-- Event logs table (console logs, network requests)
|
|
269
|
+
CREATE TABLE feedback_event_logs (
|
|
270
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
271
|
+
feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
|
|
272
|
+
timestamp_ms INTEGER NOT NULL, -- Milliseconds from recording start
|
|
273
|
+
log_type VARCHAR(20) NOT NULL CHECK (log_type IN ('log', 'warn', 'error', 'info', 'network')),
|
|
274
|
+
message TEXT NOT NULL,
|
|
275
|
+
data JSONB, -- Additional log data
|
|
276
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
-- Element info table (for element selection feedback)
|
|
280
|
+
CREATE TABLE feedback_element_info (
|
|
281
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
282
|
+
feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
|
|
283
|
+
tag_name VARCHAR(50),
|
|
284
|
+
element_id VARCHAR(255),
|
|
285
|
+
class_name TEXT,
|
|
286
|
+
css_selector TEXT,
|
|
287
|
+
inner_text TEXT,
|
|
288
|
+
position_x INTEGER,
|
|
289
|
+
position_y INTEGER,
|
|
290
|
+
width INTEGER,
|
|
291
|
+
height INTEGER,
|
|
292
|
+
background_color VARCHAR(50),
|
|
293
|
+
color VARCHAR(50),
|
|
294
|
+
font_size VARCHAR(20),
|
|
295
|
+
|
|
296
|
+
-- React component info
|
|
297
|
+
source_file TEXT,
|
|
298
|
+
line_number INTEGER,
|
|
299
|
+
column_number INTEGER,
|
|
300
|
+
component_stack TEXT[],
|
|
301
|
+
|
|
302
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
-- Status history table (track status changes)
|
|
306
|
+
CREATE TABLE feedback_status_history (
|
|
307
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
308
|
+
feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
|
|
309
|
+
old_status VARCHAR(20),
|
|
310
|
+
new_status VARCHAR(20) NOT NULL,
|
|
311
|
+
comment TEXT,
|
|
312
|
+
changed_by VARCHAR(255),
|
|
313
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
-- Indexes for performance
|
|
317
|
+
CREATE INDEX idx_feedback_status ON feedback(status);
|
|
318
|
+
CREATE INDEX idx_feedback_user_email ON feedback(user_email);
|
|
319
|
+
CREATE INDEX idx_feedback_created_at ON feedback(created_at DESC);
|
|
320
|
+
CREATE INDEX idx_feedback_type ON feedback(type);
|
|
321
|
+
CREATE INDEX idx_event_logs_feedback_id ON feedback_event_logs(feedback_id);
|
|
322
|
+
CREATE INDEX idx_event_logs_timestamp ON feedback_event_logs(timestamp_ms);
|
|
323
|
+
CREATE INDEX idx_status_history_feedback_id ON feedback_status_history(feedback_id);
|
|
324
|
+
|
|
325
|
+
-- Update timestamp trigger
|
|
326
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
327
|
+
RETURNS TRIGGER AS $$
|
|
328
|
+
BEGIN
|
|
329
|
+
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
330
|
+
RETURN NEW;
|
|
331
|
+
END;
|
|
332
|
+
$$ language 'plpgsql';
|
|
333
|
+
|
|
334
|
+
CREATE TRIGGER update_feedback_updated_at
|
|
335
|
+
BEFORE UPDATE ON feedback
|
|
336
|
+
FOR EACH ROW
|
|
337
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
338
|
+
```
|
|
339
339
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
340
|
+
### MySQL
|
|
341
|
+
|
|
342
|
+
```sql
|
|
343
|
+
-- Main feedback table
|
|
344
|
+
CREATE TABLE feedback (
|
|
345
|
+
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
|
346
|
+
feedback TEXT NOT NULL,
|
|
347
|
+
type ENUM('bug', 'feature', 'improvement', 'question', 'other') DEFAULT 'bug',
|
|
348
|
+
status ENUM('new', 'open', 'inProgress', 'underReview', 'onHold', 'resolved', 'closed', 'wontFix') DEFAULT 'new',
|
|
349
|
+
|
|
350
|
+
-- User info
|
|
351
|
+
user_name VARCHAR(255) DEFAULT 'Anonymous',
|
|
352
|
+
user_email VARCHAR(255),
|
|
353
|
+
|
|
354
|
+
-- Page context
|
|
355
|
+
url TEXT,
|
|
356
|
+
user_agent TEXT,
|
|
357
|
+
viewport_width INT,
|
|
358
|
+
viewport_height INT,
|
|
359
|
+
|
|
360
|
+
-- Timestamps
|
|
361
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
362
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
363
|
+
|
|
364
|
+
INDEX idx_status (status),
|
|
365
|
+
INDEX idx_user_email (user_email),
|
|
366
|
+
INDEX idx_created_at (created_at DESC),
|
|
367
|
+
INDEX idx_type (type)
|
|
368
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
369
|
+
|
|
370
|
+
-- Screenshots table
|
|
371
|
+
CREATE TABLE feedback_screenshots (
|
|
372
|
+
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
|
373
|
+
feedback_id CHAR(36) NOT NULL,
|
|
374
|
+
screenshot LONGTEXT NOT NULL,
|
|
375
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
376
|
+
|
|
377
|
+
FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE
|
|
378
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
379
|
+
|
|
380
|
+
-- Videos table
|
|
381
|
+
CREATE TABLE feedback_videos (
|
|
382
|
+
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
|
383
|
+
feedback_id CHAR(36) NOT NULL,
|
|
384
|
+
video_data LONGTEXT,
|
|
385
|
+
video_url TEXT,
|
|
386
|
+
duration_ms INT,
|
|
387
|
+
file_size_bytes BIGINT,
|
|
388
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
389
|
+
|
|
390
|
+
FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE
|
|
391
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
392
|
+
|
|
393
|
+
-- Event logs table
|
|
394
|
+
CREATE TABLE feedback_event_logs (
|
|
395
|
+
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
|
396
|
+
feedback_id CHAR(36) NOT NULL,
|
|
397
|
+
timestamp_ms INT NOT NULL,
|
|
398
|
+
log_type ENUM('log', 'warn', 'error', 'info', 'network') NOT NULL,
|
|
399
|
+
message TEXT NOT NULL,
|
|
400
|
+
data JSON,
|
|
401
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
402
|
+
|
|
403
|
+
FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE,
|
|
404
|
+
INDEX idx_feedback_id (feedback_id),
|
|
405
|
+
INDEX idx_timestamp (timestamp_ms)
|
|
406
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
407
|
+
|
|
408
|
+
-- Element info table
|
|
409
|
+
CREATE TABLE feedback_element_info (
|
|
410
|
+
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
|
411
|
+
feedback_id CHAR(36) NOT NULL,
|
|
412
|
+
tag_name VARCHAR(50),
|
|
413
|
+
element_id VARCHAR(255),
|
|
414
|
+
class_name TEXT,
|
|
415
|
+
css_selector TEXT,
|
|
416
|
+
inner_text TEXT,
|
|
417
|
+
position_x INT,
|
|
418
|
+
position_y INT,
|
|
419
|
+
width INT,
|
|
420
|
+
height INT,
|
|
421
|
+
background_color VARCHAR(50),
|
|
422
|
+
color VARCHAR(50),
|
|
423
|
+
font_size VARCHAR(20),
|
|
424
|
+
source_file TEXT,
|
|
425
|
+
line_number INT,
|
|
426
|
+
column_number INT,
|
|
427
|
+
component_stack JSON,
|
|
428
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
429
|
+
|
|
430
|
+
FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE
|
|
431
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
432
|
+
|
|
433
|
+
-- Status history table
|
|
434
|
+
CREATE TABLE feedback_status_history (
|
|
435
|
+
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
|
436
|
+
feedback_id CHAR(36) NOT NULL,
|
|
437
|
+
old_status VARCHAR(20),
|
|
438
|
+
new_status VARCHAR(20) NOT NULL,
|
|
439
|
+
comment TEXT,
|
|
440
|
+
changed_by VARCHAR(255),
|
|
441
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
442
|
+
|
|
443
|
+
FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE,
|
|
444
|
+
INDEX idx_feedback_id (feedback_id)
|
|
445
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
446
|
+
```
|
|
344
447
|
|
|
345
|
-
|
|
448
|
+
### Example API Endpoints (Node.js/Express)
|
|
449
|
+
|
|
450
|
+
```javascript
|
|
451
|
+
// POST /api/feedback - Submit new feedback
|
|
452
|
+
app.post('/api/feedback', async (req, res) => {
|
|
453
|
+
const { feedback, type, userName, userEmail, url, userAgent,
|
|
454
|
+
viewport, screenshot, video, eventLogs, elementInfo } = req.body;
|
|
455
|
+
|
|
456
|
+
// Start transaction
|
|
457
|
+
const client = await pool.connect();
|
|
458
|
+
try {
|
|
459
|
+
await client.query('BEGIN');
|
|
460
|
+
|
|
461
|
+
// Insert main feedback
|
|
462
|
+
const feedbackResult = await client.query(`
|
|
463
|
+
INSERT INTO feedback (feedback, type, user_name, user_email, url, user_agent, viewport_width, viewport_height)
|
|
464
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
465
|
+
RETURNING id
|
|
466
|
+
`, [feedback, type, userName, userEmail, url, userAgent, viewport?.width, viewport?.height]);
|
|
467
|
+
|
|
468
|
+
const feedbackId = feedbackResult.rows[0].id;
|
|
469
|
+
|
|
470
|
+
// Insert screenshot if exists
|
|
471
|
+
if (screenshot) {
|
|
472
|
+
await client.query(`
|
|
473
|
+
INSERT INTO feedback_screenshots (feedback_id, screenshot)
|
|
474
|
+
VALUES ($1, $2)
|
|
475
|
+
`, [feedbackId, screenshot]);
|
|
476
|
+
}
|
|
346
477
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
478
|
+
// Insert video if exists
|
|
479
|
+
if (video) {
|
|
480
|
+
await client.query(`
|
|
481
|
+
INSERT INTO feedback_videos (feedback_id, video_data)
|
|
482
|
+
VALUES ($1, $2)
|
|
483
|
+
`, [feedbackId, video]);
|
|
484
|
+
}
|
|
351
485
|
|
|
352
|
-
|
|
353
|
-
|
|
486
|
+
// Insert event logs if exist
|
|
487
|
+
if (eventLogs?.length) {
|
|
488
|
+
for (const log of eventLogs) {
|
|
489
|
+
await client.query(`
|
|
490
|
+
INSERT INTO feedback_event_logs (feedback_id, timestamp_ms, log_type, message, data)
|
|
491
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
492
|
+
`, [feedbackId, log.timestamp, log.type, log.message, JSON.stringify(log.data)]);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
354
495
|
|
|
355
|
-
|
|
356
|
-
|
|
496
|
+
// Insert element info if exists
|
|
497
|
+
if (elementInfo) {
|
|
498
|
+
await client.query(`
|
|
499
|
+
INSERT INTO feedback_element_info
|
|
500
|
+
(feedback_id, tag_name, element_id, class_name, css_selector, inner_text,
|
|
501
|
+
position_x, position_y, width, height, source_file, line_number, column_number, component_stack)
|
|
502
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
|
503
|
+
`, [feedbackId, elementInfo.tagName, elementInfo.id, elementInfo.className,
|
|
504
|
+
elementInfo.selector, elementInfo.text, elementInfo.position?.x, elementInfo.position?.y,
|
|
505
|
+
elementInfo.position?.width, elementInfo.position?.height, elementInfo.sourceFile,
|
|
506
|
+
elementInfo.lineNumber, elementInfo.columnNumber, elementInfo.componentStack]);
|
|
507
|
+
}
|
|
357
508
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
509
|
+
await client.query('COMMIT');
|
|
510
|
+
res.json({ success: true, id: feedbackId });
|
|
511
|
+
} catch (error) {
|
|
512
|
+
await client.query('ROLLBACK');
|
|
513
|
+
res.status(500).json({ error: error.message });
|
|
514
|
+
} finally {
|
|
515
|
+
client.release();
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// PATCH /api/feedback/:id/status - Update status
|
|
520
|
+
app.patch('/api/feedback/:id/status', async (req, res) => {
|
|
521
|
+
const { id } = req.params;
|
|
522
|
+
const { status, comment, changedBy } = req.body;
|
|
523
|
+
|
|
524
|
+
const client = await pool.connect();
|
|
525
|
+
try {
|
|
526
|
+
await client.query('BEGIN');
|
|
527
|
+
|
|
528
|
+
// Get current status
|
|
529
|
+
const current = await client.query('SELECT status FROM feedback WHERE id = $1', [id]);
|
|
530
|
+
const oldStatus = current.rows[0]?.status;
|
|
531
|
+
|
|
532
|
+
// Update status
|
|
533
|
+
await client.query('UPDATE feedback SET status = $1 WHERE id = $2', [status, id]);
|
|
534
|
+
|
|
535
|
+
// Record history
|
|
536
|
+
await client.query(`
|
|
537
|
+
INSERT INTO feedback_status_history (feedback_id, old_status, new_status, comment, changed_by)
|
|
538
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
539
|
+
`, [id, oldStatus, status, comment, changedBy]);
|
|
540
|
+
|
|
541
|
+
await client.query('COMMIT');
|
|
542
|
+
res.json({ success: true });
|
|
543
|
+
} catch (error) {
|
|
544
|
+
await client.query('ROLLBACK');
|
|
545
|
+
res.status(500).json({ error: error.message });
|
|
546
|
+
} finally {
|
|
547
|
+
client.release();
|
|
548
|
+
}
|
|
549
|
+
});
|
|
362
550
|
```
|
|
363
551
|
|
|
364
|
-
|
|
552
|
+
## Keyboard Shortcuts
|
|
365
553
|
|
|
366
|
-
|
|
554
|
+
| Shortcut | Action |
|
|
555
|
+
|----------|--------|
|
|
556
|
+
| `Alt+Q` | Activate feedback mode |
|
|
557
|
+
| `Alt+Shift+Q` | Open dashboard |
|
|
558
|
+
| `Esc` | Cancel/close |
|
|
367
559
|
|
|
368
|
-
|
|
369
|
-
import {
|
|
370
|
-
FeedbackProvider, // Main provider component
|
|
371
|
-
useFeedback, // Hook to control feedback state
|
|
372
|
-
FeedbackUpdatesNotification // Notification component for updates
|
|
373
|
-
} from 'react-visual-feedback';
|
|
374
|
-
```
|
|
560
|
+
## Status Options
|
|
375
561
|
|
|
376
|
-
|
|
562
|
+
| Status | Description |
|
|
563
|
+
|--------|-------------|
|
|
564
|
+
| **New** | Initial submission |
|
|
565
|
+
| **Open** | Acknowledged |
|
|
566
|
+
| **In Progress** | Being worked on |
|
|
567
|
+
| **Under Review** | Code review |
|
|
568
|
+
| **On Hold** | Paused |
|
|
569
|
+
| **Resolved** | Fixed |
|
|
570
|
+
| **Closed** | Completed |
|
|
571
|
+
| **Won't Fix** | Not planned |
|
|
377
572
|
|
|
378
|
-
|
|
379
|
-
- 💬 Developers can add optional comments when changing status
|
|
380
|
-
- 📝 Comments are passed to `onStatusChange` callback
|
|
381
|
-
- 👥 Comments visible to users as developer responses
|
|
573
|
+
## Browser Support
|
|
382
574
|
|
|
383
|
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
-
|
|
387
|
-
- 👋 Dismiss individual or all updates
|
|
575
|
+
- Chrome/Edge (recommended for screen recording)
|
|
576
|
+
- Firefox
|
|
577
|
+
- Safari
|
|
578
|
+
- Opera
|
|
388
579
|
|
|
389
580
|
## License
|
|
390
581
|
|
|
391
|
-
MIT
|
|
392
|
-
|
|
393
|
-
## Contributing
|
|
394
|
-
|
|
395
|
-
Contributions welcome! Please submit a Pull Request.
|
|
396
|
-
|
|
397
|
-
## Issues
|
|
398
|
-
|
|
399
|
-
Report issues at: https://github.com/Murali1889/react-feedback-widget/issues
|
|
582
|
+
MIT
|
|
400
583
|
|
|
401
584
|
## Author
|
|
402
585
|
|
|
@@ -405,4 +588,4 @@ Email: murali.g@hyperverge.co
|
|
|
405
588
|
|
|
406
589
|
---
|
|
407
590
|
|
|
408
|
-
Made with
|
|
591
|
+
Made with care for better user feedback collection
|