Skip to main content

Analytics Events

The @54vie/analytics package provides a comprehensive event tracking system with predefined standard events and support for custom event tracking. This guide covers the StandardEvents enum, event properties, and best practices for event tracking.

Installation

npm install @54vie/analytics
# or
yarn add @54vie/analytics

StandardEvents Enum

The StandardEvents enum provides predefined event names for common user interactions. Using standard events ensures consistent naming and enables automatic reporting in the 54Vie dashboard.

Import

import { StandardEvents } from '@54vie/analytics';

Available Standard Events

enum StandardEvents {
// Page & Navigation
PAGE_VIEW = 'page_view',
PAGE_LEAVE = 'page_leave',
SCREEN_VIEW = 'screen_view',

// Session
SESSION_START = 'session_start',
SESSION_END = 'session_end',

// User Authentication
SIGN_UP = 'sign_up',
LOGIN = 'login',
LOGOUT = 'logout',
PASSWORD_RESET = 'password_reset',

// E-commerce
VIEW_ITEM = 'view_item',
VIEW_ITEM_LIST = 'view_item_list',
SELECT_ITEM = 'select_item',
ADD_TO_CART = 'add_to_cart',
REMOVE_FROM_CART = 'remove_from_cart',
VIEW_CART = 'view_cart',
BEGIN_CHECKOUT = 'begin_checkout',
ADD_SHIPPING_INFO = 'add_shipping_info',
ADD_PAYMENT_INFO = 'add_payment_info',
PURCHASE = 'purchase',
REFUND = 'refund',

// Engagement
SEARCH = 'search',
SHARE = 'share',
GENERATE_LEAD = 'generate_lead',
SUBSCRIBE = 'subscribe',
UNSUBSCRIBE = 'unsubscribe',

// Content
VIEW_CONTENT = 'view_content',
CONTENT_INTERACTION = 'content_interaction',
VIDEO_START = 'video_start',
VIDEO_PROGRESS = 'video_progress',
VIDEO_COMPLETE = 'video_complete',

// Forms
FORM_START = 'form_start',
FORM_SUBMIT = 'form_submit',
FORM_ERROR = 'form_error',
FORM_ABANDON = 'form_abandon',

// Errors
ERROR = 'error',
EXCEPTION = 'exception',

// Experiments
EXPERIMENT_EXPOSURE = 'experiment_exposure',
EXPERIMENT_CONVERSION = 'experiment_conversion',

// Feature Flags
FEATURE_FLAG_EVALUATED = 'feature_flag_evaluated',

// Custom Conversion Goals
CONVERSION = 'conversion',
GOAL_COMPLETE = 'goal_complete',
}

TypeScript Interfaces

Event Properties

/**
* Base event properties that can be attached to any event
*/
interface EventProperties {
[key: string]: EventPropertyValue;
}

type EventPropertyValue =
| string
| number
| boolean
| null
| undefined
| string[]
| number[]
| Record<string, unknown>;

/**
* Properties specific to e-commerce events
*/
interface EcommerceEventProperties {
currency?: string;
value?: number;
items?: EcommerceItem[];
coupon?: string;
discount?: number;
shipping?: number;
tax?: number;
transactionId?: string;
affiliation?: string;
}

interface EcommerceItem {
itemId: string;
itemName: string;
price?: number;
quantity?: number;
category?: string;
brand?: string;
variant?: string;
position?: number;
coupon?: string;
discount?: number;
}

/**
* Properties for page view events
*/
interface PageViewProperties {
path?: string;
title?: string;
referrer?: string;
search?: string;
hash?: string;
url?: string;
}

/**
* Properties for search events
*/
interface SearchProperties {
searchTerm: string;
resultsCount?: number;
category?: string;
filters?: Record<string, unknown>;
}

/**
* Properties for error events
*/
interface ErrorEventProperties {
errorMessage: string;
errorCode?: string;
errorType?: string;
stackTrace?: string;
componentName?: string;
fatal?: boolean;
}

