diff --git a/src/components/shared/ImageUploader.test.tsx b/src/components/shared/ImageUploader.test.tsx index 740c0ed9..a9889ea0 100644 --- a/src/components/shared/ImageUploader.test.tsx +++ b/src/components/shared/ImageUploader.test.tsx @@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'; import type React from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import ImageUploader from './ImageUploader'; +import { dataWarehouse } from '@/lib/dataWarehouse'; vi.mock('next/image', () => ({ default: ({ @@ -30,10 +31,12 @@ vi.mock('next/image', () => ({ describe('ImageUploader', () => { const createObjectURL = vi.fn(); const revokeObjectURL = vi.fn(); + const trackEventSpy = vi.spyOn(dataWarehouse, 'trackEvent').mockImplementation(async () => {}); beforeEach(() => { createObjectURL.mockReset(); revokeObjectURL.mockReset(); + trackEventSpy.mockClear(); Object.defineProperty(URL, 'createObjectURL', { configurable: true, @@ -57,6 +60,11 @@ describe('ImageUploader', () => { expect(createObjectURL).toHaveBeenCalledWith(avatar); expect(onImageSelect).toHaveBeenCalledWith(avatar); + expect(trackEventSpy).toHaveBeenCalledWith('IMAGE_UPLOADED', { + fileName: 'avatar.png', + fileSize: avatar.size, + fileType: 'image/png', + }); expect(screen.getByRole('img', { name: 'Profile Preview' })).toHaveAttribute( 'src', 'blob:profile-preview', diff --git a/src/components/shared/ImageUploader.tsx b/src/components/shared/ImageUploader.tsx index d265619b..132af5d9 100644 --- a/src/components/shared/ImageUploader.tsx +++ b/src/components/shared/ImageUploader.tsx @@ -3,6 +3,7 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react'; import type { ChangeEvent } from 'react'; import Image from 'next/image'; +import { dataWarehouse } from '@/lib/dataWarehouse'; interface ImageUploaderProps { onImageSelect: (file: File) => void; @@ -38,6 +39,13 @@ function ImageUploader({ objectUrlRef.current = objectUrl; setPreviewUrl(objectUrl); + // Track the image upload event + dataWarehouse.trackEvent('IMAGE_UPLOADED', { + fileName: file.name, + fileSize: file.size, + fileType: file.type, + }).catch(console.error); + onImageSelect(file); } }, [onImageSelect]); diff --git a/src/lib/dataWarehouse.test.ts b/src/lib/dataWarehouse.test.ts new file mode 100644 index 00000000..bacd7197 --- /dev/null +++ b/src/lib/dataWarehouse.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { dataWarehouse } from './dataWarehouse'; + +describe('DataWarehouseService', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('should be a singleton', () => { + const instance1 = dataWarehouse; + const instance2 = dataWarehouse; + expect(instance1).toBe(instance2); + }); + + it('should track an event successfully', async () => { + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + // Temporarily change NODE_ENV to trigger console.log + const originalEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'development'; + + await dataWarehouse.trackEvent('TEST_EVENT', { foo: 'bar' }); + + expect(consoleSpy).toHaveBeenCalled(); + const logOutput = consoleSpy.mock.calls[0][1]; + const parsed = JSON.parse(logOutput); + + expect(parsed.eventName).toBe('TEST_EVENT'); + expect(parsed.properties).toEqual({ foo: 'bar' }); + expect(parsed.timestamp).toBeDefined(); + + process.env.NODE_ENV = originalEnv; + }); +}); diff --git a/src/lib/dataWarehouse.ts b/src/lib/dataWarehouse.ts new file mode 100644 index 00000000..95939b09 --- /dev/null +++ b/src/lib/dataWarehouse.ts @@ -0,0 +1,44 @@ +/** + * DataWarehouseService + * + * A singleton service for tracking events to a data warehouse. + * Currently uses a console mock, but designed to be easily extensible + * for providers like BigQuery, Snowflake, Mixpanel, etc. + */ + +export interface TrackEventPayload { + eventName: string; + timestamp: string; + properties?: Record; +} + +class DataWarehouseService { + private static instance: DataWarehouseService; + + private constructor() {} + + public static getInstance(): DataWarehouseService { + if (!DataWarehouseService.instance) { + DataWarehouseService.instance = new DataWarehouseService(); + } + return DataWarehouseService.instance; + } + + public async trackEvent(eventName: string, properties?: Record): Promise { + const payload: TrackEventPayload = { + eventName, + timestamp: new Date().toISOString(), + properties, + }; + + // Mock implementation for the data warehouse integration + if (process.env.NODE_ENV !== 'test') { + console.log('[DataWarehouse] Event tracked:', JSON.stringify(payload, null, 2)); + } + + // In a real implementation, you would do something like: + // await fetch('https://api.datawarehouse.com/track', { method: 'POST', body: JSON.stringify(payload) }) + } +} + +export const dataWarehouse = DataWarehouseService.getInstance();