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