From 6f7ba846765e75be60c6e2acd2ee2ea902600313 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:03:19 -0800 Subject: [PATCH 01/20] Update pricing page to use demo_form from reflex_ui --- pcweb/pages/sales.py | 179 +------------------------------------------ 1 file changed, 2 insertions(+), 177 deletions(-) diff --git a/pcweb/pages/sales.py b/pcweb/pages/sales.py index 45646927b..79aa942ba 100644 --- a/pcweb/pages/sales.py +++ b/pcweb/pages/sales.py @@ -1,190 +1,15 @@ -import httpx import reflex as rx +from reflex_ui.blocks.demo_form import demo_form -from pcweb.components.button import button -from pcweb.constants import REFLEX_DEV_WEB_LANDING_FORM_SALES_CALL_WEBHOOK_URL from pcweb.templates.webpage import webpage -class FormState(rx.State): - is_loading: bool = False - email_sent: bool = False - - @rx.event - async def submit(self, form_data: dict): - self.is_loading = True - yield - - try: - with httpx.Client() as client: - response = client.post( - REFLEX_DEV_WEB_LANDING_FORM_SALES_CALL_WEBHOOK_URL, - json=form_data, - ) - response.raise_for_status() - - self.is_loading = False - self.email_sent = True - yield rx.toast.success("Demo request submitted successfully!") - - except httpx.HTTPError: - self.is_loading = False - self.email_sent = False - yield rx.toast.error("Failed to submit request. Please try again later.") - - -def dialog(trigger: rx.Component, content: rx.Component) -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - trigger, - ), - rx.dialog.content( - content, - class_name="bg-white-1 p-4 rounded-[1.625rem] w-[26rem]", - ), - ) - - -def form() -> rx.Component: - input_class_name = "box-border border-slate-5 focus:border-violet-9 focus:border-1 bg-slate-1 p-[0.5rem_0.75rem] border rounded-[10px] w-full font-small text-slate-11 placeholder:text-slate-9 outline-none focus:outline-none" - return rx.box( - rx.form( - rx.box( - rx.text( - "Contact the Sales Team", - class_name="text-2xl text-slate-12 font-bold leading-6 scroll-m-[7rem]", - id="form-title", - ), - rx.text( - "Explore custom plans and pricing", - class_name="font-base text-slate-9", - ), - class_name="flex flex-col gap-2 mb-4 items-start", - ), - rx.box( - rx.hstack( - rx.el.input( - name="first_name", - type="text", - placeholder="First name *", - required=True, - class_name=input_class_name, - ), - rx.el.input( - name="last_name", - type="text", - placeholder="Last name *", - required=True, - class_name=input_class_name, - ), - spacing="2", - width="100%", - class_name="mb-2.5 gap-2", - ), - rx.hstack( - rx.el.input( - name="email", - type="email", - placeholder="Business email *", - required=True, - class_name=input_class_name, - ), - rx.el.input( - name="linkedin_profile", - type="text", - placeholder="LinkedIn profile", - required=False, - class_name=input_class_name, - ), - spacing="2", - width="100%", - class_name="mb-2.5 gap-2", - ), - rx.hstack( - rx.el.input( - name="company_name", - type="text", - placeholder="Company name *", - required=True, - class_name=input_class_name, - ), - rx.el.input( - name="title", - type="text", - placeholder="Title *", - required=True, - class_name=input_class_name, - ), - spacing="2", - width="100%", - class_name="mb-2.5 gap-2", - ), - rx.el.textarea( - name="project_description", - placeholder="Your company needs", - class_name=input_class_name + " h-24 mb-4 resize-none", - ), - class_name="flex flex-col", - ), - rx.cond( - FormState.is_loading, - button( - "Sending...", - variant="secondary", - type="submit", - class_name="opacity-80 !cursor-not-allowed pointer-events-none !w-min", - ), - button( - "Submit", - type="submit", - class_name="!w-min", - ), - ), - on_submit=FormState.submit, - class_name="flex flex-col", - ), - rx.box( - rx.text( - "If you have any questions, feel free to contact us", - class_name="font-small text-slate-9", - ), - rx.link( - "sales@reflex.dev", - href="mailto:sales@reflex.dev", - underline="always", - class_name="text-slate-9 font-small", - ), - class_name="flex flex-row justify-between items-center gap-2 mt-4", - ), - class_name="relative flex flex-col gap-4 border-slate-4 bg-slate-2 shadow-large p-8 border rounded-[1.125rem] self-stretch scroll-[3rem]", - ) - - @webpage(path="/sales", title="Pricing · Reflex") def sales(): return rx.el.section( rx.box( rx.box( - rx.cond( - FormState.email_sent, - rx.box( - rx.box( - rx.text( - """Thanks for your interest in Reflex! -You'll get a reply from us soon.""", - class_name="font-large text-slate-12 whitespace-pre text-center", - ), - class_name="flex flex-row items-center gap-2", - ), - button( - "Back", - on_click=FormState.setvar("email_sent", False), - class_name="mt-4", - ), - class_name="flex flex-col items-center gap-2", - ), - form(), - ), + demo_form(), class_name="mt-12 w-full", ), class_name="flex flex-col justify-center items-center w-full max-w-[84.5rem]", From ec2c92078b695433333851b69b9ec27e4479be57 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:05:10 -0800 Subject: [PATCH 02/20] Replace Cal.com embed with demo_form in enterprise demo form --- pcweb/pages/pricing/enterprise_demo_form.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pcweb/pages/pricing/enterprise_demo_form.py b/pcweb/pages/pricing/enterprise_demo_form.py index ed422a411..81a0ca8dc 100644 --- a/pcweb/pages/pricing/enterprise_demo_form.py +++ b/pcweb/pages/pricing/enterprise_demo_form.py @@ -1,5 +1,5 @@ import reflex as rx -from reflex_ui.blocks.calcom import cal_embed +from reflex_ui.blocks.demo_form import demo_form from pcweb.pages.framework.views.companies import pricing_page_companies @@ -25,8 +25,9 @@ def book_a_demo_form() -> rx.Component: class_name="mb-8 lg:mb-0 text-center sm:text-left", ), # Right column - Form - cal_embed( - class_name="relative bg-slate-1 rounded-2xl border-2 border-violet-9 shadow-lg w-full max-w-md mx-auto lg:max-w-none lg:mx-0 overflow-hidden" + rx.box( + demo_form(), + class_name="relative bg-slate-1 rounded-2xl border-2 border-violet-9 shadow-lg w-full max-w-md mx-auto lg:max-w-none lg:mx-0 overflow-hidden", ), class_name="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-2 max-w-7xl mx-auto items-start", ), From 5e9140ba54bf20e7652a01fe836b1d42f84d1e43 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:06:55 -0800 Subject: [PATCH 03/20] Remove purple border from pricing form --- pcweb/pages/pricing/enterprise_demo_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcweb/pages/pricing/enterprise_demo_form.py b/pcweb/pages/pricing/enterprise_demo_form.py index 81a0ca8dc..b6f480122 100644 --- a/pcweb/pages/pricing/enterprise_demo_form.py +++ b/pcweb/pages/pricing/enterprise_demo_form.py @@ -27,7 +27,7 @@ def book_a_demo_form() -> rx.Component: # Right column - Form rx.box( demo_form(), - class_name="relative bg-slate-1 rounded-2xl border-2 border-violet-9 shadow-lg w-full max-w-md mx-auto lg:max-w-none lg:mx-0 overflow-hidden", + class_name="relative bg-slate-1 rounded-2xl shadow-lg w-full max-w-md mx-auto lg:max-w-none lg:mx-0 overflow-hidden", ), class_name="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-2 max-w-7xl mx-auto items-start", ), From c397b682625191d329d3834ac681f1759f4349fa Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:07:15 -0800 Subject: [PATCH 04/20] Add gray border to pricing form --- pcweb/pages/pricing/enterprise_demo_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcweb/pages/pricing/enterprise_demo_form.py b/pcweb/pages/pricing/enterprise_demo_form.py index b6f480122..9d9dd41ef 100644 --- a/pcweb/pages/pricing/enterprise_demo_form.py +++ b/pcweb/pages/pricing/enterprise_demo_form.py @@ -27,7 +27,7 @@ def book_a_demo_form() -> rx.Component: # Right column - Form rx.box( demo_form(), - class_name="relative bg-slate-1 rounded-2xl shadow-lg w-full max-w-md mx-auto lg:max-w-none lg:mx-0 overflow-hidden", + class_name="relative bg-slate-1 rounded-2xl border border-slate-3 shadow-lg w-full max-w-md mx-auto lg:max-w-none lg:mx-0 overflow-hidden", ), class_name="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-2 max-w-7xl mx-auto items-start", ), From 7e8dc54c7e32e59047ff055aa56ec79e680540b9 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:08:29 -0800 Subject: [PATCH 05/20] Use demo_form dialog for 'Book a Demo' in navbar --- pcweb/components/docpage/navbar/navbar.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py index 44015e63f..da00c279f 100644 --- a/pcweb/components/docpage/navbar/navbar.py +++ b/pcweb/components/docpage/navbar/navbar.py @@ -3,6 +3,7 @@ import reflex as rx import reflex_ui as ui from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL from pcweb.pages.blog import blogs @@ -741,12 +742,18 @@ def new_component_section() -> rx.Component: class_name="desktop-only", ), ui.navigation_menu.item( - render_=ui.button( - "Book a Demo", - size="sm", - variant="primary", - custom_attrs=get_cal_attrs(), - class_name="font-semibold whitespace-nowrap max-xl:hidden", + ui.dialog.root( + ui.dialog.trigger( + ui.button( + "Book a Demo", + size="sm", + variant="primary", + class_name="font-semibold whitespace-nowrap max-xl:hidden", + ), + ), + ui.dialog.content( + demo_form(), + ), ), unstyled=True, class_name="xl:flex hidden", From 3f1891e1e74420d918f2c44b1a81091559958a41 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:14:59 -0800 Subject: [PATCH 06/20] Use demo_form_dialog in navbar --- pcweb/components/docpage/navbar/navbar.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py index da00c279f..7680468c5 100644 --- a/pcweb/components/docpage/navbar/navbar.py +++ b/pcweb/components/docpage/navbar/navbar.py @@ -3,7 +3,7 @@ import reflex as rx import reflex_ui as ui from reflex_ui.blocks.calcom import get_cal_attrs -from reflex_ui.blocks.demo_form import demo_form +from reflex_ui.blocks.demo_form import demo_form, demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL from pcweb.pages.blog import blogs @@ -742,17 +742,12 @@ def new_component_section() -> rx.Component: class_name="desktop-only", ), ui.navigation_menu.item( - ui.dialog.root( - ui.dialog.trigger( - ui.button( - "Book a Demo", - size="sm", - variant="primary", - class_name="font-semibold whitespace-nowrap max-xl:hidden", - ), - ), - ui.dialog.content( - demo_form(), + demo_form_dialog( + trigger=ui.button( + "Book a Demo", + size="sm", + variant="primary", + class_name="font-semibold whitespace-nowrap max-xl:hidden", ), ), unstyled=True, From 98a5e36a7ae46c76933b89a4d2b68c4e2a6fb948 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:23:53 -0800 Subject: [PATCH 07/20] Replace all Cal.com integrations with demo_form_dialog --- blog/2025-12-05-on-premises-deployment.md | 13 +++++----- inspect_demo_form_dialog.py | 14 ++++++++++ pcweb/components/docpage/navbar/navbar.py | 1 - pcweb/pages/databricks/views/video.py | 13 +++++----- pcweb/pages/demo/header.py | 13 +++++----- pcweb/pages/gallery/apps.py | 13 +++++----- pcweb/pages/landing/views/start_building.py | 13 +++++----- pcweb/pages/pricing/faq.py | 13 +++++----- pcweb/pages/pricing/plan_cards.py | 15 ++++++----- pcweb/pages/pricing/slider_calculator.py | 13 +++++----- pcweb/pages/pricing/table.py | 13 +++++----- pcweb/pages/use_cases/common/final_section.py | 15 ++++++----- pcweb/pages/use_cases/common/hero.py | 26 ++++++++++--------- pcweb/whitelist.py | 2 +- 14 files changed, 101 insertions(+), 76 deletions(-) create mode 100644 inspect_demo_form_dialog.py diff --git a/blog/2025-12-05-on-premises-deployment.md b/blog/2025-12-05-on-premises-deployment.md index 3d0fd3fc2..49b12a531 100644 --- a/blog/2025-12-05-on-premises-deployment.md +++ b/blog/2025-12-05-on-premises-deployment.md @@ -33,7 +33,7 @@ import reflex as rx import reflex_ui as ui from pcweb import pages, constants from reflex_image_zoom import image_zoom -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog ``` For organizations with strict security, compliance, or data sovereignty requirements, building applications on your own infrastructure isn't just a preference — it's a necessity. With Reflex Enterprise, you can now run **Reflex Build**—our AI-powered app builder—on-premises or in your own private cloud, giving you complete control over your development environment while maintaining all the productivity benefits of building apps with AI. You can securely hook up with all your company data sources, internal services, documentation, databases, and APIs—all within your own infrastructure. @@ -150,11 +150,12 @@ If you're interested in deploying Reflex on-premises, book a demo to discuss you ```python eval rx.el.div( - ui.button( - "Book a Demo", - variant="primary", - class_name="font-semibold", - custom_attrs=get_cal_attrs(), + demo_form_dialog( + trigger=ui.button( + "Book a Demo", + variant="primary", + class_name="font-semibold", + ), ), class_name="flex justify-center items-center my-8", ) diff --git a/inspect_demo_form_dialog.py b/inspect_demo_form_dialog.py new file mode 100644 index 000000000..a237a6ab1 --- /dev/null +++ b/inspect_demo_form_dialog.py @@ -0,0 +1,14 @@ +try: + from reflex_ui.blocks.demo_form import demo_form_dialog + print("Found reflex_ui.blocks.demo_form.demo_form_dialog") + import inspect + print(inspect.signature(demo_form_dialog)) +except ImportError as e: + print(f"Could not import reflex_ui.blocks.demo_form.demo_form_dialog: {e}") + +try: + from reflex_ui.blocks.demo_form import DemoFormDialog + print("Found reflex_ui.blocks.demo_form.DemoFormDialog") +except ImportError as e: + print(f"Could not import reflex_ui.blocks.demo_form.DemoFormDialog: {e}") + diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py index 7680468c5..98258341d 100644 --- a/pcweb/components/docpage/navbar/navbar.py +++ b/pcweb/components/docpage/navbar/navbar.py @@ -2,7 +2,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.calcom import get_cal_attrs from reflex_ui.blocks.demo_form import demo_form, demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL diff --git a/pcweb/pages/databricks/views/video.py b/pcweb/pages/databricks/views/video.py index cb4d867be..e4e356f39 100644 --- a/pcweb/pages/databricks/views/video.py +++ b/pcweb/pages/databricks/views/video.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog VIDEO_URL = "https://youtu.be/Hy3uhBVRdtk?si=Z5szIyInkBfeG2lk&t=92s" @@ -37,11 +37,12 @@ def text() -> rx.Component: ), class_name="text-slate-12 lg:text-3xl text-2xl font-semibold max-w-[57rem]", ), - ui.button( - "Get Personalized Walkthrough", - size="lg", - class_name="w-fit font-semibold mr-auto rounded-[0.625rem]", - custom_attrs=get_cal_attrs(), + demo_form_dialog( + trigger=ui.button( + "Get Personalized Walkthrough", + size="lg", + class_name="w-fit font-semibold mr-auto rounded-[0.625rem]", + ), ), class_name="flex flex-col gap-6 items-start justify-center lg:py-20 py-8 px-10", ) diff --git a/pcweb/pages/demo/header.py b/pcweb/pages/demo/header.py index 36194ed16..618cf5f0c 100644 --- a/pcweb/pages/demo/header.py +++ b/pcweb/pages/demo/header.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.pages.framework.views.companies import pricing_page_companies @@ -19,11 +19,12 @@ def custom_quote_form() -> rx.Component: class_name="text-slate-11 text-md leading-relaxed font-medium text-center max-w-xl mx-auto", ), rx.el.div( - ui.button( - "Contact Sales", - class_name="font-semibold", - size="lg", - custom_attrs=get_cal_attrs(), + demo_form_dialog( + trigger=ui.button( + "Contact Sales", + class_name="font-semibold", + size="lg", + ), ), class_name="p-3 border border-slate-3 rounded-[1.375rem] border-solid mt-2", ), diff --git a/pcweb/pages/gallery/apps.py b/pcweb/pages/gallery/apps.py index b8f7b88e4..82ec41681 100644 --- a/pcweb/pages/gallery/apps.py +++ b/pcweb/pages/gallery/apps.py @@ -4,7 +4,7 @@ import flexdown import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.button import button, button_with_icon from pcweb.components.code_card import gallery_app_card @@ -200,11 +200,12 @@ def page(document, is_reflex_template: bool) -> rx.Component: *( [ rx.box( - button_with_icon( - "Book a Demo", - icon="new_tab", - custom_attrs=get_cal_attrs(), - class_name="flex-row-reverse gap-2 !w-full", + demo_form_dialog( + trigger=button_with_icon( + "Book a Demo", + icon="new_tab", + class_name="flex-row-reverse gap-2 !w-full", + ), ), class_name="flex justify-center items-center h-full !w-full [&_button]:!w-full", ) diff --git a/pcweb/pages/landing/views/start_building.py b/pcweb/pages/landing/views/start_building.py index 86df9c27e..a973613b9 100644 --- a/pcweb/pages/landing/views/start_building.py +++ b/pcweb/pages/landing/views/start_building.py @@ -1,5 +1,5 @@ import reflex as rx -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.new_button import button @@ -22,11 +22,12 @@ def start_building() -> rx.Component: "Start building with Reflex", class_name="text-secondary-11 text-2xl lg:text-4xl font-semibold text-center", ), - button( - "Contact sales", - size="xl", - class_name="mt-6", - custom_attrs=get_cal_attrs(), + demo_form_dialog( + trigger=button( + "Contact sales", + size="xl", + class_name="mt-6", + ), ), class_name="flex flex-col justify-center items-center lg:mx-auto md:w-full max-w-[64.19rem] lg:border-x border-slate-3 pb-[6.31rem] border-t border-slate-3 py-[6rem] relative z-[1] overflow-hidden isolate w-screen -mx-4", ) diff --git a/pcweb/pages/pricing/faq.py b/pcweb/pages/pricing/faq.py index 5ae0448a4..b537440d1 100644 --- a/pcweb/pages/pricing/faq.py +++ b/pcweb/pages/pricing/faq.py @@ -1,5 +1,5 @@ import reflex as rx -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.button import button @@ -28,12 +28,13 @@ def sales_button() -> rx.Component: return rx.el.div( rx.el.div( glow(), - button( - "Need more help? Contact sales", - variant="secondary", - class_name="!text-slate-11 !font-semibold !text-sm w-fit", + demo_form_dialog( + trigger=button( + "Need more help? Contact sales", + variant="secondary", + class_name="!text-slate-11 !font-semibold !text-sm w-fit", + ), ), - custom_attrs=get_cal_attrs(), ), class_name="self-center relative", ) diff --git a/pcweb/pages/pricing/plan_cards.py b/pcweb/pages/pricing/plan_cards.py index e7110ed06..901e19164 100644 --- a/pcweb/pages/pricing/plan_cards.py +++ b/pcweb/pages/pricing/plan_cards.py @@ -4,7 +4,7 @@ import reflex as rx import reflex_ui as ui from reflex.experimental.client_state import ClientStateVar -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.components.number_flow import number_flow @@ -262,12 +262,13 @@ def pricing_cards() -> rx.Component: Feature("QuestionIcon", "Dedicated Support Channel"), Feature("CustomerSupportIcon", "Onboarding support"), ], - ui.button( - "Contact sales", - variant="primary", - size="lg", - custom_attrs=get_cal_attrs(), - class_name="w-full font-semibold", + demo_form_dialog( + trigger=ui.button( + "Contact sales", + variant="primary", + size="lg", + class_name="w-full font-semibold", + ), ), ), class_name=ui.cn( diff --git a/pcweb/pages/pricing/slider_calculator.py b/pcweb/pages/pricing/slider_calculator.py index d753c07b6..9689c3d0b 100644 --- a/pcweb/pages/pricing/slider_calculator.py +++ b/pcweb/pages/pricing/slider_calculator.py @@ -3,7 +3,7 @@ import reflex as rx import reflex_ui as ui from reflex.experimental.client_state import ClientStateVar -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.constants import PRO_TIERS_TABLE, REFLEX_CLOUD_URL from pcweb.pages.pricing.calculator import ( @@ -269,11 +269,12 @@ def total_credits_card() -> rx.Component: ), rx.cond( get_is_enterprise_tier(MachineState.messages_tier_index), - ui.button( - "Contact Sales", - size="sm", - custom_attrs=get_cal_attrs(), - class_name="font-semibold w-full", + demo_form_dialog( + trigger=ui.button( + "Contact Sales", + size="sm", + class_name="font-semibold w-full", + ), ), ui.link( render_=ui.button( diff --git a/pcweb/pages/pricing/table.py b/pcweb/pages/pricing/table.py index c5af452c3..f2dfe3e7f 100644 --- a/pcweb/pages/pricing/table.py +++ b/pcweb/pages/pricing/table.py @@ -2,7 +2,7 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL @@ -395,11 +395,12 @@ def header_item(text: str, button: rx.Component) -> rx.Component: # Enterprise column with button header_item( "Enterprise", - ui.button( - "Get a demo", - variant="primary", - class_name="font-semibold w-full", - custom_attrs=get_cal_attrs(), + demo_form_dialog( + trigger=ui.button( + "Get a demo", + variant="primary", + class_name="font-semibold w-full", + ), ), ), class_name=ui.cn( diff --git a/pcweb/pages/use_cases/common/final_section.py b/pcweb/pages/use_cases/common/final_section.py index 1eb40bbf2..761572c1a 100644 --- a/pcweb/pages/use_cases/common/final_section.py +++ b/pcweb/pages/use_cases/common/final_section.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL from pcweb.pages.pricing.plan_cards import radial_circle @@ -41,12 +41,13 @@ def left_content(h1: str, description: str) -> rx.Component: description, class_name="text-m-slate-11 dark:text-m-slate-9 text-sm font-medium", ), - ui.button( - ui.icon("Calendar04Icon"), - "Schedule a demo", - size="lg", - custom_attrs=get_cal_attrs(), - class_name="w-fit font-semibold mt-4", + demo_form_dialog( + trigger=ui.button( + ui.icon("Calendar04Icon"), + "Schedule a demo", + size="lg", + class_name="w-fit font-semibold mt-4", + ), ), class_name="flex flex-col gap-4 items-start justify-center lg:py-20 py-8 lg:pl-20 pl-8 lg:pr-[7.5rem] pr-8 relative overflow-hidden min-h-fit", ) diff --git a/pcweb/pages/use_cases/common/hero.py b/pcweb/pages/use_cases/common/hero.py index 1eae0cca3..351313e61 100644 --- a/pcweb/pages/use_cases/common/hero.py +++ b/pcweb/pages/use_cases/common/hero.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.calcom import get_cal_attrs +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.numbers_pattern import numbers_pattern @@ -22,18 +22,20 @@ def left_content( class_name="text-m-slate-11 dark:text-slate-9 text-base font-medium", ), rx.el.div( - ui.button( - button_1, - size="lg", - class_name="font-semibold", - custom_attrs=get_cal_attrs(), + demo_form_dialog( + trigger=ui.button( + button_1, + size="lg", + class_name="font-semibold", + ), ), - ui.button( - button_2, - size="lg", - variant="outline", - class_name="font-semibold text-m-slate-11 dark:text-slate-9 border-m-slate-5 dark:border-m-slate-12", - custom_attrs=get_cal_attrs(), + demo_form_dialog( + trigger=ui.button( + button_2, + size="lg", + variant="outline", + class_name="font-semibold text-m-slate-11 dark:text-slate-9 border-m-slate-5 dark:border-m-slate-12", + ), ), class_name="flex lg:flex-row flex-col items-center max-lg:justify-center gap-4 mt-2", ), diff --git a/pcweb/whitelist.py b/pcweb/whitelist.py index 816e080b9..7db549a11 100644 --- a/pcweb/whitelist.py +++ b/pcweb/whitelist.py @@ -10,7 +10,7 @@ - Incorrect: WHITELISTED_PAGES = ["/docs/getting-started/introduction/"] """ -WHITELISTED_PAGES = [] +WHITELISTED_PAGES = ["/pricing"] def _check_whitelisted_path(path: str): From b8df6f5a82758fc973b07ebc32c363a9959202bf Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:27:27 -0800 Subject: [PATCH 08/20] Customize demo_form to redirect to thank-you page --- blog/2025-12-05-on-premises-deployment.md | 2 +- check_import.py | 1 + pcweb/components/demo_form.py | 552 ++++++++++++++++++ pcweb/components/docpage/navbar/navbar.py | 2 +- pcweb/pages/databricks/views/video.py | 2 +- pcweb/pages/demo/header.py | 2 +- pcweb/pages/gallery/apps.py | 2 +- pcweb/pages/landing/views/start_building.py | 2 +- pcweb/pages/pricing/enterprise_demo_form.py | 2 +- pcweb/pages/pricing/faq.py | 2 +- pcweb/pages/pricing/plan_cards.py | 2 +- pcweb/pages/pricing/slider_calculator.py | 2 +- pcweb/pages/pricing/table.py | 2 +- pcweb/pages/sales.py | 2 +- pcweb/pages/use_cases/common/final_section.py | 2 +- pcweb/pages/use_cases/common/hero.py | 2 +- 16 files changed, 567 insertions(+), 14 deletions(-) create mode 100644 check_import.py create mode 100644 pcweb/components/demo_form.py diff --git a/blog/2025-12-05-on-premises-deployment.md b/blog/2025-12-05-on-premises-deployment.md index 49b12a531..8a42d9a2f 100644 --- a/blog/2025-12-05-on-premises-deployment.md +++ b/blog/2025-12-05-on-premises-deployment.md @@ -33,7 +33,7 @@ import reflex as rx import reflex_ui as ui from pcweb import pages, constants from reflex_image_zoom import image_zoom -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog ``` For organizations with strict security, compliance, or data sovereignty requirements, building applications on your own infrastructure isn't just a preference — it's a necessity. With Reflex Enterprise, you can now run **Reflex Build**—our AI-powered app builder—on-premises or in your own private cloud, giving you complete control over your development environment while maintaining all the productivity benefits of building apps with AI. You can securely hook up with all your company data sources, internal services, documentation, databases, and APIs—all within your own infrastructure. diff --git a/check_import.py b/check_import.py new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/check_import.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pcweb/components/demo_form.py b/pcweb/components/demo_form.py new file mode 100644 index 000000000..c47307a4b --- /dev/null +++ b/pcweb/components/demo_form.py @@ -0,0 +1,552 @@ +"""Demo form component for collecting user information and scheduling enterprise calls. + +This module provides a comprehensive demo form that validates company emails, +sends data to PostHog and Slack, and redirects users to appropriate Cal.com links +based on company size. +""" + +import asyncio +import os +import urllib.parse +import uuid +from collections.abc import Sequence +from dataclasses import asdict, dataclass +from typing import Any + +import httpx +import reflex as rx +from reflex.experimental.client_state import ClientStateVar +from reflex.utils.console import log + +import reflex_ui as ui + +demo_form_error_message = ClientStateVar.create("demo_form_error_message", "") +is_sending_demo_form = ClientStateVar.create("is_sending_demo_form", False) +demo_form_open_cs = ClientStateVar.create("demo_form_open", False) + +COMMONROOM_DESTINATION_ID = os.getenv("COMMONROOM_DESTINATION_ID", "") +COMMONROOM_API_TOKEN = os.getenv("COMMONROOM_API_TOKEN", "") +CAL_REQUEST_DEMO_URL = os.getenv( + "CAL_REQUEST_DEMO_URL", "https://cal.com/team/reflex/reflex-intro-call" +) +JH_CAL_URL = os.getenv("JH_CAL_URL", "https://cal.com/team/reflex/demo-with-reflex") +INTRO_CAL_URL = os.getenv("INTRO_CAL_URL", "https://cal.com/team/reflex/reflex-intro") +CAL_ENTERPRISE_FOLLOW_UP_URL = os.getenv( + "CAL_ENTERPRISE_FOLLOW_UP_URL", + "https://cal.com/team/reflex/reflex-intro", +) +SLACK_DEMO_WEBHOOK_URL = os.getenv("SLACK_DEMO_WEBHOOK_URL", "") +POSTHOG_API_KEY = os.getenv("POSTHOG_API_KEY", "") + + +@dataclass(kw_only=True) +class PosthogEvent: + """Base event structure.""" + + distinct_id: str + + def to_dict(self) -> dict[str, Any]: + """Convert the event instance to a dictionary representation. + + Returns: + A dictionary containing all the event data. + """ + return asdict(self) + + +@dataclass +class DemoEvent(PosthogEvent): + """Event for demo booking.""" + + first_name: str + last_name: str + company_email: str + job_title: str + company_name: str + num_employees: str + internal_tools: str + technical_level: str + referral_source: str + + +def input_field( + label: str, + placeholder: str, + name: str, + type: str = "text", + required: bool = False, +) -> rx.Component: + """Create a labeled input field component. + + Args: + label: The label text to display above the input + placeholder: Placeholder text for the input + name: The name attribute for the input field + type: The input type (text, email, tel, etc.) + required: Whether the field is required + + Returns: + A Reflex component containing the labeled input field + """ + return rx.el.div( + rx.el.label( + label + (" *" if required else ""), + class_name="block text-sm font-medium text-secondary-12", + ), + ui.input( + placeholder=placeholder, + name=name, + type=type, + required=required, + max_length=255, + class_name="w-full", + ), + class_name="flex flex-col gap-1.5", + ) + + +def text_area_field( + label: str, placeholder: str, name: str, required: bool = False +) -> rx.Component: + """Create a labeled textarea field component. + + Args: + label: The label text to display above the textarea + placeholder: Placeholder text for the textarea + name: The name attribute for the textarea field + required: Whether the field is required + + Returns: + A Reflex component containing the labeled textarea field + """ + return rx.el.div( + rx.el.label(label, class_name="block text-sm font-medium text-secondary-12"), + ui.textarea( + placeholder=placeholder, + name=name, + required=required, + class_name="w-full min-h-14", + max_length=800, + ), + class_name="flex flex-col gap-1.5", + ) + + +def select_field( + label: str, + name: str, + items: list[str], + required: bool = False, +) -> rx.Component: + """Create a labeled select field component. + + Args: + label: The label text to display above the select + name: The name attribute for the select field + items: List of options to display in the select + required: Whether the field is required + + Returns: + A Reflex component containing the labeled select field + """ + return rx.el.div( + rx.el.label( + label + (" *" if required else ""), + class_name="block text-xs lg:text-sm font-medium text-secondary-12 truncate min-w-0", + ), + ui.select( + default_value="Select", + name=name, + items=items, + required=required, + class_name="w-full", + ), + class_name="flex flex-col gap-1.5 min-w-0", + ) + + +def is_small_company(num_employees: str) -> bool: + """Check if company up to 5 employees.""" + return num_employees in ["1", "2-5"] + + +def check_if_company_email(email: str) -> bool: + """Check if an email address is from a company domain (not a personal email provider). + + Args: + email: The email address to check + + Returns: + True if it's likely a company email, False if it's from a personal provider + """ + if not email or "@" not in email: + return False + + domain = email.split("@")[-1].lower() + + # List of common personal email providers + personal_domains = { + "gmail.com", + "outlook.com", + "hotmail.com", + "yahoo.com", + "icloud.com", + "aol.com", + "protonmail.com", + "proton.me", + "mail.com", + "yandex.com", + "zoho.com", + "live.com", + "msn.com", + "me.com", + "mac.com", + "googlemail.com", + "yahoo.co.uk", + "yahoo.ca", + "yahoo.co.in", + "outlook.co.uk", + "hotmail.co.uk", + } + + return domain not in personal_domains and ".edu" not in domain + + +def check_if_default_value_is_selected(value: str) -> bool: + """Check if the default value is selected.""" + return value.strip() != "Select" + + +class DemoFormStateUI(rx.State): + """State for handling demo form submissions and integrations.""" + + @rx.event(background=True) + async def on_submit(self, form_data: dict[str, Any]): + """Handle form submission with validation and routing logic. + + Validates company email, sends data to PostHog and Slack, + then redirects to appropriate Cal.com link based on company size. + + Args: + form_data: Form data dictionary containing user inputs + """ + if not check_if_company_email(form_data.get("email", "")): + yield rx.set_focus("email") + yield rx.toast.error( + "Please enter a valid company email - gmails, aol, me, etc are not allowed", + position="top-center", + ) + yield demo_form_error_message.push( + "Please enter a valid company email - gmails, aol, me, etc are not allowed" + ) + return + # Check if the has selected a number of employees + if not check_if_default_value_is_selected( + form_data.get("number_of_employees", "") + ): + yield rx.toast.error( + "Please select a number of employees", + position="top-center", + ) + yield demo_form_error_message.push("Please select a number of employees") + return + # Check if the has entered a referral source + if not check_if_default_value_is_selected( + form_data.get("how_did_you_hear_about_us", "") + ): + yield rx.toast.error( + "Please select how did you hear about us", + position="top-center", + ) + yield demo_form_error_message.push( + "Please select how did you hear about us" + ) + return + # Check if the has entered a technical level + if not check_if_default_value_is_selected(form_data.get("technical_level", "")): + yield rx.set_focus("technical_level") + yield rx.toast.error( + "Please select a technical level", + position="top-center", + ) + yield demo_form_error_message.push("Please select a technical level") + return + yield is_sending_demo_form.push(True) + # Send to PostHog and Slack for all submissions + yield DemoFormStateUI.send_demo_event(form_data) + # Send data to Google Ads conversion tracking + yield rx.call_script("gtag_report_conversion()") + + notes_content = f""" +Name: {form_data.get("first_name", "")} {form_data.get("last_name", "")} +Business Email: {form_data.get("email", "")} +Job Title: {form_data.get("job_title", "")} +Company Name: {form_data.get("company_name", "")} +Number of Employees: {form_data.get("number_of_employees", "")} +Internal Tools to Build: {form_data.get("internal_tools", "")} +Technical Level: {form_data.get("technical_level", "")} +How they heard about Reflex: {form_data.get("how_did_you_hear_about_us", "")}""" + params = { + "email": form_data.get("email", ""), + "name": f"{form_data.get('first_name', '')} {form_data.get('last_name', '')}", + "notes": notes_content, + } + + query_string = urllib.parse.urlencode(params) + technical_level = form_data.get("technical_level", "") + + # Route based on company size and technical level + # if is_small_company(form_data.get("number_of_employees", "")): + # # Small companies (up to 5 employees) always go to JH_CAL_URL + # cal_url = JH_CAL_URL + # else: + # # Large companies (more than 5 employees) + # if technical_level == "Non-technical": + # cal_url = JH_CAL_URL + # else: # Neutral or Technical + # cal_url = INTRO_CAL_URL + + # cal_url_with_params = f"{cal_url}?{query_string}" + yield [is_sending_demo_form.push(False), rx.redirect("/thank-you")] + + @rx.event(background=True) + async def send_demo_event(self, form_data: dict[str, Any]): + """Create and send demo event to PostHog and Slack. + + Converts form data into a DemoEvent and sends to both analytics + platforms. Logs errors but doesn't raise exceptions. + + Args: + form_data: Form data dictionary containing user inputs + """ + first_name = form_data.get("first_name", "") + last_name = form_data.get("last_name", "") + demo_event = DemoEvent( + distinct_id=f"{first_name} {last_name}", + first_name=first_name, + last_name=last_name, + company_email=form_data.get("email", ""), + job_title=form_data.get("job_title", ""), + company_name=form_data.get("company_name", ""), + num_employees=form_data.get("number_of_employees", ""), + internal_tools=form_data.get("internal_tools", ""), + technical_level=form_data.get("technical_level", ""), + referral_source=form_data.get("how_did_you_hear_about_us", ""), + ) + + # Send data to PostHog, Common Room, and Slack + await asyncio.gather( + self.send_data_to_posthog(demo_event), + self.send_data_to_common_room(demo_event), + self.send_data_to_slack(demo_event), + ) + + async def send_data_to_posthog(self, event_instance: PosthogEvent): + """Send data to PostHog using class introspection. + + Args: + event_instance: An instance of a PosthogEvent subclass. + + Raises: + httpx.HTTPError: When there is an error sending data to PostHog. + """ + event_data = { + "api_key": POSTHOG_API_KEY, + "event": event_instance.__class__.__name__, + "properties": event_instance.to_dict(), + } + try: + async with httpx.AsyncClient() as client: + response = await client.post( + "https://app.posthog.com/capture/", json=event_data + ) + response.raise_for_status() + except Exception: + log("Error sending data to PostHog") + + async def send_data_to_common_room(self, event_instance: DemoEvent): + """Update CommonRoom with user login information.""" + tags: Sequence[str] = [ + "Requested Demo", + ] + + try: + async with httpx.AsyncClient() as client: + await client.post( + f"https://api.commonroom.io/community/v1/source/{COMMONROOM_DESTINATION_ID}/activity", + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {COMMONROOM_API_TOKEN}", + }, + json={ + "id": "requested_demo", + "activityType": "requested_demo", + "user": { + "id": str(uuid.uuid4()), + "email": event_instance.company_email, + }, + "tags": [ + { + "type": "name", + "name": tag, + } + for tag in tags + ], + }, + ) + except Exception as ex: + log( + f"CommonRoom: Failed to identify user with email {event_instance.company_email}, err: {ex}" + ) + + async def send_data_to_slack(self, event_instance: DemoEvent): + """Send demo form data to Slack webhook. + + Args: + event_instance: An instance of DemoEvent with form data. + """ + slack_payload = { + "technicalLevel": event_instance.technical_level, + "lookingToBuild": event_instance.internal_tools, + "businessEmail": event_instance.company_email, + "howDidYouHear": event_instance.referral_source, + "jobTitle": event_instance.job_title, + "numEmployees": event_instance.num_employees, + "companyName": event_instance.company_name, + "firstName": event_instance.first_name, + "lastName": event_instance.last_name, + } + try: + async with httpx.AsyncClient() as client: + response = await client.post( + SLACK_DEMO_WEBHOOK_URL, + json=slack_payload, + headers={"Content-Type": "application/json"}, + ) + response.raise_for_status() + except Exception as e: + log(f"Error sending data to Slack webhook: {e}") + + +def demo_form(**props) -> rx.Component: + """Create and return the demo form component. + + Builds a complete form with all required fields, validation, + and styling. The form includes personal info, company details, + and preferences. + + Args: + **props: Additional properties to pass to the form component + + Returns: + A Reflex form component with all demo form fields + """ + return rx.el.form( + rx.el.div( + input_field("First name", "John", "first_name", "text", True), + input_field("Last name", "Smith", "last_name", "text", True), + class_name="grid grid-cols-2 gap-4", + ), + input_field("Business Email", "john@company.com", "email", "email", True), + rx.el.div( + input_field("Job title", "CTO", "job_title", "text", True), + input_field("Company name", "Pynecone, Inc.", "company_name", "text", True), + class_name="grid grid-cols-2 gap-4", + ), + text_area_field( + "What are you looking to build? *", + "Please list any apps, requirements, or data sources you plan on using", + "internal_tools", + True, + ), + rx.el.div( + select_field( + "Number of employees?", + "number_of_employees", + ["1", "2-5", "6-10", "11-50", "51-100", "101-500", "500+"], + ), + select_field( + "How did you hear about us?", + "how_did_you_hear_about_us", + [ + "Google Search", + "Social Media", + "Word of Mouth", + "Blog", + "Conference", + "Other", + ], + ), + class_name="grid grid-cols-1 md:grid-cols-2 gap-4", + ), + select_field( + "How technical are you?", + "technical_level", + ["Non-technical", "Neutral", "Technical"], + True, + ), + rx.cond( + demo_form_error_message.value, + rx.el.span( + demo_form_error_message.value, + class_name="text-destructive-10 text-sm font-medium px-2 py-1 rounded-md bg-destructive-3 border border-destructive-4", + ), + ), + ui.button( + "Submit", + type="submit", + class_name="w-full", + loading=is_sending_demo_form.value, + ), + on_submit=DemoFormStateUI.on_submit, + class_name=ui.cn( + "@container flex flex-col lg:gap-6 gap-2 p-6", + props.pop("class_name", ""), + ), + **props, + ) + + +def demo_form_dialog(trigger: rx.Component | None, **props) -> rx.Component: + """Return a demo form dialog container element. + + Args: + trigger: The component that triggers the dialog + **props: Additional properties to pass to the dialog root + + Returns: + A Reflex dialog component containing the demo form + """ + class_name = ui.cn("w-auto", props.pop("class_name", "")) + return ui.dialog.root( + ui.dialog.trigger(render_=trigger), + ui.dialog.portal( + ui.dialog.backdrop(), + ui.dialog.popup( + rx.el.div( + rx.el.div( + rx.el.h1( + "Book a Demo", + class_name="text-xl font-bold text-secondary-12", + ), + ui.dialog.close( + render_=ui.button( + ui.hi("Cancel01Icon"), + variant="ghost", + size="icon-sm", + class_name="text-secondary-11", + ), + ), + class_name="flex flex-row justify-between items-center gap-1 px-6 pt-4 -mb-4", + ), + demo_form(class_name="w-full max-w-md"), + class_name="relative isolate overflow-hidden -m-px w-full max-w-md", + ), + class_name="h-fit mt-1 overflow-hidden w-full max-w-md", + ), + ), + open=demo_form_open_cs.value, + on_open_change=demo_form_open_cs.set_value, + class_name=class_name, + **props, + ) diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py index 98258341d..83164244f 100644 --- a/pcweb/components/docpage/navbar/navbar.py +++ b/pcweb/components/docpage/navbar/navbar.py @@ -2,7 +2,7 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form, demo_form_dialog +from pcweb.components.demo_form import demo_form, demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL from pcweb.pages.blog import blogs diff --git a/pcweb/pages/databricks/views/video.py b/pcweb/pages/databricks/views/video.py index e4e356f39..d6094f9f8 100644 --- a/pcweb/pages/databricks/views/video.py +++ b/pcweb/pages/databricks/views/video.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog VIDEO_URL = "https://youtu.be/Hy3uhBVRdtk?si=Z5szIyInkBfeG2lk&t=92s" diff --git a/pcweb/pages/demo/header.py b/pcweb/pages/demo/header.py index 618cf5f0c..a534a2f3c 100644 --- a/pcweb/pages/demo/header.py +++ b/pcweb/pages/demo/header.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.pages.framework.views.companies import pricing_page_companies diff --git a/pcweb/pages/gallery/apps.py b/pcweb/pages/gallery/apps.py index 82ec41681..e46a40fc5 100644 --- a/pcweb/pages/gallery/apps.py +++ b/pcweb/pages/gallery/apps.py @@ -4,7 +4,7 @@ import flexdown import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.components.button import button, button_with_icon from pcweb.components.code_card import gallery_app_card diff --git a/pcweb/pages/landing/views/start_building.py b/pcweb/pages/landing/views/start_building.py index a973613b9..26c79122e 100644 --- a/pcweb/pages/landing/views/start_building.py +++ b/pcweb/pages/landing/views/start_building.py @@ -1,5 +1,5 @@ import reflex as rx -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.components.new_button import button diff --git a/pcweb/pages/pricing/enterprise_demo_form.py b/pcweb/pages/pricing/enterprise_demo_form.py index 9d9dd41ef..750fcdc55 100644 --- a/pcweb/pages/pricing/enterprise_demo_form.py +++ b/pcweb/pages/pricing/enterprise_demo_form.py @@ -1,5 +1,5 @@ import reflex as rx -from reflex_ui.blocks.demo_form import demo_form +from pcweb.components.demo_form import demo_form from pcweb.pages.framework.views.companies import pricing_page_companies diff --git a/pcweb/pages/pricing/faq.py b/pcweb/pages/pricing/faq.py index b537440d1..5c9b7e48e 100644 --- a/pcweb/pages/pricing/faq.py +++ b/pcweb/pages/pricing/faq.py @@ -1,5 +1,5 @@ import reflex as rx -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.components.button import button diff --git a/pcweb/pages/pricing/plan_cards.py b/pcweb/pages/pricing/plan_cards.py index 901e19164..14a0dd72c 100644 --- a/pcweb/pages/pricing/plan_cards.py +++ b/pcweb/pages/pricing/plan_cards.py @@ -4,7 +4,7 @@ import reflex as rx import reflex_ui as ui from reflex.experimental.client_state import ClientStateVar -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.components.number_flow import number_flow diff --git a/pcweb/pages/pricing/slider_calculator.py b/pcweb/pages/pricing/slider_calculator.py index 9689c3d0b..08b9a5949 100644 --- a/pcweb/pages/pricing/slider_calculator.py +++ b/pcweb/pages/pricing/slider_calculator.py @@ -3,7 +3,7 @@ import reflex as rx import reflex_ui as ui from reflex.experimental.client_state import ClientStateVar -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.constants import PRO_TIERS_TABLE, REFLEX_CLOUD_URL from pcweb.pages.pricing.calculator import ( diff --git a/pcweb/pages/pricing/table.py b/pcweb/pages/pricing/table.py index f2dfe3e7f..8a815e75c 100644 --- a/pcweb/pages/pricing/table.py +++ b/pcweb/pages/pricing/table.py @@ -2,7 +2,7 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL diff --git a/pcweb/pages/sales.py b/pcweb/pages/sales.py index 79aa942ba..aa8ce6ce1 100644 --- a/pcweb/pages/sales.py +++ b/pcweb/pages/sales.py @@ -1,5 +1,5 @@ import reflex as rx -from reflex_ui.blocks.demo_form import demo_form +from pcweb.components.demo_form import demo_form from pcweb.templates.webpage import webpage diff --git a/pcweb/pages/use_cases/common/final_section.py b/pcweb/pages/use_cases/common/final_section.py index 761572c1a..d26594a78 100644 --- a/pcweb/pages/use_cases/common/final_section.py +++ b/pcweb/pages/use_cases/common/final_section.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL from pcweb.pages.pricing.plan_cards import radial_circle diff --git a/pcweb/pages/use_cases/common/hero.py b/pcweb/pages/use_cases/common/hero.py index 351313e61..b356c8a1f 100644 --- a/pcweb/pages/use_cases/common/hero.py +++ b/pcweb/pages/use_cases/common/hero.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form_dialog +from pcweb.components.demo_form import demo_form_dialog from pcweb.components.numbers_pattern import numbers_pattern From 899be50a7bb6cf166ba7f8c79a2ca44d5fdbea10 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:29:22 -0800 Subject: [PATCH 09/20] Revert to using reflex_ui demo_form and remove custom copy --- blog/2025-12-05-on-premises-deployment.md | 2 +- pcweb/components/demo_form.py | 552 ------------------ pcweb/components/docpage/navbar/navbar.py | 2 +- pcweb/pages/databricks/views/video.py | 2 +- pcweb/pages/demo/header.py | 2 +- pcweb/pages/gallery/apps.py | 2 +- pcweb/pages/landing/views/start_building.py | 2 +- pcweb/pages/pricing/enterprise_demo_form.py | 2 +- pcweb/pages/pricing/faq.py | 2 +- pcweb/pages/pricing/plan_cards.py | 2 +- pcweb/pages/pricing/slider_calculator.py | 2 +- pcweb/pages/pricing/table.py | 2 +- pcweb/pages/sales.py | 2 +- pcweb/pages/use_cases/common/final_section.py | 2 +- pcweb/pages/use_cases/common/hero.py | 2 +- 15 files changed, 14 insertions(+), 566 deletions(-) delete mode 100644 pcweb/components/demo_form.py diff --git a/blog/2025-12-05-on-premises-deployment.md b/blog/2025-12-05-on-premises-deployment.md index 8a42d9a2f..49b12a531 100644 --- a/blog/2025-12-05-on-premises-deployment.md +++ b/blog/2025-12-05-on-premises-deployment.md @@ -33,7 +33,7 @@ import reflex as rx import reflex_ui as ui from pcweb import pages, constants from reflex_image_zoom import image_zoom -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog ``` For organizations with strict security, compliance, or data sovereignty requirements, building applications on your own infrastructure isn't just a preference — it's a necessity. With Reflex Enterprise, you can now run **Reflex Build**—our AI-powered app builder—on-premises or in your own private cloud, giving you complete control over your development environment while maintaining all the productivity benefits of building apps with AI. You can securely hook up with all your company data sources, internal services, documentation, databases, and APIs—all within your own infrastructure. diff --git a/pcweb/components/demo_form.py b/pcweb/components/demo_form.py deleted file mode 100644 index c47307a4b..000000000 --- a/pcweb/components/demo_form.py +++ /dev/null @@ -1,552 +0,0 @@ -"""Demo form component for collecting user information and scheduling enterprise calls. - -This module provides a comprehensive demo form that validates company emails, -sends data to PostHog and Slack, and redirects users to appropriate Cal.com links -based on company size. -""" - -import asyncio -import os -import urllib.parse -import uuid -from collections.abc import Sequence -from dataclasses import asdict, dataclass -from typing import Any - -import httpx -import reflex as rx -from reflex.experimental.client_state import ClientStateVar -from reflex.utils.console import log - -import reflex_ui as ui - -demo_form_error_message = ClientStateVar.create("demo_form_error_message", "") -is_sending_demo_form = ClientStateVar.create("is_sending_demo_form", False) -demo_form_open_cs = ClientStateVar.create("demo_form_open", False) - -COMMONROOM_DESTINATION_ID = os.getenv("COMMONROOM_DESTINATION_ID", "") -COMMONROOM_API_TOKEN = os.getenv("COMMONROOM_API_TOKEN", "") -CAL_REQUEST_DEMO_URL = os.getenv( - "CAL_REQUEST_DEMO_URL", "https://cal.com/team/reflex/reflex-intro-call" -) -JH_CAL_URL = os.getenv("JH_CAL_URL", "https://cal.com/team/reflex/demo-with-reflex") -INTRO_CAL_URL = os.getenv("INTRO_CAL_URL", "https://cal.com/team/reflex/reflex-intro") -CAL_ENTERPRISE_FOLLOW_UP_URL = os.getenv( - "CAL_ENTERPRISE_FOLLOW_UP_URL", - "https://cal.com/team/reflex/reflex-intro", -) -SLACK_DEMO_WEBHOOK_URL = os.getenv("SLACK_DEMO_WEBHOOK_URL", "") -POSTHOG_API_KEY = os.getenv("POSTHOG_API_KEY", "") - - -@dataclass(kw_only=True) -class PosthogEvent: - """Base event structure.""" - - distinct_id: str - - def to_dict(self) -> dict[str, Any]: - """Convert the event instance to a dictionary representation. - - Returns: - A dictionary containing all the event data. - """ - return asdict(self) - - -@dataclass -class DemoEvent(PosthogEvent): - """Event for demo booking.""" - - first_name: str - last_name: str - company_email: str - job_title: str - company_name: str - num_employees: str - internal_tools: str - technical_level: str - referral_source: str - - -def input_field( - label: str, - placeholder: str, - name: str, - type: str = "text", - required: bool = False, -) -> rx.Component: - """Create a labeled input field component. - - Args: - label: The label text to display above the input - placeholder: Placeholder text for the input - name: The name attribute for the input field - type: The input type (text, email, tel, etc.) - required: Whether the field is required - - Returns: - A Reflex component containing the labeled input field - """ - return rx.el.div( - rx.el.label( - label + (" *" if required else ""), - class_name="block text-sm font-medium text-secondary-12", - ), - ui.input( - placeholder=placeholder, - name=name, - type=type, - required=required, - max_length=255, - class_name="w-full", - ), - class_name="flex flex-col gap-1.5", - ) - - -def text_area_field( - label: str, placeholder: str, name: str, required: bool = False -) -> rx.Component: - """Create a labeled textarea field component. - - Args: - label: The label text to display above the textarea - placeholder: Placeholder text for the textarea - name: The name attribute for the textarea field - required: Whether the field is required - - Returns: - A Reflex component containing the labeled textarea field - """ - return rx.el.div( - rx.el.label(label, class_name="block text-sm font-medium text-secondary-12"), - ui.textarea( - placeholder=placeholder, - name=name, - required=required, - class_name="w-full min-h-14", - max_length=800, - ), - class_name="flex flex-col gap-1.5", - ) - - -def select_field( - label: str, - name: str, - items: list[str], - required: bool = False, -) -> rx.Component: - """Create a labeled select field component. - - Args: - label: The label text to display above the select - name: The name attribute for the select field - items: List of options to display in the select - required: Whether the field is required - - Returns: - A Reflex component containing the labeled select field - """ - return rx.el.div( - rx.el.label( - label + (" *" if required else ""), - class_name="block text-xs lg:text-sm font-medium text-secondary-12 truncate min-w-0", - ), - ui.select( - default_value="Select", - name=name, - items=items, - required=required, - class_name="w-full", - ), - class_name="flex flex-col gap-1.5 min-w-0", - ) - - -def is_small_company(num_employees: str) -> bool: - """Check if company up to 5 employees.""" - return num_employees in ["1", "2-5"] - - -def check_if_company_email(email: str) -> bool: - """Check if an email address is from a company domain (not a personal email provider). - - Args: - email: The email address to check - - Returns: - True if it's likely a company email, False if it's from a personal provider - """ - if not email or "@" not in email: - return False - - domain = email.split("@")[-1].lower() - - # List of common personal email providers - personal_domains = { - "gmail.com", - "outlook.com", - "hotmail.com", - "yahoo.com", - "icloud.com", - "aol.com", - "protonmail.com", - "proton.me", - "mail.com", - "yandex.com", - "zoho.com", - "live.com", - "msn.com", - "me.com", - "mac.com", - "googlemail.com", - "yahoo.co.uk", - "yahoo.ca", - "yahoo.co.in", - "outlook.co.uk", - "hotmail.co.uk", - } - - return domain not in personal_domains and ".edu" not in domain - - -def check_if_default_value_is_selected(value: str) -> bool: - """Check if the default value is selected.""" - return value.strip() != "Select" - - -class DemoFormStateUI(rx.State): - """State for handling demo form submissions and integrations.""" - - @rx.event(background=True) - async def on_submit(self, form_data: dict[str, Any]): - """Handle form submission with validation and routing logic. - - Validates company email, sends data to PostHog and Slack, - then redirects to appropriate Cal.com link based on company size. - - Args: - form_data: Form data dictionary containing user inputs - """ - if not check_if_company_email(form_data.get("email", "")): - yield rx.set_focus("email") - yield rx.toast.error( - "Please enter a valid company email - gmails, aol, me, etc are not allowed", - position="top-center", - ) - yield demo_form_error_message.push( - "Please enter a valid company email - gmails, aol, me, etc are not allowed" - ) - return - # Check if the has selected a number of employees - if not check_if_default_value_is_selected( - form_data.get("number_of_employees", "") - ): - yield rx.toast.error( - "Please select a number of employees", - position="top-center", - ) - yield demo_form_error_message.push("Please select a number of employees") - return - # Check if the has entered a referral source - if not check_if_default_value_is_selected( - form_data.get("how_did_you_hear_about_us", "") - ): - yield rx.toast.error( - "Please select how did you hear about us", - position="top-center", - ) - yield demo_form_error_message.push( - "Please select how did you hear about us" - ) - return - # Check if the has entered a technical level - if not check_if_default_value_is_selected(form_data.get("technical_level", "")): - yield rx.set_focus("technical_level") - yield rx.toast.error( - "Please select a technical level", - position="top-center", - ) - yield demo_form_error_message.push("Please select a technical level") - return - yield is_sending_demo_form.push(True) - # Send to PostHog and Slack for all submissions - yield DemoFormStateUI.send_demo_event(form_data) - # Send data to Google Ads conversion tracking - yield rx.call_script("gtag_report_conversion()") - - notes_content = f""" -Name: {form_data.get("first_name", "")} {form_data.get("last_name", "")} -Business Email: {form_data.get("email", "")} -Job Title: {form_data.get("job_title", "")} -Company Name: {form_data.get("company_name", "")} -Number of Employees: {form_data.get("number_of_employees", "")} -Internal Tools to Build: {form_data.get("internal_tools", "")} -Technical Level: {form_data.get("technical_level", "")} -How they heard about Reflex: {form_data.get("how_did_you_hear_about_us", "")}""" - params = { - "email": form_data.get("email", ""), - "name": f"{form_data.get('first_name', '')} {form_data.get('last_name', '')}", - "notes": notes_content, - } - - query_string = urllib.parse.urlencode(params) - technical_level = form_data.get("technical_level", "") - - # Route based on company size and technical level - # if is_small_company(form_data.get("number_of_employees", "")): - # # Small companies (up to 5 employees) always go to JH_CAL_URL - # cal_url = JH_CAL_URL - # else: - # # Large companies (more than 5 employees) - # if technical_level == "Non-technical": - # cal_url = JH_CAL_URL - # else: # Neutral or Technical - # cal_url = INTRO_CAL_URL - - # cal_url_with_params = f"{cal_url}?{query_string}" - yield [is_sending_demo_form.push(False), rx.redirect("/thank-you")] - - @rx.event(background=True) - async def send_demo_event(self, form_data: dict[str, Any]): - """Create and send demo event to PostHog and Slack. - - Converts form data into a DemoEvent and sends to both analytics - platforms. Logs errors but doesn't raise exceptions. - - Args: - form_data: Form data dictionary containing user inputs - """ - first_name = form_data.get("first_name", "") - last_name = form_data.get("last_name", "") - demo_event = DemoEvent( - distinct_id=f"{first_name} {last_name}", - first_name=first_name, - last_name=last_name, - company_email=form_data.get("email", ""), - job_title=form_data.get("job_title", ""), - company_name=form_data.get("company_name", ""), - num_employees=form_data.get("number_of_employees", ""), - internal_tools=form_data.get("internal_tools", ""), - technical_level=form_data.get("technical_level", ""), - referral_source=form_data.get("how_did_you_hear_about_us", ""), - ) - - # Send data to PostHog, Common Room, and Slack - await asyncio.gather( - self.send_data_to_posthog(demo_event), - self.send_data_to_common_room(demo_event), - self.send_data_to_slack(demo_event), - ) - - async def send_data_to_posthog(self, event_instance: PosthogEvent): - """Send data to PostHog using class introspection. - - Args: - event_instance: An instance of a PosthogEvent subclass. - - Raises: - httpx.HTTPError: When there is an error sending data to PostHog. - """ - event_data = { - "api_key": POSTHOG_API_KEY, - "event": event_instance.__class__.__name__, - "properties": event_instance.to_dict(), - } - try: - async with httpx.AsyncClient() as client: - response = await client.post( - "https://app.posthog.com/capture/", json=event_data - ) - response.raise_for_status() - except Exception: - log("Error sending data to PostHog") - - async def send_data_to_common_room(self, event_instance: DemoEvent): - """Update CommonRoom with user login information.""" - tags: Sequence[str] = [ - "Requested Demo", - ] - - try: - async with httpx.AsyncClient() as client: - await client.post( - f"https://api.commonroom.io/community/v1/source/{COMMONROOM_DESTINATION_ID}/activity", - headers={ - "Content-Type": "application/json", - "Authorization": f"Bearer {COMMONROOM_API_TOKEN}", - }, - json={ - "id": "requested_demo", - "activityType": "requested_demo", - "user": { - "id": str(uuid.uuid4()), - "email": event_instance.company_email, - }, - "tags": [ - { - "type": "name", - "name": tag, - } - for tag in tags - ], - }, - ) - except Exception as ex: - log( - f"CommonRoom: Failed to identify user with email {event_instance.company_email}, err: {ex}" - ) - - async def send_data_to_slack(self, event_instance: DemoEvent): - """Send demo form data to Slack webhook. - - Args: - event_instance: An instance of DemoEvent with form data. - """ - slack_payload = { - "technicalLevel": event_instance.technical_level, - "lookingToBuild": event_instance.internal_tools, - "businessEmail": event_instance.company_email, - "howDidYouHear": event_instance.referral_source, - "jobTitle": event_instance.job_title, - "numEmployees": event_instance.num_employees, - "companyName": event_instance.company_name, - "firstName": event_instance.first_name, - "lastName": event_instance.last_name, - } - try: - async with httpx.AsyncClient() as client: - response = await client.post( - SLACK_DEMO_WEBHOOK_URL, - json=slack_payload, - headers={"Content-Type": "application/json"}, - ) - response.raise_for_status() - except Exception as e: - log(f"Error sending data to Slack webhook: {e}") - - -def demo_form(**props) -> rx.Component: - """Create and return the demo form component. - - Builds a complete form with all required fields, validation, - and styling. The form includes personal info, company details, - and preferences. - - Args: - **props: Additional properties to pass to the form component - - Returns: - A Reflex form component with all demo form fields - """ - return rx.el.form( - rx.el.div( - input_field("First name", "John", "first_name", "text", True), - input_field("Last name", "Smith", "last_name", "text", True), - class_name="grid grid-cols-2 gap-4", - ), - input_field("Business Email", "john@company.com", "email", "email", True), - rx.el.div( - input_field("Job title", "CTO", "job_title", "text", True), - input_field("Company name", "Pynecone, Inc.", "company_name", "text", True), - class_name="grid grid-cols-2 gap-4", - ), - text_area_field( - "What are you looking to build? *", - "Please list any apps, requirements, or data sources you plan on using", - "internal_tools", - True, - ), - rx.el.div( - select_field( - "Number of employees?", - "number_of_employees", - ["1", "2-5", "6-10", "11-50", "51-100", "101-500", "500+"], - ), - select_field( - "How did you hear about us?", - "how_did_you_hear_about_us", - [ - "Google Search", - "Social Media", - "Word of Mouth", - "Blog", - "Conference", - "Other", - ], - ), - class_name="grid grid-cols-1 md:grid-cols-2 gap-4", - ), - select_field( - "How technical are you?", - "technical_level", - ["Non-technical", "Neutral", "Technical"], - True, - ), - rx.cond( - demo_form_error_message.value, - rx.el.span( - demo_form_error_message.value, - class_name="text-destructive-10 text-sm font-medium px-2 py-1 rounded-md bg-destructive-3 border border-destructive-4", - ), - ), - ui.button( - "Submit", - type="submit", - class_name="w-full", - loading=is_sending_demo_form.value, - ), - on_submit=DemoFormStateUI.on_submit, - class_name=ui.cn( - "@container flex flex-col lg:gap-6 gap-2 p-6", - props.pop("class_name", ""), - ), - **props, - ) - - -def demo_form_dialog(trigger: rx.Component | None, **props) -> rx.Component: - """Return a demo form dialog container element. - - Args: - trigger: The component that triggers the dialog - **props: Additional properties to pass to the dialog root - - Returns: - A Reflex dialog component containing the demo form - """ - class_name = ui.cn("w-auto", props.pop("class_name", "")) - return ui.dialog.root( - ui.dialog.trigger(render_=trigger), - ui.dialog.portal( - ui.dialog.backdrop(), - ui.dialog.popup( - rx.el.div( - rx.el.div( - rx.el.h1( - "Book a Demo", - class_name="text-xl font-bold text-secondary-12", - ), - ui.dialog.close( - render_=ui.button( - ui.hi("Cancel01Icon"), - variant="ghost", - size="icon-sm", - class_name="text-secondary-11", - ), - ), - class_name="flex flex-row justify-between items-center gap-1 px-6 pt-4 -mb-4", - ), - demo_form(class_name="w-full max-w-md"), - class_name="relative isolate overflow-hidden -m-px w-full max-w-md", - ), - class_name="h-fit mt-1 overflow-hidden w-full max-w-md", - ), - ), - open=demo_form_open_cs.value, - on_open_change=demo_form_open_cs.set_value, - class_name=class_name, - **props, - ) diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py index 83164244f..98258341d 100644 --- a/pcweb/components/docpage/navbar/navbar.py +++ b/pcweb/components/docpage/navbar/navbar.py @@ -2,7 +2,7 @@ import reflex as rx import reflex_ui as ui -from pcweb.components.demo_form import demo_form, demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form, demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL from pcweb.pages.blog import blogs diff --git a/pcweb/pages/databricks/views/video.py b/pcweb/pages/databricks/views/video.py index d6094f9f8..e4e356f39 100644 --- a/pcweb/pages/databricks/views/video.py +++ b/pcweb/pages/databricks/views/video.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog VIDEO_URL = "https://youtu.be/Hy3uhBVRdtk?si=Z5szIyInkBfeG2lk&t=92s" diff --git a/pcweb/pages/demo/header.py b/pcweb/pages/demo/header.py index a534a2f3c..618cf5f0c 100644 --- a/pcweb/pages/demo/header.py +++ b/pcweb/pages/demo/header.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.pages.framework.views.companies import pricing_page_companies diff --git a/pcweb/pages/gallery/apps.py b/pcweb/pages/gallery/apps.py index e46a40fc5..82ec41681 100644 --- a/pcweb/pages/gallery/apps.py +++ b/pcweb/pages/gallery/apps.py @@ -4,7 +4,7 @@ import flexdown import reflex as rx import reflex_ui as ui -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.button import button, button_with_icon from pcweb.components.code_card import gallery_app_card diff --git a/pcweb/pages/landing/views/start_building.py b/pcweb/pages/landing/views/start_building.py index 26c79122e..a973613b9 100644 --- a/pcweb/pages/landing/views/start_building.py +++ b/pcweb/pages/landing/views/start_building.py @@ -1,5 +1,5 @@ import reflex as rx -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.new_button import button diff --git a/pcweb/pages/pricing/enterprise_demo_form.py b/pcweb/pages/pricing/enterprise_demo_form.py index 750fcdc55..9d9dd41ef 100644 --- a/pcweb/pages/pricing/enterprise_demo_form.py +++ b/pcweb/pages/pricing/enterprise_demo_form.py @@ -1,5 +1,5 @@ import reflex as rx -from pcweb.components.demo_form import demo_form +from reflex_ui.blocks.demo_form import demo_form from pcweb.pages.framework.views.companies import pricing_page_companies diff --git a/pcweb/pages/pricing/faq.py b/pcweb/pages/pricing/faq.py index 5c9b7e48e..b537440d1 100644 --- a/pcweb/pages/pricing/faq.py +++ b/pcweb/pages/pricing/faq.py @@ -1,5 +1,5 @@ import reflex as rx -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.button import button diff --git a/pcweb/pages/pricing/plan_cards.py b/pcweb/pages/pricing/plan_cards.py index 14a0dd72c..901e19164 100644 --- a/pcweb/pages/pricing/plan_cards.py +++ b/pcweb/pages/pricing/plan_cards.py @@ -4,7 +4,7 @@ import reflex as rx import reflex_ui as ui from reflex.experimental.client_state import ClientStateVar -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.components.number_flow import number_flow diff --git a/pcweb/pages/pricing/slider_calculator.py b/pcweb/pages/pricing/slider_calculator.py index 08b9a5949..9689c3d0b 100644 --- a/pcweb/pages/pricing/slider_calculator.py +++ b/pcweb/pages/pricing/slider_calculator.py @@ -3,7 +3,7 @@ import reflex as rx import reflex_ui as ui from reflex.experimental.client_state import ClientStateVar -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.constants import PRO_TIERS_TABLE, REFLEX_CLOUD_URL from pcweb.pages.pricing.calculator import ( diff --git a/pcweb/pages/pricing/table.py b/pcweb/pages/pricing/table.py index 8a815e75c..f2dfe3e7f 100644 --- a/pcweb/pages/pricing/table.py +++ b/pcweb/pages/pricing/table.py @@ -2,7 +2,7 @@ import reflex as rx import reflex_ui as ui -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.hosting_banner import HostingBannerState from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL diff --git a/pcweb/pages/sales.py b/pcweb/pages/sales.py index aa8ce6ce1..79aa942ba 100644 --- a/pcweb/pages/sales.py +++ b/pcweb/pages/sales.py @@ -1,5 +1,5 @@ import reflex as rx -from pcweb.components.demo_form import demo_form +from reflex_ui.blocks.demo_form import demo_form from pcweb.templates.webpage import webpage diff --git a/pcweb/pages/use_cases/common/final_section.py b/pcweb/pages/use_cases/common/final_section.py index d26594a78..761572c1a 100644 --- a/pcweb/pages/use_cases/common/final_section.py +++ b/pcweb/pages/use_cases/common/final_section.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL from pcweb.pages.pricing.plan_cards import radial_circle diff --git a/pcweb/pages/use_cases/common/hero.py b/pcweb/pages/use_cases/common/hero.py index b356c8a1f..351313e61 100644 --- a/pcweb/pages/use_cases/common/hero.py +++ b/pcweb/pages/use_cases/common/hero.py @@ -1,6 +1,6 @@ import reflex as rx import reflex_ui as ui -from pcweb.components.demo_form import demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.components.numbers_pattern import numbers_pattern From a7f28ad3ab0a713ad31c159cbc61406d95d16f73 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:30:14 -0800 Subject: [PATCH 10/20] Configure environment variables to redirect demo form to /thank-you --- pcweb/pcweb.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pcweb/pcweb.py b/pcweb/pcweb.py index 6943dd012..ab38608e8 100644 --- a/pcweb/pcweb.py +++ b/pcweb/pcweb.py @@ -3,6 +3,12 @@ import os import sys +# Set Cal.com redirect URLs to /thank-you +os.environ["JH_CAL_URL"] = "/thank-you" +os.environ["INTRO_CAL_URL"] = "/thank-you" +os.environ["CAL_REQUEST_DEMO_URL"] = "/thank-you" +os.environ["CAL_ENTERPRISE_FOLLOW_UP_URL"] = "/thank-you" + import reflex as rx import reflex_enterprise as rxe from reflex_ui.blocks.calcom import calcom_popup_embed From a60ff7249749cb477762055f5fdb8ca36315f5ab Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:34:01 -0800 Subject: [PATCH 11/20] Remove postog_metrics.py --- pcweb/telemetry/postog_metrics.py | 93 ------------------------------- 1 file changed, 93 deletions(-) delete mode 100644 pcweb/telemetry/postog_metrics.py diff --git a/pcweb/telemetry/postog_metrics.py b/pcweb/telemetry/postog_metrics.py deleted file mode 100644 index 60e0c5b73..000000000 --- a/pcweb/telemetry/postog_metrics.py +++ /dev/null @@ -1,93 +0,0 @@ -import contextlib -from dataclasses import asdict, dataclass -from typing import Any - -import httpx -from posthog import Posthog -from reflex.utils.console import log - -from pcweb.constants import POSTHOG_API_KEY, SLACK_DEMO_WEBHOOK_URL - -with contextlib.suppress(Exception): - posthog = Posthog(POSTHOG_API_KEY, host="https://us.i.posthog.com") - - -@dataclass(kw_only=True) -class PosthogEvent: - """Base event structure.""" - - distinct_id: str - - def to_dict(self) -> dict[str, Any]: - return asdict(self) - - -@dataclass -class DemoEvent(PosthogEvent): - """Event for demo booking.""" - - first_name: str - last_name: str - company_email: str - linkedin_url: str - job_title: str - company_name: str - num_employees: str - internal_tools: str - referral_source: str - phone_number: str = "" - - -async def send_data_to_posthog(event_instance: PosthogEvent): - """Send data to PostHog using class introspection. - - Args: - event_instance: An instance of a PosthogEvent subclass. - - Raises: - httpx.HTTPError: When there is an error sending data to PostHog. - """ - event_data = { - "api_key": POSTHOG_API_KEY, - "event": event_instance.__class__.__name__, - "properties": event_instance.to_dict(), - } - try: - async with httpx.AsyncClient() as client: - response = await client.post( - "https://app.posthog.com/capture/", json=event_data - ) - response.raise_for_status() - except Exception: - log("Error sending data to PostHog") - - -async def send_data_to_slack(event_instance: DemoEvent): - """Send demo form data to Slack webhook. - - Args: - event_instance: An instance of DemoEvent with form data. - """ - slack_payload = { - "lookingToBuild": event_instance.internal_tools, - "businessEmail": event_instance.company_email, - "howDidYouHear": event_instance.referral_source, - "linkedinUrl": event_instance.linkedin_url, - "jobTitle": event_instance.job_title, - "numEmployees": event_instance.num_employees, - "companyName": event_instance.company_name, - "firstName": event_instance.first_name, - "lastName": event_instance.last_name, - "phoneNumber": event_instance.phone_number, - } - - try: - async with httpx.AsyncClient() as client: - response = await client.post( - SLACK_DEMO_WEBHOOK_URL, - json=slack_payload, - headers={"Content-Type": "application/json"}, - ) - response.raise_for_status() - except Exception as e: - log(f"Error sending data to Slack webhook: {e}") From 5697aac1296e830ab9a940b13b9378f173826352 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:35:13 -0800 Subject: [PATCH 12/20] Reset whitelist to build all pages --- pcweb/whitelist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcweb/whitelist.py b/pcweb/whitelist.py index 7db549a11..816e080b9 100644 --- a/pcweb/whitelist.py +++ b/pcweb/whitelist.py @@ -10,7 +10,7 @@ - Incorrect: WHITELISTED_PAGES = ["/docs/getting-started/introduction/"] """ -WHITELISTED_PAGES = ["/pricing"] +WHITELISTED_PAGES = [] def _check_whitelisted_path(path: str): From bafcd193612b9272571317a7b95551c1b56415dd Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:45:07 -0800 Subject: [PATCH 13/20] Cleanup remaining test files --- check_import.py | 1 - inspect_demo_form_dialog.py | 14 -------------- pcweb/constants.py | 3 --- pcweb/telemetry/pixels.py | 10 ---------- 4 files changed, 28 deletions(-) delete mode 100644 check_import.py delete mode 100644 inspect_demo_form_dialog.py diff --git a/check_import.py b/check_import.py deleted file mode 100644 index 0519ecba6..000000000 --- a/check_import.py +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/inspect_demo_form_dialog.py b/inspect_demo_form_dialog.py deleted file mode 100644 index a237a6ab1..000000000 --- a/inspect_demo_form_dialog.py +++ /dev/null @@ -1,14 +0,0 @@ -try: - from reflex_ui.blocks.demo_form import demo_form_dialog - print("Found reflex_ui.blocks.demo_form.demo_form_dialog") - import inspect - print(inspect.signature(demo_form_dialog)) -except ImportError as e: - print(f"Could not import reflex_ui.blocks.demo_form.demo_form_dialog: {e}") - -try: - from reflex_ui.blocks.demo_form import DemoFormDialog - print("Found reflex_ui.blocks.demo_form.DemoFormDialog") -except ImportError as e: - print(f"Could not import reflex_ui.blocks.demo_form.DemoFormDialog: {e}") - diff --git a/pcweb/constants.py b/pcweb/constants.py index 80f9745d9..8ec69e904 100644 --- a/pcweb/constants.py +++ b/pcweb/constants.py @@ -99,9 +99,6 @@ REFLEX_DOMAIN = "reflex.dev" TWITTER_CREATOR = "@getreflex" -# Posthog -POSTHOG_API_KEY = os.getenv("POSTHOG_API_KEY") - SLACK_DEMO_WEBHOOK_URL: str = os.environ.get("SLACK_DEMO_WEBHOOK_URL") # Enable free tier flag diff --git a/pcweb/telemetry/pixels.py b/pcweb/telemetry/pixels.py index 95d81fff0..4c2d3bfb2 100644 --- a/pcweb/telemetry/pixels.py +++ b/pcweb/telemetry/pixels.py @@ -2,11 +2,7 @@ import reflex as rx from reflex_ui.blocks.telemetry import ( - get_clearbit_trackers, - get_common_room_trackers, get_google_analytics_trackers, - get_posthog_trackers, - get_rb2b_trackers, get_unify_trackers, gtag_report_conversion, ) @@ -15,15 +11,9 @@ def get_pixel_website_trackers() -> list[rx.Component]: """Get the pixel trackers for the website.""" return [ - get_common_room_trackers(site_id="b608b3c3-5dea-4365-b685-6b6635c7fda5"), *get_google_analytics_trackers(tracking_id="G-4T7C8ZD9TR"), gtag_report_conversion( conversion_id_and_label="AW-11360851250/ASB4COvpisIbELKqo6kq" ), - get_clearbit_trackers(public_key="pk_3d711a6e26de5ddb47443d8db170d506"), - get_posthog_trackers( - project_id="phc_A0MAR0wCGhXrizWmowRZcYqyZ8PMhPPQW06KEwD43aC" - ), - *get_rb2b_trackers(), get_unify_trackers(), ] From 1c8335747889c67cad2894a00153050b07a5af62 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:47:42 -0800 Subject: [PATCH 14/20] Remove manual environment variable configuration for Cal.com URLs --- pcweb/pcweb.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pcweb/pcweb.py b/pcweb/pcweb.py index ab38608e8..6943dd012 100644 --- a/pcweb/pcweb.py +++ b/pcweb/pcweb.py @@ -3,12 +3,6 @@ import os import sys -# Set Cal.com redirect URLs to /thank-you -os.environ["JH_CAL_URL"] = "/thank-you" -os.environ["INTRO_CAL_URL"] = "/thank-you" -os.environ["CAL_REQUEST_DEMO_URL"] = "/thank-you" -os.environ["CAL_ENTERPRISE_FOLLOW_UP_URL"] = "/thank-you" - import reflex as rx import reflex_enterprise as rxe from reflex_ui.blocks.calcom import calcom_popup_embed From 3dda6855b5e47baef365d1265a55272d298649b1 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:48:12 -0800 Subject: [PATCH 15/20] Add Default.com tracking script to head --- pcweb/pcweb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pcweb/pcweb.py b/pcweb/pcweb.py index 6943dd012..d73ef7c11 100644 --- a/pcweb/pcweb.py +++ b/pcweb/pcweb.py @@ -51,6 +51,9 @@ href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap", rel="stylesheet", ), + rx.el.script( + """!function(e,t){var _=0;e.__default__=e.__default__||{},e.__default__.form_id=268792,e.__default__.team_id=654,e.__default__.listenToIds=[],function e(){var o=t.createElement("script");o.async=!0,o.src="https://import-cdn.default.com",o.onload=function(){!0,console.info("[Default.com] Powered by Default.com")},o.onerror=function(){++_<=3&&setTimeout(e,1e3*_)},t.head.appendChild(o)}()}(window,document);""" + ), ], ) From bd325421a743ebaaeaaa529c883fa6a2adc74017 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:55:40 -0800 Subject: [PATCH 16/20] Fix unused import in navbar.py --- pcweb/components/docpage/navbar/navbar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py index 98258341d..a439f2138 100644 --- a/pcweb/components/docpage/navbar/navbar.py +++ b/pcweb/components/docpage/navbar/navbar.py @@ -2,7 +2,7 @@ import reflex as rx import reflex_ui as ui -from reflex_ui.blocks.demo_form import demo_form, demo_form_dialog +from reflex_ui.blocks.demo_form import demo_form_dialog from pcweb.constants import REFLEX_BUILD_URL, REFLEX_CLOUD_URL from pcweb.pages.blog import blogs From 133337a2416fced996c785d74d89105df6d4b1f7 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:56:55 -0800 Subject: [PATCH 17/20] Remove unused posthog dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9c47feb24..1045dc4ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,6 @@ dependencies = [ "openai", "pandas", "plotly-express", - "posthog", "psycopg[binary]", "python-frontmatter", "reflex @ git+https://github.com/reflex-dev/reflex@main", From 80b4c9a61ce5edae9383d71e1b78bb9fead2d9d6 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 9 Jan 2026 19:58:08 -0800 Subject: [PATCH 18/20] Update uv.lock after removing posthog --- uv.lock | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/uv.lock b/uv.lock index 5734c4db5..7fde5a498 100644 --- a/uv.lock +++ b/uv.lock @@ -206,15 +206,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - [[package]] name = "bidict" version = "0.23.1" @@ -1914,23 +1905,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "posthog" -version = "7.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backoff" }, - { name = "distro" }, - { name = "python-dateutil" }, - { name = "requests" }, - { name = "six" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/d4/b9afe855a8a7a1bf4459c28ae4c300b40338122dc850acabefcf2c3df24d/posthog-7.0.1.tar.gz", hash = "sha256:21150562c2630a599c1d7eac94bc5c64eb6f6acbf3ff52ccf1e57345706db05a", size = 126985, upload-time = "2025-11-15T12:44:22.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/0c/8b6b20b0be71725e6e8a32dcd460cdbf62fe6df9bc656a650150dc98fedd/posthog-7.0.1-py3-none-any.whl", hash = "sha256:efe212d8d88a9ba80a20c588eab4baf4b1a5e90e40b551160a5603bb21e96904", size = 145234, upload-time = "2025-11-15T12:44:21.247Z" }, -] - [[package]] name = "pre-commit" version = "4.5.0" @@ -2592,7 +2566,6 @@ dependencies = [ { name = "openai" }, { name = "pandas" }, { name = "plotly-express" }, - { name = "posthog" }, { name = "psycopg", extra = ["binary"] }, { name = "python-frontmatter" }, { name = "reflex" }, @@ -2627,7 +2600,6 @@ requires-dist = [ { name = "openai" }, { name = "pandas" }, { name = "plotly-express" }, - { name = "posthog" }, { name = "psycopg", extras = ["binary"] }, { name = "python-frontmatter" }, { name = "reflex", git = "https://github.com/reflex-dev/reflex?rev=main" }, From ea97f41a318b16bc94fe2cc72f99d69199e01511 Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 12 Jan 2026 16:59:39 +0100 Subject: [PATCH 19/20] bump reflex ui --- pcweb/pcweb.py | 7 ------- pcweb/telemetry/pixels.py | 2 ++ uv.lock | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pcweb/pcweb.py b/pcweb/pcweb.py index d73ef7c11..ba2594a8d 100644 --- a/pcweb/pcweb.py +++ b/pcweb/pcweb.py @@ -5,7 +5,6 @@ import reflex as rx import reflex_enterprise as rxe -from reflex_ui.blocks.calcom import calcom_popup_embed from pcweb import styles from pcweb.meta.meta import favicons_links @@ -32,9 +31,6 @@ radius="large", accent_color="violet", ), - extra_app_wraps={ - (55, "Calcom Popup Embed"): lambda _: calcom_popup_embed(), - }, head_components=get_pixel_website_trackers() + favicons_links() + [ @@ -51,9 +47,6 @@ href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap", rel="stylesheet", ), - rx.el.script( - """!function(e,t){var _=0;e.__default__=e.__default__||{},e.__default__.form_id=268792,e.__default__.team_id=654,e.__default__.listenToIds=[],function e(){var o=t.createElement("script");o.async=!0,o.src="https://import-cdn.default.com",o.onload=function(){!0,console.info("[Default.com] Powered by Default.com")},o.onerror=function(){++_<=3&&setTimeout(e,1e3*_)},t.head.appendChild(o)}()}(window,document);""" - ), ], ) diff --git a/pcweb/telemetry/pixels.py b/pcweb/telemetry/pixels.py index 4c2d3bfb2..88411e39c 100644 --- a/pcweb/telemetry/pixels.py +++ b/pcweb/telemetry/pixels.py @@ -2,6 +2,7 @@ import reflex as rx from reflex_ui.blocks.telemetry import ( + get_default_telemetry_script, get_google_analytics_trackers, get_unify_trackers, gtag_report_conversion, @@ -16,4 +17,5 @@ def get_pixel_website_trackers() -> list[rx.Component]: conversion_id_and_label="AW-11360851250/ASB4COvpisIbELKqo6kq" ), get_unify_trackers(), + get_default_telemetry_script(), ] diff --git a/uv.lock b/uv.lock index 7fde5a498..194ed8363 100644 --- a/uv.lock +++ b/uv.lock @@ -2548,7 +2548,7 @@ wheels = [ [[package]] name = "reflex-ui" version = "0.0.1" -source = { git = "https://github.com/reflex-dev/reflex-ui?rev=main#d2432c30fbecfcd96ad90be5effae8cd5bcc8ea9" } +source = { git = "https://github.com/reflex-dev/reflex-ui?rev=main#d20570d8dbb327f2dc28365b9dcc4e384ad536d2" } dependencies = [ { name = "reflex" }, ] From bca1bb4775e320f67fc00282b4a9ea58f6f172f7 Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 12 Jan 2026 18:43:47 +0100 Subject: [PATCH 20/20] bump reflex ui to close when submit --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 194ed8363..42377add7 100644 --- a/uv.lock +++ b/uv.lock @@ -2548,7 +2548,7 @@ wheels = [ [[package]] name = "reflex-ui" version = "0.0.1" -source = { git = "https://github.com/reflex-dev/reflex-ui?rev=main#d20570d8dbb327f2dc28365b9dcc4e384ad536d2" } +source = { git = "https://github.com/reflex-dev/reflex-ui?rev=main#2acfc6364b9a37c8475288b1d3c088a9510d5ae3" } dependencies = [ { name = "reflex" }, ]