@product7/feedback-sdk 1.4.4 → 1.4.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/README.md +4 -7
- package/dist/README.md +4 -7
- package/dist/feedback-sdk.js +94 -69
- 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/docs/framework-integrations.md +707 -0
- package/src/styles/feedback.js +6 -1
- package/src/widgets/ButtonWidget.js +21 -9
- package/src/widgets/ChangelogWidget.js +52 -51
- package/src/widgets/MessengerWidget.js +16 -9
- package/src/widgets/messenger/MessengerState.js +1 -1
- package/src/widgets/messenger/views/ChangelogView.js +1 -1
- package/src/widgets/messenger/views/HelpView.js +1 -1
- package/src/widgets/messenger/views/HomeView.js +1 -1
package/package.json
CHANGED
|
@@ -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.
|