@product7/feedback-sdk 1.0.1
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 +227 -0
- package/dist/README.md +227 -0
- package/dist/feedback-sdk.js +2483 -0
- package/dist/feedback-sdk.js.map +1 -0
- package/dist/feedback-sdk.min.js +2 -0
- package/dist/feedback-sdk.min.js.map +1 -0
- package/package.json +111 -0
- package/src/core/APIService.js +338 -0
- package/src/core/EventBus.js +54 -0
- package/src/core/FeedbackSDK.js +285 -0
- package/src/docs/api.md +686 -0
- package/src/docs/example.md +823 -0
- package/src/docs/installation.md +264 -0
- package/src/index.js +281 -0
- package/src/styles/styles.js +557 -0
- package/src/types/index.d.ts +12 -0
- package/src/utils/errors.js +142 -0
- package/src/utils/helpers.js +219 -0
- package/src/widgets/BaseWidget.js +334 -0
- package/src/widgets/ButtonWidget.js +62 -0
- package/src/widgets/InlineWidget.js +145 -0
- package/src/widgets/TabWidget.js +65 -0
- package/src/widgets/WidgetFactory.js +64 -0
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Real-world examples of using the Feedback Widget SDK.
|
|
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
|
|
61
|
+
|
|
62
|
+
Create different widgets for different purposes:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
const feedback = new FeedbackSDK({
|
|
66
|
+
workspace: 'my-company',
|
|
67
|
+
debug: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await feedback.init();
|
|
71
|
+
|
|
72
|
+
// Button for general feedback
|
|
73
|
+
const generalButton = feedback.createWidget('button', {
|
|
74
|
+
position: 'bottom-right',
|
|
75
|
+
boardId: 'general-feedback',
|
|
76
|
+
});
|
|
77
|
+
generalButton.mount();
|
|
78
|
+
|
|
79
|
+
// Tab for bug reports
|
|
80
|
+
const bugTab = feedback.createWidget('tab', {
|
|
81
|
+
position: 'bottom-left',
|
|
82
|
+
boardId: 'bug-reports',
|
|
83
|
+
});
|
|
84
|
+
bugTab.mount();
|
|
85
|
+
|
|
86
|
+
// Inline form in footer
|
|
87
|
+
const footerForm = feedback.createWidget('inline', {
|
|
88
|
+
boardId: 'feature-requests',
|
|
89
|
+
});
|
|
90
|
+
footerForm.mount('#footer-feedback');
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Custom Styling
|
|
94
|
+
|
|
95
|
+
Override default styles with CSS:
|
|
96
|
+
|
|
97
|
+
```html
|
|
98
|
+
<style>
|
|
99
|
+
/* Custom button color */
|
|
100
|
+
.feedback-widget {
|
|
101
|
+
--feedback-primary-color: #10b981;
|
|
102
|
+
--feedback-primary-hover: #059669;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Custom positioning */
|
|
106
|
+
.feedback-widget-button.position-bottom-right {
|
|
107
|
+
bottom: 100px;
|
|
108
|
+
right: 30px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Custom font */
|
|
112
|
+
.feedback-widget {
|
|
113
|
+
--feedback-font-family: 'Poppins', sans-serif;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Custom border radius */
|
|
117
|
+
.feedback-trigger-btn {
|
|
118
|
+
border-radius: 8px !important;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
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>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Dynamic Theme Switching
|
|
136
|
+
|
|
137
|
+
Switch themes based on user preference:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const feedback = new FeedbackSDK({
|
|
141
|
+
workspace: 'my-company',
|
|
142
|
+
boardId: 'general-feedback',
|
|
143
|
+
theme: 'light',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await feedback.init();
|
|
147
|
+
|
|
148
|
+
const widget = feedback.createWidget('button');
|
|
149
|
+
widget.mount();
|
|
150
|
+
|
|
151
|
+
// Theme toggle function
|
|
152
|
+
function toggleTheme() {
|
|
153
|
+
const isDark = document.body.classList.contains('dark-mode');
|
|
154
|
+
const newTheme = isDark ? 'light' : 'dark';
|
|
155
|
+
|
|
156
|
+
// Update SDK theme
|
|
157
|
+
feedback.updateConfig({ theme: newTheme });
|
|
158
|
+
|
|
159
|
+
// Update your site theme
|
|
160
|
+
document.body.classList.toggle('dark-mode');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Bind to your theme toggle button
|
|
164
|
+
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 📱 Responsive Examples
|
|
168
|
+
|
|
169
|
+
### Mobile-First Design
|
|
170
|
+
|
|
171
|
+
Optimize for mobile devices:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
const feedback = new FeedbackSDK({
|
|
175
|
+
workspace: 'my-company',
|
|
176
|
+
boardId: 'mobile-feedback',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await feedback.init();
|
|
180
|
+
|
|
181
|
+
// Different widgets for different screen sizes
|
|
182
|
+
if (window.innerWidth <= 768) {
|
|
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
|
+
}
|
|
195
|
+
|
|
196
|
+
// Handle screen resize
|
|
197
|
+
window.addEventListener('resize', () => {
|
|
198
|
+
// Destroy current widgets and recreate based on new size
|
|
199
|
+
feedback.widgets.forEach((widget) => widget.destroy());
|
|
200
|
+
|
|
201
|
+
if (window.innerWidth <= 768) {
|
|
202
|
+
const mobileWidget = feedback.createWidget('tab');
|
|
203
|
+
mobileWidget.mount();
|
|
204
|
+
} else {
|
|
205
|
+
const desktopWidget = feedback.createWidget('button');
|
|
206
|
+
desktopWidget.mount();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## 🎯 Event-Driven Examples
|
|
212
|
+
|
|
213
|
+
### Analytics Integration
|
|
214
|
+
|
|
215
|
+
Track feedback events with your analytics:
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
const feedback = new FeedbackSDK({
|
|
219
|
+
workspace: 'my-company',
|
|
220
|
+
boardId: 'analytics-feedback',
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
await feedback.init();
|
|
224
|
+
|
|
225
|
+
// Track feedback interactions
|
|
226
|
+
feedback.eventBus.on('widget:mounted', (data) => {
|
|
227
|
+
// Track widget load
|
|
228
|
+
gtag('event', 'feedback_widget_loaded', {
|
|
229
|
+
widget_type: data.widget.type,
|
|
230
|
+
widget_id: data.widget.id,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
feedback.eventBus.on('feedback:submitted', (data) => {
|
|
235
|
+
// Track successful submission
|
|
236
|
+
gtag('event', 'feedback_submitted', {
|
|
237
|
+
widget_type: data.widget.type,
|
|
238
|
+
feedback_length: data.feedback.content?.length || 0,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Show thank you message
|
|
242
|
+
showToast('Thank you for your feedback! 🙏');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
feedback.eventBus.on('feedback:error', (error) => {
|
|
246
|
+
// Track errors
|
|
247
|
+
gtag('event', 'feedback_error', {
|
|
248
|
+
error_type: error.error.name,
|
|
249
|
+
error_message: error.error.message,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Show error message
|
|
253
|
+
showToast('Failed to send feedback. Please try again.', 'error');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const widget = feedback.createWidget('button');
|
|
257
|
+
widget.mount();
|
|
258
|
+
|
|
259
|
+
function showToast(message, type = 'success') {
|
|
260
|
+
// Your toast implementation
|
|
261
|
+
console.log(`${type}: ${message}`);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### User Authentication Integration
|
|
266
|
+
|
|
267
|
+
Integrate with your user system:
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
// Get user info from your auth system
|
|
271
|
+
const currentUser = getCurrentUser();
|
|
272
|
+
|
|
273
|
+
const feedback = new FeedbackSDK({
|
|
274
|
+
workspace: 'my-company',
|
|
275
|
+
boardId: 'user-feedback',
|
|
276
|
+
apiKey: currentUser?.apiKey, // Use user's API key if available
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await feedback.init();
|
|
280
|
+
|
|
281
|
+
const widget = feedback.createWidget('inline');
|
|
282
|
+
widget.mount('#feedback-section');
|
|
283
|
+
|
|
284
|
+
// Pre-populate user email
|
|
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
|
+
}
|
|
295
|
+
|
|
296
|
+
// Add user context to feedback
|
|
297
|
+
feedback.eventBus.on('feedback:submitted', (data) => {
|
|
298
|
+
// Log user-specific feedback submission
|
|
299
|
+
console.log(`Feedback from ${currentUser.email}:`, data.feedback);
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## 🌐 Framework Integration Examples
|
|
304
|
+
|
|
305
|
+
### React Integration
|
|
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
|
+
}
|
|
351
|
+
|
|
352
|
+
// For button/tab widgets, no container needed
|
|
353
|
+
return null;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Usage
|
|
357
|
+
export const App = () => {
|
|
358
|
+
return (
|
|
359
|
+
<div>
|
|
360
|
+
<h1>My React App</h1>
|
|
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
|
+
};
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Vue.js Integration
|
|
383
|
+
|
|
384
|
+
```vue
|
|
385
|
+
<template>
|
|
386
|
+
<div>
|
|
387
|
+
<h1>My Vue App</h1>
|
|
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>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Angular Integration
|
|
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
|
+
}
|
|
490
|
+
|
|
491
|
+
ngOnDestroy() {
|
|
492
|
+
this.cleanup();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private async initializeFeedback() {
|
|
496
|
+
this.sdk = new FeedbackSDK({
|
|
497
|
+
workspace: this.workspace,
|
|
498
|
+
boardId: this.boardId,
|
|
499
|
+
debug: !environment.production,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
await this.sdk.init();
|
|
503
|
+
|
|
504
|
+
// Set up event listeners
|
|
505
|
+
this.sdk.eventBus.on('feedback:submitted', (data) => {
|
|
506
|
+
console.log('Feedback submitted:', data);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Create widget
|
|
510
|
+
this.widget = this.sdk.createWidget(this.type);
|
|
511
|
+
|
|
512
|
+
if (this.type === 'inline' && this.containerRef) {
|
|
513
|
+
this.widget.mount(this.containerRef.nativeElement);
|
|
514
|
+
} else {
|
|
515
|
+
this.widget.mount();
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private cleanup() {
|
|
520
|
+
if (this.widget) this.widget.destroy();
|
|
521
|
+
if (this.sdk) this.sdk.destroy();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## 🚀 Advanced Examples
|
|
527
|
+
|
|
528
|
+
### A/B Testing
|
|
529
|
+
|
|
530
|
+
Test different widget configurations:
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
// A/B test: Button vs Tab widget
|
|
534
|
+
const variant = Math.random() < 0.5 ? 'button' : 'tab';
|
|
535
|
+
|
|
536
|
+
const feedback = new FeedbackSDK({
|
|
537
|
+
workspace: 'my-company',
|
|
538
|
+
boardId: 'ab-test-feedback',
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
await feedback.init();
|
|
542
|
+
|
|
543
|
+
// Track A/B test variant
|
|
544
|
+
gtag('event', 'feedback_widget_variant', {
|
|
545
|
+
variant: variant,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const widget = feedback.createWidget(variant, {
|
|
549
|
+
position: variant === 'button' ? 'bottom-right' : 'bottom-left',
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
widget.mount();
|
|
553
|
+
|
|
554
|
+
// Track which variant gets more engagement
|
|
555
|
+
feedback.eventBus.on('feedback:submitted', (data) => {
|
|
556
|
+
gtag('event', 'feedback_submitted_variant', {
|
|
557
|
+
variant: variant,
|
|
558
|
+
widget_type: data.widget.type,
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Conditional Loading
|
|
564
|
+
|
|
565
|
+
Load feedback widget based on conditions:
|
|
566
|
+
|
|
567
|
+
```javascript
|
|
568
|
+
async function loadFeedbackWidget() {
|
|
569
|
+
// Only load for logged-in users
|
|
570
|
+
if (!isUserLoggedIn()) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Only load on certain pages
|
|
575
|
+
const allowedPages = ['/dashboard', '/profile', '/settings'];
|
|
576
|
+
if (!allowedPages.includes(window.location.pathname)) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Don't load if user has submitted feedback recently
|
|
581
|
+
const lastFeedback = localStorage.getItem('lastFeedbackSubmission');
|
|
582
|
+
if (
|
|
583
|
+
lastFeedback &&
|
|
584
|
+
Date.now() - parseInt(lastFeedback) < 7 * 24 * 60 * 60 * 1000
|
|
585
|
+
) {
|
|
586
|
+
return; // 7 days cooldown
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Initialize SDK
|
|
590
|
+
const feedback = new FeedbackSDK({
|
|
591
|
+
workspace: 'my-company',
|
|
592
|
+
boardId: 'conditional-feedback',
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
await feedback.init();
|
|
596
|
+
|
|
597
|
+
// Track feedback submission timestamp
|
|
598
|
+
feedback.eventBus.on('feedback:submitted', () => {
|
|
599
|
+
localStorage.setItem('lastFeedbackSubmission', Date.now().toString());
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const widget = feedback.createWidget('button');
|
|
603
|
+
widget.mount();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Load when page is ready
|
|
607
|
+
document.addEventListener('DOMContentLoaded', loadFeedbackWidget);
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Custom Widget Creation
|
|
611
|
+
|
|
612
|
+
Create your own widget type:
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
// Custom popup widget
|
|
616
|
+
class PopupWidget extends FeedbackSDK.BaseWidget {
|
|
617
|
+
constructor(options) {
|
|
618
|
+
super({ ...options, type: 'popup' });
|
|
619
|
+
this.showDelay = options.showDelay || 5000;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
_render() {
|
|
623
|
+
const popup = document.createElement('div');
|
|
624
|
+
popup.className = `feedback-popup theme-${this.options.theme}`;
|
|
625
|
+
popup.innerHTML = `
|
|
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;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
_attachEvents() {
|
|
649
|
+
const popup = this.element;
|
|
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
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Register the custom widget
|
|
688
|
+
FeedbackSDK.WidgetFactory.register('popup', PopupWidget);
|
|
689
|
+
|
|
690
|
+
// Use the custom widget
|
|
691
|
+
const feedback = new FeedbackSDK({
|
|
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();
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
## 💡 Best Practices Examples
|
|
706
|
+
|
|
707
|
+
### Performance Optimization
|
|
708
|
+
|
|
709
|
+
Lazy load the SDK when needed:
|
|
710
|
+
|
|
711
|
+
```javascript
|
|
712
|
+
// Lazy load function
|
|
713
|
+
async function loadFeedbackSDK() {
|
|
714
|
+
if (window.FeedbackSDK) {
|
|
715
|
+
return window.FeedbackSDK;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return new Promise((resolve) => {
|
|
719
|
+
const script = document.createElement('script');
|
|
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);
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Load SDK only when user interacts with feedback trigger
|
|
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
|
+
});
|
|
737
|
+
|
|
738
|
+
await feedback.init();
|
|
739
|
+
const widget = feedback.createWidget('button');
|
|
740
|
+
widget.mount();
|
|
741
|
+
|
|
742
|
+
// Open modal immediately
|
|
743
|
+
widget.openModal();
|
|
744
|
+
});
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### Error Handling
|
|
748
|
+
|
|
749
|
+
Robust error handling and fallbacks:
|
|
750
|
+
|
|
751
|
+
```javascript
|
|
752
|
+
async function initializeFeedback() {
|
|
753
|
+
try {
|
|
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
|
+
}
|
|
790
|
+
|
|
791
|
+
function getUserFriendlyErrorMessage(error) {
|
|
792
|
+
if (error.name === 'APIError') {
|
|
793
|
+
if (error.status === 0) {
|
|
794
|
+
return 'Network error. Please check your connection.';
|
|
795
|
+
} else if (error.status >= 500) {
|
|
796
|
+
return 'Server error. Please try again later.';
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return 'Failed to submit feedback. Please try again.';
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function showFallbackFeedback() {
|
|
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
|
+
}
|
|
815
|
+
|
|
816
|
+
function showNotification(message, type) {
|
|
817
|
+
// Your notification system
|
|
818
|
+
console.log(`${type}: ${message}`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Initialize with error handling
|
|
822
|
+
initializeFeedback();
|
|
823
|
+
```
|