diff --git a/src/app/api/analytics/download-count/route.ts b/src/app/api/analytics/download-count/route.ts index afac36b..dbfd7da 100644 --- a/src/app/api/analytics/download-count/route.ts +++ b/src/app/api/analytics/download-count/route.ts @@ -1,16 +1,24 @@ import { NextResponse } from 'next/server'; +import { BetaAnalyticsDataClient } from '@google-analytics/data'; -// Note: This is a simplified implementation for demonstration. -// For production, you would need to: -// 1. Install Google Analytics Data API: npm install @google-analytics/data google-auth-library -// 2. Set up a service account with GA4 read permissions -// 3. Add environment variables for authentication +// Note: This implementation uses the Google Analytics Data API (GA4). +// It requires: +// 1. @google-analytics/data and google-auth-library installed +// 2. A service account with GA4 read permissions configured via Application Default Credentials +// 3. GA4_PROPERTY_ID environment variable set to your GA4 property ID + +const analyticsDataClient = new BetaAnalyticsDataClient({ + credentials: JSON.parse( + process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON || '{}' + ), +}); export async function GET(request: Request) { const { searchParams } = new URL(request.url); const fileName = searchParams.get('fileName'); const fileType = searchParams.get('fileType'); const resourceType = searchParams.get('resourceType'); + const bannerAnalyticsId = searchParams.get('banner_analytics_id'); if (!fileName) { return NextResponse.json({ error: 'fileName is required' }, { status: 400 }); @@ -18,24 +26,81 @@ export async function GET(request: Request) { try { const measurementId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID; + const propertyId = process.env.GA4_PROPERTY_ID; if (!measurementId) { console.error('NEXT_PUBLIC_GA_MEASUREMENT_ID not configured'); return NextResponse.json({ error: 'Analytics not configured' }, { status: 500 }); } - // For now, return a mock count that simulates GA data - // In production, this would query the actual Google Analytics Data API - const mockCount = Math.floor(Math.random() * 1000) + 50; // Random count between 50-1050 - - //console.log(`[Analytics] Fetching download count for: ${fileName} (${fileType}, ${resourceType})`); - + if (!propertyId) { + console.error('GA4_PROPERTY_ID not configured'); + return NextResponse.json({ error: 'Analytics property not configured' }, { status: 500 }); + } + + if (!bannerAnalyticsId) { + console.error('banner_analytics_id is required to fetch download count'); + return NextResponse.json({ error: 'banner_analytics_id is required' }, { status: 400 }); + } + + const bannerId = bannerAnalyticsId as string; + + const property = propertyId.startsWith('properties/') + ? propertyId + : `properties/${propertyId}`; + + const [report] = await analyticsDataClient.runReport({ + property, + dateRanges: [{ startDate: '2025-11-11', endDate: 'today' }], + dimensions: [ + { name: 'eventName' }, + { name: 'customEvent:banner_analytics_id' }, + ], + metrics: [{ name: 'eventCount' }], + dimensionFilter: { + andGroup: { + expressions: [ + { + filter: { + fieldName: 'eventName', + stringFilter: { + matchType: 'EXACT', + value: 'resource_file_download', + }, + }, + }, + { + filter: { + fieldName: 'customEvent:banner_analytics_id', + stringFilter: { + matchType: 'EXACT', + value: bannerId, + }, + }, + }, + ], + }, + }, + }); + + let count = 0; + if (report.rows && report.rows.length > 0) { + const metricValue = report.rows[0].metricValues?.[0]?.value; + if (metricValue !== undefined) { + const parsed = parseInt(metricValue || '0', 10); + if (!Number.isNaN(parsed)) { + count = parsed; + } + } + } + return NextResponse.json({ - count: mockCount, + count, fileName, fileType, resourceType, - source: 'mock_data' // This indicates it's mock data + bannerAnalyticsId, + source: 'ga4_data_api' }); } catch (error) { diff --git a/src/app/api/banners/route.ts b/src/app/api/banners/route.ts index 3e29c18..52cf457 100644 --- a/src/app/api/banners/route.ts +++ b/src/app/api/banners/route.ts @@ -128,7 +128,6 @@ export async function GET(req: NextRequest) { ...baseBanner, templateType: 'resource-project', resourceType: banner.ResourceProject?.ResourceFile?.ResourceType, - downloadCount: banner.ResourceProject?.ResourceFile?.DownloadCount, lastUpdated: banner.ResourceProject?.ResourceFile?.LastUpdated ? new Date(banner.ResourceProject.ResourceFile.LastUpdated).toISOString() : undefined, diff --git a/src/components/Banners/ResourceProjectBanner.tsx b/src/components/Banners/ResourceProjectBanner.tsx index 1e9d0ad..a60ccd6 100644 --- a/src/components/Banners/ResourceProjectBanner.tsx +++ b/src/components/Banners/ResourceProjectBanner.tsx @@ -18,6 +18,7 @@ interface ResourceProjectBannerProps extends ResourceProjectProps { } const ResourceProjectBanner: React.FC = ({ + id, title, description, subtitle, @@ -33,14 +34,13 @@ const ResourceProjectBanner: React.FC = ({ endDate, badgeText, resourceType, - downloadCount: initialDownloadCount, lastUpdated, fileSize, fileType, trackingContext = 'resource-project', className = '' }) => { - const [downloadCount, setDownloadCount] = useState(initialDownloadCount); + const [downloadCount, setDownloadCount] = useState(0); // Fetch download count from Google Analytics useEffect(() => { @@ -51,6 +51,7 @@ const ResourceProjectBanner: React.FC = ({ if (downloadButton) { const params = new URLSearchParams({ + banner_analytics_id: id, fileName: title, ...(fileType && { fileType }), ...(resourceType && { resourceType }), @@ -59,10 +60,8 @@ const ResourceProjectBanner: React.FC = ({ const response = await fetch(`/api/analytics/download-count?${params.toString()}`); if (response.ok) { - // TODO: Uncomment it when we get value of downloadCount from GA4 - // const data = await response.json(); - // setDownloadCount(data.count); - setDownloadCount(undefined); + const data = await response.json(); + setDownloadCount(data.count); } } } catch (error) { @@ -71,7 +70,7 @@ const ResourceProjectBanner: React.FC = ({ }; fetchDownloadCount(); - }, [title, fileType, resourceType, ctaButtons]); + }, [title, fileType, resourceType, ctaButtons, id]); const backgroundClasses = generateBackgroundClasses(background); const backgroundStyles = generateBackgroundStyles(background); @@ -166,6 +165,19 @@ const ResourceProjectBanner: React.FC = ({ button_position: index + 1, tracking_context: button.trackingContext || trackingContext }); + + // Track downloads specifically: by default we set up file download to the first button + if (index === 0) { + window.gtag('event', 'resource_file_download', { + banner_analytics_id: id, + resource_title: title, + resource_type: resourceType || 'unknown', + file_type: fileType || 'unknown', + cta_position: index + 1, + button_label: button.label, + tracking_context: button.trackingContext || trackingContext + }); + } } // Track downloads specifically: by default we set up file download to the first button @@ -277,8 +289,8 @@ const ResourceProjectBanner: React.FC = ({ {/* Resource Stats */} {/* Update to lg:grid-cols-3 when we configure downloadCount */} -
- {downloadCount !== undefined && downloadCount !== 0 && ( +
+ {downloadCount !== undefined && (