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 +731 -254
- 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,771 @@ 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
|
}
|
|
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
|
-
|
|
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
|
-
###
|
|
171
|
+
### SessionReplay Props (for custom implementations)
|
|
134
172
|
|
|
135
173
|
```jsx
|
|
136
|
-
import
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
570
|
+
## Keyboard Shortcuts
|
|
199
571
|
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
581
|
+
### Default Statuses
|
|
213
582
|
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
###
|
|
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
|
-
```
|
|
269
|
-
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
720
|
+
### Status Key Normalization
|
|
293
721
|
|
|
294
|
-
|
|
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
|
-
|
|
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
|
-
|
|
341
|
-
- ✅ Firefox
|
|
342
|
-
- ✅ Safari
|
|
343
|
-
- ✅ Opera
|
|
734
|
+
## Next.js Usage
|
|
344
735
|
|
|
345
|
-
|
|
736
|
+
This package uses browser-only APIs and requires client-side rendering. Use dynamic import with `ssr: false`:
|
|
346
737
|
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
cd react-feedback-widget
|
|
738
|
+
```tsx
|
|
739
|
+
// providers/FeedbackProviderClient.tsx
|
|
740
|
+
'use client';
|
|
351
741
|
|
|
352
|
-
|
|
353
|
-
npm install
|
|
742
|
+
import dynamic from 'next/dynamic';
|
|
354
743
|
|
|
355
|
-
|
|
356
|
-
|
|
744
|
+
const FeedbackProvider = dynamic(
|
|
745
|
+
() => import('react-visual-feedback').then((mod) => mod.FeedbackProvider),
|
|
746
|
+
{ ssr: false }
|
|
747
|
+
);
|
|
357
748
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
764
|
+
Then use in your layout:
|
|
365
765
|
|
|
366
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
##
|
|
839
|
+
## Changelog
|
|
377
840
|
|
|
378
|
-
###
|
|
379
|
-
-
|
|
380
|
-
-
|
|
381
|
-
-
|
|
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
|
-
###
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
-
|
|
387
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
859
|
+
### v1.3.0
|
|
860
|
+
- **Added**: Canvas drawing and annotation support
|
|
861
|
+
- **Added**: Download all feedback as ZIP
|
|
392
862
|
|
|
393
|
-
|
|
863
|
+
### v1.2.0
|
|
864
|
+
- **Added**: Custom status configurations
|
|
865
|
+
- **Added**: Status normalization for various formats
|
|
394
866
|
|
|
395
|
-
|
|
867
|
+
### v1.1.0
|
|
868
|
+
- **Added**: Dark mode support
|
|
869
|
+
- **Added**: Developer/User mode views
|
|
396
870
|
|
|
397
|
-
|
|
871
|
+
### v1.0.0
|
|
872
|
+
- Initial release with element selection and screenshot capture
|
|
873
|
+
|
|
874
|
+
## License
|
|
398
875
|
|
|
399
|
-
|
|
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
|
|
885
|
+
Made with care for better user feedback collection
|