@product7/feedback-sdk 1.0.5 → 1.0.7
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/dist/feedback-sdk.js +327 -278
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/FeedbackSDK.js +1 -1
- package/src/docs/api.md +104 -598
- package/src/docs/example.md +317 -681
- package/src/docs/installation.md +195 -187
- package/src/styles/styles.js +202 -201
- package/src/widgets/BaseWidget.js +127 -79
- package/src/widgets/ButtonWidget.js +2 -2
package/src/docs/example.md
CHANGED
|
@@ -1,823 +1,459 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## 🚀 Quick Start Examples
|
|
6
|
-
|
|
7
|
-
### Basic Button Widget
|
|
8
|
-
|
|
9
|
-
The simplest implementation - just a feedback button:
|
|
10
|
-
|
|
11
|
-
```html
|
|
12
|
-
<!DOCTYPE html>
|
|
13
|
-
<html>
|
|
14
|
-
<head>
|
|
15
|
-
<title>Basic Feedback Button</title>
|
|
16
|
-
</head>
|
|
17
|
-
<body>
|
|
18
|
-
<h1>My Website</h1>
|
|
19
|
-
<p>This is my awesome website content...</p>
|
|
20
|
-
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/@product7/feedback-sdk@1/dist/feedback-sdk.min.js"></script>
|
|
22
|
-
<script>
|
|
23
|
-
const feedback = new FeedbackSDK({
|
|
24
|
-
workspace: 'my-company',
|
|
25
|
-
boardId: 'general-feedback',
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
feedback.init().then(() => {
|
|
29
|
-
const widget = feedback.createWidget('button');
|
|
30
|
-
widget.mount();
|
|
31
|
-
});
|
|
32
|
-
</script>
|
|
33
|
-
</body>
|
|
34
|
-
</html>
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Auto-Initialization
|
|
38
|
-
|
|
39
|
-
Let the SDK set itself up automatically:
|
|
40
|
-
|
|
41
|
-
```html
|
|
42
|
-
<script>
|
|
43
|
-
// Configure before loading the SDK
|
|
44
|
-
window.FeedbackSDKConfig = {
|
|
45
|
-
workspace: 'my-company',
|
|
46
|
-
boardId: 'general-feedback',
|
|
47
|
-
theme: 'light',
|
|
48
|
-
autoCreate: {
|
|
49
|
-
type: 'button',
|
|
50
|
-
position: 'bottom-right',
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
</script>
|
|
54
|
-
<script src="https://cdn.jsdelivr.net/npm/@product7/feedback-sdk@1/dist/feedback-sdk.min.js"></script>
|
|
55
|
-
<!-- Widget appears automatically! -->
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## 🎨 Widget Customization Examples
|
|
59
|
-
|
|
60
|
-
### Multiple Widgets
|
|
3
|
+
## Basic Usage
|
|
61
4
|
|
|
62
|
-
|
|
5
|
+
### Simple Setup
|
|
63
6
|
|
|
64
7
|
```javascript
|
|
65
|
-
|
|
66
|
-
workspace: 'my-company',
|
|
67
|
-
debug: true,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await feedback.init();
|
|
8
|
+
import FeedbackSDK from '@product7/feedback-sdk';
|
|
71
9
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
10
|
+
const sdk = new FeedbackSDK({
|
|
11
|
+
workspace: 'my-workspace',
|
|
12
|
+
userContext: {
|
|
13
|
+
user_id: 'user_123',
|
|
14
|
+
email: 'user@example.com'
|
|
15
|
+
}
|
|
76
16
|
});
|
|
77
|
-
generalButton.mount();
|
|
78
17
|
|
|
79
|
-
|
|
80
|
-
const bugTab = feedback.createWidget('tab', {
|
|
81
|
-
position: 'bottom-left',
|
|
82
|
-
boardId: 'bug-reports',
|
|
83
|
-
});
|
|
84
|
-
bugTab.mount();
|
|
18
|
+
await sdk.init();
|
|
85
19
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
boardId: 'feature-requests',
|
|
89
|
-
});
|
|
90
|
-
footerForm.mount('#footer-feedback');
|
|
20
|
+
const widget = sdk.createWidget('button');
|
|
21
|
+
widget.mount();
|
|
91
22
|
```
|
|
92
23
|
|
|
93
|
-
|
|
24
|
+
---
|
|
94
25
|
|
|
95
|
-
|
|
26
|
+
## User Context
|
|
96
27
|
|
|
97
|
-
|
|
98
|
-
<style>
|
|
99
|
-
/* Custom button color */
|
|
100
|
-
.feedback-widget {
|
|
101
|
-
--feedback-primary-color: #10b981;
|
|
102
|
-
--feedback-primary-hover: #059669;
|
|
103
|
-
}
|
|
28
|
+
### Basic User Context
|
|
104
29
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
30
|
+
```javascript
|
|
31
|
+
const sdk = new FeedbackSDK({
|
|
32
|
+
workspace: 'my-workspace',
|
|
33
|
+
userContext: {
|
|
34
|
+
user_id: 'user_123',
|
|
35
|
+
email: 'user@example.com',
|
|
36
|
+
name: 'John Doe'
|
|
109
37
|
}
|
|
38
|
+
});
|
|
39
|
+
```
|
|
110
40
|
|
|
111
|
-
|
|
112
|
-
.feedback-widget {
|
|
113
|
-
--feedback-font-family: 'Poppins', sans-serif;
|
|
114
|
-
}
|
|
41
|
+
### With Custom Fields
|
|
115
42
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
43
|
+
```javascript
|
|
44
|
+
userContext: {
|
|
45
|
+
user_id: 'user_123',
|
|
46
|
+
email: 'user@example.com',
|
|
47
|
+
name: 'John Doe',
|
|
48
|
+
custom_fields: {
|
|
49
|
+
role: 'admin',
|
|
50
|
+
plan: 'pro',
|
|
51
|
+
signup_date: '2024-01-01'
|
|
119
52
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<script>
|
|
123
|
-
const feedback = new FeedbackSDK({
|
|
124
|
-
workspace: 'my-company',
|
|
125
|
-
boardId: 'styled-feedback',
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
feedback.init().then(() => {
|
|
129
|
-
const widget = feedback.createWidget('button');
|
|
130
|
-
widget.mount();
|
|
131
|
-
});
|
|
132
|
-
</script>
|
|
53
|
+
}
|
|
133
54
|
```
|
|
134
55
|
|
|
135
|
-
###
|
|
136
|
-
|
|
137
|
-
Switch themes based on user preference:
|
|
56
|
+
### With Company Data
|
|
138
57
|
|
|
139
58
|
```javascript
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
59
|
+
userContext: {
|
|
60
|
+
user_id: 'user_123',
|
|
61
|
+
email: 'user@example.com',
|
|
62
|
+
name: 'John Doe',
|
|
63
|
+
company: {
|
|
64
|
+
id: 'company_456',
|
|
65
|
+
name: 'Acme Corp',
|
|
66
|
+
monthly_spend: 1000
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
150
70
|
|
|
151
|
-
|
|
152
|
-
function toggleTheme() {
|
|
153
|
-
const isDark = document.body.classList.contains('dark-mode');
|
|
154
|
-
const newTheme = isDark ? 'light' : 'dark';
|
|
71
|
+
### Extract from Auth
|
|
155
72
|
|
|
156
|
-
|
|
157
|
-
|
|
73
|
+
```javascript
|
|
74
|
+
const authData = {
|
|
75
|
+
sub: 'user_123',
|
|
76
|
+
email: 'user@example.com',
|
|
77
|
+
name: 'John Doe',
|
|
78
|
+
role: 'admin'
|
|
79
|
+
};
|
|
158
80
|
|
|
159
|
-
|
|
160
|
-
document.body.classList.toggle('dark-mode');
|
|
161
|
-
}
|
|
81
|
+
const userContext = FeedbackSDK.extractUserContextFromAuth(authData);
|
|
162
82
|
|
|
163
|
-
|
|
164
|
-
|
|
83
|
+
const sdk = new FeedbackSDK({
|
|
84
|
+
workspace: 'my-workspace',
|
|
85
|
+
userContext
|
|
86
|
+
});
|
|
165
87
|
```
|
|
166
88
|
|
|
167
|
-
|
|
89
|
+
---
|
|
168
90
|
|
|
169
|
-
|
|
91
|
+
## Widget Customization
|
|
170
92
|
|
|
171
|
-
|
|
93
|
+
### Different Positions
|
|
172
94
|
|
|
173
95
|
```javascript
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
96
|
+
// Bottom right (default)
|
|
97
|
+
const widget1 = sdk.createWidget('button', {
|
|
98
|
+
position: 'bottom-right'
|
|
177
99
|
});
|
|
178
100
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Mobile: Use tab widget to save space
|
|
184
|
-
const mobileWidget = feedback.createWidget('tab', {
|
|
185
|
-
position: 'bottom-right',
|
|
186
|
-
});
|
|
187
|
-
mobileWidget.mount();
|
|
188
|
-
} else {
|
|
189
|
-
// Desktop: Use button widget
|
|
190
|
-
const desktopWidget = feedback.createWidget('button', {
|
|
191
|
-
position: 'bottom-right',
|
|
192
|
-
});
|
|
193
|
-
desktopWidget.mount();
|
|
194
|
-
}
|
|
101
|
+
// Bottom left
|
|
102
|
+
const widget2 = sdk.createWidget('button', {
|
|
103
|
+
position: 'bottom-left'
|
|
104
|
+
});
|
|
195
105
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
106
|
+
// Top right
|
|
107
|
+
const widget3 = sdk.createWidget('button', {
|
|
108
|
+
position: 'top-right'
|
|
109
|
+
});
|
|
200
110
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
} else {
|
|
205
|
-
const desktopWidget = feedback.createWidget('button');
|
|
206
|
-
desktopWidget.mount();
|
|
207
|
-
}
|
|
111
|
+
// Top left
|
|
112
|
+
const widget4 = sdk.createWidget('button', {
|
|
113
|
+
position: 'top-left'
|
|
208
114
|
});
|
|
209
115
|
```
|
|
210
116
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
### Analytics Integration
|
|
214
|
-
|
|
215
|
-
Track feedback events with your analytics:
|
|
117
|
+
### Dark Theme
|
|
216
118
|
|
|
217
119
|
```javascript
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
boardId: 'analytics-feedback',
|
|
120
|
+
const widget = sdk.createWidget('button', {
|
|
121
|
+
theme: 'dark'
|
|
221
122
|
});
|
|
123
|
+
widget.mount();
|
|
124
|
+
```
|
|
222
125
|
|
|
223
|
-
|
|
126
|
+
### Custom Board
|
|
224
127
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
gtag('event', 'feedback_widget_loaded', {
|
|
229
|
-
widget_type: data.widget.type,
|
|
230
|
-
widget_id: data.widget.id,
|
|
231
|
-
});
|
|
128
|
+
```javascript
|
|
129
|
+
const widget = sdk.createWidget('button', {
|
|
130
|
+
boardId: 'feature-requests'
|
|
232
131
|
});
|
|
132
|
+
widget.mount();
|
|
133
|
+
```
|
|
233
134
|
|
|
234
|
-
|
|
235
|
-
// Track successful submission
|
|
236
|
-
gtag('event', 'feedback_submitted', {
|
|
237
|
-
widget_type: data.widget.type,
|
|
238
|
-
feedback_length: data.feedback.content?.length || 0,
|
|
239
|
-
});
|
|
135
|
+
### Multiple Widgets
|
|
240
136
|
|
|
241
|
-
|
|
242
|
-
|
|
137
|
+
```javascript
|
|
138
|
+
// Feedback button
|
|
139
|
+
const feedbackWidget = sdk.createWidget('button', {
|
|
140
|
+
position: 'bottom-right',
|
|
141
|
+
boardId: 'general'
|
|
243
142
|
});
|
|
143
|
+
feedbackWidget.mount();
|
|
244
144
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
error_message: error.error.message,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Show error message
|
|
253
|
-
showToast('Failed to send feedback. Please try again.', 'error');
|
|
145
|
+
// Bug report button
|
|
146
|
+
const bugWidget = sdk.createWidget('button', {
|
|
147
|
+
position: 'bottom-left',
|
|
148
|
+
boardId: 'bug-reports'
|
|
254
149
|
});
|
|
150
|
+
bugWidget.updateText('Report Bug');
|
|
151
|
+
bugWidget.mount();
|
|
152
|
+
```
|
|
255
153
|
|
|
256
|
-
|
|
154
|
+
### Update Widget
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const widget = sdk.createWidget('button');
|
|
257
158
|
widget.mount();
|
|
258
159
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
160
|
+
// Update text
|
|
161
|
+
widget.updateText('Send Feedback');
|
|
162
|
+
|
|
163
|
+
// Update position
|
|
164
|
+
widget.updatePosition('top-right');
|
|
165
|
+
|
|
166
|
+
// Show/hide
|
|
167
|
+
widget.hide();
|
|
168
|
+
widget.show();
|
|
263
169
|
```
|
|
264
170
|
|
|
265
|
-
|
|
171
|
+
---
|
|
266
172
|
|
|
267
|
-
|
|
173
|
+
## Event Handling
|
|
268
174
|
|
|
269
|
-
|
|
270
|
-
// Get user info from your auth system
|
|
271
|
-
const currentUser = getCurrentUser();
|
|
175
|
+
### Track Submissions
|
|
272
176
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
177
|
+
```javascript
|
|
178
|
+
sdk.on('feedback:submitted', ({ widget, feedback }) => {
|
|
179
|
+
console.log('Feedback submitted:', feedback.id);
|
|
180
|
+
|
|
181
|
+
// Track in analytics
|
|
182
|
+
analytics.track('Feedback Submitted', {
|
|
183
|
+
feedbackId: feedback.id,
|
|
184
|
+
board: feedback.board
|
|
185
|
+
});
|
|
277
186
|
});
|
|
187
|
+
```
|
|
278
188
|
|
|
279
|
-
|
|
189
|
+
### Handle Errors
|
|
280
190
|
|
|
281
|
-
|
|
282
|
-
|
|
191
|
+
```javascript
|
|
192
|
+
sdk.on('feedback:error', ({ widget, error }) => {
|
|
193
|
+
console.error('Submission failed:', error.message);
|
|
194
|
+
|
|
195
|
+
// Show custom error notification
|
|
196
|
+
toast.error('Failed to submit feedback. Please try again.');
|
|
197
|
+
});
|
|
198
|
+
```
|
|
283
199
|
|
|
284
|
-
|
|
285
|
-
if (currentUser?.email) {
|
|
286
|
-
feedback.eventBus.on('widget:mounted', () => {
|
|
287
|
-
// Pre-fill email field
|
|
288
|
-
const emailInput = document.querySelector('input[name="email"]');
|
|
289
|
-
if (emailInput) {
|
|
290
|
-
emailInput.value = currentUser.email;
|
|
291
|
-
emailInput.disabled = true; // Prevent editing
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
}
|
|
200
|
+
### Widget Lifecycle
|
|
295
201
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
console.log(`Feedback from ${currentUser.email}:`, data.feedback);
|
|
202
|
+
```javascript
|
|
203
|
+
sdk.on('widget:mounted', ({ widget }) => {
|
|
204
|
+
console.log('Widget mounted:', widget.id);
|
|
300
205
|
});
|
|
301
|
-
```
|
|
302
206
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
```jsx
|
|
308
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
309
|
-
import { FeedbackSDK } from '@product7/feedback-sdk';
|
|
310
|
-
|
|
311
|
-
const FeedbackWidget = ({ workspace, boardId, type = 'button' }) => {
|
|
312
|
-
const [sdk, setSdk] = useState(null);
|
|
313
|
-
const [widget, setWidget] = useState(null);
|
|
314
|
-
const containerRef = useRef(null);
|
|
315
|
-
|
|
316
|
-
useEffect(() => {
|
|
317
|
-
// Initialize SDK
|
|
318
|
-
const initSDK = async () => {
|
|
319
|
-
const feedbackSDK = new FeedbackSDK({
|
|
320
|
-
workspace,
|
|
321
|
-
boardId,
|
|
322
|
-
debug: process.env.NODE_ENV === 'development',
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
await feedbackSDK.init();
|
|
326
|
-
setSdk(feedbackSDK);
|
|
327
|
-
|
|
328
|
-
// Create and mount widget
|
|
329
|
-
const feedbackWidget = feedbackSDK.createWidget(type);
|
|
330
|
-
if (type === 'inline' && containerRef.current) {
|
|
331
|
-
feedbackWidget.mount(containerRef.current);
|
|
332
|
-
} else {
|
|
333
|
-
feedbackWidget.mount();
|
|
334
|
-
}
|
|
335
|
-
setWidget(feedbackWidget);
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
initSDK();
|
|
339
|
-
|
|
340
|
-
// Cleanup
|
|
341
|
-
return () => {
|
|
342
|
-
if (widget) widget.destroy();
|
|
343
|
-
if (sdk) sdk.destroy();
|
|
344
|
-
};
|
|
345
|
-
}, [workspace, boardId, type]);
|
|
346
|
-
|
|
347
|
-
// For inline widgets, provide a container
|
|
348
|
-
if (type === 'inline') {
|
|
349
|
-
return <div ref={containerRef} className="feedback-container" />;
|
|
350
|
-
}
|
|
207
|
+
sdk.on('widget:destroyed', ({ widget }) => {
|
|
208
|
+
console.log('Widget destroyed:', widget.id);
|
|
209
|
+
});
|
|
210
|
+
```
|
|
351
211
|
|
|
352
|
-
|
|
353
|
-
return null;
|
|
354
|
-
};
|
|
212
|
+
### Multiple Events
|
|
355
213
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
{/* Button widget */}
|
|
363
|
-
<FeedbackWidget
|
|
364
|
-
workspace="my-company"
|
|
365
|
-
boardId="react-feedback"
|
|
366
|
-
type="button"
|
|
367
|
-
/>
|
|
368
|
-
|
|
369
|
-
{/* Inline widget in footer */}
|
|
370
|
-
<footer>
|
|
371
|
-
<FeedbackWidget
|
|
372
|
-
workspace="my-company"
|
|
373
|
-
boardId="footer-feedback"
|
|
374
|
-
type="inline"
|
|
375
|
-
/>
|
|
376
|
-
</footer>
|
|
377
|
-
</div>
|
|
378
|
-
);
|
|
379
|
-
};
|
|
214
|
+
```javascript
|
|
215
|
+
sdk
|
|
216
|
+
.on('feedback:submitted', handleSubmit)
|
|
217
|
+
.on('feedback:error', handleError)
|
|
218
|
+
.on('config:updated', handleConfigUpdate);
|
|
380
219
|
```
|
|
381
220
|
|
|
382
|
-
###
|
|
383
|
-
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
<!-- Container for inline widget -->
|
|
390
|
-
<div v-if="widgetType === 'inline'" ref="feedbackContainer"></div>
|
|
391
|
-
</div>
|
|
392
|
-
</template>
|
|
393
|
-
|
|
394
|
-
<script>
|
|
395
|
-
import { FeedbackSDK } from '@product7/feedback-sdk';
|
|
396
|
-
|
|
397
|
-
export default {
|
|
398
|
-
name: 'FeedbackWidget',
|
|
399
|
-
props: {
|
|
400
|
-
workspace: { type: String, required: true },
|
|
401
|
-
boardId: { type: String, required: true },
|
|
402
|
-
widgetType: { type: String, default: 'button' },
|
|
403
|
-
},
|
|
404
|
-
data() {
|
|
405
|
-
return {
|
|
406
|
-
sdk: null,
|
|
407
|
-
widget: null,
|
|
408
|
-
};
|
|
409
|
-
},
|
|
410
|
-
async mounted() {
|
|
411
|
-
await this.initFeedback();
|
|
412
|
-
},
|
|
413
|
-
beforeUnmount() {
|
|
414
|
-
this.cleanup();
|
|
415
|
-
},
|
|
416
|
-
methods: {
|
|
417
|
-
async initFeedback() {
|
|
418
|
-
this.sdk = new FeedbackSDK({
|
|
419
|
-
workspace: this.workspace,
|
|
420
|
-
boardId: this.boardId,
|
|
421
|
-
debug: process.env.NODE_ENV === 'development',
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
await this.sdk.init();
|
|
425
|
-
|
|
426
|
-
// Listen to events
|
|
427
|
-
this.sdk.eventBus.on('feedback:submitted', this.onFeedbackSubmitted);
|
|
428
|
-
this.sdk.eventBus.on('feedback:error', this.onFeedbackError);
|
|
429
|
-
|
|
430
|
-
// Create widget
|
|
431
|
-
this.widget = this.sdk.createWidget(this.widgetType);
|
|
432
|
-
|
|
433
|
-
if (this.widgetType === 'inline') {
|
|
434
|
-
this.widget.mount(this.$refs.feedbackContainer);
|
|
435
|
-
} else {
|
|
436
|
-
this.widget.mount();
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
onFeedbackSubmitted(data) {
|
|
440
|
-
this.$emit('feedback-submitted', data);
|
|
441
|
-
this.$toast.success('Thank you for your feedback!');
|
|
442
|
-
},
|
|
443
|
-
onFeedbackError(error) {
|
|
444
|
-
this.$emit('feedback-error', error);
|
|
445
|
-
this.$toast.error('Failed to submit feedback');
|
|
446
|
-
},
|
|
447
|
-
cleanup() {
|
|
448
|
-
if (this.widget) this.widget.destroy();
|
|
449
|
-
if (this.sdk) this.sdk.destroy();
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
};
|
|
453
|
-
</script>
|
|
221
|
+
### One-time Events
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
sdk.once('sdk:initialized', (data) => {
|
|
225
|
+
console.log('SDK ready!', data.config);
|
|
226
|
+
});
|
|
454
227
|
```
|
|
455
228
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
// feedback-widget.component.ts
|
|
460
|
-
import {
|
|
461
|
-
Component,
|
|
462
|
-
Input,
|
|
463
|
-
OnInit,
|
|
464
|
-
OnDestroy,
|
|
465
|
-
ElementRef,
|
|
466
|
-
ViewChild,
|
|
467
|
-
} from '@angular/core';
|
|
468
|
-
import { FeedbackSDK } from '@product7/feedback-sdk';
|
|
469
|
-
|
|
470
|
-
@Component({
|
|
471
|
-
selector: 'app-feedback-widget',
|
|
472
|
-
template: `
|
|
473
|
-
<div #feedbackContainer *ngIf="type === 'inline'"></div>
|
|
474
|
-
`,
|
|
475
|
-
styleUrls: ['./feedback-widget.component.css'],
|
|
476
|
-
})
|
|
477
|
-
export class FeedbackWidgetComponent implements OnInit, OnDestroy {
|
|
478
|
-
@Input() workspace: string = '';
|
|
479
|
-
@Input() boardId: string = '';
|
|
480
|
-
@Input() type: string = 'button';
|
|
481
|
-
|
|
482
|
-
@ViewChild('feedbackContainer') containerRef?: ElementRef;
|
|
483
|
-
|
|
484
|
-
private sdk?: FeedbackSDK;
|
|
485
|
-
private widget?: any;
|
|
486
|
-
|
|
487
|
-
async ngOnInit() {
|
|
488
|
-
await this.initializeFeedback();
|
|
489
|
-
}
|
|
229
|
+
---
|
|
490
230
|
|
|
491
|
-
|
|
492
|
-
this.cleanup();
|
|
493
|
-
}
|
|
231
|
+
## Authentication Flows
|
|
494
232
|
|
|
495
|
-
|
|
496
|
-
this.sdk = new FeedbackSDK({
|
|
497
|
-
workspace: this.workspace,
|
|
498
|
-
boardId: this.boardId,
|
|
499
|
-
debug: !environment.production,
|
|
500
|
-
});
|
|
233
|
+
### On Login
|
|
501
234
|
|
|
502
|
-
|
|
235
|
+
```javascript
|
|
236
|
+
async function handleLogin(authResponse) {
|
|
237
|
+
const userContext = FeedbackSDK.extractUserContextFromAuth(authResponse);
|
|
238
|
+
await sdk.reinitialize(userContext);
|
|
239
|
+
|
|
240
|
+
// Create widget after login
|
|
241
|
+
const widget = sdk.createWidget('button');
|
|
242
|
+
widget.mount();
|
|
243
|
+
}
|
|
244
|
+
```
|
|
503
245
|
|
|
504
|
-
|
|
505
|
-
this.sdk.eventBus.on('feedback:submitted', (data) => {
|
|
506
|
-
console.log('Feedback submitted:', data);
|
|
507
|
-
});
|
|
246
|
+
### On Logout
|
|
508
247
|
|
|
509
|
-
|
|
510
|
-
|
|
248
|
+
```javascript
|
|
249
|
+
function handleLogout() {
|
|
250
|
+
sdk.apiService.clearSession();
|
|
251
|
+
sdk.destroyAllWidgets();
|
|
252
|
+
}
|
|
253
|
+
```
|
|
511
254
|
|
|
512
|
-
|
|
513
|
-
this.widget.mount(this.containerRef.nativeElement);
|
|
514
|
-
} else {
|
|
515
|
-
this.widget.mount();
|
|
516
|
-
}
|
|
517
|
-
}
|
|
255
|
+
### Check Session
|
|
518
256
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
257
|
+
```javascript
|
|
258
|
+
if (!sdk.apiService.isSessionValid()) {
|
|
259
|
+
await sdk.init();
|
|
523
260
|
}
|
|
524
261
|
```
|
|
525
262
|
|
|
526
|
-
|
|
263
|
+
---
|
|
527
264
|
|
|
528
|
-
|
|
265
|
+
## Configuration Updates
|
|
529
266
|
|
|
530
|
-
|
|
267
|
+
### Update Theme
|
|
531
268
|
|
|
532
269
|
```javascript
|
|
533
|
-
|
|
534
|
-
|
|
270
|
+
sdk.updateConfig({ theme: 'dark' });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Update Multiple Options
|
|
535
274
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
275
|
+
```javascript
|
|
276
|
+
sdk.updateConfig({
|
|
277
|
+
theme: 'dark',
|
|
278
|
+
position: 'top-right',
|
|
279
|
+
showBackdrop: false
|
|
539
280
|
});
|
|
281
|
+
```
|
|
540
282
|
|
|
541
|
-
|
|
283
|
+
### Update User Context
|
|
542
284
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
285
|
+
```javascript
|
|
286
|
+
sdk.setUserContext({
|
|
287
|
+
user_id: 'user_456',
|
|
288
|
+
email: 'newuser@example.com',
|
|
289
|
+
name: 'Jane Smith'
|
|
546
290
|
});
|
|
291
|
+
```
|
|
547
292
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Programmatic Control
|
|
551
296
|
|
|
297
|
+
### Open Panel Programmatically
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
const widget = sdk.createWidget('button');
|
|
552
301
|
widget.mount();
|
|
553
302
|
|
|
554
|
-
//
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
widget_type: data.widget.type,
|
|
559
|
-
});
|
|
560
|
-
});
|
|
303
|
+
// Open panel from code
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
widget.openPanel();
|
|
306
|
+
}, 3000);
|
|
561
307
|
```
|
|
562
308
|
|
|
563
|
-
###
|
|
564
|
-
|
|
565
|
-
Load feedback widget based on conditions:
|
|
309
|
+
### Close Panel
|
|
566
310
|
|
|
567
311
|
```javascript
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (!isUserLoggedIn()) {
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
312
|
+
widget.closePanel();
|
|
313
|
+
```
|
|
573
314
|
|
|
574
|
-
|
|
575
|
-
const allowedPages = ['/dashboard', '/profile', '/settings'];
|
|
576
|
-
if (!allowedPages.includes(window.location.pathname)) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
315
|
+
### Trigger from Custom Button
|
|
579
316
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
return; // 7 days cooldown
|
|
317
|
+
```javascript
|
|
318
|
+
const customButton = document.getElementById('feedback-btn');
|
|
319
|
+
customButton.addEventListener('click', () => {
|
|
320
|
+
const widget = sdk.getWidget('widget_123');
|
|
321
|
+
if (widget) {
|
|
322
|
+
widget.openPanel();
|
|
587
323
|
}
|
|
324
|
+
});
|
|
325
|
+
```
|
|
588
326
|
|
|
589
|
-
|
|
590
|
-
const feedback = new FeedbackSDK({
|
|
591
|
-
workspace: 'my-company',
|
|
592
|
-
boardId: 'conditional-feedback',
|
|
593
|
-
});
|
|
327
|
+
---
|
|
594
328
|
|
|
595
|
-
|
|
329
|
+
## Error Handling
|
|
596
330
|
|
|
597
|
-
|
|
598
|
-
feedback.eventBus.on('feedback:submitted', () => {
|
|
599
|
-
localStorage.setItem('lastFeedbackSubmission', Date.now().toString());
|
|
600
|
-
});
|
|
331
|
+
### Try-Catch Pattern
|
|
601
332
|
|
|
602
|
-
|
|
333
|
+
```javascript
|
|
334
|
+
try {
|
|
335
|
+
await sdk.init();
|
|
336
|
+
const widget = sdk.createWidget('button');
|
|
603
337
|
widget.mount();
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (error instanceof ConfigError) {
|
|
340
|
+
console.error('Config error:', error.message);
|
|
341
|
+
} else if (error instanceof APIError) {
|
|
342
|
+
console.error('API error:', error.status);
|
|
343
|
+
} else {
|
|
344
|
+
console.error('Unexpected error:', error);
|
|
345
|
+
}
|
|
604
346
|
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Handle API Errors
|
|
605
350
|
|
|
606
|
-
|
|
607
|
-
|
|
351
|
+
```javascript
|
|
352
|
+
try {
|
|
353
|
+
await sdk.apiService.submitFeedback(data);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (error.isNetworkError()) {
|
|
356
|
+
alert('No internet connection');
|
|
357
|
+
} else if (error.isClientError()) {
|
|
358
|
+
alert('Invalid data. Please check your input.');
|
|
359
|
+
} else if (error.isServerError()) {
|
|
360
|
+
alert('Server error. Please try again later.');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
608
363
|
```
|
|
609
364
|
|
|
610
|
-
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Advanced Usage
|
|
611
368
|
|
|
612
|
-
|
|
369
|
+
### Custom Widget Creation
|
|
613
370
|
|
|
614
371
|
```javascript
|
|
615
|
-
|
|
616
|
-
|
|
372
|
+
import { BaseWidget, WidgetFactory } from '@product7/feedback-sdk';
|
|
373
|
+
|
|
374
|
+
class CustomWidget extends BaseWidget {
|
|
617
375
|
constructor(options) {
|
|
618
|
-
super({ ...options, type: '
|
|
619
|
-
this.showDelay = options.showDelay || 5000;
|
|
376
|
+
super({ ...options, type: 'custom' });
|
|
620
377
|
}
|
|
621
378
|
|
|
622
379
|
_render() {
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
<div class="popup-content">
|
|
627
|
-
<button class="popup-close">×</button>
|
|
628
|
-
<h3>Quick Feedback</h3>
|
|
629
|
-
<p>How was your experience?</p>
|
|
630
|
-
<div class="popup-buttons">
|
|
631
|
-
<button class="popup-btn good">😊 Good</button>
|
|
632
|
-
<button class="popup-btn okay">😐 Okay</button>
|
|
633
|
-
<button class="popup-btn bad">😞 Bad</button>
|
|
634
|
-
</div>
|
|
635
|
-
</div>
|
|
636
|
-
`;
|
|
637
|
-
|
|
638
|
-
// Auto-hide after delay
|
|
639
|
-
setTimeout(() => {
|
|
640
|
-
if (this.mounted && !this.destroyed) {
|
|
641
|
-
this.show();
|
|
642
|
-
}
|
|
643
|
-
}, this.showDelay);
|
|
644
|
-
|
|
645
|
-
return popup;
|
|
380
|
+
const element = document.createElement('div');
|
|
381
|
+
element.innerHTML = '<button>Custom Feedback</button>';
|
|
382
|
+
return element;
|
|
646
383
|
}
|
|
647
384
|
|
|
648
385
|
_attachEvents() {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// Close button
|
|
652
|
-
popup.querySelector('.popup-close').addEventListener('click', () => {
|
|
653
|
-
this.hide();
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
// Rating buttons
|
|
657
|
-
popup.querySelectorAll('.popup-btn').forEach((btn) => {
|
|
658
|
-
btn.addEventListener('click', (e) => {
|
|
659
|
-
const rating = e.target.classList.contains('good')
|
|
660
|
-
? 5
|
|
661
|
-
: e.target.classList.contains('okay')
|
|
662
|
-
? 3
|
|
663
|
-
: 1;
|
|
664
|
-
|
|
665
|
-
this.submitQuickFeedback(rating);
|
|
666
|
-
this.hide();
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
async submitQuickFeedback(rating) {
|
|
672
|
-
try {
|
|
673
|
-
const payload = {
|
|
674
|
-
title: `Quick Feedback - Rating: ${rating}`,
|
|
675
|
-
content: `User gave a ${rating}/5 rating`,
|
|
676
|
-
board_id: this.options.boardId,
|
|
677
|
-
};
|
|
678
|
-
|
|
679
|
-
await this.apiService.submitFeedback(payload);
|
|
680
|
-
this.sdk.eventBus.emit('feedback:submitted', { widget: this, rating });
|
|
681
|
-
} catch (error) {
|
|
682
|
-
this.sdk.eventBus.emit('feedback:error', { widget: this, error });
|
|
683
|
-
}
|
|
386
|
+
this.element.querySelector('button')
|
|
387
|
+
.addEventListener('click', this.openPanel);
|
|
684
388
|
}
|
|
685
389
|
}
|
|
686
390
|
|
|
687
|
-
|
|
688
|
-
FeedbackSDK.WidgetFactory.register('popup', PopupWidget);
|
|
391
|
+
WidgetFactory.register('custom', CustomWidget);
|
|
689
392
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
workspace: 'my-company',
|
|
693
|
-
boardId: 'popup-feedback',
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
await feedback.init();
|
|
697
|
-
|
|
698
|
-
const popupWidget = feedback.createWidget('popup', {
|
|
699
|
-
showDelay: 3000, // Show after 3 seconds
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
popupWidget.mount();
|
|
393
|
+
const widget = sdk.createWidget('custom');
|
|
394
|
+
widget.mount();
|
|
703
395
|
```
|
|
704
396
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
### Performance Optimization
|
|
708
|
-
|
|
709
|
-
Lazy load the SDK when needed:
|
|
397
|
+
### Conditional Widget Loading
|
|
710
398
|
|
|
711
399
|
```javascript
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
script.src =
|
|
721
|
-
'https://cdn.jsdelivr.net/npm/@product7/feedback-sdk@1/dist/feedback-sdk.min.js';
|
|
722
|
-
script.onload = () => resolve(window.FeedbackSDK);
|
|
723
|
-
document.head.appendChild(script);
|
|
400
|
+
// Only show feedback for authenticated users
|
|
401
|
+
if (user.isAuthenticated) {
|
|
402
|
+
const sdk = new FeedbackSDK({
|
|
403
|
+
workspace: 'my-workspace',
|
|
404
|
+
userContext: {
|
|
405
|
+
user_id: user.id,
|
|
406
|
+
email: user.email
|
|
407
|
+
}
|
|
724
408
|
});
|
|
409
|
+
|
|
410
|
+
await sdk.init();
|
|
411
|
+
const widget = sdk.createWidget('button');
|
|
412
|
+
widget.mount();
|
|
725
413
|
}
|
|
414
|
+
```
|
|
726
415
|
|
|
727
|
-
|
|
728
|
-
document
|
|
729
|
-
.getElementById('feedback-trigger')
|
|
730
|
-
.addEventListener('click', async () => {
|
|
731
|
-
const FeedbackSDK = await loadFeedbackSDK();
|
|
732
|
-
|
|
733
|
-
const feedback = new FeedbackSDK({
|
|
734
|
-
workspace: 'my-company',
|
|
735
|
-
boardId: 'lazy-feedback',
|
|
736
|
-
});
|
|
416
|
+
### Environment-based Configuration
|
|
737
417
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
418
|
+
```javascript
|
|
419
|
+
const config = {
|
|
420
|
+
workspace: 'my-workspace',
|
|
421
|
+
userContext: getUserContext(),
|
|
422
|
+
apiUrl: process.env.NODE_ENV === 'production'
|
|
423
|
+
? 'https://api.product7.io/api/v1'
|
|
424
|
+
: 'https://api.staging.product7.io/api/v1',
|
|
425
|
+
debug: process.env.NODE_ENV === 'development'
|
|
426
|
+
};
|
|
741
427
|
|
|
742
|
-
|
|
743
|
-
widget.openModal();
|
|
744
|
-
});
|
|
428
|
+
const sdk = new FeedbackSDK(config);
|
|
745
429
|
```
|
|
746
430
|
|
|
747
|
-
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Testing
|
|
748
434
|
|
|
749
|
-
|
|
435
|
+
### Mock SDK
|
|
750
436
|
|
|
751
437
|
```javascript
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const feedback = new FeedbackSDK({
|
|
755
|
-
workspace: 'my-company',
|
|
756
|
-
boardId: 'error-handling-feedback',
|
|
757
|
-
debug: true,
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
await feedback.init();
|
|
761
|
-
|
|
762
|
-
// Set up comprehensive error handling
|
|
763
|
-
feedback.eventBus.on('feedback:error', (error) => {
|
|
764
|
-
console.error('Feedback error:', error);
|
|
765
|
-
|
|
766
|
-
// Show user-friendly error message
|
|
767
|
-
const errorMsg = getUserFriendlyErrorMessage(error.error);
|
|
768
|
-
showNotification(errorMsg, 'error');
|
|
769
|
-
|
|
770
|
-
// Track error for monitoring
|
|
771
|
-
if (window.Sentry) {
|
|
772
|
-
Sentry.captureException(error.error);
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
const widget = feedback.createWidget('button');
|
|
777
|
-
widget.mount();
|
|
778
|
-
|
|
779
|
-
// Success tracking
|
|
780
|
-
feedback.eventBus.on('feedback:submitted', () => {
|
|
781
|
-
showNotification('Feedback submitted successfully!', 'success');
|
|
782
|
-
});
|
|
783
|
-
} catch (initError) {
|
|
784
|
-
console.error('Failed to initialize feedback SDK:', initError);
|
|
785
|
-
|
|
786
|
-
// Fallback: show simple mailto link
|
|
787
|
-
showFallbackFeedback();
|
|
788
|
-
}
|
|
789
|
-
}
|
|
438
|
+
// In tests
|
|
439
|
+
jest.mock('@product7/feedback-sdk');
|
|
790
440
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
441
|
+
test('should initialize SDK', async () => {
|
|
442
|
+
const sdk = new FeedbackSDK({
|
|
443
|
+
workspace: 'test',
|
|
444
|
+
userContext: { user_id: '123' }
|
|
445
|
+
});
|
|
446
|
+
await sdk.init();
|
|
447
|
+
expect(sdk.initialized).toBe(true);
|
|
448
|
+
});
|
|
449
|
+
```
|
|
801
450
|
|
|
802
|
-
|
|
803
|
-
const fallback = document.createElement('div');
|
|
804
|
-
fallback.innerHTML = `
|
|
805
|
-
<div style="position: fixed; bottom: 20px; right: 20px; z-index: 9999;">
|
|
806
|
-
<a href="mailto:feedback@my-company.com?subject=Website Feedback"
|
|
807
|
-
style="background: #155EEF; color: white; padding: 12px 20px;
|
|
808
|
-
border-radius: 25px; text-decoration: none;">
|
|
809
|
-
Send Feedback
|
|
810
|
-
</a>
|
|
811
|
-
</div>
|
|
812
|
-
`;
|
|
813
|
-
document.body.appendChild(fallback);
|
|
814
|
-
}
|
|
451
|
+
### Check Widget Exists
|
|
815
452
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
453
|
+
```javascript
|
|
454
|
+
const widget = sdk.createWidget('button');
|
|
455
|
+
widget.mount();
|
|
820
456
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
```
|
|
457
|
+
const found = sdk.getWidget(widget.id);
|
|
458
|
+
expect(found).toBeDefined();
|
|
459
|
+
```
|