@product7/feedback-sdk 1.4.3 → 1.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@product7/feedback-sdk",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "JavaScript SDK for integrating Product7 feedback widgets into any website",
5
5
  "main": "dist/feedback-sdk.js",
6
6
  "module": "src/index.js",
@@ -0,0 +1,707 @@
1
+ # Framework Integrations
2
+
3
+ Use this document to integrate `@product7/feedback-sdk` in supported frontend frameworks.
4
+
5
+ This guide includes:
6
+
7
+ 1. Next.js
8
+ 2. React
9
+ 3. Vue + Nuxt
10
+ 4. Angular
11
+ 5. Svelte + SvelteKit
12
+ 6. Astro
13
+
14
+ ## Before You Start
15
+
16
+ Install the SDK:
17
+
18
+ ```bash
19
+ npm install @product7/feedback-sdk
20
+ ```
21
+
22
+ Prepare your runtime values:
23
+
24
+ - `workspace` (required)
25
+ - `boardId` (recommended for feedback button)
26
+ - `userContext` (recommended for personalization and messenger)
27
+
28
+ Example configuration:
29
+
30
+ ```ts
31
+ const sdkConfig = {
32
+ workspace: 'your-workspace',
33
+ boardId: 'feature-requests',
34
+ debug: false,
35
+ userContext: {
36
+ user_id: 'user_123',
37
+ email: 'user@example.com',
38
+ name: 'Jane Doe',
39
+ custom_fields: {
40
+ plan: 'pro',
41
+ role: 'admin',
42
+ },
43
+ company: {
44
+ id: 'company_123',
45
+ name: 'Acme Inc',
46
+ },
47
+ },
48
+ };
49
+ ```
50
+
51
+ Integration rules:
52
+
53
+ - Run SDK initialization on the client side only.
54
+ - Always call `await sdk.init()` before creating widgets.
55
+ - Prefer a single shared SDK instance per app session.
56
+ - Destroy widgets and SDK on app teardown where applicable.
57
+
58
+ ## Framework-Specific Configuration
59
+
60
+ Use framework-native environment variables so values stay deployment-safe.
61
+
62
+ ### Next.js
63
+
64
+ - Use `NEXT_PUBLIC_*` for client-side values.
65
+ - Build `userContext` from your auth/session payload.
66
+ - When logged-in user changes, call `sdk.setUserContext(nextUserContext)`.
67
+
68
+ ```ts
69
+ const sdkConfig = {
70
+ workspace: process.env.NEXT_PUBLIC_PRODUCT7_WORKSPACE!,
71
+ boardId: process.env.NEXT_PUBLIC_PRODUCT7_BOARD_ID || 'feature-requests',
72
+ debug: process.env.NEXT_PUBLIC_PRODUCT7_DEBUG === 'true',
73
+ userContext: {
74
+ user_id: session.user.id,
75
+ email: session.user.email,
76
+ name: session.user.name,
77
+ },
78
+ };
79
+ ```
80
+
81
+ ### React (Vite / CRA)
82
+
83
+ - Vite: use `import.meta.env.VITE_*`.
84
+ - CRA: use `process.env.REACT_APP_*`.
85
+ - Keep config in one helper so all components use the same values.
86
+
87
+ ```ts
88
+ const sdkConfig = {
89
+ workspace: import.meta.env.VITE_PRODUCT7_WORKSPACE,
90
+ boardId: import.meta.env.VITE_PRODUCT7_BOARD_ID || 'feature-requests',
91
+ debug: import.meta.env.DEV,
92
+ userContext: currentUser
93
+ ? {
94
+ user_id: currentUser.id,
95
+ email: currentUser.email,
96
+ name: currentUser.name,
97
+ }
98
+ : undefined,
99
+ };
100
+ ```
101
+
102
+ ### Vue + Nuxt
103
+
104
+ - Vue (Vite): use `import.meta.env.VITE_*`.
105
+ - Nuxt: use `useRuntimeConfig().public`.
106
+ - In Nuxt, initialize in a `.client` plugin and expose SDK through `provide`.
107
+
108
+ ```ts
109
+ // Nuxt example
110
+ const runtime = useRuntimeConfig();
111
+ const sdkConfig = {
112
+ workspace: runtime.public.product7Workspace,
113
+ boardId: runtime.public.product7BoardId || 'feature-requests',
114
+ debug: runtime.public.product7Debug === 'true',
115
+ userContext,
116
+ };
117
+ ```
118
+
119
+ ### Angular
120
+
121
+ - Keep SDK values in `environment.ts` and `environment.prod.ts`.
122
+ - Map app auth user into `userContext` in your singleton service.
123
+
124
+ ```ts
125
+ const sdkConfig = {
126
+ workspace: environment.product7.workspace,
127
+ boardId: environment.product7.boardId || 'feature-requests',
128
+ debug: !environment.production,
129
+ userContext: {
130
+ user_id: user.id,
131
+ email: user.email,
132
+ name: user.fullName,
133
+ },
134
+ };
135
+ ```
136
+
137
+ ### Svelte + SvelteKit
138
+
139
+ - Svelte: use `import.meta.env.VITE_*`.
140
+ - SvelteKit: use `$env/static/public` (or `$env/dynamic/public`) values.
141
+ - Reuse one config builder in `+layout.svelte` to avoid repeated setup.
142
+
143
+ ```ts
144
+ const sdkConfig = {
145
+ workspace: PUBLIC_PRODUCT7_WORKSPACE,
146
+ boardId: PUBLIC_PRODUCT7_BOARD_ID || 'feature-requests',
147
+ debug: PUBLIC_PRODUCT7_DEBUG === 'true',
148
+ userContext,
149
+ };
150
+ ```
151
+
152
+ ### Astro
153
+
154
+ - Expose client-safe values with `PUBLIC_*`.
155
+ - Run SDK code only in client-loaded components (`client:load` / `client:visible`).
156
+
157
+ ```ts
158
+ const sdkConfig = {
159
+ workspace: import.meta.env.PUBLIC_PRODUCT7_WORKSPACE,
160
+ boardId: import.meta.env.PUBLIC_PRODUCT7_BOARD_ID || 'feature-requests',
161
+ debug: import.meta.env.PUBLIC_PRODUCT7_DEBUG === 'true',
162
+ userContext,
163
+ };
164
+ ```
165
+
166
+ ### Optional SDK Keys You Can Add Per Framework
167
+
168
+ - `apiUrl`: custom API endpoint override.
169
+ - `env`: force `'staging'` or `'production'`.
170
+ - `mock`: enable mock responses for local development.
171
+ - `theme`: default `'light'` or `'dark'`.
172
+ - `position`: default widget position.
173
+
174
+ ## 1) Next.js
175
+
176
+ ### App Router
177
+
178
+ Create a client provider:
179
+
180
+ `app/providers.tsx`
181
+
182
+ ```tsx
183
+ 'use client';
184
+
185
+ import { useEffect, useRef } from 'react';
186
+ import FeedbackSDK from '@product7/feedback-sdk';
187
+
188
+ export function FeedbackProvider() {
189
+ const sdkRef = useRef<any>(null);
190
+ const widgetRef = useRef<any>(null);
191
+
192
+ useEffect(() => {
193
+ let cancelled = false;
194
+
195
+ (async () => {
196
+ const sdk = FeedbackSDK.create({
197
+ workspace: 'your-workspace',
198
+ boardId: 'feature-requests',
199
+ userContext: {
200
+ user_id: 'user_123',
201
+ email: 'user@example.com',
202
+ name: 'Jane Doe',
203
+ },
204
+ });
205
+
206
+ await sdk.init();
207
+ if (cancelled) {
208
+ sdk.destroy();
209
+ return;
210
+ }
211
+
212
+ sdkRef.current = sdk;
213
+ widgetRef.current = sdk.createWidget('button', {
214
+ position: 'bottom-right',
215
+ });
216
+ widgetRef.current.mount();
217
+ })();
218
+
219
+ return () => {
220
+ cancelled = true;
221
+ widgetRef.current?.destroy();
222
+ sdkRef.current?.destroy();
223
+ };
224
+ }, []);
225
+
226
+ return null;
227
+ }
228
+ ```
229
+
230
+ Render provider in layout:
231
+
232
+ `app/layout.tsx`
233
+
234
+ ```tsx
235
+ import { FeedbackProvider } from './providers';
236
+
237
+ export default function RootLayout({
238
+ children,
239
+ }: {
240
+ children: React.ReactNode;
241
+ }) {
242
+ return (
243
+ <html lang="en">
244
+ <body>
245
+ <FeedbackProvider />
246
+ {children}
247
+ </body>
248
+ </html>
249
+ );
250
+ }
251
+ ```
252
+
253
+ ### Pages Router
254
+
255
+ `pages/_app.tsx`
256
+
257
+ ```tsx
258
+ import type { AppProps } from 'next/app';
259
+ import { useEffect } from 'react';
260
+ import FeedbackSDK from '@product7/feedback-sdk';
261
+
262
+ export default function App({ Component, pageProps }: AppProps) {
263
+ useEffect(() => {
264
+ const sdk = FeedbackSDK.create({
265
+ workspace: 'your-workspace',
266
+ boardId: 'feature-requests',
267
+ userContext: { user_id: 'user_123', email: 'user@example.com' },
268
+ });
269
+
270
+ let widget: any;
271
+ (async () => {
272
+ await sdk.init();
273
+ widget = sdk.createWidget('button', { position: 'bottom-right' });
274
+ widget.mount();
275
+ })();
276
+
277
+ return () => {
278
+ widget?.destroy();
279
+ sdk.destroy();
280
+ };
281
+ }, []);
282
+
283
+ return <Component {...pageProps} />;
284
+ }
285
+ ```
286
+
287
+ ## 2) React
288
+
289
+ `src/App.tsx`
290
+
291
+ ```tsx
292
+ import { useEffect, useRef } from 'react';
293
+ import FeedbackSDK from '@product7/feedback-sdk';
294
+
295
+ export default function App() {
296
+ const sdkRef = useRef<any>(null);
297
+
298
+ useEffect(() => {
299
+ let widget: any;
300
+
301
+ (async () => {
302
+ const sdk = FeedbackSDK.create({
303
+ workspace: 'your-workspace',
304
+ boardId: 'feature-requests',
305
+ userContext: {
306
+ user_id: 'user_123',
307
+ email: 'user@example.com',
308
+ name: 'Jane Doe',
309
+ },
310
+ });
311
+
312
+ await sdk.init();
313
+ sdkRef.current = sdk;
314
+
315
+ widget = sdk.createWidget('button', {
316
+ position: 'bottom-right',
317
+ });
318
+ widget.mount();
319
+ })();
320
+
321
+ return () => {
322
+ widget?.destroy();
323
+ sdkRef.current?.destroy();
324
+ };
325
+ }, []);
326
+
327
+ return <main>My App</main>;
328
+ }
329
+ ```
330
+
331
+ ## 3) Vue + Nuxt
332
+
333
+ ### Vue
334
+
335
+ `src/App.vue`
336
+
337
+ ```vue
338
+ <script setup lang="ts">
339
+ import { onMounted, onUnmounted, ref } from 'vue';
340
+ import FeedbackSDK from '@product7/feedback-sdk';
341
+
342
+ const sdk = ref<any>(null);
343
+ const widget = ref<any>(null);
344
+
345
+ onMounted(async () => {
346
+ sdk.value = FeedbackSDK.create({
347
+ workspace: 'your-workspace',
348
+ boardId: 'feature-requests',
349
+ userContext: {
350
+ user_id: 'user_123',
351
+ email: 'user@example.com',
352
+ name: 'Jane Doe',
353
+ },
354
+ });
355
+
356
+ await sdk.value.init();
357
+ widget.value = sdk.value.createWidget('button', {
358
+ position: 'bottom-right',
359
+ });
360
+ widget.value.mount();
361
+ });
362
+
363
+ onUnmounted(() => {
364
+ widget.value?.destroy();
365
+ sdk.value?.destroy();
366
+ });
367
+ </script>
368
+
369
+ <template>
370
+ <div>My App</div>
371
+ </template>
372
+ ```
373
+
374
+ ### Nuxt
375
+
376
+ Create a client plugin:
377
+
378
+ `plugins/feedback.client.ts`
379
+
380
+ ```ts
381
+ import FeedbackSDK from '@product7/feedback-sdk';
382
+
383
+ export default defineNuxtPlugin(async () => {
384
+ const sdk = FeedbackSDK.create({
385
+ workspace: 'your-workspace',
386
+ boardId: 'feature-requests',
387
+ userContext: {
388
+ user_id: 'user_123',
389
+ email: 'user@example.com',
390
+ name: 'Jane Doe',
391
+ },
392
+ });
393
+
394
+ await sdk.init();
395
+
396
+ return {
397
+ provide: {
398
+ feedbackSDK: sdk,
399
+ },
400
+ };
401
+ });
402
+ ```
403
+
404
+ Mount a widget in `app.vue` (or a layout):
405
+
406
+ ```vue
407
+ <script setup lang="ts">
408
+ const { $feedbackSDK } = useNuxtApp();
409
+ let widget: any;
410
+
411
+ onMounted(() => {
412
+ widget = $feedbackSDK.createWidget('button', {
413
+ position: 'bottom-right',
414
+ boardId: 'feature-requests',
415
+ });
416
+ widget.mount();
417
+ });
418
+
419
+ onBeforeUnmount(() => {
420
+ widget?.destroy();
421
+ });
422
+ </script>
423
+ ```
424
+
425
+ For larger Nuxt apps, use a Pinia store with `isReady` checks to avoid duplicate widget creation.
426
+
427
+ ## 4) Angular
428
+
429
+ Create a singleton service:
430
+
431
+ `feedback.service.ts`
432
+
433
+ ```ts
434
+ import { Injectable } from '@angular/core';
435
+ import FeedbackSDK from '@product7/feedback-sdk';
436
+
437
+ @Injectable({ providedIn: 'root' })
438
+ export class FeedbackService {
439
+ private sdk: any = null;
440
+ private buttonWidget: any = null;
441
+
442
+ async init() {
443
+ if (this.sdk) return;
444
+
445
+ this.sdk = FeedbackSDK.create({
446
+ workspace: 'your-workspace',
447
+ boardId: 'feature-requests',
448
+ userContext: {
449
+ user_id: 'user_123',
450
+ email: 'user@example.com',
451
+ name: 'Jane Doe',
452
+ },
453
+ });
454
+
455
+ await this.sdk.init();
456
+ }
457
+
458
+ mountButton() {
459
+ if (!this.sdk || this.buttonWidget) return;
460
+ this.buttonWidget = this.sdk.createWidget('button', {
461
+ position: 'bottom-right',
462
+ });
463
+ this.buttonWidget.mount();
464
+ }
465
+
466
+ destroy() {
467
+ this.buttonWidget?.destroy();
468
+ this.buttonWidget = null;
469
+ this.sdk?.destroy();
470
+ this.sdk = null;
471
+ }
472
+ }
473
+ ```
474
+
475
+ Use it in the root component:
476
+
477
+ `app.component.ts`
478
+
479
+ ```ts
480
+ import { Component, OnDestroy, OnInit } from '@angular/core';
481
+ import { FeedbackService } from './feedback.service';
482
+
483
+ @Component({
484
+ selector: 'app-root',
485
+ template: '<router-outlet></router-outlet>',
486
+ })
487
+ export class AppComponent implements OnInit, OnDestroy {
488
+ constructor(private feedback: FeedbackService) {}
489
+
490
+ async ngOnInit() {
491
+ await this.feedback.init();
492
+ this.feedback.mountButton();
493
+ }
494
+
495
+ ngOnDestroy() {
496
+ this.feedback.destroy();
497
+ }
498
+ }
499
+ ```
500
+
501
+ ## 5) Svelte + SvelteKit
502
+
503
+ ### Svelte
504
+
505
+ `src/App.svelte`
506
+
507
+ ```svelte
508
+ <script lang="ts">
509
+ import { onMount } from 'svelte';
510
+ import FeedbackSDK from '@product7/feedback-sdk';
511
+
512
+ let sdk: any = null;
513
+ let widget: any = null;
514
+
515
+ onMount(() => {
516
+ let disposed = false;
517
+
518
+ (async () => {
519
+ sdk = FeedbackSDK.create({
520
+ workspace: 'your-workspace',
521
+ boardId: 'feature-requests',
522
+ userContext: {
523
+ user_id: 'user_123',
524
+ email: 'user@example.com',
525
+ name: 'Jane Doe',
526
+ },
527
+ });
528
+
529
+ await sdk.init();
530
+ if (disposed) return;
531
+
532
+ widget = sdk.createWidget('button', { position: 'bottom-right' });
533
+ widget.mount();
534
+ })();
535
+
536
+ return () => {
537
+ disposed = true;
538
+ widget?.destroy();
539
+ sdk?.destroy();
540
+ };
541
+ });
542
+ </script>
543
+
544
+ <main>My App</main>
545
+ ```
546
+
547
+ ### SvelteKit
548
+
549
+ `src/routes/+layout.svelte`
550
+
551
+ ```svelte
552
+ <script lang="ts">
553
+ import { browser } from '$app/environment';
554
+ import { onMount } from 'svelte';
555
+ import FeedbackSDK from '@product7/feedback-sdk';
556
+
557
+ let sdk: any;
558
+ let widget: any;
559
+
560
+ onMount(() => {
561
+ if (!browser) return;
562
+
563
+ (async () => {
564
+ sdk = FeedbackSDK.create({
565
+ workspace: 'your-workspace',
566
+ boardId: 'feature-requests',
567
+ userContext: {
568
+ user_id: 'user_123',
569
+ email: 'user@example.com',
570
+ },
571
+ });
572
+
573
+ await sdk.init();
574
+ widget = sdk.createWidget('button', { position: 'bottom-right' });
575
+ widget.mount();
576
+ })();
577
+
578
+ return () => {
579
+ widget?.destroy();
580
+ sdk?.destroy();
581
+ };
582
+ });
583
+ </script>
584
+
585
+ <slot />
586
+ ```
587
+
588
+ ## 6) Astro
589
+
590
+ Astro renders on the server by default, so initialize the SDK from a client-loaded component.
591
+
592
+ `src/components/FeedbackWidget.astro`
593
+
594
+ ```astro
595
+ <script>
596
+ import FeedbackSDK from '@product7/feedback-sdk';
597
+
598
+ let sdk;
599
+ let widget;
600
+
601
+ (async () => {
602
+ sdk = FeedbackSDK.create({
603
+ workspace: 'your-workspace',
604
+ boardId: 'feature-requests',
605
+ userContext: {
606
+ user_id: 'user_123',
607
+ email: 'user@example.com',
608
+ name: 'Jane Doe',
609
+ },
610
+ });
611
+
612
+ await sdk.init();
613
+ widget = sdk.createWidget('button', { position: 'bottom-right' });
614
+ widget.mount();
615
+ })();
616
+ </script>
617
+ ```
618
+
619
+ Use the component in a page:
620
+
621
+ ```astro
622
+ ---
623
+ import FeedbackWidget from '../components/FeedbackWidget.astro';
624
+ ---
625
+
626
+ <html lang="en">
627
+ <body>
628
+ <FeedbackWidget client:load />
629
+ </body>
630
+ </html>
631
+ ```
632
+
633
+ ## Add More Widgets (Any Framework)
634
+
635
+ You can create survey, messenger, and changelog widgets from the same SDK instance:
636
+
637
+ ```ts
638
+ const survey = sdk.createWidget('survey', {
639
+ surveyType: 'nps',
640
+ position: 'center',
641
+ theme: 'light',
642
+ title: 'How likely are you to recommend us?',
643
+ description: 'Your feedback helps us improve.',
644
+ lowLabel: 'Not likely',
645
+ highLabel: 'Very likely',
646
+ onSubmit: (response) => {
647
+ console.log('Survey submitted:', response);
648
+ // Optional: send to analytics
649
+ // analytics.track('survey_submitted', { type: response.type, score: response.score });
650
+ },
651
+ onDismiss: () => {
652
+ console.log('Survey dismissed');
653
+ },
654
+ });
655
+ survey.show();
656
+ // survey.hide();
657
+ // survey.destroy();
658
+
659
+ const messenger = sdk.createWidget('messenger', {
660
+ position: 'bottom-right',
661
+ theme: 'light',
662
+ teamName: 'Support Team',
663
+ welcomeMessage: 'How can we help?',
664
+ enableHelp: true,
665
+ enableChangelog: true,
666
+ primaryColor: '#155EEF',
667
+ onSendMessage: (conversationId, message) => {
668
+ console.log('Message sent:', conversationId, message);
669
+ },
670
+ onArticleClick: (article) => {
671
+ console.log('Article clicked:', article);
672
+ },
673
+ onChangelogClick: (item) => {
674
+ console.log('Changelog clicked:', item);
675
+ },
676
+ });
677
+ messenger.mount();
678
+ // messenger.open();
679
+ // messenger.navigateTo('help');
680
+ // messenger.close();
681
+
682
+ const changelog = sdk.createWidget('changelog', {
683
+ position: 'bottom-left',
684
+ theme: 'light',
685
+ triggerText: "What's New",
686
+ showBadge: true,
687
+ title: "What's New",
688
+ viewButtonText: 'View Update',
689
+ onViewUpdate: (item) => {
690
+ console.log('Viewed changelog item:', item);
691
+ },
692
+ });
693
+ changelog.mount();
694
+ // changelog.openSidebar();
695
+ // changelog.closeSidebar();
696
+ ```
697
+
698
+ ## Troubleshooting
699
+
700
+ - `SDK must be initialized before creating widgets`
701
+ Call `await sdk.init()` before any `createWidget(...)` call.
702
+ - Widget not visible
703
+ Make sure you call `widget.mount()` for mounted widget types.
704
+ - `window is not defined` or `document is not defined`
705
+ Move SDK code to client-only lifecycle hooks/components.
706
+ - Duplicate widget instances
707
+ Cache widget references and guard against double mount.