/**
* Properties for form events
*/
interface FormEventProperties {
formId?: string;
formName?: string;
formStep?: number;
fieldName?: string;
fieldValue?: unknown;
validationError?: string;
}

/**
* Properties for video events
*/
interface VideoEventProperties {
videoId: string;
videoTitle?: string;
videoDuration?: number;
currentTime?: number;
percentComplete?: number;
provider?: string;
}

Tracking Events

Using the useAnalytics Hook

import { useAnalytics, StandardEvents } from '@54vie/analytics';

function ProductCard({ product }) {
const { track, trackStandard } = useAnalytics();

const handleViewProduct = () => {
trackStandard(StandardEvents.VIEW_ITEM, {
itemId: product.id,
itemName: product.name,
price: product.price,
currency: 'USD',
category: product.category,
brand: product.brand,
});
};

const handleAddToCart = () => {
trackStandard(StandardEvents.ADD_TO_CART, {
currency: 'USD',
value: product.price,
items: [
{
itemId: product.id,
itemName: product.name,
price: product.price,
quantity: 1,
category: product.category,
},
],
});
};

return (
<div onClick={handleViewProduct}>
<h3>{product.name}</h3>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
}

Custom Event Tracking

import { useAnalytics } from '@54vie/analytics';

function FeatureComponent() {
const { track } = useAnalytics();

const handleFeatureUsed = () => {
track('feature_used', {
featureName: 'advanced_filters',
filterType: 'date_range',
filterValue: 'last_30_days',
userPlan: 'premium',
});
};

const handleCustomAction = () => {
// Track with nested properties
track('custom_action', {
actionType: 'export',
format: 'csv',
recordCount: 150,
filters: {
status: 'active',
dateRange: 'last_month',
},
metadata: {
source: 'dashboard',
trigger: 'button_click',
},
});
};

return (
<div>
<button onClick={handleFeatureUsed}>Use Feature</button>
<button onClick={handleCustomAction}>Export Data</button>
</div>
);
}

E-commerce Event Examples

Complete Purchase Flow

import { useAnalytics, StandardEvents } from '@54vie/analytics';

function CheckoutFlow() {
const { trackStandard } = useAnalytics();

// Track when user views product
const trackViewItem = (product: Product) => {
trackStandard(StandardEvents.VIEW_ITEM, {
currency: 'USD',
value: product.price,
items: [
{
itemId: product.id,
itemName: product.name,
price: product.price,
category: product.category,
brand: product.brand,
},
],
});
};

// Track when user adds to cart
const trackAddToCart = (product: Product, quantity: number) => {
trackStandard(StandardEvents.ADD_TO_CART, {
currency: 'USD',
value: product.price * quantity,
items: [
{
itemId: product.id,
itemName: product.name,
price: product.price,
quantity,
category: product.category,
},
],
});
};

// Track checkout initiation
const trackBeginCheckout = (cart: CartItem[]) => {
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);

trackStandard(StandardEvents.BEGIN_CHECKOUT, {
currency: 'USD',
value: total,
items: cart.map((item) => ({
itemId: item.id,
itemName: item.name,
price: item.price,
quantity: item.quantity,
})),
});
};

// Track shipping info added
const trackShippingInfo = (shippingMethod: string) => {
trackStandard(StandardEvents.ADD_SHIPPING_INFO, {
shippingMethod,
shippingCost: getShippingCost(shippingMethod),
});
};

// Track payment info added
const trackPaymentInfo = (paymentMethod: string) => {
trackStandard(StandardEvents.ADD_PAYMENT_INFO, {
paymentMethod,
});
};

// Track completed purchase
const trackPurchase = (order: Order) => {
trackStandard(StandardEvents.PURCHASE, {
transactionId: order.id,
currency: 'USD',
value: order.total,
tax: order.tax,
shipping: order.shippingCost,
coupon: order.couponCode,
discount: order.discount,
items: order.items.map((item) => ({
itemId: item.productId,
itemName: item.name,
price: item.price,
quantity: item.quantity,
category: item.category,
})),
});
};

// Track refund
const trackRefund = (orderId: string, items?: RefundItem[]) => {
trackStandard(StandardEvents.REFUND, {
transactionId: orderId,
currency: 'USD',
value: items?.reduce((sum, item) => sum + item.refundAmount, 0),
items: items?.map((item) => ({
itemId: item.productId,
itemName: item.name,
price: item.refundAmount,
quantity: item.quantity,
})),
});
};

return <div>{/* Checkout UI */}</div>;
}

