@intentai/react 1.0.0
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 +407 -0
- package/dist/index.d.mts +379 -0
- package/dist/index.d.ts +379 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +2 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# @intentai/react
|
|
2
|
+
|
|
3
|
+
Official React SDK for [Intent AI](https://intent-ai.com) - AI-powered feedback collection widget.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @intentai/react
|
|
9
|
+
# or
|
|
10
|
+
yarn add @intentai/react
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @intentai/react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Get your Public Key
|
|
18
|
+
|
|
19
|
+
Get your public key (`pk_live_*` or `pk_test_*`) from the [Intent AI Dashboard](https://app.intent-ai.com/dashboard/settings).
|
|
20
|
+
|
|
21
|
+
### 2. Add the Provider
|
|
22
|
+
|
|
23
|
+
Wrap your app with `IntentAIProvider`:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// app/layout.tsx (Next.js App Router)
|
|
27
|
+
import { IntentAIProvider } from '@intentai/react';
|
|
28
|
+
|
|
29
|
+
export default function RootLayout({ children }) {
|
|
30
|
+
return (
|
|
31
|
+
<html>
|
|
32
|
+
<body>
|
|
33
|
+
<IntentAIProvider apiKey="pk_live_xxxxx">
|
|
34
|
+
{children}
|
|
35
|
+
</IntentAIProvider>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That's it! The feedback widget will appear in the bottom-right corner.
|
|
43
|
+
|
|
44
|
+
## Environment Variables
|
|
45
|
+
|
|
46
|
+
The SDK automatically detects your API key from environment variables:
|
|
47
|
+
|
|
48
|
+
```env
|
|
49
|
+
# Next.js
|
|
50
|
+
NEXT_PUBLIC_INTENT_AI_KEY=pk_live_xxxxx
|
|
51
|
+
|
|
52
|
+
# Create React App
|
|
53
|
+
REACT_APP_INTENT_AI_KEY=pk_live_xxxxx
|
|
54
|
+
|
|
55
|
+
# Vite
|
|
56
|
+
VITE_INTENT_AI_KEY=pk_live_xxxxx
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With environment variables set, you can omit the `apiKey` prop:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<IntentAIProvider>
|
|
63
|
+
{children}
|
|
64
|
+
</IntentAIProvider>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
### Basic Setup
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { IntentAIProvider } from '@intentai/react';
|
|
73
|
+
|
|
74
|
+
function App() {
|
|
75
|
+
return (
|
|
76
|
+
<IntentAIProvider
|
|
77
|
+
apiKey="pk_live_xxxxx"
|
|
78
|
+
position="bottom-right"
|
|
79
|
+
theme="auto"
|
|
80
|
+
>
|
|
81
|
+
<YourApp />
|
|
82
|
+
</IntentAIProvider>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Custom Feedback Button
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { IntentAIProvider, FeedbackButton } from '@intentai/react';
|
|
91
|
+
|
|
92
|
+
function App() {
|
|
93
|
+
return (
|
|
94
|
+
<IntentAIProvider apiKey="pk_live_xxxxx" autoShow={false}>
|
|
95
|
+
<FeedbackButton className="btn btn-primary">
|
|
96
|
+
Send Feedback
|
|
97
|
+
</FeedbackButton>
|
|
98
|
+
</IntentAIProvider>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Using the Hook
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { useIntentAI } from '@intentai/react';
|
|
107
|
+
|
|
108
|
+
function MyComponent() {
|
|
109
|
+
const { open, close, isReady, error } = useIntentAI();
|
|
110
|
+
|
|
111
|
+
if (error) {
|
|
112
|
+
return <div>Failed to load feedback widget</div>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<button onClick={() => open()} disabled={!isReady}>
|
|
117
|
+
Give Feedback
|
|
118
|
+
</button>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Identify Users
|
|
124
|
+
|
|
125
|
+
Using the `useIdentify` hook (recommended):
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { useIdentify } from '@intentai/react';
|
|
129
|
+
|
|
130
|
+
function AuthenticatedApp({ user }) {
|
|
131
|
+
// Automatically identifies user when ready
|
|
132
|
+
useIdentify(user ? {
|
|
133
|
+
id: user.id,
|
|
134
|
+
email: user.email,
|
|
135
|
+
name: user.name,
|
|
136
|
+
plan: user.subscription.plan,
|
|
137
|
+
} : null);
|
|
138
|
+
|
|
139
|
+
return <YourApp />;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Using the `IdentifyUser` component:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { IntentAIProvider, IdentifyUser } from '@intentai/react';
|
|
147
|
+
|
|
148
|
+
function App({ user }) {
|
|
149
|
+
return (
|
|
150
|
+
<IntentAIProvider apiKey="pk_live_xxxxx">
|
|
151
|
+
<IdentifyUser user={{
|
|
152
|
+
id: user.id,
|
|
153
|
+
email: user.email,
|
|
154
|
+
name: user.name
|
|
155
|
+
}}>
|
|
156
|
+
<YourApp />
|
|
157
|
+
</IdentifyUser>
|
|
158
|
+
</IntentAIProvider>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or using the hook directly:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import { useIntentAI } from '@intentai/react';
|
|
167
|
+
|
|
168
|
+
function AuthenticatedApp({ user }) {
|
|
169
|
+
const { identify, isReady } = useIntentAI();
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (isReady && user) {
|
|
173
|
+
identify({
|
|
174
|
+
id: user.id,
|
|
175
|
+
email: user.email,
|
|
176
|
+
name: user.name,
|
|
177
|
+
plan: user.subscription.plan, // Custom properties
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}, [isReady, user]);
|
|
181
|
+
|
|
182
|
+
return <YourApp />;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Add Metadata
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
import { SetMetadata } from '@intentai/react';
|
|
190
|
+
|
|
191
|
+
function PricingPage() {
|
|
192
|
+
return (
|
|
193
|
+
<SetMetadata metadata={{ page: 'pricing', feature: 'comparison' }}>
|
|
194
|
+
<PricingContent />
|
|
195
|
+
</SetMetadata>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Or using the hook:
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
const { setMetadata } = useIntentAI();
|
|
204
|
+
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
setMetadata({
|
|
207
|
+
page: window.location.pathname,
|
|
208
|
+
viewport: `${window.innerWidth}x${window.innerHeight}`
|
|
209
|
+
});
|
|
210
|
+
}, []);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Track Events
|
|
214
|
+
|
|
215
|
+
Using the `TrackEvent` component:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { TrackEvent } from '@intentai/react';
|
|
219
|
+
|
|
220
|
+
function CheckoutPage() {
|
|
221
|
+
return (
|
|
222
|
+
<TrackEvent event="checkout_viewed" properties={{ items: 3 }}>
|
|
223
|
+
<CheckoutContent />
|
|
224
|
+
</TrackEvent>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Using the hook:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
const { track } = useIntentAI();
|
|
233
|
+
|
|
234
|
+
function handlePurchase(item) {
|
|
235
|
+
track('purchase', {
|
|
236
|
+
itemId: item.id,
|
|
237
|
+
price: item.price
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Custom Trigger with FeedbackTrigger
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { FeedbackTrigger } from '@intentai/react';
|
|
246
|
+
|
|
247
|
+
function App() {
|
|
248
|
+
return (
|
|
249
|
+
<FeedbackTrigger>
|
|
250
|
+
{({ open, isReady }) => (
|
|
251
|
+
<MyCustomButton onClick={() => open()} disabled={!isReady}>
|
|
252
|
+
Feedback
|
|
253
|
+
</MyCustomButton>
|
|
254
|
+
)}
|
|
255
|
+
</FeedbackTrigger>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Pre-fill Feedback
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
const { open } = useIntentAI();
|
|
264
|
+
|
|
265
|
+
// Open with pre-selected type and prefilled text
|
|
266
|
+
open({
|
|
267
|
+
type: 'bug',
|
|
268
|
+
prefill: { text: 'I found an issue with...' },
|
|
269
|
+
metadata: { page: 'checkout' }
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Configuration
|
|
274
|
+
|
|
275
|
+
### IntentAIProvider Props
|
|
276
|
+
|
|
277
|
+
| Prop | Type | Default | Description |
|
|
278
|
+
|------|------|---------|-------------|
|
|
279
|
+
| `apiKey` | `string` | - | Your Intent AI public key (or use env vars) |
|
|
280
|
+
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Widget position |
|
|
281
|
+
| `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color theme |
|
|
282
|
+
| `primaryColor` | `string` | `'#6366f1'` | Primary brand color (hex) |
|
|
283
|
+
| `autoShow` | `boolean` | `true` | Show widget button automatically |
|
|
284
|
+
| `user` | `UserIdentity` | - | Initial user identification |
|
|
285
|
+
| `metadata` | `Record<string, unknown>` | - | Custom metadata |
|
|
286
|
+
| `onReady` | `() => void` | - | Called when widget is ready |
|
|
287
|
+
| `onOpen` | `() => void` | - | Called when widget opens |
|
|
288
|
+
| `onClose` | `() => void` | - | Called when widget closes |
|
|
289
|
+
| `onSubmit` | `(data: FeedbackData) => void` | - | Called on feedback submission |
|
|
290
|
+
| `onError` | `(error: Error) => void` | - | Called on errors |
|
|
291
|
+
|
|
292
|
+
### useIntentAI Hook
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
const {
|
|
296
|
+
isReady, // boolean - Widget loaded and ready
|
|
297
|
+
error, // Error | null - Error if widget failed to load
|
|
298
|
+
open, // (options?: OpenOptions) => void - Open the widget
|
|
299
|
+
close, // () => void - Close the widget
|
|
300
|
+
identify, // (user: UserIdentity) => void - Identify user
|
|
301
|
+
setMetadata,// (data: Record<string, unknown>) => void - Set metadata
|
|
302
|
+
track, // (event: string, properties?: Record<string, unknown>) => void - Track event
|
|
303
|
+
} = useIntentAI();
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### useIdentify Hook
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
// Automatically identifies user when widget is ready
|
|
310
|
+
useIdentify({
|
|
311
|
+
id: 'user_123',
|
|
312
|
+
email: 'user@example.com',
|
|
313
|
+
name: 'John Doe',
|
|
314
|
+
plan: 'pro',
|
|
315
|
+
company: 'Acme Inc',
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Pass null to skip identification
|
|
319
|
+
useIdentify(user ? { id: user.id, email: user.email } : null);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### OpenOptions
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
interface OpenOptions {
|
|
326
|
+
type?: 'bug' | 'feature' | 'question' | 'praise' | 'other';
|
|
327
|
+
prefill?: { text?: string };
|
|
328
|
+
metadata?: Record<string, unknown>;
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### UserIdentity
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
interface UserIdentity {
|
|
336
|
+
id: string; // Required
|
|
337
|
+
email?: string;
|
|
338
|
+
name?: string;
|
|
339
|
+
avatar?: string;
|
|
340
|
+
plan?: string;
|
|
341
|
+
company?: string;
|
|
342
|
+
[key: string]: string | number | boolean | undefined; // Custom properties
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Next.js App Router
|
|
347
|
+
|
|
348
|
+
The SDK is fully compatible with Next.js App Router and Server Components:
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
// app/providers.tsx
|
|
352
|
+
'use client';
|
|
353
|
+
|
|
354
|
+
import { IntentAIProvider } from '@intentai/react';
|
|
355
|
+
|
|
356
|
+
export function Providers({ children }) {
|
|
357
|
+
return (
|
|
358
|
+
<IntentAIProvider>
|
|
359
|
+
{children}
|
|
360
|
+
</IntentAIProvider>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// app/layout.tsx
|
|
365
|
+
import { Providers } from './providers';
|
|
366
|
+
|
|
367
|
+
export default function RootLayout({ children }) {
|
|
368
|
+
return (
|
|
369
|
+
<html>
|
|
370
|
+
<body>
|
|
371
|
+
<Providers>{children}</Providers>
|
|
372
|
+
</body>
|
|
373
|
+
</html>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## TypeScript
|
|
379
|
+
|
|
380
|
+
Full TypeScript support with exported types:
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
import type {
|
|
384
|
+
UserIdentity,
|
|
385
|
+
IntentAIConfig,
|
|
386
|
+
FeedbackData,
|
|
387
|
+
OpenOptions,
|
|
388
|
+
FeedbackType,
|
|
389
|
+
} from '@intentai/react';
|
|
390
|
+
|
|
391
|
+
const user: UserIdentity = {
|
|
392
|
+
id: '123',
|
|
393
|
+
email: 'user@example.com',
|
|
394
|
+
name: 'John Doe',
|
|
395
|
+
};
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Browser Support
|
|
399
|
+
|
|
400
|
+
- Chrome (latest)
|
|
401
|
+
- Firefox (latest)
|
|
402
|
+
- Safari (latest)
|
|
403
|
+
- Edge (latest)
|
|
404
|
+
|
|
405
|
+
## License
|
|
406
|
+
|
|
407
|
+
MIT © [Intent AI](https://intent-ai.com)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* User identity for feedback attribution
|
|
6
|
+
*/
|
|
7
|
+
interface UserIdentity {
|
|
8
|
+
/** Unique user identifier */
|
|
9
|
+
id: string;
|
|
10
|
+
/** User email address */
|
|
11
|
+
email?: string;
|
|
12
|
+
/** User display name */
|
|
13
|
+
name?: string;
|
|
14
|
+
/** User avatar URL */
|
|
15
|
+
avatar?: string;
|
|
16
|
+
/** Subscription plan */
|
|
17
|
+
plan?: string;
|
|
18
|
+
/** Company name */
|
|
19
|
+
company?: string;
|
|
20
|
+
/** Custom properties */
|
|
21
|
+
[key: string]: string | number | boolean | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Widget position on screen
|
|
25
|
+
*/
|
|
26
|
+
type WidgetPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
27
|
+
/**
|
|
28
|
+
* Widget color theme
|
|
29
|
+
*/
|
|
30
|
+
type WidgetTheme = 'light' | 'dark' | 'auto';
|
|
31
|
+
/**
|
|
32
|
+
* Feedback type categories
|
|
33
|
+
*/
|
|
34
|
+
type FeedbackType = 'bug' | 'feature' | 'question' | 'praise' | 'other';
|
|
35
|
+
/**
|
|
36
|
+
* Sentiment analysis result
|
|
37
|
+
*/
|
|
38
|
+
type Sentiment = 'positive' | 'neutral' | 'negative';
|
|
39
|
+
/**
|
|
40
|
+
* Data returned when feedback is submitted
|
|
41
|
+
*/
|
|
42
|
+
interface FeedbackData {
|
|
43
|
+
/** Feedback ID */
|
|
44
|
+
id: string;
|
|
45
|
+
/** Type of feedback */
|
|
46
|
+
type: FeedbackType;
|
|
47
|
+
/** Text content */
|
|
48
|
+
content: string;
|
|
49
|
+
/** Voice recording URL */
|
|
50
|
+
voiceUrl?: string;
|
|
51
|
+
/** Screenshot data URL */
|
|
52
|
+
screenshot?: string;
|
|
53
|
+
/** AI-analyzed sentiment */
|
|
54
|
+
sentiment?: Sentiment;
|
|
55
|
+
/** Submission timestamp */
|
|
56
|
+
createdAt: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Options for opening the widget
|
|
60
|
+
*/
|
|
61
|
+
interface OpenOptions {
|
|
62
|
+
/** Pre-select feedback type */
|
|
63
|
+
type?: FeedbackType;
|
|
64
|
+
/** Prefill text content */
|
|
65
|
+
prefill?: {
|
|
66
|
+
text?: string;
|
|
67
|
+
};
|
|
68
|
+
/** Additional metadata for this feedback */
|
|
69
|
+
metadata?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Configuration options for the Intent AI widget
|
|
73
|
+
*/
|
|
74
|
+
interface IntentAIConfig {
|
|
75
|
+
/**
|
|
76
|
+
* Your Intent AI public key (pk_live_* or pk_test_*)
|
|
77
|
+
* Can also be set via environment variables:
|
|
78
|
+
* - NEXT_PUBLIC_INTENT_AI_KEY
|
|
79
|
+
* - REACT_APP_INTENT_AI_KEY
|
|
80
|
+
* - VITE_INTENT_AI_KEY
|
|
81
|
+
*/
|
|
82
|
+
apiKey?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Widget position on screen
|
|
85
|
+
* @default "bottom-right"
|
|
86
|
+
*/
|
|
87
|
+
position?: WidgetPosition;
|
|
88
|
+
/**
|
|
89
|
+
* Color theme
|
|
90
|
+
* @default "auto"
|
|
91
|
+
*/
|
|
92
|
+
theme?: WidgetTheme;
|
|
93
|
+
/**
|
|
94
|
+
* Primary brand color (hex)
|
|
95
|
+
* @default "#6366f1"
|
|
96
|
+
*/
|
|
97
|
+
primaryColor?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Show widget button automatically
|
|
100
|
+
* @default true
|
|
101
|
+
*/
|
|
102
|
+
autoShow?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Initial user identification
|
|
105
|
+
*/
|
|
106
|
+
user?: UserIdentity;
|
|
107
|
+
/**
|
|
108
|
+
* Custom metadata attached to all feedback
|
|
109
|
+
*/
|
|
110
|
+
metadata?: Record<string, unknown>;
|
|
111
|
+
/**
|
|
112
|
+
* Callback when widget is ready
|
|
113
|
+
*/
|
|
114
|
+
onReady?: () => void;
|
|
115
|
+
/**
|
|
116
|
+
* Callback when widget opens
|
|
117
|
+
*/
|
|
118
|
+
onOpen?: () => void;
|
|
119
|
+
/**
|
|
120
|
+
* Callback when widget closes
|
|
121
|
+
*/
|
|
122
|
+
onClose?: () => void;
|
|
123
|
+
/**
|
|
124
|
+
* Callback when feedback is submitted
|
|
125
|
+
*/
|
|
126
|
+
onSubmit?: (feedback: FeedbackData) => void;
|
|
127
|
+
/**
|
|
128
|
+
* Callback when an error occurs
|
|
129
|
+
*/
|
|
130
|
+
onError?: (error: Error) => void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Props for IntentAIProvider component
|
|
134
|
+
*/
|
|
135
|
+
interface IntentAIProviderProps extends IntentAIConfig {
|
|
136
|
+
children: React.ReactNode;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Context value for IntentAI provider
|
|
140
|
+
*/
|
|
141
|
+
interface IntentAIContextValue {
|
|
142
|
+
/** Whether the widget is loaded and ready */
|
|
143
|
+
isReady: boolean;
|
|
144
|
+
/** Error if widget failed to load */
|
|
145
|
+
error: Error | null;
|
|
146
|
+
/** Open the feedback widget */
|
|
147
|
+
open: (options?: OpenOptions) => void;
|
|
148
|
+
/** Close the feedback widget */
|
|
149
|
+
close: () => void;
|
|
150
|
+
/** Identify a user */
|
|
151
|
+
identify: (user: UserIdentity) => void;
|
|
152
|
+
/** Track a custom event */
|
|
153
|
+
track: (event: string, properties?: Record<string, unknown>) => void;
|
|
154
|
+
/** Set custom metadata */
|
|
155
|
+
setMetadata: (metadata: Record<string, unknown>) => void;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Intent AI widget instance methods
|
|
159
|
+
*/
|
|
160
|
+
interface IntentAIInstance {
|
|
161
|
+
open: (options?: OpenOptions) => void;
|
|
162
|
+
close: () => void;
|
|
163
|
+
identify: (user: UserIdentity) => void;
|
|
164
|
+
track: (event: string, properties?: Record<string, unknown>) => void;
|
|
165
|
+
setMetadata: (metadata: Record<string, unknown>) => void;
|
|
166
|
+
destroy: () => void;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Window augmentation for Intent AI global
|
|
170
|
+
*/
|
|
171
|
+
declare global {
|
|
172
|
+
interface Window {
|
|
173
|
+
IntentAI?: IntentAIInstance;
|
|
174
|
+
IntentAIConfig?: IntentAIConfig;
|
|
175
|
+
}
|
|
176
|
+
interface WindowEventMap {
|
|
177
|
+
'intentai:ready': CustomEvent;
|
|
178
|
+
'intentai:open': CustomEvent;
|
|
179
|
+
'intentai:close': CustomEvent;
|
|
180
|
+
'intentai:submit': CustomEvent<FeedbackData>;
|
|
181
|
+
'intentai:error': CustomEvent<Error>;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Provider component for Intent AI widget
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```tsx
|
|
190
|
+
* import { IntentAIProvider } from '@intentai/react';
|
|
191
|
+
*
|
|
192
|
+
* function App() {
|
|
193
|
+
* return (
|
|
194
|
+
* <IntentAIProvider apiKey="pk_live_xxxxx">
|
|
195
|
+
* <YourApp />
|
|
196
|
+
* </IntentAIProvider>
|
|
197
|
+
* );
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
declare function IntentAIProvider({ children, apiKey, user, position, theme, primaryColor, autoShow, metadata, onReady, onOpen, onClose, onSubmit, onError, }: IntentAIProviderProps): react_jsx_runtime.JSX.Element;
|
|
202
|
+
/**
|
|
203
|
+
* Hook to access Intent AI widget controls
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```tsx
|
|
207
|
+
* import { useIntentAI } from '@intentai/react';
|
|
208
|
+
*
|
|
209
|
+
* function FeedbackButton() {
|
|
210
|
+
* const { open, isReady } = useIntentAI();
|
|
211
|
+
*
|
|
212
|
+
* return (
|
|
213
|
+
* <button onClick={() => open()} disabled={!isReady}>
|
|
214
|
+
* Give Feedback
|
|
215
|
+
* </button>
|
|
216
|
+
* );
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
declare function useIntentAI(): IntentAIContextValue;
|
|
221
|
+
/**
|
|
222
|
+
* Hook to automatically identify a user when they're available
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```tsx
|
|
226
|
+
* import { useIdentify } from '@intentai/react';
|
|
227
|
+
*
|
|
228
|
+
* function Profile({ user }) {
|
|
229
|
+
* useIdentify(user ? { id: user.id, email: user.email } : null);
|
|
230
|
+
* return <div>...</div>;
|
|
231
|
+
* }
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
declare function useIdentify(user: UserIdentity | null | undefined): void;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Props for FeedbackButton component
|
|
238
|
+
*/
|
|
239
|
+
interface FeedbackButtonProps extends React$1.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
240
|
+
/** Options to pass when opening the widget */
|
|
241
|
+
openOptions?: OpenOptions;
|
|
242
|
+
/** Custom children to render */
|
|
243
|
+
children?: React$1.ReactNode;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Pre-built button component to open the feedback widget
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```tsx
|
|
250
|
+
* import { FeedbackButton } from '@intentai/react';
|
|
251
|
+
*
|
|
252
|
+
* function App() {
|
|
253
|
+
* return (
|
|
254
|
+
* <FeedbackButton className="my-custom-class">
|
|
255
|
+
* Send Feedback
|
|
256
|
+
* </FeedbackButton>
|
|
257
|
+
* );
|
|
258
|
+
* }
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
declare const FeedbackButton: React$1.ForwardRefExoticComponent<FeedbackButtonProps & React$1.RefAttributes<HTMLButtonElement>>;
|
|
262
|
+
/**
|
|
263
|
+
* Props for FeedbackTrigger component
|
|
264
|
+
*/
|
|
265
|
+
interface FeedbackTriggerProps {
|
|
266
|
+
/** Render function or element */
|
|
267
|
+
children: React$1.ReactElement | ((props: {
|
|
268
|
+
open: (options?: OpenOptions) => void;
|
|
269
|
+
isReady: boolean;
|
|
270
|
+
error: Error | null;
|
|
271
|
+
}) => React$1.ReactElement);
|
|
272
|
+
/** Options to pass when opening */
|
|
273
|
+
openOptions?: OpenOptions;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Headless component for custom feedback triggers
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```tsx
|
|
280
|
+
* import { FeedbackTrigger } from '@intentai/react';
|
|
281
|
+
*
|
|
282
|
+
* function App() {
|
|
283
|
+
* return (
|
|
284
|
+
* <FeedbackTrigger>
|
|
285
|
+
* {({ open, isReady }) => (
|
|
286
|
+
* <MyCustomButton onClick={() => open()} disabled={!isReady}>
|
|
287
|
+
* Feedback
|
|
288
|
+
* </MyCustomButton>
|
|
289
|
+
* )}
|
|
290
|
+
* </FeedbackTrigger>
|
|
291
|
+
* );
|
|
292
|
+
* }
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
declare function FeedbackTrigger({ children, openOptions }: FeedbackTriggerProps): React$1.ReactElement<any, string | React$1.JSXElementConstructor<any>>;
|
|
296
|
+
/**
|
|
297
|
+
* Props for IdentifyUser component
|
|
298
|
+
*/
|
|
299
|
+
interface IdentifyUserProps {
|
|
300
|
+
/** User to identify */
|
|
301
|
+
user: UserIdentity | null | undefined;
|
|
302
|
+
/** Children to render */
|
|
303
|
+
children?: React$1.ReactNode;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Component to identify a user declaratively
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```tsx
|
|
310
|
+
* import { IdentifyUser } from '@intentai/react';
|
|
311
|
+
*
|
|
312
|
+
* function App({ user }) {
|
|
313
|
+
* return (
|
|
314
|
+
* <IdentifyUser user={user ? { id: user.id, email: user.email } : null}>
|
|
315
|
+
* <YourApp />
|
|
316
|
+
* </IdentifyUser>
|
|
317
|
+
* );
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
declare function IdentifyUser({ user, children }: IdentifyUserProps): react_jsx_runtime.JSX.Element;
|
|
322
|
+
/**
|
|
323
|
+
* Props for SetMetadata component
|
|
324
|
+
*/
|
|
325
|
+
interface SetMetadataProps {
|
|
326
|
+
/** Metadata to set */
|
|
327
|
+
metadata: Record<string, unknown>;
|
|
328
|
+
/** Children to render */
|
|
329
|
+
children?: React$1.ReactNode;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Component to set metadata declaratively
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```tsx
|
|
336
|
+
* import { SetMetadata } from '@intentai/react';
|
|
337
|
+
*
|
|
338
|
+
* function PricingPage() {
|
|
339
|
+
* return (
|
|
340
|
+
* <SetMetadata metadata={{ page: 'pricing', plan: 'pro' }}>
|
|
341
|
+
* <PricingContent />
|
|
342
|
+
* </SetMetadata>
|
|
343
|
+
* );
|
|
344
|
+
* }
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
declare function SetMetadata({ metadata, children }: SetMetadataProps): react_jsx_runtime.JSX.Element;
|
|
348
|
+
/**
|
|
349
|
+
* Props for TrackEvent component
|
|
350
|
+
*/
|
|
351
|
+
interface TrackEventProps {
|
|
352
|
+
/** Event name to track */
|
|
353
|
+
event: string;
|
|
354
|
+
/** Event properties */
|
|
355
|
+
properties?: Record<string, unknown>;
|
|
356
|
+
/** When to track: 'mount' (default) or 'render' */
|
|
357
|
+
trigger?: 'mount' | 'render';
|
|
358
|
+
/** Children to render */
|
|
359
|
+
children?: React$1.ReactNode;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Component to track events declaratively
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```tsx
|
|
366
|
+
* import { TrackEvent } from '@intentai/react';
|
|
367
|
+
*
|
|
368
|
+
* function CheckoutPage() {
|
|
369
|
+
* return (
|
|
370
|
+
* <TrackEvent event="checkout_viewed" properties={{ items: 3 }}>
|
|
371
|
+
* <CheckoutContent />
|
|
372
|
+
* </TrackEvent>
|
|
373
|
+
* );
|
|
374
|
+
* }
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
declare function TrackEvent({ event, properties, trigger, children, }: TrackEventProps): react_jsx_runtime.JSX.Element;
|
|
378
|
+
|
|
379
|
+
export { FeedbackButton, type FeedbackButtonProps, type FeedbackData, FeedbackTrigger, type FeedbackTriggerProps, type FeedbackType, IdentifyUser, type IdentifyUserProps, type IntentAIConfig, type IntentAIContextValue, type IntentAIInstance, IntentAIProvider, type IntentAIProviderProps, type OpenOptions, type Sentiment, SetMetadata, type SetMetadataProps, TrackEvent, type TrackEventProps, type UserIdentity, type WidgetPosition, type WidgetTheme, useIdentify, useIntentAI };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* User identity for feedback attribution
|
|
6
|
+
*/
|
|
7
|
+
interface UserIdentity {
|
|
8
|
+
/** Unique user identifier */
|
|
9
|
+
id: string;
|
|
10
|
+
/** User email address */
|
|
11
|
+
email?: string;
|
|
12
|
+
/** User display name */
|
|
13
|
+
name?: string;
|
|
14
|
+
/** User avatar URL */
|
|
15
|
+
avatar?: string;
|
|
16
|
+
/** Subscription plan */
|
|
17
|
+
plan?: string;
|
|
18
|
+
/** Company name */
|
|
19
|
+
company?: string;
|
|
20
|
+
/** Custom properties */
|
|
21
|
+
[key: string]: string | number | boolean | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Widget position on screen
|
|
25
|
+
*/
|
|
26
|
+
type WidgetPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
27
|
+
/**
|
|
28
|
+
* Widget color theme
|
|
29
|
+
*/
|
|
30
|
+
type WidgetTheme = 'light' | 'dark' | 'auto';
|
|
31
|
+
/**
|
|
32
|
+
* Feedback type categories
|
|
33
|
+
*/
|
|
34
|
+
type FeedbackType = 'bug' | 'feature' | 'question' | 'praise' | 'other';
|
|
35
|
+
/**
|
|
36
|
+
* Sentiment analysis result
|
|
37
|
+
*/
|
|
38
|
+
type Sentiment = 'positive' | 'neutral' | 'negative';
|
|
39
|
+
/**
|
|
40
|
+
* Data returned when feedback is submitted
|
|
41
|
+
*/
|
|
42
|
+
interface FeedbackData {
|
|
43
|
+
/** Feedback ID */
|
|
44
|
+
id: string;
|
|
45
|
+
/** Type of feedback */
|
|
46
|
+
type: FeedbackType;
|
|
47
|
+
/** Text content */
|
|
48
|
+
content: string;
|
|
49
|
+
/** Voice recording URL */
|
|
50
|
+
voiceUrl?: string;
|
|
51
|
+
/** Screenshot data URL */
|
|
52
|
+
screenshot?: string;
|
|
53
|
+
/** AI-analyzed sentiment */
|
|
54
|
+
sentiment?: Sentiment;
|
|
55
|
+
/** Submission timestamp */
|
|
56
|
+
createdAt: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Options for opening the widget
|
|
60
|
+
*/
|
|
61
|
+
interface OpenOptions {
|
|
62
|
+
/** Pre-select feedback type */
|
|
63
|
+
type?: FeedbackType;
|
|
64
|
+
/** Prefill text content */
|
|
65
|
+
prefill?: {
|
|
66
|
+
text?: string;
|
|
67
|
+
};
|
|
68
|
+
/** Additional metadata for this feedback */
|
|
69
|
+
metadata?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Configuration options for the Intent AI widget
|
|
73
|
+
*/
|
|
74
|
+
interface IntentAIConfig {
|
|
75
|
+
/**
|
|
76
|
+
* Your Intent AI public key (pk_live_* or pk_test_*)
|
|
77
|
+
* Can also be set via environment variables:
|
|
78
|
+
* - NEXT_PUBLIC_INTENT_AI_KEY
|
|
79
|
+
* - REACT_APP_INTENT_AI_KEY
|
|
80
|
+
* - VITE_INTENT_AI_KEY
|
|
81
|
+
*/
|
|
82
|
+
apiKey?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Widget position on screen
|
|
85
|
+
* @default "bottom-right"
|
|
86
|
+
*/
|
|
87
|
+
position?: WidgetPosition;
|
|
88
|
+
/**
|
|
89
|
+
* Color theme
|
|
90
|
+
* @default "auto"
|
|
91
|
+
*/
|
|
92
|
+
theme?: WidgetTheme;
|
|
93
|
+
/**
|
|
94
|
+
* Primary brand color (hex)
|
|
95
|
+
* @default "#6366f1"
|
|
96
|
+
*/
|
|
97
|
+
primaryColor?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Show widget button automatically
|
|
100
|
+
* @default true
|
|
101
|
+
*/
|
|
102
|
+
autoShow?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Initial user identification
|
|
105
|
+
*/
|
|
106
|
+
user?: UserIdentity;
|
|
107
|
+
/**
|
|
108
|
+
* Custom metadata attached to all feedback
|
|
109
|
+
*/
|
|
110
|
+
metadata?: Record<string, unknown>;
|
|
111
|
+
/**
|
|
112
|
+
* Callback when widget is ready
|
|
113
|
+
*/
|
|
114
|
+
onReady?: () => void;
|
|
115
|
+
/**
|
|
116
|
+
* Callback when widget opens
|
|
117
|
+
*/
|
|
118
|
+
onOpen?: () => void;
|
|
119
|
+
/**
|
|
120
|
+
* Callback when widget closes
|
|
121
|
+
*/
|
|
122
|
+
onClose?: () => void;
|
|
123
|
+
/**
|
|
124
|
+
* Callback when feedback is submitted
|
|
125
|
+
*/
|
|
126
|
+
onSubmit?: (feedback: FeedbackData) => void;
|
|
127
|
+
/**
|
|
128
|
+
* Callback when an error occurs
|
|
129
|
+
*/
|
|
130
|
+
onError?: (error: Error) => void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Props for IntentAIProvider component
|
|
134
|
+
*/
|
|
135
|
+
interface IntentAIProviderProps extends IntentAIConfig {
|
|
136
|
+
children: React.ReactNode;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Context value for IntentAI provider
|
|
140
|
+
*/
|
|
141
|
+
interface IntentAIContextValue {
|
|
142
|
+
/** Whether the widget is loaded and ready */
|
|
143
|
+
isReady: boolean;
|
|
144
|
+
/** Error if widget failed to load */
|
|
145
|
+
error: Error | null;
|
|
146
|
+
/** Open the feedback widget */
|
|
147
|
+
open: (options?: OpenOptions) => void;
|
|
148
|
+
/** Close the feedback widget */
|
|
149
|
+
close: () => void;
|
|
150
|
+
/** Identify a user */
|
|
151
|
+
identify: (user: UserIdentity) => void;
|
|
152
|
+
/** Track a custom event */
|
|
153
|
+
track: (event: string, properties?: Record<string, unknown>) => void;
|
|
154
|
+
/** Set custom metadata */
|
|
155
|
+
setMetadata: (metadata: Record<string, unknown>) => void;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Intent AI widget instance methods
|
|
159
|
+
*/
|
|
160
|
+
interface IntentAIInstance {
|
|
161
|
+
open: (options?: OpenOptions) => void;
|
|
162
|
+
close: () => void;
|
|
163
|
+
identify: (user: UserIdentity) => void;
|
|
164
|
+
track: (event: string, properties?: Record<string, unknown>) => void;
|
|
165
|
+
setMetadata: (metadata: Record<string, unknown>) => void;
|
|
166
|
+
destroy: () => void;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Window augmentation for Intent AI global
|
|
170
|
+
*/
|
|
171
|
+
declare global {
|
|
172
|
+
interface Window {
|
|
173
|
+
IntentAI?: IntentAIInstance;
|
|
174
|
+
IntentAIConfig?: IntentAIConfig;
|
|
175
|
+
}
|
|
176
|
+
interface WindowEventMap {
|
|
177
|
+
'intentai:ready': CustomEvent;
|
|
178
|
+
'intentai:open': CustomEvent;
|
|
179
|
+
'intentai:close': CustomEvent;
|
|
180
|
+
'intentai:submit': CustomEvent<FeedbackData>;
|
|
181
|
+
'intentai:error': CustomEvent<Error>;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Provider component for Intent AI widget
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```tsx
|
|
190
|
+
* import { IntentAIProvider } from '@intentai/react';
|
|
191
|
+
*
|
|
192
|
+
* function App() {
|
|
193
|
+
* return (
|
|
194
|
+
* <IntentAIProvider apiKey="pk_live_xxxxx">
|
|
195
|
+
* <YourApp />
|
|
196
|
+
* </IntentAIProvider>
|
|
197
|
+
* );
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
declare function IntentAIProvider({ children, apiKey, user, position, theme, primaryColor, autoShow, metadata, onReady, onOpen, onClose, onSubmit, onError, }: IntentAIProviderProps): react_jsx_runtime.JSX.Element;
|
|
202
|
+
/**
|
|
203
|
+
* Hook to access Intent AI widget controls
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```tsx
|
|
207
|
+
* import { useIntentAI } from '@intentai/react';
|
|
208
|
+
*
|
|
209
|
+
* function FeedbackButton() {
|
|
210
|
+
* const { open, isReady } = useIntentAI();
|
|
211
|
+
*
|
|
212
|
+
* return (
|
|
213
|
+
* <button onClick={() => open()} disabled={!isReady}>
|
|
214
|
+
* Give Feedback
|
|
215
|
+
* </button>
|
|
216
|
+
* );
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
declare function useIntentAI(): IntentAIContextValue;
|
|
221
|
+
/**
|
|
222
|
+
* Hook to automatically identify a user when they're available
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```tsx
|
|
226
|
+
* import { useIdentify } from '@intentai/react';
|
|
227
|
+
*
|
|
228
|
+
* function Profile({ user }) {
|
|
229
|
+
* useIdentify(user ? { id: user.id, email: user.email } : null);
|
|
230
|
+
* return <div>...</div>;
|
|
231
|
+
* }
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
declare function useIdentify(user: UserIdentity | null | undefined): void;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Props for FeedbackButton component
|
|
238
|
+
*/
|
|
239
|
+
interface FeedbackButtonProps extends React$1.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
240
|
+
/** Options to pass when opening the widget */
|
|
241
|
+
openOptions?: OpenOptions;
|
|
242
|
+
/** Custom children to render */
|
|
243
|
+
children?: React$1.ReactNode;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Pre-built button component to open the feedback widget
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```tsx
|
|
250
|
+
* import { FeedbackButton } from '@intentai/react';
|
|
251
|
+
*
|
|
252
|
+
* function App() {
|
|
253
|
+
* return (
|
|
254
|
+
* <FeedbackButton className="my-custom-class">
|
|
255
|
+
* Send Feedback
|
|
256
|
+
* </FeedbackButton>
|
|
257
|
+
* );
|
|
258
|
+
* }
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
declare const FeedbackButton: React$1.ForwardRefExoticComponent<FeedbackButtonProps & React$1.RefAttributes<HTMLButtonElement>>;
|
|
262
|
+
/**
|
|
263
|
+
* Props for FeedbackTrigger component
|
|
264
|
+
*/
|
|
265
|
+
interface FeedbackTriggerProps {
|
|
266
|
+
/** Render function or element */
|
|
267
|
+
children: React$1.ReactElement | ((props: {
|
|
268
|
+
open: (options?: OpenOptions) => void;
|
|
269
|
+
isReady: boolean;
|
|
270
|
+
error: Error | null;
|
|
271
|
+
}) => React$1.ReactElement);
|
|
272
|
+
/** Options to pass when opening */
|
|
273
|
+
openOptions?: OpenOptions;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Headless component for custom feedback triggers
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```tsx
|
|
280
|
+
* import { FeedbackTrigger } from '@intentai/react';
|
|
281
|
+
*
|
|
282
|
+
* function App() {
|
|
283
|
+
* return (
|
|
284
|
+
* <FeedbackTrigger>
|
|
285
|
+
* {({ open, isReady }) => (
|
|
286
|
+
* <MyCustomButton onClick={() => open()} disabled={!isReady}>
|
|
287
|
+
* Feedback
|
|
288
|
+
* </MyCustomButton>
|
|
289
|
+
* )}
|
|
290
|
+
* </FeedbackTrigger>
|
|
291
|
+
* );
|
|
292
|
+
* }
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
declare function FeedbackTrigger({ children, openOptions }: FeedbackTriggerProps): React$1.ReactElement<any, string | React$1.JSXElementConstructor<any>>;
|
|
296
|
+
/**
|
|
297
|
+
* Props for IdentifyUser component
|
|
298
|
+
*/
|
|
299
|
+
interface IdentifyUserProps {
|
|
300
|
+
/** User to identify */
|
|
301
|
+
user: UserIdentity | null | undefined;
|
|
302
|
+
/** Children to render */
|
|
303
|
+
children?: React$1.ReactNode;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Component to identify a user declaratively
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```tsx
|
|
310
|
+
* import { IdentifyUser } from '@intentai/react';
|
|
311
|
+
*
|
|
312
|
+
* function App({ user }) {
|
|
313
|
+
* return (
|
|
314
|
+
* <IdentifyUser user={user ? { id: user.id, email: user.email } : null}>
|
|
315
|
+
* <YourApp />
|
|
316
|
+
* </IdentifyUser>
|
|
317
|
+
* );
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
declare function IdentifyUser({ user, children }: IdentifyUserProps): react_jsx_runtime.JSX.Element;
|
|
322
|
+
/**
|
|
323
|
+
* Props for SetMetadata component
|
|
324
|
+
*/
|
|
325
|
+
interface SetMetadataProps {
|
|
326
|
+
/** Metadata to set */
|
|
327
|
+
metadata: Record<string, unknown>;
|
|
328
|
+
/** Children to render */
|
|
329
|
+
children?: React$1.ReactNode;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Component to set metadata declaratively
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```tsx
|
|
336
|
+
* import { SetMetadata } from '@intentai/react';
|
|
337
|
+
*
|
|
338
|
+
* function PricingPage() {
|
|
339
|
+
* return (
|
|
340
|
+
* <SetMetadata metadata={{ page: 'pricing', plan: 'pro' }}>
|
|
341
|
+
* <PricingContent />
|
|
342
|
+
* </SetMetadata>
|
|
343
|
+
* );
|
|
344
|
+
* }
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
declare function SetMetadata({ metadata, children }: SetMetadataProps): react_jsx_runtime.JSX.Element;
|
|
348
|
+
/**
|
|
349
|
+
* Props for TrackEvent component
|
|
350
|
+
*/
|
|
351
|
+
interface TrackEventProps {
|
|
352
|
+
/** Event name to track */
|
|
353
|
+
event: string;
|
|
354
|
+
/** Event properties */
|
|
355
|
+
properties?: Record<string, unknown>;
|
|
356
|
+
/** When to track: 'mount' (default) or 'render' */
|
|
357
|
+
trigger?: 'mount' | 'render';
|
|
358
|
+
/** Children to render */
|
|
359
|
+
children?: React$1.ReactNode;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Component to track events declaratively
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```tsx
|
|
366
|
+
* import { TrackEvent } from '@intentai/react';
|
|
367
|
+
*
|
|
368
|
+
* function CheckoutPage() {
|
|
369
|
+
* return (
|
|
370
|
+
* <TrackEvent event="checkout_viewed" properties={{ items: 3 }}>
|
|
371
|
+
* <CheckoutContent />
|
|
372
|
+
* </TrackEvent>
|
|
373
|
+
* );
|
|
374
|
+
* }
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
declare function TrackEvent({ event, properties, trigger, children, }: TrackEventProps): react_jsx_runtime.JSX.Element;
|
|
378
|
+
|
|
379
|
+
export { FeedbackButton, type FeedbackButtonProps, type FeedbackData, FeedbackTrigger, type FeedbackTriggerProps, type FeedbackType, IdentifyUser, type IdentifyUserProps, type IntentAIConfig, type IntentAIContextValue, type IntentAIInstance, IntentAIProvider, type IntentAIProviderProps, type OpenOptions, type Sentiment, SetMetadata, type SetMetadataProps, TrackEvent, type TrackEventProps, type UserIdentity, type WidgetPosition, type WidgetTheme, useIdentify, useIntentAI };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var G=require('react'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var G__default=/*#__PURE__*/_interopDefault(G);var Y="https://app.intent-ai.com/widget/v2.js",k=null,E=false;function H(e){return E&&window.IntentAI?Promise.resolve():k||(k=new Promise((t,n)=>{if(document.querySelector(`script[src^="${Y}"]`)){window.IntentAI?(E=true,t()):window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true});return}let o=document.createElement("script");o.src=`${Y}?key=${encodeURIComponent(e.apiKey)}`,o.async=true,o.id="intentai-widget-script",o.onload=()=>{window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true}),setTimeout(()=>{!E&&window.IntentAI&&(E=true,t());},3e3);},o.onerror=()=>{k=null,n(new Error("Failed to load Intent AI widget script"));},document.head.appendChild(o);}),k)}var L={};var q=G.createContext(null),v=typeof window!="undefined";function j(e){if(e)return e;if(typeof process!="undefined"&&process.env){let t=process.env.NEXT_PUBLIC_INTENT_AI_KEY||process.env.REACT_APP_INTENT_AI_KEY||process.env.VITE_INTENT_AI_KEY;if(t)return t}try{if(typeof L!="undefined"&&L.env)return L.env.VITE_INTENT_AI_KEY}catch(t){}}function ee({children:e,apiKey:t,user:n,position:o="bottom-right",theme:p="auto",primaryColor:s,autoShow:a=true,metadata:u,onReady:f,onOpen:x,onClose:m,onSubmit:C,onError:O}){let[R,z]=G.useState(false),[N,F]=G.useState(null),b=G.useRef(n),l=G.useRef({onReady:f,onOpen:x,onClose:m,onSubmit:C,onError:O});l.current={onReady:f,onOpen:x,onClose:m,onSubmit:C,onError:O};let w=G.useMemo(()=>j(t),[t]);G.useEffect(()=>{w&&!w.startsWith("pk_")&&console.error('[IntentAI] Invalid API key format. Public keys should start with "pk_". Never use secret keys (sk_*) in client-side code.');},[w]),G.useEffect(()=>{if(!v)return;if(!w){console.warn("[IntentAI] No API key provided. Set the apiKey prop or NEXT_PUBLIC_INTENT_AI_KEY environment variable.");return}window.IntentAIConfig={apiKey:w,position:o,theme:p,primaryColor:s,autoShow:a,metadata:u};let d=()=>{var i,r,c;z(true),(r=(i=l.current).onReady)==null||r.call(i),b.current&&((c=window.IntentAI)==null||c.identify(b.current));},y=()=>{var i,r;(r=(i=l.current).onOpen)==null||r.call(i);},A=()=>{var i,r;(r=(i=l.current).onClose)==null||r.call(i);},W=i=>{var r,c;(c=(r=l.current).onSubmit)==null||c.call(r,i.detail);},V=i=>{var r,c;F(i.detail),(c=(r=l.current).onError)==null||c.call(r,i.detail);};return window.addEventListener("intentai:ready",d),window.addEventListener("intentai:open",y),window.addEventListener("intentai:close",A),window.addEventListener("intentai:submit",W),window.addEventListener("intentai:error",V),H({apiKey:w}).catch(i=>{var r,c;F(i),(c=(r=l.current).onError)==null||c.call(r,i);}),()=>{window.removeEventListener("intentai:ready",d),window.removeEventListener("intentai:open",y),window.removeEventListener("intentai:close",A),window.removeEventListener("intentai:submit",W),window.removeEventListener("intentai:error",V);}},[w,o,p,s,a,u]),G.useEffect(()=>{b.current=n,v&&R&&n&&window.IntentAI&&window.IntentAI.identify(n);},[n,R]);let M=G.useCallback(d=>{v&&(window.IntentAI?window.IntentAI.open(d):console.warn("[IntentAI] Widget not ready. Call will be ignored."));},[]),U=G.useCallback(()=>{var d;v&&((d=window.IntentAI)==null||d.close());},[]),B=G.useCallback(d=>{v&&(window.IntentAI?window.IntentAI.identify(d):b.current=d);},[]),K=G.useCallback((d,y)=>{var A;v&&((A=window.IntentAI)==null||A.track(d,y));},[]),S=G.useCallback(d=>{var y;v&&((y=window.IntentAI)==null||y.setMetadata(d));},[]),J=G.useMemo(()=>({isReady:R,error:N,open:M,close:U,identify:B,track:K,setMetadata:S}),[R,N,M,U,B,K,S]);return jsxRuntime.jsx(q.Provider,{value:J,children:e})}function I(){let e=G.useContext(q);if(!e)throw new Error('useIntentAI must be used within an IntentAIProvider. Wrap your app with <IntentAIProvider apiKey="pk_...">.');return e}function te(e){let{identify:t,isReady:n}=I();G.useEffect(()=>{n&&(e!=null&&e.id)&&t(e);},[n,e==null?void 0:e.id,e==null?void 0:e.email,e==null?void 0:e.name,t,e]);}var re=G.forwardRef(function({openOptions:t,children:n="Feedback",onClick:o,disabled:p,...s},a){let{open:u,isReady:f}=I();return jsxRuntime.jsx("button",{ref:a,type:"button",onClick:m=>{o==null||o(m),m.defaultPrevented||u(t);},disabled:p||!f,"aria-label":"Open feedback",...s,children:n})});function ie({children:e,openOptions:t}){let{open:n,isReady:o,error:p}=I(),s=a=>{n(a||t);};return typeof e=="function"?e({open:s,isReady:o,error:p}):G__default.default.cloneElement(e,{onClick:a=>{var u,f;(f=(u=e.props).onClick)==null||f.call(u,a),a.defaultPrevented||s();},disabled:e.props.disabled||!o})}function de({user:e,children:t}){let{identify:n,isReady:o}=I();return G.useEffect(()=>{o&&(e!=null&&e.id)&&n(e);},[o,e,n]),jsxRuntime.jsx(jsxRuntime.Fragment,{children:t})}function ae({metadata:e,children:t}){let{setMetadata:n,isReady:o}=I();return G.useEffect(()=>{o&&e&&n(e);},[o,e,n]),jsxRuntime.jsx(jsxRuntime.Fragment,{children:t})}function se({event:e,properties:t,trigger:n="mount",children:o}){let{track:p,isReady:s}=I(),a=G__default.default.useRef(false);return G.useEffect(()=>{n==="mount"&&s&&!a.current&&(p(e,t),a.current=true);},[s,e,t,p,n]),n==="render"&&s&&p(e,t),jsxRuntime.jsx(jsxRuntime.Fragment,{children:o})}
|
|
2
|
+
exports.FeedbackButton=re;exports.FeedbackTrigger=ie;exports.IdentifyUser=de;exports.IntentAIProvider=ee;exports.SetMetadata=ae;exports.TrackEvent=se;exports.useIdentify=te;exports.useIntentAI=I;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import G,{createContext,forwardRef,useContext,useState,useRef,useMemo,useEffect,useCallback}from'react';import {jsx,Fragment}from'react/jsx-runtime';var Y="https://app.intent-ai.com/widget/v2.js",k=null,E=false;function H(e){return E&&window.IntentAI?Promise.resolve():k||(k=new Promise((t,n)=>{if(document.querySelector(`script[src^="${Y}"]`)){window.IntentAI?(E=true,t()):window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true});return}let o=document.createElement("script");o.src=`${Y}?key=${encodeURIComponent(e.apiKey)}`,o.async=true,o.id="intentai-widget-script",o.onload=()=>{window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true}),setTimeout(()=>{!E&&window.IntentAI&&(E=true,t());},3e3);},o.onerror=()=>{k=null,n(new Error("Failed to load Intent AI widget script"));},document.head.appendChild(o);}),k)}var L={};var q=createContext(null),v=typeof window!="undefined";function j(e){if(e)return e;if(typeof process!="undefined"&&process.env){let t=process.env.NEXT_PUBLIC_INTENT_AI_KEY||process.env.REACT_APP_INTENT_AI_KEY||process.env.VITE_INTENT_AI_KEY;if(t)return t}try{if(typeof L!="undefined"&&L.env)return L.env.VITE_INTENT_AI_KEY}catch(t){}}function ee({children:e,apiKey:t,user:n,position:o="bottom-right",theme:p="auto",primaryColor:s,autoShow:a=true,metadata:u,onReady:f,onOpen:x,onClose:m,onSubmit:C,onError:O}){let[R,z]=useState(false),[N,F]=useState(null),b=useRef(n),l=useRef({onReady:f,onOpen:x,onClose:m,onSubmit:C,onError:O});l.current={onReady:f,onOpen:x,onClose:m,onSubmit:C,onError:O};let w=useMemo(()=>j(t),[t]);useEffect(()=>{w&&!w.startsWith("pk_")&&console.error('[IntentAI] Invalid API key format. Public keys should start with "pk_". Never use secret keys (sk_*) in client-side code.');},[w]),useEffect(()=>{if(!v)return;if(!w){console.warn("[IntentAI] No API key provided. Set the apiKey prop or NEXT_PUBLIC_INTENT_AI_KEY environment variable.");return}window.IntentAIConfig={apiKey:w,position:o,theme:p,primaryColor:s,autoShow:a,metadata:u};let d=()=>{var i,r,c;z(true),(r=(i=l.current).onReady)==null||r.call(i),b.current&&((c=window.IntentAI)==null||c.identify(b.current));},y=()=>{var i,r;(r=(i=l.current).onOpen)==null||r.call(i);},A=()=>{var i,r;(r=(i=l.current).onClose)==null||r.call(i);},W=i=>{var r,c;(c=(r=l.current).onSubmit)==null||c.call(r,i.detail);},V=i=>{var r,c;F(i.detail),(c=(r=l.current).onError)==null||c.call(r,i.detail);};return window.addEventListener("intentai:ready",d),window.addEventListener("intentai:open",y),window.addEventListener("intentai:close",A),window.addEventListener("intentai:submit",W),window.addEventListener("intentai:error",V),H({apiKey:w}).catch(i=>{var r,c;F(i),(c=(r=l.current).onError)==null||c.call(r,i);}),()=>{window.removeEventListener("intentai:ready",d),window.removeEventListener("intentai:open",y),window.removeEventListener("intentai:close",A),window.removeEventListener("intentai:submit",W),window.removeEventListener("intentai:error",V);}},[w,o,p,s,a,u]),useEffect(()=>{b.current=n,v&&R&&n&&window.IntentAI&&window.IntentAI.identify(n);},[n,R]);let M=useCallback(d=>{v&&(window.IntentAI?window.IntentAI.open(d):console.warn("[IntentAI] Widget not ready. Call will be ignored."));},[]),U=useCallback(()=>{var d;v&&((d=window.IntentAI)==null||d.close());},[]),B=useCallback(d=>{v&&(window.IntentAI?window.IntentAI.identify(d):b.current=d);},[]),K=useCallback((d,y)=>{var A;v&&((A=window.IntentAI)==null||A.track(d,y));},[]),S=useCallback(d=>{var y;v&&((y=window.IntentAI)==null||y.setMetadata(d));},[]),J=useMemo(()=>({isReady:R,error:N,open:M,close:U,identify:B,track:K,setMetadata:S}),[R,N,M,U,B,K,S]);return jsx(q.Provider,{value:J,children:e})}function I(){let e=useContext(q);if(!e)throw new Error('useIntentAI must be used within an IntentAIProvider. Wrap your app with <IntentAIProvider apiKey="pk_...">.');return e}function te(e){let{identify:t,isReady:n}=I();useEffect(()=>{n&&(e!=null&&e.id)&&t(e);},[n,e==null?void 0:e.id,e==null?void 0:e.email,e==null?void 0:e.name,t,e]);}var re=forwardRef(function({openOptions:t,children:n="Feedback",onClick:o,disabled:p,...s},a){let{open:u,isReady:f}=I();return jsx("button",{ref:a,type:"button",onClick:m=>{o==null||o(m),m.defaultPrevented||u(t);},disabled:p||!f,"aria-label":"Open feedback",...s,children:n})});function ie({children:e,openOptions:t}){let{open:n,isReady:o,error:p}=I(),s=a=>{n(a||t);};return typeof e=="function"?e({open:s,isReady:o,error:p}):G.cloneElement(e,{onClick:a=>{var u,f;(f=(u=e.props).onClick)==null||f.call(u,a),a.defaultPrevented||s();},disabled:e.props.disabled||!o})}function de({user:e,children:t}){let{identify:n,isReady:o}=I();return useEffect(()=>{o&&(e!=null&&e.id)&&n(e);},[o,e,n]),jsx(Fragment,{children:t})}function ae({metadata:e,children:t}){let{setMetadata:n,isReady:o}=I();return useEffect(()=>{o&&e&&n(e);},[o,e,n]),jsx(Fragment,{children:t})}function se({event:e,properties:t,trigger:n="mount",children:o}){let{track:p,isReady:s}=I(),a=G.useRef(false);return useEffect(()=>{n==="mount"&&s&&!a.current&&(p(e,t),a.current=true);},[s,e,t,p,n]),n==="render"&&s&&p(e,t),jsx(Fragment,{children:o})}
|
|
2
|
+
export{re as FeedbackButton,ie as FeedbackTrigger,de as IdentifyUser,ee as IntentAIProvider,ae as SetMetadata,se as TrackEvent,te as useIdentify,I as useIntentAI};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intentai/react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official React SDK for Intent AI - AI-powered feedback collection widget",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
22
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
23
|
+
"lint": "tsc --noEmit",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react",
|
|
28
|
+
"nextjs",
|
|
29
|
+
"feedback",
|
|
30
|
+
"widget",
|
|
31
|
+
"intent-ai",
|
|
32
|
+
"intentai",
|
|
33
|
+
"user-feedback",
|
|
34
|
+
"voice-feedback",
|
|
35
|
+
"ai-feedback",
|
|
36
|
+
"customer-feedback",
|
|
37
|
+
"nps",
|
|
38
|
+
"csat"
|
|
39
|
+
],
|
|
40
|
+
"author": "Intent AI <hello@intent-ai.com>",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/intentai/react-sdk.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://intent-ai.com/docs/react",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/intentai/react-sdk/issues"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"react": ">=16.8.0",
|
|
52
|
+
"react-dom": ">=16.8.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^20.0.0",
|
|
56
|
+
"@types/react": "^18.2.0",
|
|
57
|
+
"@types/react-dom": "^18.2.0",
|
|
58
|
+
"react": "^18.2.0",
|
|
59
|
+
"react-dom": "^18.2.0",
|
|
60
|
+
"tsup": "^8.0.0",
|
|
61
|
+
"typescript": "^5.3.0"
|
|
62
|
+
}
|
|
63
|
+
}
|