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
19 changes: 19 additions & 0 deletions src/lib/analytic/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Analytics events
*
* Uses the `category:object_action` naming convention to ensure consistent and descriptive event naming.
* - **category**: The context or domain where the event occurred (e.g., `auth`, `account_settings`, `signup_flow`)
* - **object**: The component, feature, or location involved (e.g., `signup_button`, `pricing_page`)
* - **action**: The user action or system event that occurred (e.g., `click`, `submit`, `view`, `cancel`)
*
* @examples
* - `account_settings:forgot_password_button_click`
* - `signup_flow:pricing_page_view`
* - `registration:sign_up_button_click`
* - `registration_v2:sign_up_button_click` - version your events
*
* @see {@link https://posthog.com/docs/product-analytics/best-practices#2-implement-a-naming-convention}
*/
export enum AnalyticEvent {
EVENT = 'category:object_action' /** Example */,
}
65 changes: 65 additions & 0 deletions src/lib/analytic/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { usePostHog } from '@posthog/react';
import type { CaptureOptions } from 'posthog-js';
import type { EventMessage } from 'posthog-node';
import * as React from 'react';

import { type AnalyticEvent } from '~/lib/analytic/events';
import type { AnalyticCaptureEventProps } from '~/lib/analytic/types';
import { createPosthogClient } from '~/lib/posthog/server';

/**
* Hook for capturing analytics events using PostHog.
*
* Provides a typed interface for tracking analytics events with associated properties.
* This hook wraps the PostHog client to ensure type-safe event capturing.
*/
export function useAnalytic() {
const posthog = usePostHog();

return React.useRef({
capture<TEvent extends AnalyticEvent>({
event,
properties,
...options
}: AnalyticCaptureEventProps<TEvent, CaptureOptions>) {
return posthog.capture(event, properties, options);
},
}).current;
}

/**
* Creates a server-side analytics client using PostHog.
*
* This function initializes a PostHog client for server-side analytics tracking,
* providing a type-safe interface for capturing analytics events immediately.
* Useful for tracking events that occur during server-side operations.
*/
export function createAnalyticClient() {
const posthog = createPosthogClient();

/** Capture an event immediately (synchronously) */
function captureImmediate<TEvent extends AnalyticEvent>({
event,
properties,
...props
}: AnalyticCaptureEventProps<
TEvent,
Omit<EventMessage, 'event' | 'properties'>
>) {
return posthog.captureImmediate({ event, properties, ...props });
}

/** Capture an event manually (asynchronously) */
function capture<TEvent extends AnalyticEvent>({
event,
properties,
...props
}: AnalyticCaptureEventProps<
TEvent,
Omit<EventMessage, 'event' | 'properties'>
>) {
return posthog.capture({ event, properties, ...props });
}

return { capture, captureImmediate };
}
26 changes: 26 additions & 0 deletions src/lib/analytic/properties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { AnalyticEvent } from '~/lib/analytic/events';

/**
* Analytics properties - contextual data attached to events
*
* Uses naming conventions to ensure consistent and descriptive property naming.
* - **object_adjective pattern**: Use descriptive property names (e.g., `user_id`, `item_price`, `member_count`)
* - **boolean prefixes**: Use `is_` or `has_` for boolean properties (e.g., `is_subscribed`, `has_seen_upsell`)
* - **temporal suffixes**: For dates/timestamps, include `_date` or `_timestamp` (e.g., `user_creation_date`, `last_login_timestamp`)
*
* @examples
* - `user_id`
* - `item_price`
* - `member_count`
* - `is_subscribed`
* - `has_seen_upsell`
* - `last_login_timestamp`
* - `user_creation_date`
*
* @see {@link https://posthog.com/docs/product-analytics/best-practices#2-implement-a-naming-convention}
*/
export interface AnalyticProperty {
[AnalyticEvent.EVENT]: {
event_id: string;
} /** Example */;
}
19 changes: 19 additions & 0 deletions src/lib/analytic/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { AnalyticEvent } from '~/lib/analytic/events';
import type { AnalyticProperty } from '~/lib/analytic/properties';

/**
* Props for capturing an analytics event.
*
* Conditionally requires `properties` based on whether the event has a
* corresponding entry in `AnalyticProperty`. If the event is mapped, `properties`
* is required and typed accordingly; otherwise `properties` is not allowed.
*
* @template TEvent - The analytic event type being captured.
* @template TOthers - Additional options to merge into the props (e.g. PostHog `CaptureOptions`).
*/
export type AnalyticCaptureEventProps<
TEvent extends AnalyticEvent,
TOthers extends {} = {},
> = TEvent extends keyof AnalyticProperty
? { event: TEvent; properties: AnalyticProperty[TEvent] } & TOthers
: { event: TEvent; properties?: undefined } & TOthers;