ahoy_analytics 0.1.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 (198) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +163 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/ahoy_analytics/build/assets/Combination-BpSXUjp9.js +41 -0
  6. data/app/assets/ahoy_analytics/build/assets/analytics-5KyfCxh6.css +1 -0
  7. data/app/assets/ahoy_analytics/build/assets/analytics-dashboard-uOXx8zYZ.js +1 -0
  8. data/app/assets/ahoy_analytics/build/assets/analytics-layout-ClAft5OU.js +1 -0
  9. data/app/assets/ahoy_analytics/build/assets/analytics-tracker-B3f8P98z.js +1 -0
  10. data/app/assets/ahoy_analytics/build/assets/analytics-ui-DMSkNqd6.js +90 -0
  11. data/app/assets/ahoy_analytics/build/assets/behaviors-panel-ChNGYbdH.js +1 -0
  12. data/app/assets/ahoy_analytics/build/assets/button-JVCrlR4s.js +1 -0
  13. data/app/assets/ahoy_analytics/build/assets/cable-DO-7y1-E.js +1 -0
  14. data/app/assets/ahoy_analytics/build/assets/createLucideIcon-BGzacY2v.js +1 -0
  15. data/app/assets/ahoy_analytics/build/assets/date-range-dialog-DWDp3cLG.js +1 -0
  16. data/app/assets/ahoy_analytics/build/assets/details-button-NqKfSGEG.js +1 -0
  17. data/app/assets/ahoy_analytics/build/assets/devices-panel-cXvlmNBY.js +1 -0
  18. data/app/assets/ahoy_analytics/build/assets/dialog-path-BBPNlB4Z.js +1 -0
  19. data/app/assets/ahoy_analytics/build/assets/dropdown-menu-Adj3O5fh.js +1 -0
  20. data/app/assets/ahoy_analytics/build/assets/filter-dialog-BN-rf4lp.js +1 -0
  21. data/app/assets/ahoy_analytics/build/assets/index-B1K1NTKT.js +3 -0
  22. data/app/assets/ahoy_analytics/build/assets/index-BcHeb-Rh.js +1 -0
  23. data/app/assets/ahoy_analytics/build/assets/index-DzpzLoG4.js +1 -0
  24. data/app/assets/ahoy_analytics/build/assets/index-vX97OY1J.js +1 -0
  25. data/app/assets/ahoy_analytics/build/assets/input-e4v_v0kE.js +1 -0
  26. data/app/assets/ahoy_analytics/build/assets/jsx-runtime-u17CrQMm.js +1 -0
  27. data/app/assets/ahoy_analytics/build/assets/last-load-context-De5uA95L.js +1 -0
  28. data/app/assets/ahoy_analytics/build/assets/list-table-ChHEzzF9.js +1 -0
  29. data/app/assets/ahoy_analytics/build/assets/live-Cp2MHECh.js +2 -0
  30. data/app/assets/ahoy_analytics/build/assets/locations-panel-BaISRmaQ.js +1 -0
  31. data/app/assets/ahoy_analytics/build/assets/mercator-BnxX5RzL.js +1 -0
  32. data/app/assets/ahoy_analytics/build/assets/pages-panel-Bh25L8mP.js +1 -0
  33. data/app/assets/ahoy_analytics/build/assets/panel-tabs-B2kvGFJx.js +1 -0
  34. data/app/assets/ahoy_analytics/build/assets/query-context-B-PgE00D.js +1 -0
  35. data/app/assets/ahoy_analytics/build/assets/remote-details-dialog-DDTcKaM5.js +1 -0
  36. data/app/assets/ahoy_analytics/build/assets/show-CCRicksg.js +1 -0
  37. data/app/assets/ahoy_analytics/build/assets/simple-tabs-D6G6Bs0k.js +1 -0
  38. data/app/assets/ahoy_analytics/build/assets/site-context-BNteYRlR.js +1 -0
  39. data/app/assets/ahoy_analytics/build/assets/sources-panel-DyB21hxD.js +1 -0
  40. data/app/assets/ahoy_analytics/build/assets/top-bar-FSiLBjq6.js +1 -0
  41. data/app/assets/ahoy_analytics/build/assets/top-stats-context-DU15P9jS.js +1 -0
  42. data/app/assets/ahoy_analytics/build/assets/use-debounce-VBpXQRL8.js +1 -0
  43. data/app/assets/ahoy_analytics/build/assets/user-context-DbYteluY.js +1 -0
  44. data/app/assets/ahoy_analytics/build/assets/visitor-globe-BWLDihid.js +4789 -0
  45. data/app/assets/ahoy_analytics/build/assets/visitor-graph-uKXjLvcu.js +1 -0
  46. data/app/assets/ahoy_analytics/images/icon/browser/brave.svg +1 -0
  47. data/app/assets/ahoy_analytics/images/icon/browser/chrome.svg +1 -0
  48. data/app/assets/ahoy_analytics/images/icon/browser/chromium.svg +1 -0
  49. data/app/assets/ahoy_analytics/images/icon/browser/duckduckgo.svg +2151 -0
  50. data/app/assets/ahoy_analytics/images/icon/browser/edge.svg +1 -0
  51. data/app/assets/ahoy_analytics/images/icon/browser/fallback.svg +5 -0
  52. data/app/assets/ahoy_analytics/images/icon/browser/firefox.svg +1 -0
  53. data/app/assets/ahoy_analytics/images/icon/browser/opera.svg +1 -0
  54. data/app/assets/ahoy_analytics/images/icon/browser/safari.png +0 -0
  55. data/app/assets/ahoy_analytics/images/icon/browser/samsung-internet.svg +1 -0
  56. data/app/assets/ahoy_analytics/images/icon/browser/uc.svg +1 -0
  57. data/app/assets/ahoy_analytics/images/icon/browser/vivaldi.svg +1 -0
  58. data/app/assets/ahoy_analytics/images/icon/browser/yandex.png +0 -0
  59. data/app/assets/ahoy_analytics/images/icon/os/android.png +0 -0
  60. data/app/assets/ahoy_analytics/images/icon/os/chrome_os.png +0 -0
  61. data/app/assets/ahoy_analytics/images/icon/os/fallback.svg +5 -0
  62. data/app/assets/ahoy_analytics/images/icon/os/fedora.png +0 -0
  63. data/app/assets/ahoy_analytics/images/icon/os/freebsd.png +0 -0
  64. data/app/assets/ahoy_analytics/images/icon/os/gnu_linux.png +0 -0
  65. data/app/assets/ahoy_analytics/images/icon/os/ios.png +0 -0
  66. data/app/assets/ahoy_analytics/images/icon/os/ipad_os.png +0 -0
  67. data/app/assets/ahoy_analytics/images/icon/os/mac.png +0 -0
  68. data/app/assets/ahoy_analytics/images/icon/os/ubuntu.png +0 -0
  69. data/app/assets/ahoy_analytics/images/icon/os/windows.png +0 -0
  70. data/app/assets/stylesheets/ahoy_analytics/application.css +15 -0
  71. data/app/channels/ahoy_analytics/analytics_channel.rb +9 -0
  72. data/app/controllers/ahoy_analytics/analytics_controller.rb +9 -0
  73. data/app/controllers/ahoy_analytics/application_controller.rb +8 -0
  74. data/app/controllers/ahoy_analytics/assets_controller.rb +46 -0
  75. data/app/controllers/ahoy_analytics/base_controller.rb +285 -0
  76. data/app/controllers/ahoy_analytics/behaviors_controller.rb +14 -0
  77. data/app/controllers/ahoy_analytics/devices_controller.rb +14 -0
  78. data/app/controllers/ahoy_analytics/export_controller.rb +12 -0
  79. data/app/controllers/ahoy_analytics/live_controller.rb +9 -0
  80. data/app/controllers/ahoy_analytics/locations_controller.rb +14 -0
  81. data/app/controllers/ahoy_analytics/main_graph_controller.rb +10 -0
  82. data/app/controllers/ahoy_analytics/pages_controller.rb +14 -0
  83. data/app/controllers/ahoy_analytics/referrers_controller.rb +34 -0
  84. data/app/controllers/ahoy_analytics/search_terms_controller.rb +47 -0
  85. data/app/controllers/ahoy_analytics/sources_controller.rb +14 -0
  86. data/app/controllers/ahoy_analytics/top_stats_controller.rb +13 -0
  87. data/app/controllers/concerns/ahoy_analytics/set_current_request.rb +17 -0
  88. data/app/frontend/components/analytics/hex-highlights.tsx +165 -0
  89. data/app/frontend/components/analytics/hex-land-layer.tsx +61 -0
  90. data/app/frontend/components/analytics/metric-card.tsx +138 -0
  91. data/app/frontend/components/analytics/sessions-by-location.tsx +62 -0
  92. data/app/frontend/components/analytics/visitor-globe.tsx +424 -0
  93. data/app/frontend/components/ui/accordion.tsx +64 -0
  94. data/app/frontend/components/ui/alert.tsx +66 -0
  95. data/app/frontend/components/ui/avatar.tsx +53 -0
  96. data/app/frontend/components/ui/badge.tsx +46 -0
  97. data/app/frontend/components/ui/button.tsx +62 -0
  98. data/app/frontend/components/ui/calendar.tsx +212 -0
  99. data/app/frontend/components/ui/card.tsx +91 -0
  100. data/app/frontend/components/ui/checkbox.tsx +32 -0
  101. data/app/frontend/components/ui/dropdown-menu.tsx +255 -0
  102. data/app/frontend/components/ui/input.tsx +21 -0
  103. data/app/frontend/components/ui/label.tsx +22 -0
  104. data/app/frontend/components/ui/popover.tsx +46 -0
  105. data/app/frontend/components/ui/select.tsx +183 -0
  106. data/app/frontend/components/ui/separator.tsx +26 -0
  107. data/app/frontend/components/ui/sheet.tsx +139 -0
  108. data/app/frontend/components/ui/sidebar.tsx +726 -0
  109. data/app/frontend/components/ui/skeleton.tsx +13 -0
  110. data/app/frontend/components/ui/sonner.tsx +33 -0
  111. data/app/frontend/components/ui/tooltip.tsx +59 -0
  112. data/app/frontend/data/countries-110m.json +1 -0
  113. data/app/frontend/data/globe-data.json +1 -0
  114. data/app/frontend/entrypoints/analytics-tracker.ts +680 -0
  115. data/app/frontend/entrypoints/analytics-ui.tsx +26 -0
  116. data/app/frontend/entrypoints/analytics.css +77 -0
  117. data/app/frontend/layouts/analytics-layout.tsx +28 -0
  118. data/app/frontend/lib/cable.ts +13 -0
  119. data/app/frontend/lib/geocode.ts +65 -0
  120. data/app/frontend/lib/utils.ts +6 -0
  121. data/app/frontend/pages/admin/analytics/api.ts +221 -0
  122. data/app/frontend/pages/admin/analytics/hooks/use-debounce.ts +36 -0
  123. data/app/frontend/pages/admin/analytics/last-load-context.tsx +29 -0
  124. data/app/frontend/pages/admin/analytics/lib/base-path.ts +28 -0
  125. data/app/frontend/pages/admin/analytics/lib/dialog-path.ts +242 -0
  126. data/app/frontend/pages/admin/analytics/lib/number-formatter.ts +100 -0
  127. data/app/frontend/pages/admin/analytics/live.tsx +608 -0
  128. data/app/frontend/pages/admin/analytics/query-context.tsx +61 -0
  129. data/app/frontend/pages/admin/analytics/show.tsx +40 -0
  130. data/app/frontend/pages/admin/analytics/site-context.tsx +22 -0
  131. data/app/frontend/pages/admin/analytics/top-stats-context.tsx +37 -0
  132. data/app/frontend/pages/admin/analytics/types.ts +161 -0
  133. data/app/frontend/pages/admin/analytics/ui/analytics-dashboard.tsx +60 -0
  134. data/app/frontend/pages/admin/analytics/ui/behaviors-panel.tsx +456 -0
  135. data/app/frontend/pages/admin/analytics/ui/date-range-dialog.tsx +173 -0
  136. data/app/frontend/pages/admin/analytics/ui/details-button.tsx +33 -0
  137. data/app/frontend/pages/admin/analytics/ui/devices-panel.tsx +474 -0
  138. data/app/frontend/pages/admin/analytics/ui/filter-dialog.tsx +558 -0
  139. data/app/frontend/pages/admin/analytics/ui/list-table.tsx +346 -0
  140. data/app/frontend/pages/admin/analytics/ui/locations-panel.tsx +566 -0
  141. data/app/frontend/pages/admin/analytics/ui/pages-panel.tsx +207 -0
  142. data/app/frontend/pages/admin/analytics/ui/panel-tabs.tsx +65 -0
  143. data/app/frontend/pages/admin/analytics/ui/remote-details-dialog.tsx +356 -0
  144. data/app/frontend/pages/admin/analytics/ui/simple-tabs.tsx +54 -0
  145. data/app/frontend/pages/admin/analytics/ui/sources-panel.tsx +771 -0
  146. data/app/frontend/pages/admin/analytics/ui/top-bar.tsx +793 -0
  147. data/app/frontend/pages/admin/analytics/ui/visitor-graph.tsx +891 -0
  148. data/app/frontend/pages/admin/analytics/user-context.tsx +22 -0
  149. data/app/frontend/styles/shared.css +156 -0
  150. data/app/helpers/ahoy_analytics/application_helper.rb +96 -0
  151. data/app/jobs/ahoy_analytics/application_job.rb +4 -0
  152. data/app/jobs/ahoy_analytics/update_job.rb +12 -0
  153. data/app/mailers/ahoy_analytics/application_mailer.rb +6 -0
  154. data/app/models/ahoy/event/filters.rb +7 -0
  155. data/app/models/ahoy/event.rb +9 -0
  156. data/app/models/ahoy/visit/cache_key.rb +15 -0
  157. data/app/models/ahoy/visit/constants.rb +11 -0
  158. data/app/models/ahoy/visit/devices.rb +144 -0
  159. data/app/models/ahoy/visit/export.rb +24 -0
  160. data/app/models/ahoy/visit/filters.rb +286 -0
  161. data/app/models/ahoy/visit/imports.rb +36 -0
  162. data/app/models/ahoy/visit/locations.rb +276 -0
  163. data/app/models/ahoy/visit/metrics.rb +473 -0
  164. data/app/models/ahoy/visit/ordering.rb +110 -0
  165. data/app/models/ahoy/visit/pages.rb +533 -0
  166. data/app/models/ahoy/visit/pagination.rb +17 -0
  167. data/app/models/ahoy/visit/ranges.rb +227 -0
  168. data/app/models/ahoy/visit/series.rb +177 -0
  169. data/app/models/ahoy/visit/sources.rb +418 -0
  170. data/app/models/ahoy/visit/url_labels.rb +32 -0
  171. data/app/models/ahoy/visit.rb +143 -0
  172. data/app/models/ahoy_analytics/application_record.rb +5 -0
  173. data/app/models/ahoy_analytics/current.rb +8 -0
  174. data/app/models/ahoy_analytics/funnel.rb +16 -0
  175. data/app/models/ahoy_analytics/imported_entry_page.rb +5 -0
  176. data/app/models/ahoy_analytics/imported_exit_page.rb +5 -0
  177. data/app/models/ahoy_analytics/imported_page.rb +5 -0
  178. data/app/models/ahoy_analytics/live_stats.rb +152 -0
  179. data/app/models/ahoy_analytics/setting.rb +19 -0
  180. data/app/models/analytics/source_catalog.rb +48 -0
  181. data/app/views/layouts/ahoy_analytics/application.html.erb +15 -0
  182. data/config/routes.rb +21 -0
  183. data/config/vite.json +22 -0
  184. data/db/migrate/20251006104056_create_ahoy_visits_and_events.rb +62 -0
  185. data/db/migrate/20251006105012_add_analytics_fields_to_ahoy_visits.rb +11 -0
  186. data/db/migrate/20251012090000_create_analytics_funnels_and_imports.rb +52 -0
  187. data/db/migrate/20251013021500_add_analytics_indexes.rb +14 -0
  188. data/lib/ahoy_analytics/ahoy_store.rb +429 -0
  189. data/lib/ahoy_analytics/asset_manifest.rb +56 -0
  190. data/lib/ahoy_analytics/device_bucket.rb +39 -0
  191. data/lib/ahoy_analytics/engine.rb +55 -0
  192. data/lib/ahoy_analytics/maxmind_geo.rb +77 -0
  193. data/lib/ahoy_analytics/version.rb +3 -0
  194. data/lib/ahoy_analytics.rb +52 -0
  195. data/lib/generators/ahoy_analytics/install/install_generator.rb +111 -0
  196. data/lib/generators/ahoy_analytics/install/templates/initializer.rb +28 -0
  197. data/lib/tasks/ahoy_analytics_tasks.rake +4 -0
  198. metadata +352 -0
