Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/components/shared/ImageUploader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: ({
Expand Down Expand Up @@ -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,
Expand All @@ -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',
Expand Down
8 changes: 8 additions & 0 deletions src/components/shared/ImageUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]);
Expand Down
34 changes: 34 additions & 0 deletions src/lib/dataWarehouse.test.ts
Original file line number Diff line number Diff line change
@@ -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;
});
});
44 changes: 44 additions & 0 deletions src/lib/dataWarehouse.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;
}

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<string, unknown>): Promise<void> {
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();
Loading