@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.
@@ -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">&times;</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
+ ```