@rovela-ai/sdk 0.4.3 → 0.5.2
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/dist/admin/components/AdminNav.d.ts.map +1 -1
- package/dist/admin/components/AdminNav.js +10 -1
- package/dist/admin/components/AdminNav.js.map +1 -1
- package/dist/admin/components/ExampleContentBanner.js +2 -2
- package/dist/admin/components/ExampleContentBanner.js.map +1 -1
- package/dist/admin/components/SetupGuide.d.ts.map +1 -1
- package/dist/admin/components/SetupGuide.js +4 -4
- package/dist/admin/components/SetupGuide.js.map +1 -1
- package/dist/admin/components/index.d.ts +0 -1
- package/dist/admin/components/index.d.ts.map +1 -1
- package/dist/admin/components/index.js +0 -1
- package/dist/admin/components/index.js.map +1 -1
- package/dist/admin/hooks/fetchAdminApi.d.ts.map +1 -1
- package/dist/admin/hooks/fetchAdminApi.js +6 -0
- package/dist/admin/hooks/fetchAdminApi.js.map +1 -1
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/styles/admin-theme.css +11 -0
- package/dist/analytics/api/dashboard.d.ts +16 -0
- package/dist/analytics/api/dashboard.d.ts.map +1 -0
- package/dist/analytics/api/dashboard.js +37 -0
- package/dist/analytics/api/dashboard.js.map +1 -0
- package/dist/analytics/api/events.d.ts +23 -0
- package/dist/analytics/api/events.d.ts.map +1 -0
- package/dist/analytics/api/events.js +55 -0
- package/dist/analytics/api/events.js.map +1 -0
- package/dist/analytics/api/index.d.ts +15 -0
- package/dist/analytics/api/index.d.ts.map +1 -0
- package/dist/analytics/api/index.js +15 -0
- package/dist/analytics/api/index.js.map +1 -0
- package/dist/analytics/api/track.d.ts +20 -0
- package/dist/analytics/api/track.d.ts.map +1 -0
- package/dist/analytics/api/track.js +233 -0
- package/dist/analytics/api/track.js.map +1 -0
- package/dist/analytics/api/visitors.d.ts +19 -0
- package/dist/analytics/api/visitors.d.ts.map +1 -0
- package/dist/analytics/api/visitors.js +49 -0
- package/dist/analytics/api/visitors.js.map +1 -0
- package/dist/analytics/client/tracker.d.ts +51 -0
- package/dist/analytics/client/tracker.d.ts.map +1 -0
- package/dist/analytics/client/tracker.js +208 -0
- package/dist/analytics/client/tracker.js.map +1 -0
- package/dist/analytics/components/AnalyticsDashboard.d.ts +2 -0
- package/dist/analytics/components/AnalyticsDashboard.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsDashboard.js +26 -0
- package/dist/analytics/components/AnalyticsDashboard.js.map +1 -0
- package/dist/analytics/components/AnalyticsPeriodContext.d.ts +13 -0
- package/dist/analytics/components/AnalyticsPeriodContext.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsPeriodContext.js +28 -0
- package/dist/analytics/components/AnalyticsPeriodContext.js.map +1 -0
- package/dist/analytics/components/AnalyticsProvider.d.ts +22 -0
- package/dist/analytics/components/AnalyticsProvider.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsProvider.js +152 -0
- package/dist/analytics/components/AnalyticsProvider.js.map +1 -0
- package/dist/analytics/components/AnalyticsTabNav.d.ts +14 -0
- package/dist/analytics/components/AnalyticsTabNav.d.ts.map +1 -0
- package/dist/analytics/components/AnalyticsTabNav.js +42 -0
- package/dist/analytics/components/AnalyticsTabNav.js.map +1 -0
- package/dist/analytics/hooks/useAnalytics.d.ts +9 -0
- package/dist/analytics/hooks/useAnalytics.d.ts.map +1 -0
- package/dist/analytics/hooks/useAnalytics.js +8 -0
- package/dist/analytics/hooks/useAnalytics.js.map +1 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.d.ts +9 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.d.ts.map +1 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.js +45 -0
- package/dist/analytics/hooks/useAnalyticsDashboard.js.map +1 -0
- package/dist/analytics/hooks/useEventsLog.d.ts +24 -0
- package/dist/analytics/hooks/useEventsLog.d.ts.map +1 -0
- package/dist/analytics/hooks/useEventsLog.js +85 -0
- package/dist/analytics/hooks/useEventsLog.js.map +1 -0
- package/dist/analytics/hooks/useVisitorsList.d.ts +20 -0
- package/dist/analytics/hooks/useVisitorsList.d.ts.map +1 -0
- package/dist/analytics/hooks/useVisitorsList.js +73 -0
- package/dist/analytics/hooks/useVisitorsList.js.map +1 -0
- package/dist/analytics/index.d.ts +44 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +39 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/server/index.d.ts +10 -0
- package/dist/analytics/server/index.d.ts.map +1 -0
- package/dist/analytics/server/index.js +9 -0
- package/dist/analytics/server/index.js.map +1 -0
- package/dist/analytics/server/normalize.d.ts +23 -0
- package/dist/analytics/server/normalize.d.ts.map +1 -0
- package/dist/analytics/server/normalize.js +75 -0
- package/dist/analytics/server/normalize.js.map +1 -0
- package/dist/analytics/server/queries.d.ts +74 -0
- package/dist/analytics/server/queries.d.ts.map +1 -0
- package/dist/analytics/server/queries.js +470 -0
- package/dist/analytics/server/queries.js.map +1 -0
- package/dist/analytics/types.d.ts +186 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics/types.js +16 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/analytics/views/DashboardsView.d.ts +6 -0
- package/dist/analytics/views/DashboardsView.d.ts.map +1 -0
- package/dist/analytics/views/DashboardsView.js +93 -0
- package/dist/analytics/views/DashboardsView.js.map +1 -0
- package/dist/analytics/views/EventsView.d.ts +6 -0
- package/dist/analytics/views/EventsView.d.ts.map +1 -0
- package/dist/analytics/views/EventsView.js +85 -0
- package/dist/analytics/views/EventsView.js.map +1 -0
- package/dist/analytics/views/VisitorsView.d.ts +6 -0
- package/dist/analytics/views/VisitorsView.d.ts.map +1 -0
- package/dist/analytics/views/VisitorsView.js +57 -0
- package/dist/analytics/views/VisitorsView.js.map +1 -0
- package/dist/cart/store.d.ts.map +1 -1
- package/dist/cart/store.js +12 -0
- package/dist/cart/store.js.map +1 -1
- package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
- package/dist/checkout/components/CheckoutFlow.js +26 -2
- package/dist/checkout/components/CheckoutFlow.js.map +1 -1
- package/dist/checkout/components/ShippingForm.js +10 -5
- package/dist/checkout/components/ShippingForm.js.map +1 -1
- package/dist/checkout/hooks/useCheckout.d.ts.map +1 -1
- package/dist/checkout/hooks/useCheckout.js +12 -1
- package/dist/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/checkout/server/handle-webhook.js +15 -0
- package/dist/checkout/server/handle-webhook.js.map +1 -1
- package/dist/checkout/types.d.ts +13 -0
- package/dist/checkout/types.d.ts.map +1 -1
- package/dist/core/db/client.d.ts +14 -0
- package/dist/core/db/client.d.ts.map +1 -1
- package/dist/core/db/client.js +12 -0
- package/dist/core/db/client.js.map +1 -1
- package/dist/core/db/index.d.ts +2 -1
- package/dist/core/db/index.d.ts.map +1 -1
- package/dist/core/db/index.js +1 -1
- package/dist/core/db/index.js.map +1 -1
- package/dist/core/db/queries.d.ts +8 -7
- package/dist/core/db/queries.d.ts.map +1 -1
- package/dist/core/db/queries.js +80 -0
- package/dist/core/db/queries.js.map +1 -1
- package/dist/core/db/schema.d.ts +340 -4
- package/dist/core/db/schema.d.ts.map +1 -1
- package/dist/core/db/schema.js +55 -1
- package/dist/core/db/schema.js.map +1 -1
- package/dist/core/server/index.d.ts +2 -2
- package/dist/core/server/index.d.ts.map +1 -1
- package/dist/core/server/index.js +2 -0
- package/dist/core/server/index.js.map +1 -1
- package/package.json +25 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/analytics/api/track
|
|
3
|
+
*
|
|
4
|
+
* POST /api/analytics/track
|
|
5
|
+
*
|
|
6
|
+
* Public endpoint — no auth (events are pseudonymous). Mounted by the store
|
|
7
|
+
* template via `export { POST } from '@rovela-ai/sdk/analytics/api/track'`.
|
|
8
|
+
*
|
|
9
|
+
* Discipline:
|
|
10
|
+
* - Bot UA → 204 (silent drop)
|
|
11
|
+
* - Admin-cookie present → 204 (no self-bias from merchant)
|
|
12
|
+
* - Validation failure → 400 (still safe; only thing it leaks is "bad shape")
|
|
13
|
+
* - DB insert failure → 204 (analytics MUST NEVER break the visitor flow;
|
|
14
|
+
* recordEvent swallows internally)
|
|
15
|
+
*
|
|
16
|
+
* sendBeacon() from the client sends Content-Type: text/plain by default;
|
|
17
|
+
* we parse JSON manually rather than rely on Next.js request.json().
|
|
18
|
+
*/
|
|
19
|
+
import { NextResponse } from 'next/server';
|
|
20
|
+
import { recordEvent } from '../server/queries';
|
|
21
|
+
import { isBotUserAgent, deviceFromUserAgent, referrerHost, normalizePath, clip, safeCountry, } from '../server/normalize';
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Inline validation (no Zod dep — keep SDK lean; the rules are explicit and
|
|
24
|
+
// the surface is bounded by AnalyticsEventKind)
|
|
25
|
+
// =============================================================================
|
|
26
|
+
const VALID_KINDS = new Set([
|
|
27
|
+
'page_view',
|
|
28
|
+
'session_start',
|
|
29
|
+
'session_heartbeat',
|
|
30
|
+
'session_end',
|
|
31
|
+
'view_item_list',
|
|
32
|
+
'view_item',
|
|
33
|
+
'select_item',
|
|
34
|
+
'add_to_cart',
|
|
35
|
+
'view_cart',
|
|
36
|
+
'begin_checkout',
|
|
37
|
+
'add_shipping_info',
|
|
38
|
+
'purchase',
|
|
39
|
+
'refund',
|
|
40
|
+
]);
|
|
41
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
42
|
+
function isString(v) {
|
|
43
|
+
return typeof v === 'string' && v.length > 0;
|
|
44
|
+
}
|
|
45
|
+
function isUuid(v) {
|
|
46
|
+
return isString(v) && UUID_RE.test(v);
|
|
47
|
+
}
|
|
48
|
+
function isNumber(v) {
|
|
49
|
+
return typeof v === 'number' && Number.isFinite(v);
|
|
50
|
+
}
|
|
51
|
+
function validate(raw) {
|
|
52
|
+
if (!raw || typeof raw !== 'object')
|
|
53
|
+
return { ok: false, error: 'body must be an object' };
|
|
54
|
+
const o = raw;
|
|
55
|
+
if (!isUuid(o.visitor_id))
|
|
56
|
+
return { ok: false, error: 'visitor_id must be a uuid' };
|
|
57
|
+
if (!isUuid(o.session_id))
|
|
58
|
+
return { ok: false, error: 'session_id must be a uuid' };
|
|
59
|
+
if (!isString(o.event) || !VALID_KINDS.has(o.event)) {
|
|
60
|
+
return { ok: false, error: 'event must be a known kind' };
|
|
61
|
+
}
|
|
62
|
+
if (!isString(o.path))
|
|
63
|
+
return { ok: false, error: 'path must be a non-empty string' };
|
|
64
|
+
// Per-kind required fields (skip server-side fields like ts/device/country)
|
|
65
|
+
const kind = o.event;
|
|
66
|
+
switch (kind) {
|
|
67
|
+
case 'view_item':
|
|
68
|
+
case 'view_cart':
|
|
69
|
+
case 'begin_checkout':
|
|
70
|
+
case 'add_shipping_info':
|
|
71
|
+
if (!isNumber(o.value_cents) || !isString(o.currency)) {
|
|
72
|
+
return { ok: false, error: `${kind} requires value_cents + currency` };
|
|
73
|
+
}
|
|
74
|
+
if (kind === 'view_item' && !isUuid(o.product_id)) {
|
|
75
|
+
return { ok: false, error: 'view_item requires product_id' };
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case 'view_item_list':
|
|
79
|
+
if (!isUuid(o.category_id)) {
|
|
80
|
+
return { ok: false, error: 'view_item_list requires category_id' };
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 'select_item':
|
|
84
|
+
if (!isUuid(o.product_id)) {
|
|
85
|
+
return { ok: false, error: 'select_item requires product_id' };
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
case 'add_to_cart':
|
|
89
|
+
if (!isUuid(o.product_id) ||
|
|
90
|
+
!isNumber(o.quantity) ||
|
|
91
|
+
!isNumber(o.value_cents) ||
|
|
92
|
+
!isString(o.currency)) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: 'add_to_cart requires product_id, quantity, value_cents, currency',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case 'purchase':
|
|
100
|
+
case 'refund':
|
|
101
|
+
// Server-fired only — client posts to track are rejected. Stripe webhook
|
|
102
|
+
// wrapper calls recordEvent() directly, bypassing this validation path.
|
|
103
|
+
return { ok: false, error: `${kind} is server-fired only` };
|
|
104
|
+
// page_view + session_start — identity + path is enough
|
|
105
|
+
}
|
|
106
|
+
// Build the validated event with explicit field selection.
|
|
107
|
+
const base = {
|
|
108
|
+
visitor_id: o.visitor_id,
|
|
109
|
+
session_id: o.session_id,
|
|
110
|
+
path: normalizePath(o.path),
|
|
111
|
+
referrer: referrerHost(o.referrer ?? null),
|
|
112
|
+
utm_source: clip(o.utm_source, 100),
|
|
113
|
+
utm_medium: clip(o.utm_medium, 100),
|
|
114
|
+
utm_campaign: clip(o.utm_campaign, 200),
|
|
115
|
+
};
|
|
116
|
+
let payload;
|
|
117
|
+
switch (kind) {
|
|
118
|
+
case 'page_view':
|
|
119
|
+
case 'session_start':
|
|
120
|
+
case 'session_heartbeat':
|
|
121
|
+
case 'session_end':
|
|
122
|
+
payload = { event: kind, ...base };
|
|
123
|
+
break;
|
|
124
|
+
case 'view_item_list':
|
|
125
|
+
payload = {
|
|
126
|
+
event: 'view_item_list',
|
|
127
|
+
category_id: o.category_id,
|
|
128
|
+
...base,
|
|
129
|
+
};
|
|
130
|
+
break;
|
|
131
|
+
case 'view_item':
|
|
132
|
+
payload = {
|
|
133
|
+
event: 'view_item',
|
|
134
|
+
product_id: o.product_id,
|
|
135
|
+
value_cents: Math.round(o.value_cents),
|
|
136
|
+
currency: o.currency.slice(0, 3).toUpperCase(),
|
|
137
|
+
...base,
|
|
138
|
+
};
|
|
139
|
+
break;
|
|
140
|
+
case 'select_item':
|
|
141
|
+
payload = {
|
|
142
|
+
event: 'select_item',
|
|
143
|
+
product_id: o.product_id,
|
|
144
|
+
category_id: isUuid(o.category_id) ? o.category_id : null,
|
|
145
|
+
...base,
|
|
146
|
+
};
|
|
147
|
+
break;
|
|
148
|
+
case 'add_to_cart':
|
|
149
|
+
payload = {
|
|
150
|
+
event: 'add_to_cart',
|
|
151
|
+
product_id: o.product_id,
|
|
152
|
+
variant_id: isUuid(o.variant_id) ? o.variant_id : null,
|
|
153
|
+
quantity: Math.round(o.quantity),
|
|
154
|
+
value_cents: Math.round(o.value_cents),
|
|
155
|
+
currency: o.currency.slice(0, 3).toUpperCase(),
|
|
156
|
+
...base,
|
|
157
|
+
};
|
|
158
|
+
break;
|
|
159
|
+
case 'view_cart':
|
|
160
|
+
payload = {
|
|
161
|
+
event: 'view_cart',
|
|
162
|
+
value_cents: Math.round(o.value_cents),
|
|
163
|
+
currency: o.currency.slice(0, 3).toUpperCase(),
|
|
164
|
+
...base,
|
|
165
|
+
};
|
|
166
|
+
break;
|
|
167
|
+
case 'begin_checkout':
|
|
168
|
+
payload = {
|
|
169
|
+
event: 'begin_checkout',
|
|
170
|
+
value_cents: Math.round(o.value_cents),
|
|
171
|
+
currency: o.currency.slice(0, 3).toUpperCase(),
|
|
172
|
+
...base,
|
|
173
|
+
};
|
|
174
|
+
break;
|
|
175
|
+
case 'add_shipping_info':
|
|
176
|
+
payload = {
|
|
177
|
+
event: 'add_shipping_info',
|
|
178
|
+
value_cents: Math.round(o.value_cents),
|
|
179
|
+
currency: o.currency.slice(0, 3).toUpperCase(),
|
|
180
|
+
...base,
|
|
181
|
+
};
|
|
182
|
+
break;
|
|
183
|
+
default:
|
|
184
|
+
// Exhaustiveness — purchase/refund handled above
|
|
185
|
+
return { ok: false, error: 'unsupported kind' };
|
|
186
|
+
}
|
|
187
|
+
return { ok: true, event: { ...base, ...payload } };
|
|
188
|
+
}
|
|
189
|
+
// =============================================================================
|
|
190
|
+
// Admin-cookie sniff (exclude merchant self-traffic)
|
|
191
|
+
// =============================================================================
|
|
192
|
+
const ADMIN_COOKIE_NAMES = [
|
|
193
|
+
'__Secure-rovela.admin.session-token',
|
|
194
|
+
'rovela.admin.session-token',
|
|
195
|
+
];
|
|
196
|
+
function hasAdminSession(cookieHeader) {
|
|
197
|
+
if (!cookieHeader)
|
|
198
|
+
return false;
|
|
199
|
+
return ADMIN_COOKIE_NAMES.some((n) => cookieHeader.includes(`${n}=`));
|
|
200
|
+
}
|
|
201
|
+
// =============================================================================
|
|
202
|
+
// Handler
|
|
203
|
+
// =============================================================================
|
|
204
|
+
export async function POST(request) {
|
|
205
|
+
// sendBeacon defaults to text/plain; parse JSON manually.
|
|
206
|
+
let raw;
|
|
207
|
+
try {
|
|
208
|
+
const text = await request.text();
|
|
209
|
+
raw = text ? JSON.parse(text) : null;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return new NextResponse(null, { status: 204 });
|
|
213
|
+
}
|
|
214
|
+
// Bot check — silent drop
|
|
215
|
+
const ua = request.headers.get('user-agent');
|
|
216
|
+
if (isBotUserAgent(ua)) {
|
|
217
|
+
return new NextResponse(null, { status: 204 });
|
|
218
|
+
}
|
|
219
|
+
// Admin-cookie sniff — silent drop (don't bias merchant's own metrics)
|
|
220
|
+
if (hasAdminSession(request.headers.get('cookie'))) {
|
|
221
|
+
return new NextResponse(null, { status: 204 });
|
|
222
|
+
}
|
|
223
|
+
const validation = validate(raw);
|
|
224
|
+
if (!validation.ok) {
|
|
225
|
+
return NextResponse.json({ error: validation.error }, { status: 400 });
|
|
226
|
+
}
|
|
227
|
+
const device = deviceFromUserAgent(ua);
|
|
228
|
+
const country = safeCountry(request.headers.get('x-vercel-ip-country'));
|
|
229
|
+
// Best-effort insert — recordEvent swallows errors internally.
|
|
230
|
+
await recordEvent({ event: validation.event, device, country });
|
|
231
|
+
return new NextResponse(null, { status: 204 });
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../../../src/analytics/api/track.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/analytics/api/visitors
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/analytics/visitors
|
|
5
|
+
*
|
|
6
|
+
* Offset-paginated per-visitor aggregate. Admin-gated, settings.read.
|
|
7
|
+
*
|
|
8
|
+
* Query params:
|
|
9
|
+
* period: today | 7d | 30d | 90d (default 30d)
|
|
10
|
+
* cursor: stringified offset from previous page's nextCursor
|
|
11
|
+
* limit: 1–200 (default 50)
|
|
12
|
+
* sortBy: last_seen | first_seen | sessions | pageviews | country (default last_seen)
|
|
13
|
+
* sortDir: asc | desc (default desc)
|
|
14
|
+
*/
|
|
15
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
16
|
+
import type { AnalyticsVisitorsPage } from '../types';
|
|
17
|
+
import type { AdminApiError } from '../../admin/types';
|
|
18
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<AnalyticsVisitorsPage | AdminApiError>>;
|
|
19
|
+
//# sourceMappingURL=visitors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitors.d.ts","sourceRoot":"","sources":["../../../src/analytics/api/visitors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAGvD,OAAO,KAAK,EAGV,qBAAqB,EACtB,MAAM,UAAU,CAAA;AACjB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAUtD,wBAAsB,GAAG,CACvB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,qBAAqB,GAAG,aAAa,CAAC,CAAC,CAoC9D"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/analytics/api/visitors
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/analytics/visitors
|
|
5
|
+
*
|
|
6
|
+
* Offset-paginated per-visitor aggregate. Admin-gated, settings.read.
|
|
7
|
+
*
|
|
8
|
+
* Query params:
|
|
9
|
+
* period: today | 7d | 30d | 90d (default 30d)
|
|
10
|
+
* cursor: stringified offset from previous page's nextCursor
|
|
11
|
+
* limit: 1–200 (default 50)
|
|
12
|
+
* sortBy: last_seen | first_seen | sessions | pageviews | country (default last_seen)
|
|
13
|
+
* sortDir: asc | desc (default desc)
|
|
14
|
+
*/
|
|
15
|
+
import { NextResponse } from 'next/server';
|
|
16
|
+
import { requireAdmin } from '../../admin/server/admin-session';
|
|
17
|
+
import { getVisitorsList } from '../server/queries';
|
|
18
|
+
const VALID_PERIODS = new Set(['today', '7d', '30d', '90d']);
|
|
19
|
+
function parsePeriod(raw) {
|
|
20
|
+
return raw && VALID_PERIODS.has(raw)
|
|
21
|
+
? raw
|
|
22
|
+
: '30d';
|
|
23
|
+
}
|
|
24
|
+
export async function GET(request) {
|
|
25
|
+
const guard = await requireAdmin({ permission: 'settings.read' });
|
|
26
|
+
if (!guard.ok) {
|
|
27
|
+
return guard.response;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const sp = request.nextUrl.searchParams;
|
|
31
|
+
const limitParam = sp.get('limit');
|
|
32
|
+
const limit = limitParam ? Math.max(1, Number(limitParam)) : 50;
|
|
33
|
+
const sortBy = sp.get('sortBy') ?? 'last_seen';
|
|
34
|
+
const sortDir = sp.get('sortDir') === 'asc' ? 'asc' : 'desc';
|
|
35
|
+
const payload = await getVisitorsList({
|
|
36
|
+
period: parsePeriod(sp.get('period')),
|
|
37
|
+
cursor: sp.get('cursor'),
|
|
38
|
+
limit,
|
|
39
|
+
sortBy,
|
|
40
|
+
sortDir,
|
|
41
|
+
});
|
|
42
|
+
return NextResponse.json(payload);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error('[Analytics Visitors API] GET error:', error);
|
|
46
|
+
return NextResponse.json({ error: 'Failed to fetch visitors list', code: 'INTERNAL_ERROR' }, { status: 500 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=visitors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitors.js","sourceRoot":"","sources":["../../../src/analytics/api/visitors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAQnD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAkB,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;AAE7E,SAAS,WAAW,CAAC,GAAkB;IACrC,OAAO,GAAG,IAAI,aAAa,CAAC,GAAG,CAAC,GAAsB,CAAC;QACrD,CAAC,CAAE,GAAuB;QAC1B,CAAC,CAAC,KAAK,CAAA;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,OAAoB;IAEpB,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAA;IACjE,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,KAAK,CAAC,QAAQ,CAAA;IACvB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAA;QACvC,MAAM,UAAU,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAClC,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAC/D,MAAM,MAAM,GACT,EAAE,CAAC,GAAG,CAAC,QAAQ,CAMP,IAAI,WAAW,CAAA;QAC1B,MAAM,OAAO,GACX,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA;QAE9C,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;YACpC,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACrC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC;YACxB,KAAK;YACL,MAAM;YACN,OAAO;SACR,CAAC,CAAA;QACF,OAAO,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAA;QAC3D,OAAO,YAAY,CAAC,IAAI,CACtB,EAAE,KAAK,EAAE,+BAA+B,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAClE,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAA;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/analytics/client/tracker
|
|
3
|
+
*
|
|
4
|
+
* Browser-side analytics primitives — pseudonymous identity + transport.
|
|
5
|
+
* No React, no hooks; safe to import from anywhere on the client (cart store,
|
|
6
|
+
* checkout flow, server-component-rendered client islands).
|
|
7
|
+
*
|
|
8
|
+
* Identity model:
|
|
9
|
+
* - visitor_id: persistent UUID in localStorage. Never expires.
|
|
10
|
+
* - session_id: per-tab UUID in sessionStorage. Rotates after 30 min of
|
|
11
|
+
* inactivity (matches GA4's default session boundary).
|
|
12
|
+
*
|
|
13
|
+
* Transport:
|
|
14
|
+
* - sendBeacon() when available (survives page unload, fire-and-forget).
|
|
15
|
+
* - fetch({keepalive:true}) fallback for browsers without sendBeacon.
|
|
16
|
+
* - Both are unblocking; the call returns synchronously, the network
|
|
17
|
+
* happens in the background, errors are swallowed.
|
|
18
|
+
*
|
|
19
|
+
* SSR safety:
|
|
20
|
+
* - Every helper short-circuits when `typeof window === 'undefined'`. Safe
|
|
21
|
+
* to call from a Server Component import chain that incidentally pulls
|
|
22
|
+
* this file in.
|
|
23
|
+
*/
|
|
24
|
+
import type { AnalyticsPayload } from '../types';
|
|
25
|
+
/** Read or mint the visitor UUID. Persisted in localStorage. */
|
|
26
|
+
export declare function getVisitorId(): string;
|
|
27
|
+
/** Read or mint the session UUID; rotate after 30 min idle. */
|
|
28
|
+
export declare function getSessionId(): {
|
|
29
|
+
id: string;
|
|
30
|
+
isNew: boolean;
|
|
31
|
+
};
|
|
32
|
+
interface Utm {
|
|
33
|
+
utm_source: string | null;
|
|
34
|
+
utm_medium: string | null;
|
|
35
|
+
utm_campaign: string | null;
|
|
36
|
+
}
|
|
37
|
+
export declare function getUtm(): Utm;
|
|
38
|
+
/**
|
|
39
|
+
* Fire a single event. Identity (visitor/session ids), UTM, and path are
|
|
40
|
+
* derived automatically from the browser; the caller supplies only the
|
|
41
|
+
* event-specific payload.
|
|
42
|
+
*
|
|
43
|
+
* Returns immediately — network happens in the background.
|
|
44
|
+
*
|
|
45
|
+
* Caller is responsible for consent gating (`useCookieConsent().analytics`)
|
|
46
|
+
* before calling this. The track endpoint also drops events from admin
|
|
47
|
+
* sessions + bots as a server-side safety net.
|
|
48
|
+
*/
|
|
49
|
+
export declare function emitEvent(payload: AnalyticsPayload): void;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/analytics/client/tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,UAAU,CAAA;AAiCpE,gEAAgE;AAChE,wBAAgB,YAAY,IAAI,MAAM,CAcrC;AAOD,+DAA+D;AAC/D,wBAAgB,YAAY,IAAI;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAwB7D;AAMD,UAAU,GAAG;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,wBAAgB,MAAM,IAAI,GAAG,CAY5B;AAuED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CA2BzD"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/analytics/client/tracker
|
|
3
|
+
*
|
|
4
|
+
* Browser-side analytics primitives — pseudonymous identity + transport.
|
|
5
|
+
* No React, no hooks; safe to import from anywhere on the client (cart store,
|
|
6
|
+
* checkout flow, server-component-rendered client islands).
|
|
7
|
+
*
|
|
8
|
+
* Identity model:
|
|
9
|
+
* - visitor_id: persistent UUID in localStorage. Never expires.
|
|
10
|
+
* - session_id: per-tab UUID in sessionStorage. Rotates after 30 min of
|
|
11
|
+
* inactivity (matches GA4's default session boundary).
|
|
12
|
+
*
|
|
13
|
+
* Transport:
|
|
14
|
+
* - sendBeacon() when available (survives page unload, fire-and-forget).
|
|
15
|
+
* - fetch({keepalive:true}) fallback for browsers without sendBeacon.
|
|
16
|
+
* - Both are unblocking; the call returns synchronously, the network
|
|
17
|
+
* happens in the background, errors are swallowed.
|
|
18
|
+
*
|
|
19
|
+
* SSR safety:
|
|
20
|
+
* - Every helper short-circuits when `typeof window === 'undefined'`. Safe
|
|
21
|
+
* to call from a Server Component import chain that incidentally pulls
|
|
22
|
+
* this file in.
|
|
23
|
+
*/
|
|
24
|
+
const VISITOR_KEY = 'rovela:av'; // localStorage
|
|
25
|
+
const SESSION_KEY = 'rovela:as'; // sessionStorage — { id, ts }
|
|
26
|
+
const TRACK_ENDPOINT = '/api/analytics/track';
|
|
27
|
+
const SESSION_TTL_MS = 30 * 60 * 1000;
|
|
28
|
+
const SSR = typeof window === 'undefined';
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// UUID — prefer crypto.randomUUID, fall back to a v4 from getRandomValues
|
|
31
|
+
// =============================================================================
|
|
32
|
+
function uuidv4() {
|
|
33
|
+
if (!SSR && typeof crypto !== 'undefined') {
|
|
34
|
+
if (typeof crypto.randomUUID === 'function')
|
|
35
|
+
return crypto.randomUUID();
|
|
36
|
+
// Fallback: RFC 4122 v4 from getRandomValues
|
|
37
|
+
const bytes = new Uint8Array(16);
|
|
38
|
+
crypto.getRandomValues(bytes);
|
|
39
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
40
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
41
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
42
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
43
|
+
}
|
|
44
|
+
// Server-side never gets here for an outbound event, but the API needs a
|
|
45
|
+
// type-safe string return. This is unreachable in practice.
|
|
46
|
+
return '00000000-0000-4000-8000-000000000000';
|
|
47
|
+
}
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Visitor + session identity
|
|
50
|
+
// =============================================================================
|
|
51
|
+
/** Read or mint the visitor UUID. Persisted in localStorage. */
|
|
52
|
+
export function getVisitorId() {
|
|
53
|
+
if (SSR)
|
|
54
|
+
return '';
|
|
55
|
+
try {
|
|
56
|
+
const existing = localStorage.getItem(VISITOR_KEY);
|
|
57
|
+
if (existing && /^[0-9a-f-]{36}$/i.test(existing))
|
|
58
|
+
return existing;
|
|
59
|
+
const fresh = uuidv4();
|
|
60
|
+
localStorage.setItem(VISITOR_KEY, fresh);
|
|
61
|
+
return fresh;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// localStorage blocked (Safari private mode, third-party iframe).
|
|
65
|
+
// Fall back to a per-pageload UUID so events still flow with sane
|
|
66
|
+
// session-level grouping (will be uniqued per page, never aggregated).
|
|
67
|
+
return uuidv4();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Read or mint the session UUID; rotate after 30 min idle. */
|
|
71
|
+
export function getSessionId() {
|
|
72
|
+
if (SSR)
|
|
73
|
+
return { id: '', isNew: false };
|
|
74
|
+
try {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const raw = sessionStorage.getItem(SESSION_KEY);
|
|
77
|
+
if (raw) {
|
|
78
|
+
const parsed = JSON.parse(raw);
|
|
79
|
+
if (parsed?.id &&
|
|
80
|
+
/^[0-9a-f-]{36}$/i.test(parsed.id) &&
|
|
81
|
+
typeof parsed.ts === 'number' &&
|
|
82
|
+
now - parsed.ts < SESSION_TTL_MS) {
|
|
83
|
+
// Bump the activity timestamp on every access.
|
|
84
|
+
sessionStorage.setItem(SESSION_KEY, JSON.stringify({ id: parsed.id, ts: now }));
|
|
85
|
+
return { id: parsed.id, isNew: false };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const fresh = uuidv4();
|
|
89
|
+
sessionStorage.setItem(SESSION_KEY, JSON.stringify({ id: fresh, ts: now }));
|
|
90
|
+
return { id: fresh, isNew: true };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { id: uuidv4(), isNew: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function getUtm() {
|
|
97
|
+
if (SSR)
|
|
98
|
+
return { utm_source: null, utm_medium: null, utm_campaign: null };
|
|
99
|
+
try {
|
|
100
|
+
const params = new URLSearchParams(window.location.search);
|
|
101
|
+
return {
|
|
102
|
+
utm_source: params.get('utm_source'),
|
|
103
|
+
utm_medium: params.get('utm_medium'),
|
|
104
|
+
utm_campaign: params.get('utm_campaign'),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return { utm_source: null, utm_medium: null, utm_campaign: null };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Read the persisted consent from localStorage and return whether analytics
|
|
113
|
+
* is granted. Returns `true` when the key is missing — the CookieConsent
|
|
114
|
+
* provider grants analytics by default when the banner is disabled (non-EU
|
|
115
|
+
* stores), and storefronts that DO show the banner write the persisted
|
|
116
|
+
* value as soon as the user clicks anything. The window between mount and
|
|
117
|
+
* first click is rare and brief; defaulting to true here preserves data
|
|
118
|
+
* from real visitors while EU-enabled stores' banner blocks events at the
|
|
119
|
+
* provider level (AnalyticsProvider only mounts `track` when consent is
|
|
120
|
+
* granted via useCookieConsent()).
|
|
121
|
+
*
|
|
122
|
+
* This second gate exists for callers OUTSIDE React (cart Zustand store,
|
|
123
|
+
* checkout hook) that can't reach useCookieConsent(). For React components,
|
|
124
|
+
* prefer useAnalytics() which composes both gates.
|
|
125
|
+
*/
|
|
126
|
+
function hasAnalyticsConsent() {
|
|
127
|
+
if (SSR)
|
|
128
|
+
return false;
|
|
129
|
+
try {
|
|
130
|
+
const raw = localStorage.getItem('rovela-cookie-consent');
|
|
131
|
+
if (!raw)
|
|
132
|
+
return true; // banner disabled OR not yet decided — see comment
|
|
133
|
+
const parsed = JSON.parse(raw);
|
|
134
|
+
return parsed.analytics === true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// Transport — fire-and-forget, never throws
|
|
142
|
+
// =============================================================================
|
|
143
|
+
function postEvent(event) {
|
|
144
|
+
if (SSR)
|
|
145
|
+
return;
|
|
146
|
+
const body = JSON.stringify(event);
|
|
147
|
+
try {
|
|
148
|
+
if (navigator.sendBeacon) {
|
|
149
|
+
// sendBeacon needs a Blob to set Content-Type; the API parses text/plain
|
|
150
|
+
// either way, but using a JSON blob keeps DevTools readable.
|
|
151
|
+
const blob = new Blob([body], { type: 'application/json' });
|
|
152
|
+
navigator.sendBeacon(TRACK_ENDPOINT, blob);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Fallback: fetch with keepalive so the request survives page unload.
|
|
156
|
+
void fetch(TRACK_ENDPOINT, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
body,
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
keepalive: true,
|
|
161
|
+
}).catch(() => undefined);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Defensive — never bubble. Analytics MUST NEVER break customer flows.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// Public API — emit a typed event
|
|
169
|
+
// =============================================================================
|
|
170
|
+
/**
|
|
171
|
+
* Fire a single event. Identity (visitor/session ids), UTM, and path are
|
|
172
|
+
* derived automatically from the browser; the caller supplies only the
|
|
173
|
+
* event-specific payload.
|
|
174
|
+
*
|
|
175
|
+
* Returns immediately — network happens in the background.
|
|
176
|
+
*
|
|
177
|
+
* Caller is responsible for consent gating (`useCookieConsent().analytics`)
|
|
178
|
+
* before calling this. The track endpoint also drops events from admin
|
|
179
|
+
* sessions + bots as a server-side safety net.
|
|
180
|
+
*/
|
|
181
|
+
export function emitEvent(payload) {
|
|
182
|
+
if (SSR)
|
|
183
|
+
return;
|
|
184
|
+
// Belt-and-suspenders consent gate — useAnalytics already gates React
|
|
185
|
+
// callers, but emitEvent is also called from the cart Zustand store and
|
|
186
|
+
// the checkout hook (non-React surfaces).
|
|
187
|
+
if (!hasAnalyticsConsent())
|
|
188
|
+
return;
|
|
189
|
+
const visitorId = getVisitorId();
|
|
190
|
+
const { id: sessionId } = getSessionId();
|
|
191
|
+
const utm = getUtm();
|
|
192
|
+
const fallbackPath = window.location.pathname || '/';
|
|
193
|
+
const fallbackReferrer = typeof document !== 'undefined' && document.referrer ? document.referrer : null;
|
|
194
|
+
// Caller-supplied path / referrer / utm win (e.g. AnalyticsProvider's
|
|
195
|
+
// per-route path); fall through to browser defaults otherwise.
|
|
196
|
+
const wire = {
|
|
197
|
+
visitor_id: visitorId,
|
|
198
|
+
session_id: sessionId,
|
|
199
|
+
...payload,
|
|
200
|
+
path: payload.path ?? fallbackPath,
|
|
201
|
+
referrer: payload.referrer ?? fallbackReferrer,
|
|
202
|
+
utm_source: payload.utm_source ?? utm.utm_source,
|
|
203
|
+
utm_medium: payload.utm_medium ?? utm.utm_medium,
|
|
204
|
+
utm_campaign: payload.utm_campaign ?? utm.utm_campaign,
|
|
205
|
+
};
|
|
206
|
+
postEvent(wire);
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../../src/analytics/client/tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsDashboard.d.ts","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsDashboard.tsx"],"names":[],"mappings":"AAwBA,wBAAgB,kBAAkB,4CAWjC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* @rovela-ai/sdk/analytics/components/AnalyticsDashboard
|
|
5
|
+
*
|
|
6
|
+
* Back-compat shim. The original single-page dashboard has been split into
|
|
7
|
+
* a tab shell (AnalyticsTabNav) + three views (DashboardsView, EventsView,
|
|
8
|
+
* VisitorsView) wired to route-per-tab pages.
|
|
9
|
+
*
|
|
10
|
+
* This component still works — it renders the AnalyticsTabNav + the
|
|
11
|
+
* DashboardsView. Templates that haven't migrated to the route-per-tab
|
|
12
|
+
* layout get the Dashboards tab content with the tab navigation visible.
|
|
13
|
+
*
|
|
14
|
+
* New templates should mount `<DashboardsView period={period} />` directly
|
|
15
|
+
* from the route page and `<AnalyticsTabNav baseHref="/admin/analytics" />`
|
|
16
|
+
* from the shared layout.
|
|
17
|
+
*/
|
|
18
|
+
import { useState } from 'react';
|
|
19
|
+
import { AnalyticsTabNav } from './AnalyticsTabNav';
|
|
20
|
+
import { PeriodSelector } from '../../admin/components/PeriodSelector';
|
|
21
|
+
import { DashboardsView } from '../views/DashboardsView';
|
|
22
|
+
export function AnalyticsDashboard() {
|
|
23
|
+
const [period, setPeriod] = useState('30d');
|
|
24
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsx(AnalyticsTabNav, { baseHref: "/admin/analytics", trailing: _jsx(PeriodSelector, { value: period, onChange: setPeriod }) }), _jsx(DashboardsView, { period: period })] }));
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=AnalyticsDashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsDashboard.js","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsDashboard.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAGxD,MAAM,UAAU,kBAAkB;IAChC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAkB,KAAK,CAAC,CAAA;IAC5D,OAAO,CACL,eAAK,SAAS,EAAC,WAAW,aACxB,KAAC,eAAe,IACd,QAAQ,EAAC,kBAAkB,EAC3B,QAAQ,EAAE,KAAC,cAAc,IAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,GAAI,GAChE,EACF,KAAC,cAAc,IAAC,MAAM,EAAE,MAAM,GAAI,IAC9B,CACP,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AnalyticsPeriod } from '../types';
|
|
2
|
+
interface AnalyticsPeriodContextValue {
|
|
3
|
+
period: AnalyticsPeriod;
|
|
4
|
+
setPeriod: (p: AnalyticsPeriod) => void;
|
|
5
|
+
}
|
|
6
|
+
export interface AnalyticsPeriodProviderProps {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
defaultPeriod?: AnalyticsPeriod;
|
|
9
|
+
}
|
|
10
|
+
export declare function AnalyticsPeriodProvider({ children, defaultPeriod, }: AnalyticsPeriodProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function useAnalyticsPeriod(): AnalyticsPeriodContextValue;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=AnalyticsPeriodContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalyticsPeriodContext.d.ts","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsPeriodContext.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE/C,UAAU,2BAA2B;IACnC,MAAM,EAAE,eAAe,CAAA;IACvB,SAAS,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,CAAA;CACxC;AAMD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,aAAa,CAAC,EAAE,eAAe,CAAA;CAChC;AAED,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,aAAqB,GACtB,EAAE,4BAA4B,2CAO9B;AAED,wBAAgB,kBAAkB,IAAI,2BAA2B,CAShE"}
|