@od-oneapp/analytics 2026.1.1301
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 +509 -0
- package/dist/ai-YMnynb-t.mjs +3347 -0
- package/dist/ai-YMnynb-t.mjs.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client-CTzJVFU5.mjs +9 -0
- package/dist/client-CTzJVFU5.mjs.map +1 -0
- package/dist/client-CcFTauAh.mjs +54 -0
- package/dist/client-CcFTauAh.mjs.map +1 -0
- package/dist/client-CeOLjbac.mjs +281 -0
- package/dist/client-CeOLjbac.mjs.map +1 -0
- package/dist/client-D339NFJS.mjs +267 -0
- package/dist/client-D339NFJS.mjs.map +1 -0
- package/dist/client-next.d.mts +62 -0
- package/dist/client-next.d.mts.map +1 -0
- package/dist/client-next.mjs +525 -0
- package/dist/client-next.mjs.map +1 -0
- package/dist/client.d.mts +30 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +186 -0
- package/dist/client.mjs.map +1 -0
- package/dist/config-DPS6bSYo.d.mts +34 -0
- package/dist/config-DPS6bSYo.d.mts.map +1 -0
- package/dist/config-P6P5adJg.mjs +287 -0
- package/dist/config-P6P5adJg.mjs.map +1 -0
- package/dist/console-8bND3mMU.mjs +128 -0
- package/dist/console-8bND3mMU.mjs.map +1 -0
- package/dist/ecommerce-Cgu4wlux.mjs +993 -0
- package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
- package/dist/emitters-6-nKo8i-.mjs +208 -0
- package/dist/emitters-6-nKo8i-.mjs.map +1 -0
- package/dist/emitters-DldkVSPp.d.mts +12 -0
- package/dist/emitters-DldkVSPp.d.mts.map +1 -0
- package/dist/index-BfNWgfa5.d.mts +1494 -0
- package/dist/index-BfNWgfa5.d.mts.map +1 -0
- package/dist/index-BkIWe--N.d.mts +953 -0
- package/dist/index-BkIWe--N.d.mts.map +1 -0
- package/dist/index-jPzXRn52.d.mts +184 -0
- package/dist/index-jPzXRn52.d.mts.map +1 -0
- package/dist/manager-DvRRjza6.d.mts +76 -0
- package/dist/manager-DvRRjza6.d.mts.map +1 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
- package/dist/providers-http-client.d.mts +37 -0
- package/dist/providers-http-client.d.mts.map +1 -0
- package/dist/providers-http-client.mjs +320 -0
- package/dist/providers-http-client.mjs.map +1 -0
- package/dist/providers-http-server.d.mts +31 -0
- package/dist/providers-http-server.d.mts.map +1 -0
- package/dist/providers-http-server.mjs +297 -0
- package/dist/providers-http-server.mjs.map +1 -0
- package/dist/providers-http.d.mts +46 -0
- package/dist/providers-http.d.mts.map +1 -0
- package/dist/providers-http.mjs +4 -0
- package/dist/server-edge.d.mts +9 -0
- package/dist/server-edge.d.mts.map +1 -0
- package/dist/server-edge.mjs +373 -0
- package/dist/server-edge.mjs.map +1 -0
- package/dist/server-next.d.mts +67 -0
- package/dist/server-next.d.mts.map +1 -0
- package/dist/server-next.mjs +193 -0
- package/dist/server-next.mjs.map +1 -0
- package/dist/server.d.mts +10 -0
- package/dist/server.mjs +7 -0
- package/dist/service-cYtBBL8x.mjs +945 -0
- package/dist/service-cYtBBL8x.mjs.map +1 -0
- package/dist/shared.d.mts +16 -0
- package/dist/shared.d.mts.map +1 -0
- package/dist/shared.mjs +93 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/types-BxBnNQ0V.d.mts +354 -0
- package/dist/types-BxBnNQ0V.d.mts.map +1 -0
- package/dist/types-CBvxUEaF.d.mts +216 -0
- package/dist/types-CBvxUEaF.d.mts.map +1 -0
- package/dist/types.d.mts +4 -0
- package/dist/types.mjs +0 -0
- package/dist/vercel-types-lwakUfoI.d.mts +102 -0
- package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
- package/package.json +129 -0
- package/src/client/index.ts +164 -0
- package/src/client/manager.ts +71 -0
- package/src/client/next/components.tsx +270 -0
- package/src/client/next/hooks.ts +217 -0
- package/src/client/next/manager.ts +141 -0
- package/src/client/next.ts +144 -0
- package/src/client-next.ts +101 -0
- package/src/client.ts +89 -0
- package/src/examples/ai-sdk-patterns.ts +583 -0
- package/src/examples/emitter-patterns.ts +476 -0
- package/src/examples/nextjs-emitter-patterns.tsx +403 -0
- package/src/next/app-router.tsx +564 -0
- package/src/next/client.ts +419 -0
- package/src/next/index.ts +84 -0
- package/src/next/middleware.ts +429 -0
- package/src/next/rsc.tsx +300 -0
- package/src/next/server.ts +253 -0
- package/src/next/types.d.ts +220 -0
- package/src/providers/base-provider.ts +419 -0
- package/src/providers/console/client.ts +10 -0
- package/src/providers/console/index.ts +152 -0
- package/src/providers/console/server.ts +6 -0
- package/src/providers/console/types.ts +15 -0
- package/src/providers/http/client.ts +464 -0
- package/src/providers/http/index.ts +30 -0
- package/src/providers/http/server.ts +396 -0
- package/src/providers/http/types.ts +135 -0
- package/src/providers/posthog/client.ts +518 -0
- package/src/providers/posthog/index.ts +11 -0
- package/src/providers/posthog/server.ts +329 -0
- package/src/providers/posthog/types.ts +104 -0
- package/src/providers/segment/client.ts +113 -0
- package/src/providers/segment/index.ts +11 -0
- package/src/providers/segment/server.ts +115 -0
- package/src/providers/segment/types.ts +51 -0
- package/src/providers/vercel/client.ts +102 -0
- package/src/providers/vercel/index.ts +11 -0
- package/src/providers/vercel/server.ts +89 -0
- package/src/providers/vercel/types.ts +27 -0
- package/src/server/index.ts +103 -0
- package/src/server/manager.ts +62 -0
- package/src/server/next.ts +210 -0
- package/src/server-edge.ts +442 -0
- package/src/server-next.ts +39 -0
- package/src/server.ts +106 -0
- package/src/shared/emitters/ai/README.md +981 -0
- package/src/shared/emitters/ai/events/agent.ts +130 -0
- package/src/shared/emitters/ai/events/artifacts.ts +167 -0
- package/src/shared/emitters/ai/events/chat.ts +126 -0
- package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
- package/src/shared/emitters/ai/events/completion.ts +103 -0
- package/src/shared/emitters/ai/events/content-generation.ts +347 -0
- package/src/shared/emitters/ai/events/conversation.ts +332 -0
- package/src/shared/emitters/ai/events/product-features.ts +1402 -0
- package/src/shared/emitters/ai/events/streaming.ts +114 -0
- package/src/shared/emitters/ai/events/tool.ts +93 -0
- package/src/shared/emitters/ai/index.ts +69 -0
- package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
- package/src/shared/emitters/ai/track-ai.ts +50 -0
- package/src/shared/emitters/ai/types.ts +1041 -0
- package/src/shared/emitters/ai/utils.ts +468 -0
- package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
- package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
- package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
- package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
- package/src/shared/emitters/ecommerce/events/order.ts +199 -0
- package/src/shared/emitters/ecommerce/events/product.ts +205 -0
- package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
- package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
- package/src/shared/emitters/ecommerce/index.ts +46 -0
- package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
- package/src/shared/emitters/ecommerce/types.ts +314 -0
- package/src/shared/emitters/ecommerce/utils.ts +216 -0
- package/src/shared/emitters/emitter-types.ts +974 -0
- package/src/shared/emitters/emitters.ts +292 -0
- package/src/shared/emitters/helpers.ts +419 -0
- package/src/shared/emitters/index.ts +66 -0
- package/src/shared/index.ts +142 -0
- package/src/shared/ingestion/index.ts +66 -0
- package/src/shared/ingestion/schemas.ts +386 -0
- package/src/shared/ingestion/service.ts +628 -0
- package/src/shared/node22-features.ts +848 -0
- package/src/shared/providers/console-provider.ts +160 -0
- package/src/shared/types/base-types.ts +54 -0
- package/src/shared/types/console-types.ts +19 -0
- package/src/shared/types/posthog-types.ts +131 -0
- package/src/shared/types/segment-types.ts +15 -0
- package/src/shared/types/types.ts +397 -0
- package/src/shared/types/vercel-types.ts +19 -0
- package/src/shared/utils/config-client.ts +19 -0
- package/src/shared/utils/config.ts +250 -0
- package/src/shared/utils/emitter-adapter.ts +212 -0
- package/src/shared/utils/manager.test.ts +36 -0
- package/src/shared/utils/manager.ts +1322 -0
- package/src/shared/utils/posthog-bootstrap.ts +136 -0
- package/src/shared/utils/posthog-client-utils.ts +48 -0
- package/src/shared/utils/posthog-next-utils.ts +282 -0
- package/src/shared/utils/posthog-server-utils.ts +210 -0
- package/src/shared/utils/rate-limit.ts +289 -0
- package/src/shared/utils/security.ts +545 -0
- package/src/shared/utils/validation-client.ts +161 -0
- package/src/shared/utils/validation.ts +399 -0
- package/src/shared.ts +155 -0
- package/src/types/index.ts +62 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Next.js Emitter Patterns - Examples for App Router
|
|
3
|
+
* Next.js Emitter Patterns - Examples for App Router
|
|
4
|
+
*
|
|
5
|
+
* This file demonstrates emitter usage patterns specifically
|
|
6
|
+
* for Next.js 15 applications using the App Router.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This is an example file. In production, use dependency injection
|
|
9
|
+
* to avoid Next.js imports in package source.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Example shows pattern - in real usage, pass cookies as parameter
|
|
13
|
+
// import { cookies } from 'next/headers'; // Remove direct import
|
|
14
|
+
import React from 'react';
|
|
15
|
+
|
|
16
|
+
import { TrackedButton, TrackedLink, useAnalytics, useTrackEvent } from '../next/app-router';
|
|
17
|
+
import { identifyServerUser, trackEventAction, trackServerEvent } from '../next/rsc';
|
|
18
|
+
import { createUserSession, EventBatch, page, track, withMetadata } from '../shared/emitters';
|
|
19
|
+
|
|
20
|
+
// ====================================
|
|
21
|
+
// CLIENT COMPONENTS WITH HOOKS
|
|
22
|
+
// ====================================
|
|
23
|
+
|
|
24
|
+
export function ProductCard({ product }: { product: any }) {
|
|
25
|
+
const analytics = useAnalytics();
|
|
26
|
+
const trackEvent = useTrackEvent();
|
|
27
|
+
|
|
28
|
+
// Method 1: Using the track method with product viewed event
|
|
29
|
+
const handleView = () => {
|
|
30
|
+
if (!analytics) return;
|
|
31
|
+
|
|
32
|
+
void analytics.track('Product Viewed', {
|
|
33
|
+
product_id: product.id,
|
|
34
|
+
name: product.name,
|
|
35
|
+
brand: product.brand,
|
|
36
|
+
category: product.category,
|
|
37
|
+
price: product.price,
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Method 2: Using the trackEvent hook
|
|
42
|
+
const handleAddToCart = () => {
|
|
43
|
+
void trackEvent('Product Added to Cart', {
|
|
44
|
+
product_id: product.id,
|
|
45
|
+
name: product.name,
|
|
46
|
+
cart_total: product.price,
|
|
47
|
+
price: product.price,
|
|
48
|
+
quantity: 1,
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
54
|
+
<div onMouseEnter={handleView} aria-label={`Product: ${product.name}`}>
|
|
55
|
+
<h3>{product.name}</h3>
|
|
56
|
+
<p>${product.price}</p>
|
|
57
|
+
<button type="button" onClick={handleAddToCart}>
|
|
58
|
+
Add to Cart
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ====================================
|
|
65
|
+
// TRACKED COMPONENTS WITH EMITTERS
|
|
66
|
+
// ====================================
|
|
67
|
+
|
|
68
|
+
export function HeroSection() {
|
|
69
|
+
// Create emitter payloads for tracked components
|
|
70
|
+
const ctaClickEvent = track('CTA Clicked', {
|
|
71
|
+
location: 'hero',
|
|
72
|
+
text: 'Start Free Trial',
|
|
73
|
+
variant: 'primary',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const learnMoreEvent = track('Link Clicked', {
|
|
77
|
+
destination: '/features',
|
|
78
|
+
location: 'hero',
|
|
79
|
+
text: 'Learn More',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<section>
|
|
84
|
+
<h1>Welcome to Our Platform</h1>
|
|
85
|
+
|
|
86
|
+
<TrackedButton
|
|
87
|
+
{...(ctaClickEvent.properties && { properties: ctaClickEvent.properties })}
|
|
88
|
+
className="btn-primary"
|
|
89
|
+
eventName={ctaClickEvent.event}
|
|
90
|
+
>
|
|
91
|
+
Start Free Trial
|
|
92
|
+
</TrackedButton>
|
|
93
|
+
|
|
94
|
+
<TrackedLink
|
|
95
|
+
href="/features"
|
|
96
|
+
{...(learnMoreEvent.properties && { properties: learnMoreEvent.properties })}
|
|
97
|
+
eventName={learnMoreEvent.event}
|
|
98
|
+
>
|
|
99
|
+
Learn More →
|
|
100
|
+
</TrackedLink>
|
|
101
|
+
</section>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ====================================
|
|
106
|
+
// FORM TRACKING WITH EMITTERS
|
|
107
|
+
// ====================================
|
|
108
|
+
|
|
109
|
+
export function SignupForm() {
|
|
110
|
+
const analytics = useAnalytics();
|
|
111
|
+
const [formData] = React.useState({
|
|
112
|
+
company: '',
|
|
113
|
+
email: '',
|
|
114
|
+
role: '',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Track form progression with event batch
|
|
118
|
+
const trackFormProgress = React.useCallback(async () => {
|
|
119
|
+
const batch = new EventBatch();
|
|
120
|
+
const filledFields = Object.entries(formData)
|
|
121
|
+
.filter(([_, value]) => value)
|
|
122
|
+
.map(([field]) => field);
|
|
123
|
+
|
|
124
|
+
if (filledFields.length === 1) {
|
|
125
|
+
batch.addTrack('Form Started', {
|
|
126
|
+
form_id: 'signup',
|
|
127
|
+
first_field: filledFields[0],
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
filledFields.forEach(field => {
|
|
132
|
+
batch.addTrack('Form Field Completed', {
|
|
133
|
+
form_id: 'signup',
|
|
134
|
+
field_name: field,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!analytics) return;
|
|
139
|
+
|
|
140
|
+
// Track each event individually since emitBatch might not be available
|
|
141
|
+
const events = batch.getEvents();
|
|
142
|
+
for (const event of events) {
|
|
143
|
+
if (event.type === 'track') {
|
|
144
|
+
await analytics.track(event.event, event.properties);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}, [formData, analytics]);
|
|
148
|
+
|
|
149
|
+
React.useEffect(() => {
|
|
150
|
+
void trackFormProgress();
|
|
151
|
+
}, [trackFormProgress]);
|
|
152
|
+
|
|
153
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
|
|
156
|
+
// Track form submission with metadata
|
|
157
|
+
const submitEvent = track('Form Submitted', {
|
|
158
|
+
form_id: 'signup',
|
|
159
|
+
fields: Object.keys(formData),
|
|
160
|
+
method: 'manual',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!analytics) return;
|
|
164
|
+
|
|
165
|
+
void analytics.track(submitEvent.event, {
|
|
166
|
+
...submitEvent.properties,
|
|
167
|
+
experiment: 'simplified-signup',
|
|
168
|
+
form_version: 'v2',
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return <form onSubmit={handleSubmit}>{/* Form fields */}</form>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ====================================
|
|
176
|
+
// SERVER COMPONENTS WITH EMITTERS
|
|
177
|
+
// ====================================
|
|
178
|
+
|
|
179
|
+
export async function ProductListingPage({
|
|
180
|
+
searchParams,
|
|
181
|
+
}: {
|
|
182
|
+
searchParams: { category?: string; sort?: string };
|
|
183
|
+
}) {
|
|
184
|
+
// Track page view in RSC
|
|
185
|
+
await trackServerEvent('Page Viewed', {
|
|
186
|
+
page_name: 'Product Listing',
|
|
187
|
+
category: searchParams.category ?? 'all',
|
|
188
|
+
path: '/products',
|
|
189
|
+
server_rendered: true,
|
|
190
|
+
sort_order: searchParams.sort ?? 'popular',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Track search/filter usage
|
|
194
|
+
if (searchParams.category) {
|
|
195
|
+
await trackServerEvent('Filter Applied', {
|
|
196
|
+
filter_type: 'category',
|
|
197
|
+
filter_value: searchParams.category,
|
|
198
|
+
page: 'products',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return <div>{/* Product listing UI */}</div>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ====================================
|
|
206
|
+
// SERVER ACTIONS WITH EMITTERS
|
|
207
|
+
// ====================================
|
|
208
|
+
|
|
209
|
+
export async function updateUserProfile(formData: FormData) {
|
|
210
|
+
'use server';
|
|
211
|
+
|
|
212
|
+
const userId = 'user_123'; // Get from session
|
|
213
|
+
const updates = {
|
|
214
|
+
name: formData.get('name') as string,
|
|
215
|
+
company: formData.get('company') as string,
|
|
216
|
+
email: formData.get('email') as string,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Create user session for consistent tracking
|
|
220
|
+
const session = createUserSession(userId, 'session_xyz');
|
|
221
|
+
|
|
222
|
+
// Track profile update
|
|
223
|
+
const updateEvent = session.track('Profile Updated', {
|
|
224
|
+
fields_changed: Object.keys(updates),
|
|
225
|
+
update_source: 'settings_page',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await trackEventAction(updateEvent.event, updateEvent.properties);
|
|
229
|
+
|
|
230
|
+
// Update user traits
|
|
231
|
+
const identifyEvent = session.identify(updates);
|
|
232
|
+
await identifyServerUser(identifyEvent.userId, identifyEvent.traits);
|
|
233
|
+
|
|
234
|
+
// Return result
|
|
235
|
+
return { success: true };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ====================================
|
|
239
|
+
// MIDDLEWARE TRACKING WITH EMITTERS
|
|
240
|
+
// ====================================
|
|
241
|
+
|
|
242
|
+
export async function trackMiddlewareEvents(
|
|
243
|
+
request: Request,
|
|
244
|
+
cookiesFn?: () => Promise<{ get: (name: string) => { value: string } | undefined }>,
|
|
245
|
+
) {
|
|
246
|
+
const url = new URL(request.url);
|
|
247
|
+
// In real usage, cookiesFn should be provided from Next.js app
|
|
248
|
+
// const cookieStore = await cookiesFn?.() || { get: () => undefined };
|
|
249
|
+
const cookieStore = cookiesFn ? await cookiesFn() : { get: () => {} };
|
|
250
|
+
const userId = cookieStore.get('userId')?.value;
|
|
251
|
+
|
|
252
|
+
// Build context for middleware events
|
|
253
|
+
const context = {
|
|
254
|
+
ip: request.headers.get('x-forwarded-for') ?? 'unknown',
|
|
255
|
+
page: {
|
|
256
|
+
url: url.toString(),
|
|
257
|
+
path: url.pathname,
|
|
258
|
+
search: url.search,
|
|
259
|
+
},
|
|
260
|
+
userAgent: request.headers.get('user-agent') ?? 'unknown',
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Track page request
|
|
264
|
+
const pageEvent = page(undefined, url.pathname, {
|
|
265
|
+
...context.page,
|
|
266
|
+
middleware: true,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Add user context if available
|
|
270
|
+
if (userId) {
|
|
271
|
+
return withMetadata(pageEvent, { userId });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return pageEvent;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ====================================
|
|
278
|
+
// REAL-TIME FEATURES WITH EMITTERS
|
|
279
|
+
// ====================================
|
|
280
|
+
|
|
281
|
+
export function LiveDashboard() {
|
|
282
|
+
const analytics = useAnalytics();
|
|
283
|
+
const [_metrics, setMetrics] = React.useState({});
|
|
284
|
+
|
|
285
|
+
React.useEffect(() => {
|
|
286
|
+
// Track dashboard view
|
|
287
|
+
if (analytics) {
|
|
288
|
+
void analytics.track('Dashboard Viewed', {
|
|
289
|
+
widgets_visible: 5,
|
|
290
|
+
view_mode: 'real-time',
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Set up WebSocket for real-time updates
|
|
295
|
+
const ws = new WebSocket('wss://api.example.com/metrics');
|
|
296
|
+
|
|
297
|
+
ws.onmessage = async event => {
|
|
298
|
+
const data = JSON.parse(event.data);
|
|
299
|
+
setMetrics(data);
|
|
300
|
+
|
|
301
|
+
// Track significant metric changes
|
|
302
|
+
if (data.alert && analytics) {
|
|
303
|
+
await analytics.track('Metric Alert', {
|
|
304
|
+
metric_name: data.alert.metric,
|
|
305
|
+
current_value: data.alert.value,
|
|
306
|
+
severity: data.alert.severity,
|
|
307
|
+
threshold: data.alert.threshold,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
return () => ws.close();
|
|
313
|
+
}, [analytics]);
|
|
314
|
+
|
|
315
|
+
return <div>{/* Dashboard UI */}</div>;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ====================================
|
|
319
|
+
// ERROR BOUNDARY WITH EMITTERS
|
|
320
|
+
// ====================================
|
|
321
|
+
|
|
322
|
+
export class AnalyticsErrorBoundary extends React.Component<
|
|
323
|
+
{ children: React.ReactNode; analytics: any },
|
|
324
|
+
{ hasError: boolean }
|
|
325
|
+
> {
|
|
326
|
+
constructor(props: { children: React.ReactNode; analytics: any }) {
|
|
327
|
+
super(props);
|
|
328
|
+
this.state = { hasError: false };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
static getDerivedStateFromError() {
|
|
332
|
+
return { hasError: true };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
336
|
+
// Track error with emitter
|
|
337
|
+
const errorEvent = track('React Error', {
|
|
338
|
+
component_stack: errorInfo.componentStack,
|
|
339
|
+
error_boundary: true,
|
|
340
|
+
error_message: error.message,
|
|
341
|
+
error_stack: error.stack,
|
|
342
|
+
severity: 'high',
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Send to analytics
|
|
346
|
+
this.props.analytics.track(errorEvent.event, errorEvent.properties);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
render() {
|
|
350
|
+
if (this.state.hasError) {
|
|
351
|
+
return <div>Something went wrong.</div>;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return this.props.children;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ====================================
|
|
359
|
+
// PROGRESSIVE ENHANCEMENT PATTERN
|
|
360
|
+
// ====================================
|
|
361
|
+
|
|
362
|
+
export function EnhancedSearchBar() {
|
|
363
|
+
const analytics = useAnalytics();
|
|
364
|
+
const [query, setQuery] = React.useState('');
|
|
365
|
+
const [suggestions, _setSuggestions] = React.useState<string[]>([]);
|
|
366
|
+
|
|
367
|
+
// Debounced search tracking
|
|
368
|
+
const timeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined);
|
|
369
|
+
const trackSearch = React.useMemo(() => {
|
|
370
|
+
return (searchQuery: string) => {
|
|
371
|
+
if (timeoutRef.current) {
|
|
372
|
+
clearTimeout(timeoutRef.current);
|
|
373
|
+
}
|
|
374
|
+
timeoutRef.current = setTimeout(() => {
|
|
375
|
+
if (analytics) {
|
|
376
|
+
void analytics.track('Search Performed', {
|
|
377
|
+
has_suggestions: suggestions.length > 0,
|
|
378
|
+
query: searchQuery,
|
|
379
|
+
query_length: searchQuery.length,
|
|
380
|
+
suggestion_count: suggestions.length,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}, 500);
|
|
384
|
+
};
|
|
385
|
+
}, [analytics, suggestions.length]);
|
|
386
|
+
|
|
387
|
+
const handleSearch = (value: string) => {
|
|
388
|
+
setQuery(value);
|
|
389
|
+
trackSearch(value);
|
|
390
|
+
// Fetch suggestions...
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<div>
|
|
395
|
+
<input
|
|
396
|
+
onChange={e => handleSearch(e.target.value)}
|
|
397
|
+
placeholder="Search..."
|
|
398
|
+
type="search"
|
|
399
|
+
value={query}
|
|
400
|
+
/>
|
|
401
|
+
</div>
|
|
402
|
+
);
|
|
403
|
+
}
|