Form Tracking

import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { useState, useEffect } from 'react';

function ContactForm() {
const { trackStandard } = useAnalytics();
const [formStarted, setFormStarted] = useState(false);

// Track form start on first interaction
const handleFieldFocus = () => {
if (!formStarted) {
setFormStarted(true);
trackStandard(StandardEvents.FORM_START, {
formId: 'contact-form',
formName: 'Contact Us',
});
}
};

// Track form submission
const handleSubmit = async (data: FormData) => {
try {
await submitForm(data);
trackStandard(StandardEvents.FORM_SUBMIT, {
formId: 'contact-form',
formName: 'Contact Us',
success: true,
});
} catch (error) {
trackStandard(StandardEvents.FORM_ERROR, {
formId: 'contact-form',
formName: 'Contact Us',
errorMessage: error.message,
});
}
};

// Track form abandonment
useEffect(() => {
return () => {
if (formStarted) {
trackStandard(StandardEvents.FORM_ABANDON, {
formId: 'contact-form',
formName: 'Contact Us',
});
}
};
}, [formStarted]);

return (
<form onSubmit={handleSubmit}>
<input onFocus={handleFieldFocus} name="email" />
<textarea onFocus={handleFieldFocus} name="message" />
<button type="submit">Send</button>
</form>
);
}

Video Tracking

import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { useRef, useEffect } from 'react';

function VideoPlayer({ videoId, title, duration }) {
const { trackStandard } = useAnalytics();
const videoRef = useRef<HTMLVideoElement>(null);
const trackedMilestones = useRef(new Set<number>());

const trackVideoStart = () => {
trackStandard(StandardEvents.VIDEO_START, {
videoId,
videoTitle: title,
videoDuration: duration,
});
};

const trackVideoProgress = (percent: number) => {
if (trackedMilestones.current.has(percent)) return;
trackedMilestones.current.add(percent);

trackStandard(StandardEvents.VIDEO_PROGRESS, {
videoId,
videoTitle: title,
videoDuration: duration,
percentComplete: percent,
currentTime: videoRef.current?.currentTime,
});
};

const trackVideoComplete = () => {
trackStandard(StandardEvents.VIDEO_COMPLETE, {
videoId,
videoTitle: title,
videoDuration: duration,
});
};

const handleTimeUpdate = () => {
const video = videoRef.current;
if (!video) return;

const percent = Math.floor((video.currentTime / video.duration) * 100);

// Track at 25%, 50%, 75% milestones
if (percent >= 25 && percent < 50) trackVideoProgress(25);
else if (percent >= 50 && percent < 75) trackVideoProgress(50);
else if (percent >= 75 && percent < 100) trackVideoProgress(75);
};

return (
<video
ref={videoRef}
onPlay={trackVideoStart}
onTimeUpdate={handleTimeUpdate}
onEnded={trackVideoComplete}
>
<source src={`/videos/${videoId}.mp4`} />
</video>
);
}

Error Tracking

import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { ErrorBoundary } from 'react-error-boundary';

