Measuring engagement and creating a report on user events
Author
james
Date Published

Step 1
Add the dependency. This bundles React chart
npm i @nouance/payload-dashboard-analytics
Step 2
Using google analytics and this git repo we add components to dashboard
draw a report on the traffic to the pages, that create the bookings the listing you created.
Case study comming
Users dont know the impact the listing has, so
Prototypes measure the journey creating a report on the user experience. Similar to Figma flow but less files size
draw a report on the traffic to the pages, that create the bookings the listing you created.


package.json
1 "@radix-ui/react-select": "^2.0.0",2 "@radix-ui/react-slot": "^1.1.1",3 "@revenuecat/purchases-js": "^0.18.2",4 "chart.js": "^4.4.9",5 "class-variance-authority": "^0.7.0",6 "clsx": "^2.1.1",7 "date-fns": "^4.1.0",8@@ -51,6 +52,7 @@9 "payload-admin-bar": "^1.0.6",10 "prism-react-renderer": "^2.3.1",11 "react": "^19.0.0",12 "react-chartjs-2": "^5.3.0",13 "react-day-picker": "^8.10.1",14 "react-dom": "^19.0.0",15 "react-hook-form": "7.45.4",
taken from google
1import { BetaAnalyticsDataClient } from '@google-analytics/data'23// Enhanced logging for initialization4console.log('=== GA4 Configuration Debug ===')5console.log('Environment:', process.env.NODE_ENV)6console.log('GA4 Property ID:', process.env.GA4_PROPERTY_ID)7console.log('Service Account JSON Length:', process.env.GOOGLE_SERVICE_ACCOUNT_JSON?.length || 0)89try {10 if (process.env.GOOGLE_SERVICE_ACCOUNT_JSON) {11 const serviceAccount = JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_JSON)12 console.log('Service Account Details:')13 console.log('- Project ID:', serviceAccount.project_id)14 console.log('- Client Email:', serviceAccount.client_email)15 console.log('- Private Key Length:', serviceAccount.private_key?.length || 0)16 } else {17 console.error('GOOGLE_SERVICE_ACCOUNT_JSON is empty or undefined')18 }19} catch (e) {20 console.error('Error parsing service account JSON:', e)21 console.error('Raw JSON:', process.env.GOOGLE_SERVICE_ACCOUNT_JSON)22}2324let analyticsDataClient: BetaAnalyticsDataClient | null = null2526// Initialize the analytics client27try {28 const serviceAccountJson = process.env.GOOGLE_SERVICE_ACCOUNT_JSON29 if (!serviceAccountJson) {30 console.error('GOOGLE_SERVICE_ACCOUNT_JSON environment variable is not set')31 } else {32 try {33 const credentials = JSON.parse(serviceAccountJson)34 analyticsDataClient = new BetaAnalyticsDataClient({35 credentials,36 })37 console.log('Analytics client initialized successfully')38 } catch (parseError) {39 console.error('Error parsing GOOGLE_SERVICE_ACCOUNT_JSON:', parseError)40 console.error('Service account JSON content:', serviceAccountJson)41 }42 }43} catch (e) {44 console.error('Error initializing analytics client:', e)45}4647export const getAnalyticsData = async () => {48 if (!analyticsDataClient) {49 console.error('Analytics client not initialized. Checking environment variables...')50 console.error('GA4_PROPERTY_ID:', process.env.GA4_PROPERTY_ID)51 console.error('GOOGLE_SERVICE_ACCOUNT_JSON exists:', !!process.env.GOOGLE_SERVICE_ACCOUNT_JSON)52 return {53 activeUsersNow: 0,54 total30DayUsers: 0,55 total30DayViews: 0,56 historicalData: [],57 }58 }5960 try {61 // Get realtime data62 console.log('Fetching realtime data...')63 console.log('Using property ID:', process.env.GA4_PROPERTY_ID)64 const realtimeResponse = await analyticsDataClient.runRealtimeReport({65 property: `properties/${process.env.GA4_PROPERTY_ID}`,66 dimensions: [{ name: 'minutesAgo' }],67 metrics: [{ name: 'activeUsers' }],68 })69 console.log('Raw realtime response:', JSON.stringify(realtimeResponse, null, 2))7071 // Get historical data72 console.log('Fetching historical data...')73 const historicalResponse = await analyticsDataClient.runReport({74 property: `properties/${process.env.GA4_PROPERTY_ID}`,75 dateRanges: [76 {77 startDate: '30daysAgo',78 endDate: 'today',79 },80 ],81 dimensions: [{ name: 'date' }],82 metrics: [{ name: 'totalUsers' }, { name: 'screenPageViews' }],83 })84 console.log('Raw historical response:', JSON.stringify(historicalResponse, null, 2))8586 // Process realtime data87 const activeUsersNow = realtimeResponse[0]?.rows?.[0]?.metricValues?.[0]?.value || '0'88 console.log('Processed activeUsersNow:', activeUsersNow)8990 // Process historical data91 const historicalData =92 historicalResponse[0]?.rows?.map((row) => {93 const processedRow = {94 date: row.dimensionValues?.[0]?.value || '',95 users: parseInt(row.metricValues?.[0]?.value || '0', 10),96 views: parseInt(row.metricValues?.[1]?.value || '0', 10),97 }98 console.log('Processing row:', processedRow)99 return processedRow100 }) || []101102 console.log('Processed historical data:', historicalData)103104 // Calculate totals105 const total30DayUsers = historicalData.reduce((sum: number, row) => sum + row.users, 0)106 const total30DayViews = historicalData.reduce((sum: number, row) => sum + row.views, 0)107108 console.log('Calculated totals:', {109 total30DayUsers,110 total30DayViews,111 })112113 return {114 activeUsersNow,115 total30DayUsers,116 total30DayViews,117 historicalData,118 }119 } catch (error) {120 console.error('Error in getAnalyticsData:', error)121 if (error instanceof Error) {122 console.error('Error details:', {123 message: error.message,124 stack: error.stack,125 })126 }127 return {128 activeUsersNow: 0,129 total30DayUsers: 0,130 total30DayViews: 0,131 historicalData: [],132 }133 }134}
|_ 📄 package.json
|_ 📁 src
| |_ 📁 AnalyticsUtilties
| | | |_ 📄 ga4.ts
| |_ 📁 app/(payload)/admin
| | | |_ 📄 importMap.js.ts
|_ 📁 componets
| |_ 📁 AnalyticsDashboard
| | |_ 📄 AnalyticsDashboard.tsx
| |_ 📄 GoogleAnalytics.tsx
|_ 📄 payload.config.ts
1'use client'2import React, { useEffect, useState } from 'react'3import { Line } from 'react-chartjs-2'4import {5 Chart as ChartJS,6 CategoryScale,7 LinearScale,8 PointElement,9 LineElement,10 Title,11 Tooltip,12 Legend,13} from 'chart.js'1415ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend)1617interface AnalyticsData {18 activeUsersNow?: number19 total30DayUsers?: number20 total30DayViews?: number21 historicalData?: Array<{22 date: string23 users: number24 views: number25 }>26}2728const AnalyticsDashboard: React.FC = () => {29 const [data, setData] = useState<AnalyticsData | null>(null)30 const [loading, setLoading] = useState(true)31 const [error, setError] = useState<string | null>(null)3233 // fallback data34 const fallbackData: AnalyticsData = {35 historicalData: [36 { date: '2025-03-15', users: 4, views: 5 },37 { date: '2025-03-16', users: 5, views: 6 },38 { date: '2025-03-17', users: 6, views: 7 },39 { date: '2025-03-18', users: 2, views: 3 },40 { date: '2025-03-19', users: 1, views: 2 },41 { date: '2025-03-20', users: 10, views: 15 },42 { date: '2025-03-21', users: 8, views: 12 },43 { date: '2025-03-22', users: 11, views: 13 },44 { date: '2025-03-23', users: 7, views: 9 },45 { date: '2025-03-24', users: 4, views: 5 },46 { date: '2025-03-25', users: 13, views: 15 },47 { date: '2025-03-26', users: 14, views: 16 },48 { date: '2025-03-27', users: 12, views: 14 },49 { date: '2025-03-28', users: 16, views: 18 },50 { date: '2025-03-29', users: 13, views: 15 },51 { date: '2025-03-30', users: 14, views: 17 },52 { date: '2025-03-31', users: 3, views: 4 },53 { date: '2025-04-01', users: 20, views: 24 },54 ],55 }5657 const fetchAnalytics = async () => {58 try {59 const response = await fetch('/api/analytics', {60 headers: { 'Content-Type': 'application/json' },61 })6263 if (!response.ok) {64 throw new Error(`HTTP error! status: ${response.status}`)65 }6667 return await response.json()68 } catch (err) {69 console.error('Fetch error:', err)70 throw err instanceof Error ? err : new Error('Failed to fetch analytics')71 }72 }7374 useEffect(() => {75 const loadData = async () => {76 try {77 const analyticsData = await fetchAnalytics()7879 // Check if the data is empty or invalid80 const isEmptyData =81 !analyticsData ||82 (analyticsData.activeUsersNow === 0 &&83 analyticsData.total30DayUsers === 0 &&84 (!analyticsData.historicalData || analyticsData.historicalData.length === 0))8586 setData(isEmptyData ? fallbackData : analyticsData)87 setError(null)88 } catch (err) {89 console.error('Error loading analytics:', err)90 setData(fallbackData) // Use fallback data on error91 setError(err instanceof Error ? err.message : 'Failed to fetch analytics')92 } finally {93 setLoading(false)94 }95 }9697 loadData()98 // const interval = setInterval(loadData, 120000)99 const interval = setInterval(loadData, 8000)100 return () => clearInterval(interval)101 }, [])102103 const Card = ({104 title,105 value,106 style = {},107 }: {108 title: string109 value: number110 style?: React.CSSProperties111 }) => {112 const [hovered, setHovered] = useState(false)113114 return (115 <div116 style={{117 padding: '1.5rem',118 backgroundColor: 'var(--theme-elevation-50)',119 borderRadius: '8px',120 boxShadow: hovered ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 6px rgba(0,0,0,0.08)',121 border: hovered ? '1px solid var(--theme-elevation-200)' : '1px solid transparent',122 transition: 'all 0.2s ease-in-out',123 cursor: 'pointer',124 display: 'flex',125 flexDirection: 'column',126 justifyContent: 'space-between',127 minHeight: '120px',128 ...style,129 }}130 onMouseEnter={() => setHovered(true)}131 onMouseLeave={() => setHovered(false)}132 >133 <h3 style={{ margin: 0, fontSize: '1rem', fontWeight: 600 }}>{title}</h3>134 <p style={{ fontSize: '2rem', fontWeight: 'bold', margin: '0.5rem 0 0' }}>{value}</p>135 </div>136 )137 }138139 // Determine which data to use (fallback if no data available)140 const displayData = data || fallbackData141 const chartData = displayData.historicalData || []142143 return (144 <div style={{ margin: '2rem 0', padding: '0 1rem' }}>145 <h2 style={{ fontSize: '1.8rem', marginBottom: '1rem' }}>📊 Google Analytics Overview</h2>146147 {loading && <div>Loading analytics data...</div>}148 {error && (149 <div150 style={{151 color: 'var(--theme-error-500)',152 backgroundColor: 'var(--theme-error-50)',153 padding: '1rem',154 borderRadius: '4px',155 marginBottom: '1rem',156 }}157 >158 {error} (using fallback data)159 </div>160 )}161162 <div163 style={{164 display: 'grid',165 marginTop: '2rem',166 gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))',167 gap: '1.5rem',168 marginBottom: '2rem',169 }}170 >171 <Card title="Active Users Now" value={displayData.activeUsersNow || 0} />172 <Card title="Total Users 30 Days" value={displayData.total30DayUsers || 0} />173 <Card title="Total Views 30 Days" value={displayData.total30DayViews || 0} />174 </div>175176 <div177 style={{178 padding: '2rem 1rem',179 backgroundColor: 'var(--theme-elevation-25)',180 borderRadius: '8px',181 boxShadow: '0 2px 6px rgba(0,0,0,0.08)',182 }}183 >184 <h3 style={{ marginTop: 0, marginBottom: '1rem' }}>📈 User Activity 30 Days</h3>185186 {chartData.length > 0 ? (187 <div style={{ width: '100%', height: '400px' }}>188 <Line189 data={{190 labels: chartData.map((item) => item.date),191 datasets: [192 {193 label: 'Users',194 data: chartData.map((item) => item.users),195 borderColor: 'rgb(75, 192, 192)',196 backgroundColor: 'rgba(75, 192, 192, 0.2)',197 tension: 0.3,198 fill: true,199 },200 {201 label: 'Views',202 data: chartData.map((item) => item.views),203 borderColor: 'rgb(53, 162, 235)',204 backgroundColor: 'rgba(53, 162, 235, 0.2)',205 tension: 0.3,206 fill: true,207 },208 ],209 }}210 options={{211 responsive: true,212 maintainAspectRatio: false,213 plugins: {214 legend: {215 position: 'top',216 },217 tooltip: {218 mode: 'index',219 intersect: false,220 },221 },222 scales: {223 x: {224 grid: {225 display: false,226 },227 },228 y: {229 beginAtZero: true,230 },231 },232 }}233 />234 </div>235 ) : (236 <p>No data available for the selected period.</p>237 )}238 </div>239 </div>240 )241}242243export default AnalyticsDashboard244
Show the traffic to the listing
|_ 📄 package.json
|_ 📁 src
| |_ 📁 AnalyticsUtilties
| | | |_ 📄 ga4.ts
| |_ 📁 app/(payload)/admin
| | | |_ 📄 importMap.js.ts
|_ 📁 componets
| |_ 📁 AnalyticsDashboard
| | |_ 📄 AnalyticsDashboard.tsx
| |_ 📄 GoogleAnalytics.tsx
|_ 📄 payload.config.ts
12import React from 'react'3import Script from 'next/script'4const GoogleAnalytics = () => {5 console.log('Google Analytics ID:', process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS)6 return (7 <>8 <Script9 strategy="lazyOnload"10 src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS}`}11 />12 <Script id="google-analytics" strategy="lazyOnload">13 {`14 window.dataLayer = window.dataLayer || [];15 function gtag(){dataLayer.push(arguments);}16 gtag('js', new Date());17 gtag('config', '${process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS}', {18 page_path: window.location.pathname,19 });20 `}21 </Script>22 </>23 )24}25export default GoogleAnalytics
register it with the payload config replaing the BeforelLogin
1export default buildConfig({2 admin: {3 components: {4 afterDashboard: ['@/components/AnalyticsDashboardData/AnalyticsDashboard'],5 // The `BeforeLogin` component renders a message that you see while logging into your admin panel.6 // Feel free to delete this at any time. Simply remove the line below and the import `BeforeLogin` statement on line 15.7 beforeLogin: ['@/components/BeforeLogin'],

Coded theme examples. Customise the branding and initiate image storage. Own your own design system

Subscription payment and Sharing the policy using a protected route with a paywall to ensure privacy of users the payment is intended for