@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 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)
@@ -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 };
@@ -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
+ }