diff --git a/src/lib/analytic/events.ts b/src/lib/analytic/events.ts new file mode 100644 index 0000000..ec5643b --- /dev/null +++ b/src/lib/analytic/events.ts @@ -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 */, +} diff --git a/src/lib/analytic/index.ts b/src/lib/analytic/index.ts new file mode 100644 index 0000000..d86f4fd --- /dev/null +++ b/src/lib/analytic/index.ts @@ -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({ + event, + properties, + ...options + }: AnalyticCaptureEventProps) { + 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({ + event, + properties, + ...props + }: AnalyticCaptureEventProps< + TEvent, + Omit + >) { + return posthog.captureImmediate({ event, properties, ...props }); + } + + /** Capture an event manually (asynchronously) */ + function capture({ + event, + properties, + ...props + }: AnalyticCaptureEventProps< + TEvent, + Omit + >) { + return posthog.capture({ event, properties, ...props }); + } + + return { capture, captureImmediate }; +} diff --git a/src/lib/analytic/properties.ts b/src/lib/analytic/properties.ts new file mode 100644 index 0000000..332a818 --- /dev/null +++ b/src/lib/analytic/properties.ts @@ -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 */; +} diff --git a/src/lib/analytic/types.ts b/src/lib/analytic/types.ts new file mode 100644 index 0000000..61a8475 --- /dev/null +++ b/src/lib/analytic/types.ts @@ -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;