function useErrorTracking() {
const { trackStandard } = useAnalytics();

const trackError = (error: Error, context?: Record<string, unknown>) => {
trackStandard(StandardEvents.ERROR, {
errorMessage: error.message,
errorType: error.name,
stackTrace: error.stack,
fatal: false,
...context,
});
};

const trackException = (error: Error, componentName?: string) => {
trackStandard(StandardEvents.EXCEPTION, {
errorMessage: error.message,
errorType: error.name,
stackTrace: error.stack,
componentName,
fatal: true,
});
};

return { trackError, trackException };
}

// Usage with Error Boundary
function App() {
const { trackException } = useErrorTracking();

return (
<ErrorBoundary
onError={(error, info) => {
trackException(error, info.componentStack);
}}
fallback={<ErrorFallback />}
>
<MainApp />
</ErrorBoundary>
);
}

Search Tracking

import { useAnalytics, StandardEvents } from '@54vie/analytics';
import { useState } from 'react';

function SearchComponent() {
const { trackStandard } = useAnalytics();
const [searchTerm, setSearchTerm] = useState('');

const handleSearch = async () => {
const results = await performSearch(searchTerm);

trackStandard(StandardEvents.SEARCH, {
searchTerm,
resultsCount: results.length,
category: 'products',
filters: {
inStock: true,
priceRange: '$50-$100',
},
});
};

const handleSelectResult = (item: SearchResult, position: number) => {
trackStandard(StandardEvents.SELECT_ITEM, {
itemId: item.id,
itemName: item.name,
itemListName: 'search_results',
itemListId: 'search',
position,
searchTerm,
});
};

return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
/>
<button onClick={handleSearch}>Search</button>
</div>
);
}

Event Validation

The SDK includes built-in validation for event names and properties.

interface EventValidationConfig {
/**
* Maximum length for event names
* @default 100
*/
maxEventNameLength: number;

/**
* Maximum number of properties per event
* @default 100
*/
maxPropertiesCount: number;

/**
* Maximum length for property keys
* @default 100
*/
maxPropertyKeyLength: number;

/**
* Maximum length for string property values
* @default 1000
*/
maxPropertyValueLength: number;

/**
* List of reserved event names that cannot be used
*/
reservedEventNames: string[];

/**
* List of reserved property keys that cannot be used
*/
reservedPropertyKeys: string[];
}

Validation Rules

  1. Event names must be non-empty strings under 100 characters
  2. Event names should use snake_case format
  3. Property keys must be non-empty strings under 100 characters
  4. String property values are truncated at 1000 characters
  5. Maximum 100 properties per event
  6. Reserved prefixes (_, 54vie_) are reserved for internal use

Best Practices

Event Naming Conventions

// Good - use snake_case, be specific
track('button_click', { buttonId: 'signup_cta' });
track('user_profile_updated', { fields: ['email', 'name'] });
track('feature_flag_evaluated', { flagName: 'new_checkout' });

// Bad - avoid these patterns
track('ButtonClick', {}); // Don't use PascalCase
track('button-click', {}); // Don't use kebab-case
track('click', {}); // Too generic
track('user did something', {}); // Don't use spaces

Property Best Practices

// Good - structured, consistent properties
track('purchase_completed', {
orderId: 'ORD-12345',
totalAmount: 99.99,
currency: 'USD',
itemCount: 3,
paymentMethod: 'credit_card',
isFirstPurchase: true,
});

// Bad - avoid these patterns
track('purchase', {
'order-id': '12345', // Use camelCase for keys
total: '$99.99', // Use numbers for numeric values
items: 'item1, item2', // Use arrays for lists
});

Avoiding PII in Events

// Good - use IDs and hashed values
track('user_action', {
userId: user.id,
emailDomain: extractDomain(user.email), // e.g., 'gmail.com'
hasPhone: !!user.phone,
});

// Bad - never include PII directly
track('user_action', {
email: user.email, // Don't include email
phone: user.phone, // Don't include phone
ssn: user.ssn, // Never include sensitive data
});
  • Provider - AnalyticsProvider setup and configuration
  • Hooks - useAnalytics, useUserProperties, useExperiment
  • Experiments - A/B testing and feature flags