react-visual-feedback 1.3.7 → 1.4.2

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 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. Users can select any element on your page, and the widget automatically captures a screenshot and context information.
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
- - 🎯 Visual element selection with hover highlighting
9
- - 📸 Automatic screenshot capture with perfect CSS rendering
10
- - 📝 Rich feedback form with context
11
- - Lightweight and performant
12
- - 🎨 Works with any CSS framework (Tailwind, Bootstrap, Material-UI, etc.)
13
- - ⌨️ Keyboard shortcuts (Ctrl+Q to activate, Esc to cancel)
14
- - 🌓 Dark mode support
15
-
16
- ### Feedback Dashboard
17
- - 📊 Professional dashboard with localStorage or custom data source
18
- - 👨‍💻 Developer mode with full technical details
19
- - 👤 User mode for simplified feedback view
20
- - 🏷️ Status management with 7 professional status options
21
- - 💬 Status change modal with developer comments
22
- - 🔒 Permission system - Users can only view, developers can manage
23
- - 🔄 Status change callbacks for database synchronization
24
- - ⌨️ Dashboard keyboard shortcut (Ctrl+Shift+Q)
25
-
26
- ### Update Notifications
27
- - 🔔 Beautiful notification component for feedback updates
28
- - 📬 Show users when their feedback status changes
29
- - 🎨 Grouped by status (Completed, In Progress, Other)
30
- - 👋 Dismiss individual updates or all at once
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
- **Important:** Import the CSS file in your application:
40
-
41
- ```jsx
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 (Feedback Only)
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 (Full Feature Set)
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
- 📊 Dashboard
89
- </button>
80
+ <button onClick={() => setIsDashboardOpen(true)}>Dashboard</button>
81
+ <button onClick={startRecording}>Record Screen</button>
90
82
  <button onClick={() => setIsActive(!isActive)}>
91
- {isActive ? 'Cancel' : '💬 Report Issue'}
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,771 @@ function App() {
117
109
  onSubmit={handleFeedbackSubmit}
118
110
  onStatusChange={handleStatusChange}
119
111
  dashboard={true}
120
- isDeveloper={true} // Set to false for user mode
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
  }
122
+ ```
123
+
124
+ ## API Reference
125
+
126
+ ### FeedbackProvider Props
127
+
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 |
141
+
142
+ ### FeedbackDashboard Props
143
+
144
+ | Prop | Type | Default | Description |
145
+ |------|------|---------|-------------|
146
+ | `isOpen` | `boolean` | required | Control dashboard visibility |
147
+ | `onClose` | `() => void` | required | Callback when dashboard closes |
148
+ | `data` | `Array` | - | Feedback data (uses localStorage if undefined) |
149
+ | `isDeveloper` | `boolean` | `false` | Enable developer mode with delete |
150
+ | `isUser` | `boolean` | `true` | Enable user mode |
151
+ | `onStatusChange` | `({id, status, comment}) => void` | - | Callback when status changes |
152
+ | `mode` | `'light' \| 'dark'` | `'light'` | Theme mode |
153
+ | `isLoading` | `boolean` | `false` | Show loading state |
154
+ | `onRefresh` | `() => void` | - | Callback for refresh button |
155
+ | `title` | `string` | `'Feedback'` | Dashboard title |
156
+ | `statuses` | `object` | `DEFAULT_STATUSES` | Custom status configurations |
157
+ | `showAllStatuses` | `boolean` | `true` | Show all statuses in filter |
158
+ | `error` | `string` | `null` | Error message to display |
129
159
 
130
- export default App;
160
+ ### useFeedback Hook
161
+
162
+ ```jsx
163
+ const {
164
+ isActive, // boolean - feedback mode active
165
+ setIsActive, // (active: boolean) => void
166
+ setIsDashboardOpen, // (open: boolean) => void
167
+ startRecording // () => void - start screen recording
168
+ } = useFeedback();
131
169
  ```
132
170
 
133
- ### With Update Notifications
171
+ ### SessionReplay Props (for custom implementations)
134
172
 
135
173
  ```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';
174
+ import { SessionReplay } from 'react-visual-feedback';
175
+
176
+ <SessionReplay
177
+ videoSrc={videoDataUrl} // Video source (data URL, blob URL, or http URL)
178
+ eventLogs={logs} // Array of log objects with timestamp
179
+ mode="light" // Theme mode
180
+ showLogsButton={true} // Show/hide logs toggle button
181
+ logsPanelWidth="320px" // Width of logs panel
182
+ defaultLogsOpen={false} // Start with logs panel open
183
+ />
184
+ ```
142
185
 
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
- ]);
186
+ ## Data Structures
166
187
 