@@ -0,0 +1,40 @@
1
+ import { Head } from '@inertiajs/react'
2
+ import AnalyticsLayout from '@/layouts/analytics-layout'
3
+
4
+ import { LastLoadProvider } from './last-load-context'
5
+ import AnalyticsDashboard from './ui/analytics-dashboard'
6
+ import { QueryProvider } from './query-context'
7
+ import { SiteProvider } from './site-context'
8
+ import { UserProvider } from './user-context'
9
+ import type { AnalyticsPageProps } from './types'
10
+
11
+ export default function AnalyticsShow(props: AnalyticsPageProps) {
12
+ const { site, user, query, topStats, mainGraph, sources, pages, locations, devices, behaviors } = props
13
+
14
+ return (
15
+ <AnalyticsLayout>
16
+ <Head title="Analytics Overview" />
17
+
18
+ <SiteProvider value={site}>
19
+ <UserProvider value={user}>
20
+ <QueryProvider initialQuery={query}>
21
+ <LastLoadProvider>
22
+ <AnalyticsDashboard
23
+ initialTopStats={topStats}
24
+ initialGraph={mainGraph}
25
+ initialSources={sources}
26
+ initialPages={pages}
27
+ initialLocations={locations}
28
+ initialDevices={devices}
29
+ initialBehaviors={behaviors}
30
+ />
31
+ <div className="mt-10 border-t border-white/12 pt-4 text-xs text-muted-foreground/80">
32
+ This product includes GeoLite2 data created by MaxMind, available from maxmind.com.
33
+ </div>
34
+ </LastLoadProvider>
35
+ </QueryProvider>
36
+ </UserProvider>
37
+ </SiteProvider>
38
+ </AnalyticsLayout>
39
+ )
40
+ }
@@ -0,0 +1,22 @@
1
+ import { createContext, useContext, type ReactNode } from 'react'
2
+ import type { SiteContextValue } from './types'
3
+
4
+ const SiteContext = createContext<SiteContextValue | null>(null)
5
+
6
+ export function SiteProvider({
7
+ value,
8
+ children
9
+ }: {
10
+ value: SiteContextValue
11
+ children: ReactNode
12
+ }) {
13
+ return <SiteContext.Provider value={value}>{children}</SiteContext.Provider>
14
+ }
15
+
16
+ export function useSiteContext() {
17
+ const context = useContext(SiteContext)
18
+ if (!context) {
19
+ throw new Error('useSiteContext must be used within a SiteProvider')
20
+ }
21
+ return context
22
+ }
@@ -0,0 +1,37 @@
1
+ import { createContext, useContext, useMemo, useState, type ReactNode, type Dispatch, type SetStateAction } from 'react'
2
+ import type { TopStatsPayload } from './types'
3
+
4
+ export type TopStatsContextValue = {
5
+ payload: TopStatsPayload
6
+ update: Dispatch<SetStateAction<TopStatsPayload>>
7
+ }
8
+
9
+ const TopStatsContext = createContext<TopStatsContextValue | null>(null)
10
+
11
+ export function TopStatsProvider({
12
+ initial,
13
+ children
14
+ }: {
15
+ initial: TopStatsPayload
16
+ children: ReactNode
17
+ }) {
18
+ const [payload, setPayload] = useState<TopStatsPayload>(initial)
19
+
20
+ const value = useMemo(
21
+ () => ({
22
+ payload,
23
+ update: setPayload
24
+ }),
25
+ [payload]
26
+ )
27
+
28
+ return <TopStatsContext.Provider value={value}>{children}</TopStatsContext.Provider>
29
+ }
30
+
31
+ export function useTopStatsContext() {
32
+ const context = useContext(TopStatsContext)
33
+ if (!context) {
34
+ throw new Error('useTopStatsContext must be used within a TopStatsProvider')
35
+ }
36
+ return context
37
+ }
@@ -0,0 +1,161 @@
1
+ export type AnalyticsQuery = {
2
+ period: 'realtime' | 'day' | '7d' | '28d' | '30d' | '91d' | 'month' | 'year' | '12mo' | 'all' | 'custom'
3
+ comparison?: 'previous_period' | 'year_over_year' | 'custom' | null
4
+ filters: Record<string, string>
5
+ // Optional human-readable labels for some filter keys (country/region/city)
6
+ labels?: Record<string, string>
7
+ // Advanced filters encoded as Plausible-style tuples, serialized into repeated f= entries.
8
+ // Each entry: [operation, dimension, clause]
9
+ // Supported operations client-side: 'is_not', 'contains'.
10
+ advancedFilters?: Array<[string, string, string]>
11
+ withImported: boolean
12
+ metric?: string
13
+ interval?: string
14
+ mode?: string
15
+ funnel?: string
16
+ // Date helpers for month/year/custom periods
17
+ date?: string | null
18
+ from?: string | null
19
+ to?: string | null
20
+ // Compare helpers
21
+ compareFrom?: string | null
22
+ compareTo?: string | null
23
+ matchDayOfWeek?: boolean
24
+ // Optional UI-only hint so dialogs can deep-link and restore open state
25
+ dialog?: string
26
+ }
27
+
28
+ export type SiteContextValue = {
29
+ domain: string
30
+ timezone: string
31
+ hasGoals: boolean
32
+ hasProps: boolean
33
+ funnelsAvailable: boolean
34
+ propsAvailable: boolean
35
+ segments: Array<{ id: string; name: string }>
36
+ flags: { dbip: boolean }
37
+ }
38
+
39
+ export type UserContextValue = {
40
+ role: 'super_admin' | 'owner' | 'admin' | 'editor' | 'viewer'
41
+ email: string
42
+ }
43
+
44
+ export type TopStat = {
45
+ name: string
46
+ value: number
47
+ graphMetric?: string
48
+ change?: number | null
49
+ comparisonValue?: number | null
50
+ }
51
+
52
+ export type TopStatsPayload = {
53
+ topStats: TopStat[]
54
+ graphableMetrics: string[]
55
+ meta: {
56
+ metricWarnings: Record<string, { code: string; message?: string }>
57
+ importsIncluded: boolean
58
+ }
59
+ interval: string
60
+ includesImported: boolean
61
+ withImportedSwitch: {
62
+ visible: boolean
63
+ togglable: boolean
64
+ tooltipMsg: string | null
65
+ }
66
+ samplePercent: number
67
+ from: string
68
+ to: string
69
+ comparingFrom?: string | null
70
+ comparingTo?: string | null
71
+ }
72
+
73
+ export type MainGraphPayload = {
74
+ metric: string
75
+ plot: number[]
76
+ labels: string[]
77
+ comparisonPlot?: number[] | null
78
+ comparisonLabels?: string[] | null
79
+ presentIndex?: number | null
80
+ interval: string
81
+ fullIntervals?: Record<string, boolean> | null
82
+ }
83
+
84
+ export type ListMetricKey =
85
+ | 'visitors'
86
+ | 'visits'
87
+ | 'percentage'
88
+ | 'uniques'
89
+ | 'total'
90
+ | 'conversionRate'
91
+ | 'exitRate'
92
+ | 'bounceRate'
93
+ | 'visitDuration'
94
+ | 'scrollDepth'
95
+ | 'timeOnPage'
96
+ | 'pageviews'
97
+ // Google Search Console style metrics (used by Search Terms dialog)
98
+ | 'impressions'
99
+ | 'ctr'
100
+ | 'position'
101
+
102
+ export type ListItem = Record<string, string | number | null | undefined> & {
103
+ name: string
104
+ }
105
+
106
+ export type ListPayload = {
107
+ results: ListItem[]
108
+ metrics: ListMetricKey[]
109
+ meta: {
110
+ hasMore: boolean
111
+ skipImportedReason: string | null
112
+ metricLabels?: Record<string, string>
113
+ }
114
+ }
115
+
116
+ export type MapPayload = {
117
+ map: {
118
+ results: Array<{
119
+ alpha3: string
120
+ alpha2?: string
121
+ numeric?: string
122
+ code?: string
123
+ name: string
124
+ visitors: number
125
+ }>
126
+ meta: Record<string, unknown>
127
+ }
128
+ }
129
+
130
+ export type DevicesPayload = ListPayload
131
+
132
+ export type BehaviorsPayload =
133
+ | {
134
+ list: ListPayload
135
+ goalHighlighted?: string | null
136
+ }
137
+ | {
138
+ funnels: string[]
139
+ active: {
140
+ name: string
141
+ steps: Array<{
142
+ name: string
143
+ visitors: number
144
+ conversionRate: number
145
+ }>
146
+ }
147
+ }
148
+ | ListPayload
149
+
150
+ export type AnalyticsPageProps = {
151
+ site: SiteContextValue
152
+ user: UserContextValue
153
+ query: AnalyticsQuery
154
+ topStats: TopStatsPayload
155
+ mainGraph: MainGraphPayload
156
+ sources: ListPayload
157
+ pages: ListPayload
158
+ locations: MapPayload | ListPayload
159
+ devices: DevicesPayload
160
+ behaviors: BehaviorsPayload
161
+ }
@@ -0,0 +1,60 @@
1
+ import { useMemo } from 'react'
2
+ import { useQueryContext } from '../query-context'
3
+ import type {
4
+ BehaviorsPayload,
5
+ DevicesPayload,
6
+ ListPayload,
7
+ MainGraphPayload,
8
+ MapPayload,
9
+ TopStatsPayload
10
+ } from '../types'
11
+
12
+ import { TopStatsProvider } from '../top-stats-context'
13
+ import TopBar from './top-bar'
14
+ import VisitorGraph from './visitor-graph'
15
+ import SourcesPanel from './sources-panel'
16
+ import PagesPanel from './pages-panel'
17
+ import LocationsPanel from './locations-panel'
18
+ import DevicesPanel from './devices-panel'
19
+ import BehaviorsPanel from './behaviors-panel'
20
+ import { useSiteContext } from '../site-context'
21
+
22
+ export type AnalyticsDashboardProps = {
23
+ initialTopStats: TopStatsPayload
24
+ initialGraph: MainGraphPayload
25
+ initialSources: ListPayload
26
+ initialPages: ListPayload
27
+ initialLocations: MapPayload | ListPayload
28
+ initialDevices: DevicesPayload
29
+ initialBehaviors: BehaviorsPayload
30
+ }
31
+
32
+ export default function AnalyticsDashboard(props: AnalyticsDashboardProps) {
33
+ const { query } = useQueryContext()
34
+ const site = useSiteContext()
35
+ const isRealtime = useMemo(() => query.period === 'realtime', [query.period])
36
+
37
+ return (
38
+ <TopStatsProvider initial={props.initialTopStats}>
39
+ <div className="flex flex-col gap-6 pb-16">
40
+ <TopBar showCurrentVisitors={!isRealtime} />
41
+
42
+ <VisitorGraph
43
+ initialGraph={props.initialGraph}
44
+ />
45
+
46
+ <section className="grid gap-6 lg:grid-cols-2">
47
+ <SourcesPanel initialData={props.initialSources} />
48
+ <PagesPanel initialData={props.initialPages} />
49
+ </section>
50
+
51
+ <section className="grid gap-6 lg:grid-cols-2">
52
+ <LocationsPanel initialData={props.initialLocations} />
53
+ <DevicesPanel initialData={props.initialDevices} />
54
+ </section>
55
+
56
+ {site.hasGoals ? <BehaviorsPanel initialData={props.initialBehaviors} /> : null}
57
+ </div>
58
+ </TopStatsProvider>
59
+ )
60
+ }