diff --git a/reflex_ui/blocks/demo_form.py b/reflex_ui/blocks/demo_form.py index 4effd0b..221a0d9 100644 --- a/reflex_ui/blocks/demo_form.py +++ b/reflex_ui/blocks/demo_form.py @@ -5,12 +5,15 @@ based on company size. """ +from typing import Any + import reflex as rx from reflex.event import EventType from reflex.experimental.client_state import ClientStateVar from reflex.vars.base import get_unique_variable_name import reflex_ui as ui +from reflex_ui.blocks.telemetry.posthog import track_demo_form_posthog_submission demo_form_error_message = ClientStateVar.create("demo_form_error_message", "") demo_form_open_cs = ClientStateVar.create("demo_form_open", False) @@ -85,6 +88,15 @@ def validate_email(self, email: str): else: yield demo_form_error_message.push("") + @rx.event + def track_demo_form_posthog(self, form_data: dict[str, Any]): + """Send demo form fields to PostHog (identify + capture) in the browser. + + Returns: + Event that runs PostHog identify and capture in the browser. + """ + return track_demo_form_posthog_submission(form_data) + def input_field( label: str, @@ -327,8 +339,10 @@ def demo_form(id_prefix: str = "", **props) -> rx.Component: "@container flex flex-col lg:gap-6 gap-2 p-6", props.pop("class_name", ""), ), - # Close the dialog when the form is submitted - on_submit=demo_form_open_cs.set_value(False), + on_submit=[ + DemoFormStateUI.track_demo_form_posthog, + rx.call_function(demo_form_open_cs.set_value(False)), + ], data_default_form_id="965991", **props, ) diff --git a/reflex_ui/blocks/intro_form.py b/reflex_ui/blocks/intro_form.py index f21ee37..a475dd8 100644 --- a/reflex_ui/blocks/intro_form.py +++ b/reflex_ui/blocks/intro_form.py @@ -6,11 +6,12 @@ from typing import Any import reflex as rx -from reflex.event import EventType +from reflex.event import EventType, IndividualEventType from reflex.experimental.client_state import ClientStateVar from reflex.vars.base import get_unique_variable_name import reflex_ui as ui +from reflex_ui.blocks.telemetry.posthog import track_intro_form_posthog_submission intro_form_error_message = ClientStateVar.create("intro_form_error_message", "") intro_form_open_cs = ClientStateVar.create("intro_form_open", False) @@ -86,6 +87,15 @@ def validate_email(self, email: str): else: yield intro_form_error_message.push("") + @rx.event + def track_intro_form_posthog(self, form_data: dict[str, Any]): + """Send intro form fields to PostHog (identify + capture) in the browser. + + Returns: + Event that runs PostHog identify and capture in the browser. + """ + return track_intro_form_posthog_submission(form_data) + def input_field( label: str, @@ -241,7 +251,7 @@ def select_field( def intro_form( id_prefix: str = "", - on_submit: EventType[dict[str, Any]] | EventType[()] | None = None, + on_submit: EventType[dict[str, Any]] | None = None, **props, ) -> rx.Component: """Create and return the intro form component. @@ -261,6 +271,11 @@ def intro_form( """ prefix = id_prefix or get_unique_variable_name() email_id = f"{prefix}_user_email" + + extra: list[IndividualEventType[dict[str, Any]]] = ( + on_submit if isinstance(on_submit, list) else [on_submit] if on_submit else [] + ) + form = rx.el.form( rx.el.div( input_field("First name", "John", "first_name", "text", True), @@ -340,7 +355,7 @@ def intro_form( "@container flex flex-col lg:gap-6 gap-2 p-6", props.pop("class_name", ""), ), - on_submit=on_submit, + on_submit=[IntroFormStateUI.track_intro_form_posthog, *extra], **props, ) return rx.fragment( @@ -351,7 +366,7 @@ def intro_form( def intro_form_dialog( trigger: rx.Component | None = None, id_prefix: str = "", - on_submit: EventType[dict[str, Any]] | EventType[()] | None = None, + on_submit: EventType[dict[str, Any]] | None = None, **props, ) -> rx.Component: """Return a intro form dialog container element. diff --git a/reflex_ui/blocks/telemetry/posthog.py b/reflex_ui/blocks/telemetry/posthog.py index 8002488..087d1b0 100644 --- a/reflex_ui/blocks/telemetry/posthog.py +++ b/reflex_ui/blocks/telemetry/posthog.py @@ -1,6 +1,7 @@ """PostHog analytics tracking integration for Reflex applications.""" import json +from typing import Any import reflex as rx @@ -60,6 +61,76 @@ def identify_posthog_user(user_id: str) -> rx.event.EventSpec: ) +def _track_form_posthog( + event_name: str, + form_data: dict[str, Any], + allowed_keys: set[str], +) -> rx.event.EventSpec: + """Identify the submitter and capture a form event in PostHog. + + Args: + event_name: PostHog event name to capture. + form_data: Submitted form fields as a string-keyed dict. + allowed_keys: Set of keys to include from form_data. + + Returns: + Event that runs PostHog identify and capture in the browser. + """ + filtered = {k: v for k, v in form_data.items() if k in allowed_keys} + props_json = json.dumps(filtered) + return rx.call_script( + f""" + if (typeof posthog !== 'undefined') {{ + const props = {props_json}; + const distinctId = props.email || ('anon_' + String(Date.now())); + posthog.identify(distinctId, {{ + email: props.email, + first_name: props.first_name, + last_name: props.last_name, + job_title: props.job_title, + company_name: props.company_name, + }}); + posthog.capture('{event_name}', props); + }} + """ + ) + + +_COMMON_KEYS = { + "email", + "first_name", + "last_name", + "job_title", + "company_name", + "number_of_employees", + "how_did_you_hear_about_us", + "internal_tools", + "technical_level", +} + + +def track_demo_form_posthog_submission(form_data: dict[str, Any]) -> rx.event.EventSpec: + """Capture a demo_request event in PostHog. + + Returns: + Event that runs PostHog identify and capture in the browser. + """ + return _track_form_posthog("demo_request", form_data, _COMMON_KEYS) + + +def track_intro_form_posthog_submission( + form_data: dict[str, Any], +) -> rx.event.EventSpec: + """Capture an intro_submit event in PostHog. + + Returns: + Event that runs PostHog identify and capture in the browser. + """ + return _track_form_posthog( + "intro_submit", form_data, _COMMON_KEYS | {"phone_number"} + ) + + def get_posthog_trackers( project_id: str, api_host: str = POSTHOG_API_HOST, diff --git a/shared/reflex_ui_shared/telemetry/pixels.py b/shared/reflex_ui_shared/telemetry/pixels.py index a3934ad..93c5d1c 100644 --- a/shared/reflex_ui_shared/telemetry/pixels.py +++ b/shared/reflex_ui_shared/telemetry/pixels.py @@ -5,6 +5,7 @@ from reflex_ui.blocks.telemetry import ( get_default_telemetry_script, get_google_analytics_trackers, + get_posthog_trackers, get_unify_trackers, gtag_report_conversion, ) @@ -19,4 +20,7 @@ def get_pixel_website_trackers() -> list[rx.Component]: ), get_unify_trackers(), get_default_telemetry_script(), + get_posthog_trackers( + project_id="phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb" + ), ]