167
- const handleDismissUpdate = (updateId) => {
168
- setUpdates(prev => prev.filter(u => u.id !== updateId));
188
+ ### Feedback Data (submitted via onSubmit)
189
+
190
+ ```typescript
191
+ interface FeedbackData {
192
+ id: string; // UUID
193
+ feedback: string; // User's feedback text
194
+ type: 'bug' | 'feature' | 'improvement' | 'question' | 'other';
195
+ userName: string;
196
+ userEmail: string | null;
197
+ status: string; // 'new', 'open', 'inProgress', etc.
198
+ timestamp: string; // ISO 8601
199
+ url: string; // Page URL
200
+ userAgent: string;
201
+ viewport: {
202
+ width: number;
203
+ height: number;
169
204
  };
170
205
 
171
- const handleDismissAll = () => {
172
- setUpdates([]);
173
- setShowNotifications(false);
206
+ // Screenshot (for element selection)
207
+ screenshot?: string; // Base64 PNG data URL
208
+
209
+ // Video (for screen recording)
210
+ video?: string; // Base64 webm data URL
211
+ eventLogs?: EventLog[]; // Console/network logs
212
+
213
+ // Element info (for element selection)
214
+ elementInfo?: {
215
+ tagName: string;
216
+ id: string;
217
+ className: string;
218
+ selector: string;
219
+ text: string;
220
+ position: { x: number; y: number; width: number; height: number };
221
+ styles: { backgroundColor: string; color: string; fontSize: string };
222
+ sourceFile?: string; // React source file path
223
+ lineNumber?: number;
224
+ columnNumber?: number;
225
+ componentStack?: string[]; // React component hierarchy
174
226
  };
227
+ }
175
228
 
176
- return (
177
- <FeedbackProvider onSubmit={handleFeedbackSubmit}>
178
- <YourApp />
229
+ interface EventLog {
230
+ timestamp: number; // Milliseconds from recording start
231
+ type: 'log' | 'warn' | 'error' | 'info' | 'network';
232
+ message: string;
233
+ data?: any;
234
+ }
235
+ ```
179
236
 
180
- <button onClick={() => setShowNotifications(true)}>
181
- 🔔 Updates ({updates.length})
182
- </button>
237
+ ## Database Schema (SQL)
238
+
239
+ ### PostgreSQL
240
+
241
+ ```sql
242
+ -- Enable UUID extension
243
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
244
+
245
+ -- Main feedback table
246
+ CREATE TABLE feedback (
247
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
248
+ feedback TEXT NOT NULL,
249
+ type VARCHAR(20) DEFAULT 'bug' CHECK (type IN ('bug', 'feature', 'improvement', 'question', 'other')),
250
+ status VARCHAR(20) DEFAULT 'new' CHECK (status IN ('new', 'open', 'inProgress', 'underReview', 'onHold', 'resolved', 'closed', 'wontFix')),
251
+
252
+ -- User info
253
+ user_name VARCHAR(255) DEFAULT 'Anonymous',
254
+ user_email VARCHAR(255),
255
+
256
+ -- Page context
257
+ url TEXT,
258
+ user_agent TEXT,
259
+ viewport_width INTEGER,
260
+ viewport_height INTEGER,
261
+
262
+ -- Timestamps
263
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
264
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
265
+ );
266
+
267
+ -- Screenshots table (separate for large data)
268
+ CREATE TABLE feedback_screenshots (
269
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
270
+ feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
271
+ screenshot TEXT NOT NULL, -- Base64 encoded PNG
272
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
273
+ );
274
+
275
+ -- Videos table (for screen recordings)
276
+ CREATE TABLE feedback_videos (
277
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
278
+ feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
279
+ video_data TEXT, -- Base64 encoded webm (for small videos)
280
+ video_url TEXT, -- URL to cloud storage (for large videos)
281
+ duration_ms INTEGER,
282
+ file_size_bytes BIGINT,
283
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
284
+ );
285
+
286
+ -- Event logs table (console logs, network requests)
287
+ CREATE TABLE feedback_event_logs (
288
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
289
+ feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
290
+ timestamp_ms INTEGER NOT NULL, -- Milliseconds from recording start
291
+ log_type VARCHAR(20) NOT NULL CHECK (log_type IN ('log', 'warn', 'error', 'info', 'network')),
292
+ message TEXT NOT NULL,
293
+ data JSONB, -- Additional log data
294
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
295
+ );
296
+
297
+ -- Element info table (for element selection feedback)
298
+ CREATE TABLE feedback_element_info (
299
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
300
+ feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
301
+ tag_name VARCHAR(50),
302
+ element_id VARCHAR(255),
303
+ class_name TEXT,
304
+ css_selector TEXT,
305
+ inner_text TEXT,
306
+ position_x INTEGER,
307
+ position_y INTEGER,
308
+ width INTEGER,
309
+ height INTEGER,
310
+ background_color VARCHAR(50),
311
+ color VARCHAR(50),
312
+ font_size VARCHAR(20),
313
+
314
+ -- React component info
315
+ source_file TEXT,
316
+ line_number INTEGER,
317
+ column_number INTEGER,
318
+ component_stack TEXT[],
319
+
320
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
321
+ );
322
+
323
+ -- Status history table (track status changes)
324
+ CREATE TABLE feedback_status_history (
325
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
326
+ feedback_id UUID NOT NULL REFERENCES feedback(id) ON DELETE CASCADE,
327
+ old_status VARCHAR(20),
328
+ new_status VARCHAR(20) NOT NULL,
329
+ comment TEXT,
330
+ changed_by VARCHAR(255),
331
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
332
+ );
333
+
334
+ -- Indexes for performance
335
+ CREATE INDEX idx_feedback_status ON feedback(status);
336
+ CREATE INDEX idx_feedback_user_email ON feedback(user_email);
337
+ CREATE INDEX idx_feedback_created_at ON feedback(created_at DESC);
338
+ CREATE INDEX idx_feedback_type ON feedback(type);
339
+ CREATE INDEX idx_event_logs_feedback_id ON feedback_event_logs(feedback_id);
340
+ CREATE INDEX idx_event_logs_timestamp ON feedback_event_logs(timestamp_ms);
341
+ CREATE INDEX idx_status_history_feedback_id ON feedback_status_history(feedback_id);
342
+
343
+ -- Update timestamp trigger
344
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
345
+ RETURNS TRIGGER AS $$
346
+ BEGIN
347
+ NEW.updated_at = CURRENT_TIMESTAMP;
348
+ RETURN NEW;
349
+ END;
350
+ $$ language 'plpgsql';
351
+
352
+ CREATE TRIGGER update_feedback_updated_at
353
+ BEFORE UPDATE ON feedback
354
+ FOR EACH ROW
355
+ EXECUTE FUNCTION update_updated_at_column();
356
+ ```
183
357
 
184
- <FeedbackUpdatesNotification
185
- isOpen={showNotifications}
186
- onClose={() => setShowNotifications(false)}
187
- updates={updates}
188
- onDismissUpdate={handleDismissUpdate}
189
- onDismissAll={handleDismissAll}
190
- />
191
- </FeedbackProvider>
192
- );
193
- }
358
+ ### MySQL
359
+
360
+ ```sql
361
+ -- Main feedback table
362
+ CREATE TABLE feedback (
363
+ id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
364
+ feedback TEXT NOT NULL,
365
+ type ENUM('bug', 'feature', 'improvement', 'question', 'other') DEFAULT 'bug',
366
+ status ENUM('new', 'open', 'inProgress', 'underReview', 'onHold', 'resolved', 'closed', 'wontFix') DEFAULT 'new',
367
+
368
+ -- User info
369
+ user_name VARCHAR(255) DEFAULT 'Anonymous',
370
+ user_email VARCHAR(255),
371
+
372
+ -- Page context
373
+ url TEXT,
374
+ user_agent TEXT,
375
+ viewport_width INT,
376
+ viewport_height INT,
377
+
378
+ -- Timestamps
379
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
380
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
381
+
382
+ INDEX idx_status (status),
383
+ INDEX idx_user_email (user_email),
384
+ INDEX idx_created_at (created_at DESC),
385
+ INDEX idx_type (type)
386
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
387
+
388
+ -- Screenshots table
389
+ CREATE TABLE feedback_screenshots (
390
+ id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
391
+ feedback_id CHAR(36) NOT NULL,
392
+ screenshot LONGTEXT NOT NULL,
393
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
394
+
395
+ FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE
396
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
397
+
398
+ -- Videos table
399
+ CREATE TABLE feedback_videos (
400
+ id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
401
+ feedback_id CHAR(36) NOT NULL,
402
+ video_data LONGTEXT,
403
+ video_url TEXT,
404
+ duration_ms INT,
405
+ file_size_bytes BIGINT,
406
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
407
+
408
+ FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE
409
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
410
+
411
+ -- Event logs table
412
+ CREATE TABLE feedback_event_logs (
413
+ id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
414
+ feedback_id CHAR(36) NOT NULL,
415
+ timestamp_ms INT NOT NULL,
416
+ log_type ENUM('log', 'warn', 'error', 'info', 'network') NOT NULL,
417
+ message TEXT NOT NULL,
418
+ data JSON,
419
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
420
+
421
+ FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE,
422
+ INDEX idx_feedback_id (feedback_id),
423
+ INDEX idx_timestamp (timestamp_ms)
424
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
425
+
426
+ -- Element info table
427
+ CREATE TABLE feedback_element_info (
428
+ id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
429
+ feedback_id CHAR(36) NOT NULL,
430
+ tag_name VARCHAR(50),
431
+ element_id VARCHAR(255),
432
+ class_name TEXT,
433
+ css_selector TEXT,
434
+ inner_text TEXT,
435
+ position_x INT,
436
+ position_y INT,
437
+ width INT,
438
+ height INT,
439
+ background_color VARCHAR(50),
440
+ color VARCHAR(50),
441
+ font_size VARCHAR(20),
442
+ source_file TEXT,
443
+ line_number INT,
444
+ column_number INT,
445
+ component_stack JSON,
446
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
447
+
448
+ FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE
449
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
450
+
451
+ -- Status history table
452
+ CREATE TABLE feedback_status_history (
453
+ id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
454
+ feedback_id CHAR(36) NOT NULL,
455
+ old_status VARCHAR(20),
456
+ new_status VARCHAR(20) NOT NULL,
457
+ comment TEXT,
458
+ changed_by VARCHAR(255),
459
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
460
+
461
+ FOREIGN KEY (feedback_id) REFERENCES feedback(id) ON DELETE CASCADE,
462
+ INDEX idx_feedback_id (feedback_id)
463
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
464
+ ```
465
+
466
+ ### Example API Endpoints (Node.js/Express)
467
+
468
+ ```javascript
469
+ // POST /api/feedback - Submit new feedback
470
+ app.post('/api/feedback', async (req, res) => {
471
+ const { feedback, type, userName, userEmail, url, userAgent,
472
+ viewport, screenshot, video, eventLogs, elementInfo } = req.body;
473
+
474
+ // Start transaction
475
+ const client = await pool.connect();
476
+ try {
477
+ await client.query('BEGIN');
478
+
479
+ // Insert main feedback
480
+ const feedbackResult = await client.query(`
481
+ INSERT INTO feedback (feedback, type, user_name, user_email, url, user_agent, viewport_width, viewport_height)
482
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
483
+ RETURNING id
484
+ `, [feedback, type, userName, userEmail, url, userAgent, viewport?.width, viewport?.height]);
485
+
486
+ const feedbackId = feedbackResult.rows[0].id;
487
+
488
+ // Insert screenshot if exists
489
+ if (screenshot) {
490
+ await client.query(`
491
+ INSERT INTO feedback_screenshots (feedback_id, screenshot)
492
+ VALUES ($1, $2)
493
+ `, [feedbackId, screenshot]);
494
+ }
495
+
496
+ // Insert video if exists
497
+ if (video) {
498
+ await client.query(`
499
+ INSERT INTO feedback_videos (feedback_id, video_data)
500
+ VALUES ($1, $2)
501
+ `, [feedbackId, video]);
502
+ }
194
503
 
195
- export default App;
504
+ // Insert event logs if exist
505
+ if (eventLogs?.length) {
506
+ for (const log of eventLogs) {
507
+ await client.query(`
508
+ INSERT INTO feedback_event_logs (feedback_id, timestamp_ms, log_type, message, data)
509
+ VALUES ($1, $2, $3, $4, $5)
510
+ `, [feedbackId, log.timestamp, log.type, log.message, JSON.stringify(log.data)]);
511
+ }
512
+ }
513
+
514
+ // Insert element info if exists
515
+ if (elementInfo) {
516
+ await client.query(`
517
+ INSERT INTO feedback_element_info
518
+ (feedback_id, tag_name, element_id, class_name, css_selector, inner_text,
519
+ position_x, position_y, width, height, source_file, line_number, column_number, component_stack)
520
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
521
+ `, [feedbackId, elementInfo.tagName, elementInfo.id, elementInfo.className,
522
+ elementInfo.selector, elementInfo.text, elementInfo.position?.x, elementInfo.position?.y,
523
+ elementInfo.position?.width, elementInfo.position?.height, elementInfo.sourceFile,
524
+ elementInfo.lineNumber, elementInfo.columnNumber, elementInfo.componentStack]);
525
+ }
526
+
527
+ await client.query('COMMIT');
528
+ res.json({ success: true, id: feedbackId });
529
+ } catch (error) {
530
+ await client.query('ROLLBACK');
531
+ res.status(500).json({ error: error.message });
532
+ } finally {
533
+ client.release();
534
+ }
535
+ });
536
+
537
+ // PATCH /api/feedback/:id/status - Update status
538
+ app.patch('/api/feedback/:id/status', async (req, res) => {
539
+ const { id } = req.params;
540
+ const { status, comment, changedBy } = req.body;
541
+
542
+ const client = await pool.connect();
543
+ try {
544
+ await client.query('BEGIN');
545
+
546
+ // Get current status
547
+ const current = await client.query('SELECT status FROM feedback WHERE id = $1', [id]);
548
+ const oldStatus = current.rows[0]?.status;
549
+
550
+ // Update status
551
+ await client.query('UPDATE feedback SET status = $1 WHERE id = $2', [status, id]);
552
+
553
+ // Record history
554
+ await client.query(`
555
+ INSERT INTO feedback_status_history (feedback_id, old_status, new_status, comment, changed_by)
556
+ VALUES ($1, $2, $3, $4, $5)
557
+ `, [id, oldStatus, status, comment, changedBy]);
558
+
559
+ await client.query('COMMIT');
560
+ res.json({ success: true });
561
+ } catch (error) {
562
+ await client.query('ROLLBACK');
563
+ res.status(500).json({ error: error.message });
564
+ } finally {
565
+ client.release();
566
+ }
567
+ });
196
568
  ```
