@rovela-ai/sdk 0.4.3 → 0.5.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.
Files changed (141) hide show
  1. package/dist/admin/components/AdminNav.d.ts.map +1 -1
  2. package/dist/admin/components/AdminNav.js +10 -1
  3. package/dist/admin/components/AdminNav.js.map +1 -1
  4. package/dist/admin/components/ExampleContentBanner.js +2 -2
  5. package/dist/admin/components/ExampleContentBanner.js.map +1 -1
  6. package/dist/admin/components/SetupGuide.d.ts.map +1 -1
  7. package/dist/admin/components/SetupGuide.js +4 -4
  8. package/dist/admin/components/SetupGuide.js.map +1 -1
  9. package/dist/admin/components/index.d.ts +0 -1
  10. package/dist/admin/components/index.d.ts.map +1 -1
  11. package/dist/admin/components/index.js +0 -1
  12. package/dist/admin/components/index.js.map +1 -1
  13. package/dist/admin/index.d.ts +1 -1
  14. package/dist/admin/index.d.ts.map +1 -1
  15. package/dist/admin/index.js +1 -1
  16. package/dist/admin/index.js.map +1 -1
  17. package/dist/admin/styles/admin-theme.css +11 -0
  18. package/dist/analytics/api/dashboard.d.ts +16 -0
  19. package/dist/analytics/api/dashboard.d.ts.map +1 -0
  20. package/dist/analytics/api/dashboard.js +37 -0
  21. package/dist/analytics/api/dashboard.js.map +1 -0
  22. package/dist/analytics/api/events.d.ts +23 -0
  23. package/dist/analytics/api/events.d.ts.map +1 -0
  24. package/dist/analytics/api/events.js +55 -0
  25. package/dist/analytics/api/events.js.map +1 -0
  26. package/dist/analytics/api/index.d.ts +15 -0
  27. package/dist/analytics/api/index.d.ts.map +1 -0
  28. package/dist/analytics/api/index.js +15 -0
  29. package/dist/analytics/api/index.js.map +1 -0
  30. package/dist/analytics/api/track.d.ts +20 -0
  31. package/dist/analytics/api/track.d.ts.map +1 -0
  32. package/dist/analytics/api/track.js +233 -0
  33. package/dist/analytics/api/track.js.map +1 -0
  34. package/dist/analytics/api/visitors.d.ts +19 -0
  35. package/dist/analytics/api/visitors.d.ts.map +1 -0
  36. package/dist/analytics/api/visitors.js +49 -0
  37. package/dist/analytics/api/visitors.js.map +1 -0
  38. package/dist/analytics/client/tracker.d.ts +51 -0
  39. package/dist/analytics/client/tracker.d.ts.map +1 -0
  40. package/dist/analytics/client/tracker.js +208 -0
  41. package/dist/analytics/client/tracker.js.map +1 -0
  42. package/dist/analytics/components/AnalyticsDashboard.d.ts +2 -0
  43. package/dist/analytics/components/AnalyticsDashboard.d.ts.map +1 -0
  44. package/dist/analytics/components/AnalyticsDashboard.js +26 -0
  45. package/dist/analytics/components/AnalyticsDashboard.js.map +1 -0
  46. package/dist/analytics/components/AnalyticsPeriodContext.d.ts +13 -0
  47. package/dist/analytics/components/AnalyticsPeriodContext.d.ts.map +1 -0
  48. package/dist/analytics/components/AnalyticsPeriodContext.js +28 -0
  49. package/dist/analytics/components/AnalyticsPeriodContext.js.map +1 -0
  50. package/dist/analytics/components/AnalyticsProvider.d.ts +22 -0
  51. package/dist/analytics/components/AnalyticsProvider.d.ts.map +1 -0
  52. package/dist/analytics/components/AnalyticsProvider.js +152 -0
  53. package/dist/analytics/components/AnalyticsProvider.js.map +1 -0
  54. package/dist/analytics/components/AnalyticsTabNav.d.ts +17 -0
  55. package/dist/analytics/components/AnalyticsTabNav.d.ts.map +1 -0
  56. package/dist/analytics/components/AnalyticsTabNav.js +42 -0
  57. package/dist/analytics/components/AnalyticsTabNav.js.map +1 -0
  58. package/dist/analytics/hooks/useAnalytics.d.ts +9 -0
  59. package/dist/analytics/hooks/useAnalytics.d.ts.map +1 -0
  60. package/dist/analytics/hooks/useAnalytics.js +8 -0
  61. package/dist/analytics/hooks/useAnalytics.js.map +1 -0
  62. package/dist/analytics/hooks/useAnalyticsDashboard.d.ts +9 -0
  63. package/dist/analytics/hooks/useAnalyticsDashboard.d.ts.map +1 -0
  64. package/dist/analytics/hooks/useAnalyticsDashboard.js +45 -0
  65. package/dist/analytics/hooks/useAnalyticsDashboard.js.map +1 -0
  66. package/dist/analytics/hooks/useEventsLog.d.ts +24 -0
  67. package/dist/analytics/hooks/useEventsLog.d.ts.map +1 -0
  68. package/dist/analytics/hooks/useEventsLog.js +81 -0
  69. package/dist/analytics/hooks/useEventsLog.js.map +1 -0
  70. package/dist/analytics/hooks/useVisitorsList.d.ts +20 -0
  71. package/dist/analytics/hooks/useVisitorsList.d.ts.map +1 -0
  72. package/dist/analytics/hooks/useVisitorsList.js +69 -0
  73. package/dist/analytics/hooks/useVisitorsList.js.map +1 -0
  74. package/dist/analytics/index.d.ts +44 -0
  75. package/dist/analytics/index.d.ts.map +1 -0
  76. package/dist/analytics/index.js +39 -0
  77. package/dist/analytics/index.js.map +1 -0
  78. package/dist/analytics/server/index.d.ts +10 -0
  79. package/dist/analytics/server/index.d.ts.map +1 -0
  80. package/dist/analytics/server/index.js +9 -0
  81. package/dist/analytics/server/index.js.map +1 -0
  82. package/dist/analytics/server/normalize.d.ts +23 -0
  83. package/dist/analytics/server/normalize.d.ts.map +1 -0
  84. package/dist/analytics/server/normalize.js +75 -0
  85. package/dist/analytics/server/normalize.js.map +1 -0
  86. package/dist/analytics/server/queries.d.ts +74 -0
  87. package/dist/analytics/server/queries.d.ts.map +1 -0
  88. package/dist/analytics/server/queries.js +470 -0
  89. package/dist/analytics/server/queries.js.map +1 -0
  90. package/dist/analytics/types.d.ts +186 -0
  91. package/dist/analytics/types.d.ts.map +1 -0
  92. package/dist/analytics/types.js +16 -0
  93. package/dist/analytics/types.js.map +1 -0
  94. package/dist/analytics/views/DashboardsView.d.ts +6 -0
  95. package/dist/analytics/views/DashboardsView.d.ts.map +1 -0
  96. package/dist/analytics/views/DashboardsView.js +93 -0
  97. package/dist/analytics/views/DashboardsView.js.map +1 -0
  98. package/dist/analytics/views/EventsView.d.ts +6 -0
  99. package/dist/analytics/views/EventsView.d.ts.map +1 -0
  100. package/dist/analytics/views/EventsView.js +85 -0
  101. package/dist/analytics/views/EventsView.js.map +1 -0
  102. package/dist/analytics/views/VisitorsView.d.ts +6 -0
  103. package/dist/analytics/views/VisitorsView.d.ts.map +1 -0
  104. package/dist/analytics/views/VisitorsView.js +57 -0
  105. package/dist/analytics/views/VisitorsView.js.map +1 -0
  106. package/dist/cart/store.d.ts.map +1 -1
  107. package/dist/cart/store.js +12 -0
  108. package/dist/cart/store.js.map +1 -1
  109. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  110. package/dist/checkout/components/CheckoutFlow.js +26 -2
  111. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  112. package/dist/checkout/components/ShippingForm.js +10 -5
  113. package/dist/checkout/components/ShippingForm.js.map +1 -1
  114. package/dist/checkout/hooks/useCheckout.d.ts.map +1 -1
  115. package/dist/checkout/hooks/useCheckout.js +12 -1
  116. package/dist/checkout/hooks/useCheckout.js.map +1 -1
  117. package/dist/checkout/server/handle-webhook.js +15 -0
  118. package/dist/checkout/server/handle-webhook.js.map +1 -1
  119. package/dist/checkout/types.d.ts +13 -0
  120. package/dist/checkout/types.d.ts.map +1 -1
  121. package/dist/core/db/client.d.ts +14 -0
  122. package/dist/core/db/client.d.ts.map +1 -1
  123. package/dist/core/db/client.js +12 -0
  124. package/dist/core/db/client.js.map +1 -1
  125. package/dist/core/db/index.d.ts +2 -1
  126. package/dist/core/db/index.d.ts.map +1 -1
  127. package/dist/core/db/index.js +1 -1
  128. package/dist/core/db/index.js.map +1 -1
  129. package/dist/core/db/queries.d.ts +8 -7
  130. package/dist/core/db/queries.d.ts.map +1 -1
  131. package/dist/core/db/queries.js +80 -0
  132. package/dist/core/db/queries.js.map +1 -1
  133. package/dist/core/db/schema.d.ts +340 -4
  134. package/dist/core/db/schema.d.ts.map +1 -1
  135. package/dist/core/db/schema.js +55 -1
  136. package/dist/core/db/schema.js.map +1 -1
  137. package/dist/core/server/index.d.ts +2 -2
  138. package/dist/core/server/index.d.ts.map +1 -1
  139. package/dist/core/server/index.js +2 -0
  140. package/dist/core/server/index.js.map +1 -1
  141. package/package.json +25 -1
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * @rovela-ai/sdk/analytics/components/AnalyticsPeriodContext
5
+ *
6
+ * Period state shared across the three Insights tabs. Mounted at the
7
+ * Insights layout level so navigating Dashboards → Events → Visitors
8
+ * preserves the selected window (Next.js App Router preserves layout
9
+ * instances across child route changes — the context lives as long as
10
+ * the user is on /admin/analytics/*).
11
+ *
12
+ * Defaults to '30d' on first mount.
13
+ */
14
+ import { createContext, useContext, useState } from 'react';
15
+ const AnalyticsPeriodContext = createContext(null);
16
+ export function AnalyticsPeriodProvider({ children, defaultPeriod = '30d', }) {
17
+ const [period, setPeriod] = useState(defaultPeriod);
18
+ return (_jsx(AnalyticsPeriodContext.Provider, { value: { period, setPeriod }, children: children }));
19
+ }
20
+ export function useAnalyticsPeriod() {
21
+ const ctx = useContext(AnalyticsPeriodContext);
22
+ if (!ctx) {
23
+ throw new Error('useAnalyticsPeriod must be used inside <AnalyticsPeriodProvider>. ' +
24
+ 'Mount the provider at the /admin/analytics layout level.');
25
+ }
26
+ return ctx;
27
+ }
28
+ //# sourceMappingURL=AnalyticsPeriodContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyticsPeriodContext.js","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsPeriodContext.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAQ3D,MAAM,sBAAsB,GAAG,aAAa,CAC1C,IAAI,CACL,CAAA;AAOD,MAAM,UAAU,uBAAuB,CAAC,EACtC,QAAQ,EACR,aAAa,GAAG,KAAK,GACQ;IAC7B,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAkB,aAAa,CAAC,CAAA;IACpE,OAAO,CACL,KAAC,sBAAsB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,YAC1D,QAAQ,GACuB,CACnC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAA;IAC9C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,oEAAoE;YAClE,0DAA0D,CAC7D,CAAA;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { AnalyticsPayload } from '../types';
2
+ interface AnalyticsContextValue {
3
+ /** True when events will actually fire (consent granted + not an admin). */
4
+ enabled: boolean;
5
+ /** Fire a single typed event. No-op if disabled. Path / utm / identity are
6
+ * derived automatically by emitEvent. */
7
+ track: (payload: AnalyticsPayload) => void;
8
+ }
9
+ export interface AnalyticsProviderProps {
10
+ children: React.ReactNode;
11
+ /** Override the consent gate (e.g. for testing). Default: respect cookie consent. */
12
+ forceEnabled?: boolean;
13
+ }
14
+ export declare function AnalyticsProvider({ children, forceEnabled, }: AnalyticsProviderProps): import("react/jsx-runtime").JSX.Element;
15
+ /**
16
+ * Storefront-component hook. Returns a track() that no-ops when consent is
17
+ * denied or an admin is browsing — so call sites can fire unconditionally
18
+ * without manual guards.
19
+ */
20
+ export declare function useAnalytics(): AnalyticsContextValue;
21
+ export {};
22
+ //# sourceMappingURL=AnalyticsProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyticsProvider.d.ts","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsProvider.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAMhD,UAAU,qBAAqB;IAC7B,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB;8CAC0C;IAC1C,KAAK,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAC3C;AAWD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,qFAAqF;IACrF,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,YAAY,GACb,EAAE,sBAAsB,2CAoHxB;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,qBAAqB,CAEpD"}
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * @rovela-ai/sdk/analytics/components/AnalyticsProvider
5
+ *
6
+ * Storefront analytics provider. Mounts ONCE inside the customer branch of
7
+ * LayoutContent.tsx (NEVER inside the admin branch). Responsibilities:
8
+ *
9
+ * 1. Auto-fire `session_start` once per session (per-tab, 30-min idle).
10
+ * 2. Auto-fire `page_view` on every route change.
11
+ * 3. Gate everything on cookie consent (§26 useCookieConsent().analytics).
12
+ * 4. Stay silent when an admin is signed in (no merchant self-bias).
13
+ * 5. Stay silent during SSR (typeof window guard).
14
+ * 6. Provide a useAnalytics() hook so SDK + storefront components can fire
15
+ * manual events (add_to_cart, begin_checkout, etc.).
16
+ *
17
+ * Order of evaluation per render:
18
+ * admin? → skip everything
19
+ * consent denied? → skip everything
20
+ * otherwise → fire (debounced page_view for rapid client-nav)
21
+ *
22
+ * SSR-safe: every effect short-circuits if `pathname` is undefined or window
23
+ * is missing.
24
+ */
25
+ import { createContext, useContext, useEffect, useRef, useState } from 'react';
26
+ import { usePathname } from 'next/navigation';
27
+ import { useCookieConsent } from '../../core/cookie-consent';
28
+ import { useAdminSession } from '../../admin/hooks/useAdminSession';
29
+ import { emitEvent, getSessionId } from '../client/tracker';
30
+ const AnalyticsContext = createContext({
31
+ enabled: false,
32
+ track: () => undefined,
33
+ });
34
+ export function AnalyticsProvider({ children, forceEnabled, }) {
35
+ const pathname = usePathname();
36
+ const { analytics: analyticsConsent } = useCookieConsent();
37
+ const { data: adminSession } = useAdminSession();
38
+ // Hydrate the gate post-mount so SSR + first paint don't fire (window not
39
+ // yet ready; consent provider value may flip after hydration).
40
+ // Pattern B — post-mount hydration to avoid SSR mismatch.
41
+ const [mounted, setMounted] = useState(false);
42
+ useEffect(() => {
43
+ // eslint-disable-next-line react-hooks/set-state-in-effect
44
+ setMounted(true);
45
+ }, []);
46
+ const isAdmin = Boolean(adminSession?.user?.id);
47
+ const enabled = mounted && !isAdmin && (forceEnabled || analyticsConsent);
48
+ // Auto-fire page_view on route change (debounced for rapid client-nav).
49
+ // sessionStorage check inside getSessionId() handles session_start.
50
+ const lastPathRef = useRef(null);
51
+ useEffect(() => {
52
+ if (!enabled || !pathname)
53
+ return;
54
+ if (lastPathRef.current === pathname)
55
+ return;
56
+ const prev = lastPathRef.current;
57
+ lastPathRef.current = pathname;
58
+ // Tiny debounce — Next.js can rapid-fire usePathname() during transitions.
59
+ const t = setTimeout(() => {
60
+ // session_start fires automatically when the session ID is fresh,
61
+ // BEFORE the first page_view of a new session. This keeps the order
62
+ // session_start → page_view consistent in dashboard reads.
63
+ const { isNew } = getSessionId();
64
+ if (isNew) {
65
+ emitEvent({ event: 'session_start', path: pathname });
66
+ }
67
+ emitEvent({
68
+ event: 'page_view',
69
+ path: pathname,
70
+ referrer: prev ? `${window.location.origin}${prev}` : document.referrer || null,
71
+ });
72
+ }, 50);
73
+ return () => clearTimeout(t);
74
+ }, [enabled, pathname]);
75
+ // Session lifecycle: heartbeat + end. Mounted once at the provider level
76
+ // (NOT per-route) so the cadence is stable across client-nav. Pauses on
77
+ // tab-hidden; resumes on tab-visible. Fires `session_end` on pagehide
78
+ // (more reliable than beforeunload on mobile Safari).
79
+ //
80
+ // 30s cadence — matches what Plausible/Fathom use. Tight enough to give
81
+ // good session-duration resolution (±15s), loose enough that a 10-min
82
+ // session generates only 20 events.
83
+ const HEARTBEAT_MS = 30_000;
84
+ useEffect(() => {
85
+ if (!enabled)
86
+ return;
87
+ if (typeof document === 'undefined')
88
+ return;
89
+ let interval = null;
90
+ const start = () => {
91
+ if (interval !== null)
92
+ return;
93
+ interval = setInterval(() => {
94
+ // Use the live pathname via window — pathname from React closure may
95
+ // be stale if a client-nav happened between heartbeats.
96
+ emitEvent({
97
+ event: 'session_heartbeat',
98
+ path: window.location.pathname,
99
+ });
100
+ }, HEARTBEAT_MS);
101
+ };
102
+ const stop = () => {
103
+ if (interval === null)
104
+ return;
105
+ clearInterval(interval);
106
+ interval = null;
107
+ };
108
+ const onVisibilityChange = () => {
109
+ if (document.visibilityState === 'visible')
110
+ start();
111
+ else
112
+ stop();
113
+ };
114
+ const onPageHide = () => {
115
+ // sendBeacon is the only reliable transport during unload — emitEvent
116
+ // already prefers it. Fire-and-forget; no await.
117
+ emitEvent({
118
+ event: 'session_end',
119
+ path: window.location.pathname,
120
+ });
121
+ stop();
122
+ };
123
+ // Initial state — start immediately if the tab is visible.
124
+ if (document.visibilityState === 'visible')
125
+ start();
126
+ document.addEventListener('visibilitychange', onVisibilityChange);
127
+ window.addEventListener('pagehide', onPageHide);
128
+ return () => {
129
+ document.removeEventListener('visibilitychange', onVisibilityChange);
130
+ window.removeEventListener('pagehide', onPageHide);
131
+ stop();
132
+ };
133
+ }, [enabled]);
134
+ const track = (payload) => {
135
+ if (!enabled)
136
+ return;
137
+ emitEvent(payload);
138
+ };
139
+ return (_jsx(AnalyticsContext.Provider, { value: { enabled, track }, children: children }));
140
+ }
141
+ // =============================================================================
142
+ // Hook
143
+ // =============================================================================
144
+ /**
145
+ * Storefront-component hook. Returns a track() that no-ops when consent is
146
+ * denied or an admin is browsing — so call sites can fire unconditionally
147
+ * without manual guards.
148
+ */
149
+ export function useAnalytics() {
150
+ return useContext(AnalyticsContext);
151
+ }
152
+ //# sourceMappingURL=AnalyticsProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyticsProvider.js","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsProvider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface AnalyticsTabNavProps {
2
+ /** Header above the tab strip (default: 'Insights'). */
3
+ title?: string;
4
+ /** Subtitle under the header (optional). */
5
+ subtitle?: string;
6
+ /** Base href for the three tab routes. Defaults to the admin path so the
7
+ * SDK admin pages don't have to pass it explicitly. The platform side
8
+ * passes `/projects/<id>/insights` to reuse the same shell. */
9
+ baseHref?: string;
10
+ /** Optional right-aligned controls (period selector, etc.). */
11
+ trailing?: React.ReactNode;
12
+ /** Show the "Default" dashboard pill below the tabs on the Dashboards
13
+ * tab. Set false on platform-side if it feels redundant. */
14
+ showDashboardPill?: boolean;
15
+ }
16
+ export declare function AnalyticsTabNav({ title, subtitle, baseHref, trailing, showDashboardPill, }: AnalyticsTabNavProps): import("react/jsx-runtime").JSX.Element;
17
+ //# sourceMappingURL=AnalyticsTabNav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyticsTabNav.d.ts","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsTabNav.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;oEAEgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B;iEAC6D;IAC7D,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAwBD,wBAAgB,eAAe,CAAC,EAC9B,KAAkB,EAClB,QAAQ,EACR,QAA6B,EAC7B,QAAQ,EACR,iBAAwB,GACzB,EAAE,oBAAoB,2CAsDtB"}
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * @rovela-ai/sdk/analytics/components/AnalyticsTabNav
5
+ *
6
+ * Underline-style tab nav for the Insights surface. Three links — Dashboards
7
+ * / Events / Visitors — wired to route-per-tab URLs. Active tab is derived
8
+ * from pathname (no controlled state needed).
9
+ *
10
+ * Matches the Ploy aesthetic: underline beneath the active tab, muted labels
11
+ * for the inactive ones, no background fills. Also renders the "Default"
12
+ * pill (single dashboard, no creation affordance) when the active tab is
13
+ * Dashboards — reserves the slot for a future multi-dashboard system.
14
+ */
15
+ import Link from 'next/link';
16
+ import { usePathname } from 'next/navigation';
17
+ const TABS = [
18
+ { id: 'dashboards', label: 'Dashboards', sub: '' },
19
+ { id: 'events', label: 'Events', sub: '/events' },
20
+ { id: 'visitors', label: 'Visitors', sub: '/visitors' },
21
+ ];
22
+ function resolveActiveTab(pathname, baseHref) {
23
+ if (!pathname)
24
+ return 'dashboards';
25
+ if (pathname.endsWith('/events') || pathname.includes('/events/'))
26
+ return 'events';
27
+ if (pathname.endsWith('/visitors') || pathname.includes('/visitors/'))
28
+ return 'visitors';
29
+ // Default — the baseHref itself or anything else under it
30
+ return pathname.startsWith(baseHref) ? 'dashboards' : 'dashboards';
31
+ }
32
+ export function AnalyticsTabNav({ title = 'Insights', subtitle, baseHref = '/admin/analytics', trailing, showDashboardPill = true, }) {
33
+ const pathname = usePathname();
34
+ const active = resolveActiveTab(pathname, baseHref);
35
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold text-foreground sm:text-3xl", children: title }), subtitle && (_jsx("p", { className: "mt-1 text-muted-foreground", children: subtitle }))] }), trailing && _jsx("div", { className: "flex items-center gap-2", children: trailing })] }), _jsx("div", { className: "border-b border-border", children: _jsx("nav", { className: "-mb-px flex gap-6", children: TABS.map((tab) => {
36
+ const isActive = active === tab.id;
37
+ return (_jsx(Link, { href: `${baseHref}${tab.sub}`, "aria-current": isActive ? 'page' : undefined, className: isActive
38
+ ? 'border-b-2 border-foreground px-1 pb-3 pt-1 text-sm font-semibold text-foreground'
39
+ : 'border-b-2 border-transparent px-1 pb-3 pt-1 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground', children: tab.label }, tab.id));
40
+ }) }) }), active === 'dashboards' && showDashboardPill && (_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "inline-flex items-center rounded-full bg-foreground px-3 py-1 text-xs font-semibold text-background", children: "Default" }) }))] }));
41
+ }
42
+ //# sourceMappingURL=AnalyticsTabNav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnalyticsTabNav.js","sourceRoot":"","sources":["../../../src/analytics/components/AnalyticsTabNav.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;GAWG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAyB7C,MAAM,IAAI,GAAU;IAClB,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAE;IAClD,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE;IACjD,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE;CACxD,CAAA;AAED,SAAS,gBAAgB,CAAC,QAAuB,EAAE,QAAgB;IACjE,IAAI,CAAC,QAAQ;QAAE,OAAO,YAAY,CAAA;IAClC,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,QAAQ,CAAA;IAClF,IAAI,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QACnE,OAAO,UAAU,CAAA;IACnB,0DAA0D;IAC1D,OAAO,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAC9B,KAAK,GAAG,UAAU,EAClB,QAAQ,EACR,QAAQ,GAAG,kBAAkB,EAC7B,QAAQ,EACR,iBAAiB,GAAG,IAAI,GACH;IACrB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAEnD,OAAO,CACL,eAAK,SAAS,EAAC,WAAW,aAExB,eAAK,SAAS,EAAC,oEAAoE,aACjF,0BACE,aAAI,SAAS,EAAC,gDAAgD,YAC3D,KAAK,GACH,EACJ,QAAQ,IAAI,CACX,YAAG,SAAS,EAAC,4BAA4B,YAAE,QAAQ,GAAK,CACzD,IACG,EACL,QAAQ,IAAI,cAAK,SAAS,EAAC,yBAAyB,YAAE,QAAQ,GAAO,IAClE,EAGN,cAAK,SAAS,EAAC,wBAAwB,YACrC,cAAK,SAAS,EAAC,mBAAmB,YAC/B,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;wBAChB,MAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,CAAC,EAAE,CAAA;wBAClC,OAAO,CACL,KAAC,IAAI,IAEH,IAAI,EAAE,GAAG,QAAQ,GAAG,GAAG,CAAC,GAAG,EAAE,kBACf,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC3C,SAAS,EACP,QAAQ;gCACN,CAAC,CAAC,mFAAmF;gCACrF,CAAC,CAAC,gIAAgI,YAGrI,GAAG,CAAC,KAAK,IATL,GAAG,CAAC,EAAE,CAUN,CACR,CAAA;oBACH,CAAC,CAAC,GACE,GACF,EAKL,MAAM,KAAK,YAAY,IAAI,iBAAiB,IAAI,CAC/C,cAAK,SAAS,EAAC,yBAAyB,YACtC,eAAM,SAAS,EAAC,qGAAqG,wBAE9G,GACH,CACP,IACG,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @rovela-ai/sdk/analytics/hooks/useAnalytics
3
+ *
4
+ * Re-export of the useAnalytics hook from the provider module. Lives here
5
+ * so the import path matches every other SDK hook (`../hooks/useFoo`).
6
+ */
7
+ export { useAnalytics, AnalyticsProvider } from '../components/AnalyticsProvider';
8
+ export type { AnalyticsProviderProps } from '../components/AnalyticsProvider';
9
+ //# sourceMappingURL=useAnalytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnalytics.d.ts","sourceRoot":"","sources":["../../../src/analytics/hooks/useAnalytics.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACjF,YAAY,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAA"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @rovela-ai/sdk/analytics/hooks/useAnalytics
3
+ *
4
+ * Re-export of the useAnalytics hook from the provider module. Lives here
5
+ * so the import path matches every other SDK hook (`../hooks/useFoo`).
6
+ */
7
+ export { useAnalytics, AnalyticsProvider } from '../components/AnalyticsProvider';
8
+ //# sourceMappingURL=useAnalytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnalytics.js","sourceRoot":"","sources":["../../../src/analytics/hooks/useAnalytics.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA"}
@@ -0,0 +1,9 @@
1
+ import type { AnalyticsDashboardPayload, AnalyticsPeriod } from '../types';
2
+ export interface UseAnalyticsDashboardReturn {
3
+ data: AnalyticsDashboardPayload | null;
4
+ isLoading: boolean;
5
+ error: string | null;
6
+ refresh: () => Promise<void>;
7
+ }
8
+ export declare function useAnalyticsDashboard(period: AnalyticsPeriod): UseAnalyticsDashboardReturn;
9
+ //# sourceMappingURL=useAnalyticsDashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnalyticsDashboard.d.ts","sourceRoot":"","sources":["../../../src/analytics/hooks/useAnalyticsDashboard.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,yBAAyB,EACzB,eAAe,EAChB,MAAM,UAAU,CAAA;AAEjB,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,yBAAyB,GAAG,IAAI,CAAA;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,GACtB,2BAA2B,CAyC7B"}
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ /**
3
+ * @rovela-ai/sdk/analytics/hooks/useAnalyticsDashboard
4
+ *
5
+ * Admin dashboard hook — fetches /api/admin/analytics scoped to a period.
6
+ * Mirrors the shape of `useAdminStats` so callers get the same {data,
7
+ * isLoading, error, refresh} ergonomics.
8
+ *
9
+ * AbortController-managed: rapid period flips cancel the prior request.
10
+ */
11
+ import { useCallback, useEffect, useRef, useState } from 'react';
12
+ import { fetchAdminApi } from '../../admin/hooks/fetchAdminApi';
13
+ export function useAnalyticsDashboard(period) {
14
+ const [data, setData] = useState(null);
15
+ const [error, setError] = useState(null);
16
+ const [isLoading, setIsLoading] = useState(true);
17
+ const abortRef = useRef(null);
18
+ const fetchDashboard = useCallback(async (p) => {
19
+ abortRef.current?.abort();
20
+ const controller = new AbortController();
21
+ abortRef.current = controller;
22
+ setIsLoading(true);
23
+ const res = await fetchAdminApi(`/api/admin/analytics?period=${p}`, { signal: controller.signal });
24
+ if (controller.signal.aborted)
25
+ return;
26
+ setIsLoading(false);
27
+ if (!res.ok) {
28
+ setError(res.error);
29
+ return;
30
+ }
31
+ setError(null);
32
+ setData(res.data);
33
+ }, []);
34
+ // Pattern C — populate state from async-loaded data.
35
+ useEffect(() => {
36
+ // eslint-disable-next-line react-hooks/set-state-in-effect
37
+ fetchDashboard(period);
38
+ return () => {
39
+ abortRef.current?.abort();
40
+ };
41
+ }, [period, fetchDashboard]);
42
+ const refresh = useCallback(() => fetchDashboard(period), [period, fetchDashboard]);
43
+ return { data, isLoading, error, refresh };
44
+ }
45
+ //# sourceMappingURL=useAnalyticsDashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAnalyticsDashboard.js","sourceRoot":"","sources":["../../../src/analytics/hooks/useAnalyticsDashboard.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAa/D,MAAM,UAAU,qBAAqB,CACnC,MAAuB;IAEvB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAmC,IAAI,CAAC,CAAA;IACxE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACvD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAA;IAErD,MAAM,cAAc,GAAG,WAAW,CAChC,KAAK,EAAE,CAAkB,EAAiB,EAAE;QAC1C,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAA;QACzB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAA;QAE7B,YAAY,CAAC,IAAI,CAAC,CAAA;QAClB,MAAM,GAAG,GAAG,MAAM,aAAa,CAC7B,+BAA+B,CAAC,EAAE,EAClC,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAC9B,CAAA;QACD,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;YAAE,OAAM;QACrC,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACnB,OAAM;QACR,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACnB,CAAC,EACD,EAAE,CACH,CAAA;IAED,qDAAqD;IACrD,SAAS,CAAC,GAAG,EAAE;QACb,2DAA2D;QAC3D,cAAc,CAAC,MAAM,CAAC,CAAA;QACtB,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAA;QAC3B,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;IAE5B,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;IAEnF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AAC5C,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { AnalyticsEventsLogPage, AnalyticsPeriod, AnalyticsSortDir } from '../types';
2
+ type SortColumn = 'ts' | 'event' | 'path' | 'referrer' | 'country';
3
+ export interface UseEventsLogOptions {
4
+ period: AnalyticsPeriod;
5
+ sortBy?: SortColumn;
6
+ sortDir?: AnalyticsSortDir;
7
+ hideHeartbeats?: boolean;
8
+ limit?: number;
9
+ }
10
+ export interface UseEventsLogReturn {
11
+ page: AnalyticsEventsLogPage | null;
12
+ isLoading: boolean;
13
+ error: string | null;
14
+ /** Cursor for the page currently displayed. null = first page. */
15
+ cursor: string | null;
16
+ /** Advance to next page (does nothing when nextCursor is null). */
17
+ nextPage: () => void;
18
+ /** Reset to first page. */
19
+ firstPage: () => void;
20
+ refresh: () => Promise<void>;
21
+ }
22
+ export declare function useEventsLog(options: UseEventsLogOptions): UseEventsLogReturn;
23
+ export {};
24
+ //# sourceMappingURL=useEventsLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEventsLog.d.ts","sourceRoot":"","sources":["../../../src/analytics/hooks/useEventsLog.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EACjB,MAAM,UAAU,CAAA;AAEjB,KAAK,UAAU,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;AAElE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,eAAe,CAAA;IACvB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAA;IACnC,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,kEAAkE;IAClE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAqFpB"}
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+ /**
3
+ * @rovela-ai/sdk/analytics/hooks/useEventsLog
4
+ *
5
+ * Admin Events tab hook — cursor-paginated event log with sort + heartbeat
6
+ * toggle. AbortController-managed: param changes cancel the prior request.
7
+ *
8
+ * Shape mirrors useAnalyticsDashboard for consistency. Returns the page +
9
+ * helpers to advance pagination and replace filters.
10
+ */
11
+ import { useCallback, useEffect, useRef, useState } from 'react';
12
+ import { fetchAdminApi } from '../../admin/hooks/fetchAdminApi';
13
+ export function useEventsLog(options) {
14
+ const [page, setPage] = useState(null);
15
+ const [error, setError] = useState(null);
16
+ const [isLoading, setIsLoading] = useState(true);
17
+ const [cursor, setCursor] = useState(null);
18
+ const abortRef = useRef(null);
19
+ const fetchPage = useCallback(async (opts, c) => {
20
+ abortRef.current?.abort();
21
+ const controller = new AbortController();
22
+ abortRef.current = controller;
23
+ setIsLoading(true);
24
+ const params = new URLSearchParams();
25
+ params.set('period', opts.period);
26
+ if (opts.sortBy)
27
+ params.set('sortBy', opts.sortBy);
28
+ if (opts.sortDir)
29
+ params.set('sortDir', opts.sortDir);
30
+ params.set('hideHeartbeats', opts.hideHeartbeats === false ? 'false' : 'true');
31
+ if (opts.limit)
32
+ params.set('limit', String(opts.limit));
33
+ if (c)
34
+ params.set('cursor', c);
35
+ const res = await fetchAdminApi(`/api/admin/analytics/events?${params.toString()}`, { signal: controller.signal });
36
+ if (controller.signal.aborted)
37
+ return;
38
+ setIsLoading(false);
39
+ if (!res.ok) {
40
+ setError(res.error);
41
+ return;
42
+ }
43
+ setError(null);
44
+ setPage(res.data);
45
+ }, []);
46
+ // Reset cursor when filters change (any options change → back to page 1).
47
+ // Pattern C — populate state from async-loaded data.
48
+ useEffect(() => {
49
+ // eslint-disable-next-line react-hooks/set-state-in-effect
50
+ setCursor(null);
51
+ }, [
52
+ options.period,
53
+ options.sortBy,
54
+ options.sortDir,
55
+ options.hideHeartbeats,
56
+ options.limit,
57
+ ]);
58
+ useEffect(() => {
59
+ // eslint-disable-next-line react-hooks/set-state-in-effect
60
+ fetchPage(options, cursor);
61
+ return () => abortRef.current?.abort();
62
+ }, [
63
+ options.period,
64
+ options.sortBy,
65
+ options.sortDir,
66
+ options.hideHeartbeats,
67
+ options.limit,
68
+ cursor,
69
+ fetchPage,
70
+ options,
71
+ ]);
72
+ const nextCursorVal = page?.nextCursor ?? null;
73
+ const nextPage = useCallback(() => {
74
+ if (nextCursorVal)
75
+ setCursor(nextCursorVal);
76
+ }, [nextCursorVal]);
77
+ const firstPage = useCallback(() => setCursor(null), []);
78
+ const refresh = useCallback(() => fetchPage(options, cursor), [options, cursor, fetchPage]);
79
+ return { page, isLoading, error, cursor, nextPage, firstPage, refresh };
80
+ }
81
+ //# sourceMappingURL=useEventsLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEventsLog.js","sourceRoot":"","sources":["../../../src/analytics/hooks/useEventsLog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { AnalyticsPeriod, AnalyticsSortDir, AnalyticsVisitorsPage } from '../types';
2
+ type SortColumn = 'last_seen' | 'first_seen' | 'sessions' | 'pageviews' | 'country';
3
+ export interface UseVisitorsListOptions {
4
+ period: AnalyticsPeriod;
5
+ sortBy?: SortColumn;
6
+ sortDir?: AnalyticsSortDir;
7
+ limit?: number;
8
+ }
9
+ export interface UseVisitorsListReturn {
10
+ page: AnalyticsVisitorsPage | null;
11
+ isLoading: boolean;
12
+ error: string | null;
13
+ cursor: string | null;
14
+ nextPage: () => void;
15
+ firstPage: () => void;
16
+ refresh: () => Promise<void>;
17
+ }
18
+ export declare function useVisitorsList(options: UseVisitorsListOptions): UseVisitorsListReturn;
19
+ export {};
20
+ //# sourceMappingURL=useVisitorsList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVisitorsList.d.ts","sourceRoot":"","sources":["../../../src/analytics/hooks/useVisitorsList.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,UAAU,CAAA;AAEjB,KAAK,UAAU,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAA;AAEnF,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,CAAA;IACvB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,OAAO,CAAC,EAAE,gBAAgB,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAAA;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAsEvB"}
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+ /**
3
+ * @rovela-ai/sdk/analytics/hooks/useVisitorsList
4
+ *
5
+ * Admin Visitors tab hook — offset-paginated per-visitor aggregate with sort.
6
+ * Mirrors useEventsLog shape.
7
+ */
8
+ import { useCallback, useEffect, useRef, useState } from 'react';
9
+ import { fetchAdminApi } from '../../admin/hooks/fetchAdminApi';
10
+ export function useVisitorsList(options) {
11
+ const [page, setPage] = useState(null);
12
+ const [error, setError] = useState(null);
13
+ const [isLoading, setIsLoading] = useState(true);
14
+ const [cursor, setCursor] = useState(null);
15
+ const abortRef = useRef(null);
16
+ const fetchPage = useCallback(async (opts, c) => {
17
+ abortRef.current?.abort();
18
+ const controller = new AbortController();
19
+ abortRef.current = controller;
20
+ setIsLoading(true);
21
+ const params = new URLSearchParams();
22
+ params.set('period', opts.period);
23
+ if (opts.sortBy)
24
+ params.set('sortBy', opts.sortBy);
25
+ if (opts.sortDir)
26
+ params.set('sortDir', opts.sortDir);
27
+ if (opts.limit)
28
+ params.set('limit', String(opts.limit));
29
+ if (c)
30
+ params.set('cursor', c);
31
+ const res = await fetchAdminApi(`/api/admin/analytics/visitors?${params.toString()}`, { signal: controller.signal });
32
+ if (controller.signal.aborted)
33
+ return;
34
+ setIsLoading(false);
35
+ if (!res.ok) {
36
+ setError(res.error);
37
+ return;
38
+ }
39
+ setError(null);
40
+ setPage(res.data);
41
+ }, []);
42
+ // Pattern C — populate state from async-loaded data.
43
+ useEffect(() => {
44
+ // eslint-disable-next-line react-hooks/set-state-in-effect
45
+ setCursor(null);
46
+ }, [options.period, options.sortBy, options.sortDir, options.limit]);
47
+ useEffect(() => {
48
+ // eslint-disable-next-line react-hooks/set-state-in-effect
49
+ fetchPage(options, cursor);
50
+ return () => abortRef.current?.abort();
51
+ }, [
52
+ options.period,
53
+ options.sortBy,
54
+ options.sortDir,
55
+ options.limit,
56
+ cursor,
57
+ fetchPage,
58
+ options,
59
+ ]);
60
+ const nextCursorVal = page?.nextCursor ?? null;
61
+ const nextPage = useCallback(() => {
62
+ if (nextCursorVal)
63
+ setCursor(nextCursorVal);
64
+ }, [nextCursorVal]);
65
+ const firstPage = useCallback(() => setCursor(null), []);
66
+ const refresh = useCallback(() => fetchPage(options, cursor), [options, cursor, fetchPage]);
67
+ return { page, isLoading, error, cursor, nextPage, firstPage, refresh };
68
+ }
69
+ //# sourceMappingURL=useVisitorsList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useVisitorsList.js","sourceRoot":"","sources":["../../../src/analytics/hooks/useVisitorsList.ts"],"names":[],"mappings":"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"}