197
569
 
198
- ## Status Options
570
+ ## Keyboard Shortcuts
199
571
 
200
- The dashboard includes 7 professional status options:
572
+ | Shortcut | Action |
573
+ |----------|--------|
574
+ | `Alt+Q` | Activate feedback mode (element selection) |
575
+ | `Alt+W` | Start screen recording |
576
+ | `Alt+Shift+Q` | Open dashboard |
577
+ | `Esc` | Cancel/close |
201
578
 
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 |
579
+ ## Status System
211
580
 
212
- ## API Reference
581
+ ### Default Statuses
213
582
 
214
- ### FeedbackProvider Props
583
+ | Key | Label | Color | Icon |
584
+ |-----|-------|-------|------|
585
+ | `new` | New | Purple (#8b5cf6) | Inbox |
586
+ | `open` | Open | Amber (#f59e0b) | AlertCircle |
587
+ | `inProgress` | In Progress | Blue (#3b82f6) | Play |
588
+ | `underReview` | Under Review | Cyan (#06b6d4) | Eye |
589
+ | `onHold` | On Hold | Gray (#6b7280) | PauseCircle |
590
+ | `resolved` | Resolved | Green (#10b981) | CheckCircle |
591
+ | `closed` | Closed | Slate (#64748b) | Archive |
592
+ | `wontFix` | Won't Fix | Red (#ef4444) | Ban |
215
593
 
216
- | Prop | Type | Required | Default | Description |
217
- |------|------|----------|---------|-------------|
218
- | `onSubmit` | `(feedbackData) => Promise<void>` | Yes | - | Callback when feedback is submitted |
219
- | `onStatusChange` | `({ id, status, comment }) => void` | No | - | Callback when status changes (includes optional developer comment) |
220
- | `children` | `ReactNode` | Yes | - | Your app components |
221
- | `dashboard` | `boolean` | No | `false` | Enable dashboard feature |
222
- | `dashboardData` | `Array` | No | `undefined` | Custom feedback data (uses localStorage if undefined) |
223
- | `isDeveloper` | `boolean` | No | `false` | Enable developer mode with full permissions |
224
- | `isUser` | `boolean` | No | `true` | Enable user mode (read-only) |
225
- | `userName` | `string` | No | `'Anonymous'` | User name for feedback submissions |
226
- | `userEmail` | `string` | No | `null` | User email for feedback submissions |
227
- | `mode` | `'light' \| 'dark'` | No | `'light'` | Theme mode |
594
+ ### Custom Statuses
228
595
 
229
- ### useFeedback Hook
596
+ You control which statuses are available. Pass your own `statuses` object to `FeedbackDashboard`. The widget will only show the statuses you provide:
230
597
 
231
598
  ```jsx
232
- const { isActive, setIsActive, setIsDashboardOpen } = useFeedback();
599
+ import { FeedbackDashboard } from 'react-visual-feedback';
600
+
601
+ // Define ONLY the statuses you want to show
602
+ const myStatuses = {
603
+ pending: {
604
+ key: 'pending',
605
+ label: 'Pending Review',
606
+ color: '#f59e0b',
607
+ bgColor: '#fef3c7',
608
+ textColor: '#92400e',
609
+ icon: 'AlertCircle'
610
+ },
611
+ approved: {
612
+ key: 'approved',
613
+ label: 'Approved',
614
+ color: '#10b981',
615
+ bgColor: '#d1fae5',
616
+ textColor: '#065f46',
617
+ icon: 'CheckCircle'
618
+ },
619
+ rejected: {
620
+ key: 'rejected',
621
+ label: 'Rejected',
622
+ color: '#ef4444',
623
+ bgColor: '#fee2e2',
624
+ textColor: '#991b1b',
625
+ icon: 'Ban'
626
+ }
627
+ };
628
+
629
+ <FeedbackDashboard
630
+ isOpen={true}
631
+ statuses={myStatuses} // Only these 3 statuses will be shown
632
+ // ... other props
633
+ />
233
634
  ```
234
635
 
235
- **Returns:**
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
636
+ #### Extending Default Statuses
637
+
638
+ If you want to keep the defaults and add more:
239
639
 
240
- ### FeedbackUpdatesNotification Props
640
+ ```jsx
641
+ import { DEFAULT_STATUSES } from 'react-visual-feedback';
642
+
643
+ const extendedStatuses = {
644
+ ...DEFAULT_STATUSES,
645
+ // Add new status
646
+ testing: {
647
+ key: 'testing',
648
+ label: 'In Testing',
649
+ color: '#8b5cf6',
650
+ bgColor: '#ede9fe',
651
+ textColor: '#6d28d9',
652
+ icon: 'Bug'
653
+ },
654
+ // Override existing
655
+ resolved: {
656
+ ...DEFAULT_STATUSES.resolved,
657
+ label: 'Fixed & Deployed'
658
+ }
659
+ };
660
+ ```
241
661
 
242
- | Prop | Type | Required | Default | Description |
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 |
662
+ ### Status Object Structure
249
663
 
250
- **Update Object Structure:**
251
664
  ```typescript
252
- {
253
- id: string,
254
- title?: string,
255
- feedback: string,
256
- status: 'reported' | 'opened' | 'inProgress' | 'resolved' | 'released' | 'blocked' | 'wontFix',
257
- responseMessage?: string,
258
- assignedTo?: string,
259
- resolvedBy?: string,
260
- estimatedResolutionDate?: string,
261
- updatedAt: string,
262
- createdAt?: string
665
+ interface StatusConfig {
666
+ key: string; // Unique identifier
667
+ label: string; // Display name in UI
668
+ color: string; // Border and icon color (hex)
669
+ bgColor: string; // Background color (hex)
670
+ textColor: string; // Text color (hex)
671
+ icon: string; // Icon name from available icons
263
672
  }
264
673
  ```
265
674
 
266
- ### Feedback Data Structure
675
+ ### Available Icons
676
+
677
+ The following icons from Lucide React are available for custom statuses:
678
+
679
+ | Icon Name | Description |
680
+ |-----------|-------------|
681
+ | `Inbox` | New items |
682
+ | `AlertCircle` | Warnings/alerts |
683
+ | `Play` | In progress |
684
+ | `Eye` | Under review |
685
+ | `PauseCircle` | Paused/on hold |
686
+ | `CheckCircle` | Completed/resolved |
687
+ | `Archive` | Archived/closed |
688
+ | `Ban` | Rejected/won't fix |
689
+ | `XCircle` | Cancelled |
690
+ | `HelpCircle` | Questions |
691
+ | `Lightbulb` | Ideas/features |
692
+ | `Bug` | Bug reports |
693
+ | `Zap` | Quick actions |
694
+ | `MessageSquare` | Comments |
695
+
696
+ ### Status Utility Functions
267
697
 
268
- ```typescript
269
- {
270
- id: string,
271
- feedback: string,
272
- userName: string,
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
289
- }
698
+ ```jsx
699
+ import {
700
+ getStatusData, // Get status config with safe defaults
701
+ getIconComponent, // Get icon component from name
702
+ normalizeStatusKey, // Normalize status key to available options
703
+ StatusBadge, // Status badge component
704
+ StatusDropdown // Status dropdown component
705
+ } from 'react-visual-feedback';
706
+
707
+ // Get status data with fallbacks for missing properties
708
+ const statusData = getStatusData('inProgress', customStatuses);
709
+ // Returns: { key, label, color, bgColor, textColor, icon }
710
+
711
+ // Get icon component from string name
712
+ const Icon = getIconComponent('CheckCircle');
713
+ // Returns: Lucide React component
714
+
715
+ // Normalize various status formats to valid keys
716
+ const key = normalizeStatusKey('in_progress', customStatuses);
717
+ // Returns: 'inProgress'
290
718
  ```
291
719
 
292
- ## Keyboard Shortcuts
720
+ ### Status Key Normalization
293
721
 
294
- | Shortcut | Action |
295
- |----------|--------|
296
- | `Ctrl+Q` | Activate feedback mode |
297
- | `Ctrl+Shift+Q` | Open dashboard (when dashboard is enabled) |
298
- | `Esc` | Cancel/close feedback mode or dashboard |
299
- | `Ctrl+Enter` | Submit feedback (when form is open) |
300
-
301
- ## Developer vs User Mode
302
-
303
- ### Developer Mode (`isDeveloper={true}`)
304
- - ✅ View all technical details (element info, CSS, viewport, user agent)
305
- - ✅ Change feedback status with optional comments
306
- - ✅ Delete feedback items
307
- - ✅ Full control over feedback management
308
-
309
- ### User Mode (`isDeveloper={false}`)
310
- - ✅ View feedback submissions
311
- - ✅ See current status and developer responses
312
- - ❌ Cannot change status
313
- - ❌ Cannot delete items
314
-
315
- ## How It Works
316
-
317
- ### Feedback Collection Flow
318
-
319
- 1. User activates the widget (Ctrl+Q or button click)
320
- 2. User hovers over elements to see them highlighted
321
- 3. User clicks on the problematic element
322
- 4. Widget captures a pixel-perfect screenshot
323
- 5. Feedback form appears with context pre-filled
324
- 6. User enters feedback and submits
325
- 7. Your `onSubmit` handler receives all data
326
-
327
- ### Dashboard Flow
328
-
329
- 1. User opens dashboard (Ctrl+Shift+Q or programmatically)
330
- 2. Dashboard displays all feedback submissions
331
- 3. Developer clicks status dropdown and selects new status
332
- 4. Status change modal appears for optional developer comment
333
- 5. Developer adds comment (optional) and confirms
334
- 6. `onStatusChange` callback triggered with `{ id, status, comment }`
335
- 7. Your backend updates the database
336
- 8. Dashboard reflects the new status
722
+ The widget automatically normalizes various status key formats:
337
723
 
338
- ## Browser Support
724
+ | Input | Normalized To |
725
+ |-------|---------------|
726
+ | `reported`, `submitted`, `pending` | `new` |
727
+ | `doing`, `in_progress` | `inProgress` |
728
+ | `review`, `under_review` | `underReview` |
729
+ | `hold`, `on_hold`, `paused` | `onHold` |
730
+ | `done`, `fixed`, `completed` | `resolved` |
731
+ | `archived` | `closed` |
732
+ | `rejected`, `wont_fix`, `cancelled` | `wontFix` |
339
733
 
340
- - Chrome/Edge
341
- - ✅ Firefox
342
- - ✅ Safari
343
- - ✅ Opera
734
+ ## Next.js Usage
344
735
 
345
- ## Local Development
736
+ This package uses browser-only APIs and requires client-side rendering. Use dynamic import with `ssr: false`:
346
737
 
347
- ```bash
348
- # Clone repository
349
- git clone https://github.com/Murali1889/react-feedback-widget.git
350
- cd react-feedback-widget
738
+ ```tsx
739
+ // providers/FeedbackProviderClient.tsx
740
+ 'use client';
351
741
 
352
- # Install dependencies
353
- npm install
742
+ import dynamic from 'next/dynamic';
354
743
 
355
- # Build the widget
356
- npm run build
744
+ const FeedbackProvider = dynamic(
745
+ () => import('react-visual-feedback').then((mod) => mod.FeedbackProvider),
746
+ { ssr: false }
747
+ );
357
748
 
358
- # Run example app
359
- cd example
360
- npm install
361
- npm run dev
749
+ export default function FeedbackProviderClient({
750
+ children,
751
+ ...props
752
+ }: {
753
+ children: React.ReactNode;
754
+ [key: string]: any;
755
+ }) {
756
+ return (
757
+ <FeedbackProvider {...props}>
758
+ {children}
759
+ </FeedbackProvider>
760
+ );
761
+ }
362
762
  ```
363
763
 
364
- Visit `http://localhost:8080` to see the demo!
764
+ Then use in your layout:
365
765
 
366
- ## Components Exported
766
+ ```tsx
767
+ // app/layout.tsx
768
+ import FeedbackProviderClient from './providers/FeedbackProviderClient';
769
+
770
+ export default function RootLayout({ children }) {
771
+ return (
772
+ <html>
773
+ <body>
774
+ <FeedbackProviderClient
775
+ onSubmit={async (data) => {
776
+ await fetch('/api/feedback', {
777
+ method: 'POST',
778
+ body: JSON.stringify(data)
779
+ });
780
+ }}
781
+ dashboard={true}
782
+ mode="light"
783
+ >
784
+ {children}
785
+ </FeedbackProviderClient>
786
+ </body>
787
+ </html>
788
+ );
789
+ }
790
+ ```
791
+
792
+ ## Browser Support
793
+
794
+ - Chrome/Edge (recommended for screen recording)
795
+ - Firefox
796
+ - Safari
797
+ - Opera
798
+
799
+ ## All Exports
367
800
 
368
801
  ```jsx
369
802
  import {
370
- FeedbackProvider, // Main provider component
371
- useFeedback, // Hook to control feedback state
372
- FeedbackUpdatesNotification // Notification component for updates
803
+ // Core components
804
+ FeedbackProvider,
805
+ FeedbackModal,
806
+ FeedbackDashboard,
807
+ FeedbackTrigger,
808
+ CanvasOverlay,
809
+
810
+ // Hooks
811
+ useFeedback,
812
+
813
+ // Status components
814
+ StatusBadge,
815
+ StatusDropdown,
816
+
817
+ // Status utilities
818
+ getStatusData,
819
+ getIconComponent,
820
+ normalizeStatusKey,
821
+ DEFAULT_STATUSES,
822
+
823
+ // Storage utilities
824
+ saveFeedbackToLocalStorage,
825
+
826
+ // Theme utilities
827
+ getTheme,
828
+ lightTheme,
829
+ darkTheme,
830
+
831
+ // General utilities
832
+ getElementInfo,
833
+ captureElementScreenshot,
834
+ getReactComponentInfo,
835
+ formatPath
373
836
  } from 'react-visual-feedback';
374
837
  ```
375
838
 
376
- ## What's New in v1.3.0
839
+ ## Changelog
377
840
 
378
- ### Status Change with Comments
379
- - 💬 Developers can add optional comments when changing status
380
- - 📝 Comments are passed to `onStatusChange` callback
381
- - 👥 Comments visible to users as developer responses
841
+ ### v1.4.2
842
+ - **Fixed**: Modal state not resetting after submission (was showing "Sending..." on reopen)
843
+ - **Added**: `Alt+W` keyboard shortcut for video recording
844
+ - **Improved**: Custom status documentation - clarified that users control which statuses appear
845
+ - **Fixed**: Prevented double submission by checking `isSubmitting` state
382
846
 
383
- ### Update Notifications Component
384
- - 🔔 New `FeedbackUpdatesNotification` component
385
- - 📬 Show users updates on their feedback
386
- - 🎨 Beautifully grouped by status
387
- - 👋 Dismiss individual or all updates
847
+ ### v1.4.1
848
+ - **Fixed**: `Cannot read properties of undefined (reading 'icon')` error when status data is malformed
849
+ - **Added**: `getStatusData()` utility function for safe status access with defaults
850
+ - **Improved**: Defensive null checks in StatusBadge, StatusDropdown, and FeedbackDashboard
851
+ - **Added**: Export of status utility functions for custom implementations
388
852
 
389
- ## License
853
+ ### v1.4.0
854
+ - **Added**: Screen recording with session replay
855
+ - **Added**: Console and network log capture during recording
856
+ - **Added**: Session replay component with expandable logs panel
857
+ - **Improved**: Dashboard UI with video playback support
390
858
 
391
- MIT © 2025 Murali Vvrsn Gurajapu
859
+ ### v1.3.0
860
+ - **Added**: Canvas drawing and annotation support
861
+ - **Added**: Download all feedback as ZIP
392
862
 
393
- ## Contributing
863
+ ### v1.2.0
864
+ - **Added**: Custom status configurations
865
+ - **Added**: Status normalization for various formats
394
866
 
395
- Contributions welcome! Please submit a Pull Request.
867
+ ### v1.1.0
868
+ - **Added**: Dark mode support
869
+ - **Added**: Developer/User mode views
396
870
 
397
- ## Issues
871
+ ### v1.0.0
872
+ - Initial release with element selection and screenshot capture
873
+
874
+ ## License
398
875
 
399
- Report issues at: https://github.com/Murali1889/react-feedback-widget/issues
876
+ MIT
400
877
 
401
878
  ## Author
402
879
 
@@ -405,4 +882,4 @@ Email: murali.g@hyperverge.co
405
882
 
406
883
  ---
407
884
 
408
- Made with ❤️ for better user feedback collection
885
+ Made with care for better user feedback collection