44
55import reflex as rx
66from reflex .event import EventType
7+ from reflex .experimental import ClientStateVar
78
89from pcweb .components .hosting_banner import HostingBannerState
910from pcweb .components .new_button import button
1011from pcweb .constants import CAL_REQUEST_DEMO_URL
1112from pcweb .pages .framework .views .companies import pricing_page_companies
1213from pcweb .telemetry .postog_metrics import DemoEvent , send_data_to_posthog
1314
15+ ThankYouDialogState = ClientStateVar .create ("thank_you_dialog_state" , False )
16+
1417SelectVariant = Literal ["primary" , "secondary" , "outline" , "transparent" ]
1518SelectSize = Literal ["sm" , "md" , "lg" ]
1619SelectItemVariant = Literal ["selectable" , "actions" , "projects" ]
@@ -63,7 +66,6 @@ def select_item(
6366 ),
6467 class_name = "w-full outline-none focus:outline-none" ,
6568 )
66- # return rx.el.button(text, **common_props)
6769
6870
6971def select (
@@ -115,28 +117,30 @@ class QuoteFormState(rx.State):
115117 num_employees : str = "500+"
116118 referral_source : str = "Google Search"
117119 banned_email : bool = False
118- banned_phone : bool = False
120+ banned_linkedin : bool = False
119121
120122 def set_select_value (self , field : str , value : str ):
121123 """Update the selected value for a given field."""
122124 setattr (self , field , value )
123125
126+ def is_small_company (self ) -> bool :
127+ """Check if company has 10 or fewer employees."""
128+ return self .num_employees in ["1" , "2-5" , "6-10" ]
129+
124130 @rx .event
125131 def submit (self , form_data : dict [str , Any ]):
126- # Phone number validation
127- phone = form_data .get ("phone_number" , "" ).strip ()
128- if phone :
129- phone_pattern = r'^(\+?1[-.\s]?)?(\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4})|(\+[1-9]\d{0,3}[-.\s]?(\(?[0-9]{1,4}\)?[-.\s]?){1,5}[0-9]{1,4})$'
130-
131- # Remove common formatting characters for validation
132- clean_phone = re .sub (r'[^\d+]' , '' , phone )
133-
134- if not re .match (phone_pattern , phone ) or len (clean_phone ) < 10 or len (clean_phone ) > 15 :
135- self .banned_phone = True
136- yield rx .set_focus ("phone_number" )
132+ # LinkedIn URL validation
133+ linkedin_url = form_data .get ("linkedin_url" , "" ).strip ()
134+ if linkedin_url :
135+ # Basic LinkedIn URL validation
136+ linkedin_pattern = r'^https?://(www\.)?linkedin\.com/(in|company)/.+$'
137+
138+ if not re .match (linkedin_pattern , linkedin_url ):
139+ self .banned_linkedin = True
140+ yield rx .set_focus ("linkedin_url" )
137141 return
138-
139- self .banned_phone = False
142+
143+ self .banned_linkedin = False
140144
141145 # Email domain validation
142146 banned_domains = [
@@ -176,13 +180,22 @@ def submit(self, form_data: dict[str, Any]):
176180 notes_content = f"""
177181Name: { form_data .get ("first_name" , "" )} { form_data .get ("last_name" , "" )}
178182Business Email: { form_data .get ("email" , "" )}
179- Phone Number : { form_data .get ("phone_number " , "" )}
183+ LinkedIn URL : { form_data .get ("linkedin_url " , "" )}
180184Job Title: { form_data .get ("job_title" , "" )}
181185Company Name: { form_data .get ("company_name" , "" )}
182186Number of Employees: { self .num_employees }
183187Internal Tools to Build: { form_data .get ("internal_tools" , "" )}
184188How they heard about Reflex: { self .referral_source } """
185189
190+ # Send to PostHog for all submissions
191+ yield QuoteFormState .send_demo_event (form_data )
192+
193+ # Check if it's a small company
194+ if self .is_small_company ():
195+ yield ThankYouDialogState .push (True )
196+ return
197+
198+ # For larger companies, redirect to calendar booking
186199 params = {
187200 "email" : form_data .get ("email" , "" ),
188201 "name" : f"{ form_data .get ('first_name' , '' )} { form_data .get ('last_name' , '' )} " ,
@@ -192,10 +205,9 @@ def submit(self, form_data: dict[str, Any]):
192205 query_string = urllib .parse .urlencode (params )
193206 cal_url = f"{ CAL_REQUEST_DEMO_URL } ?{ query_string } "
194207
195- yield QuoteFormState .send_demo_event (form_data )
196-
197208 return rx .redirect (cal_url )
198209
210+
199211 @rx .event (background = True )
200212 async def send_demo_event (self , form_data : dict [str , Any ]):
201213 first_name = form_data .get ("first_name" , "" )
@@ -206,7 +218,7 @@ async def send_demo_event(self, form_data: dict[str, Any]):
206218 first_name = first_name ,
207219 last_name = last_name ,
208220 company_email = form_data .get ("email" , "" ),
209- phone_number = form_data .get ("phone_number " , "" ),
221+ linkedin_url = form_data .get ("linkedin_url " , "" ), # Updated from phone_number
210222 job_title = form_data .get ("job_title" , "" ),
211223 company_name = form_data .get ("company_name" , "" ),
212224 num_employees = self .num_employees ,
@@ -264,22 +276,21 @@ def select_field(
264276 state_var : str = "" ,
265277):
266278 """Helper for creating custom select fields."""
279+
280+ def create_select_item (option , state_var ):
281+ return select_item (
282+ content = (
283+ option ,
284+ lambda : QuoteFormState .set_select_value (state_var , option ),
285+ ),
286+ size = "sm" ,
287+ variant = "selectable" ,
288+ class_name = "w-full justify-start px-4 py-2 hover:bg-slate-2 rounded-md" ,
289+ )
290+
267291 # Create scroll area with selectable options
268292 scroll_content = rx .box (
269- * [
270- select_item (
271- content = (
272- option ,
273- lambda opt = option , var = state_var : QuoteFormState .set_select_value (
274- var , opt
275- ),
276- ),
277- size = "sm" ,
278- variant = "selectable" ,
279- class_name = "w-full justify-start px-4 py-2 hover:bg-slate-2 rounded-md" ,
280- )
281- for option in options
282- ],
293+ * [create_select_item (option , state_var ) for option in options ],
283294 class_name = "!pt-0 !mt-0 !max-h-[15rem] bg-slate-1 border border-slate-5 rounded-lg shadow-lg py-0 overflow-y-scroll w-full" ,
284295 )
285296
@@ -308,6 +319,47 @@ def textarea_field(label: str, name: str, placeholder: str, required: bool = Fal
308319 return form_field (label , textarea_component , required )
309320
310321
322+ def thank_you_modal () -> rx .Component :
323+ """Thank you modal for small companies."""
324+
325+ return rx .dialog .root (
326+ rx .dialog .content (
327+ rx .box (
328+ rx .box (
329+ rx .text (
330+ "Thank You for Your Interest!" ,
331+ class_name = "text-2xl font-semibold text-slate-12 font-sans" ,
332+ ),
333+ rx .dialog .close (
334+ button (
335+ rx .icon ("x" , class_name = "!text-slate-9" ),
336+ variant = "transparent" ,
337+ size = "icon-sm" ,
338+ type = "button" ,
339+ class_name = "focus:outline-none" ,
340+ on_click = ThankYouDialogState .set_value (False )
341+
342+ ),
343+ ),
344+ class_name = "flex flex-row items-center gap-2 justify-between w-full" ,
345+ ),
346+ rx .text (
347+ "We've received your submission and our team will get back to you soon. We appreciate your interest in Reflex!" ,
348+ class_name = "text-slate-9 font-medium text-sm" ,
349+ ),
350+ class_name = "flex flex-col w-full gap-y-4"
351+ ),
352+ class_name = "w-full" ,
353+ on_interact_outside = ThankYouDialogState .set_value (False ),
354+ on_escape_key_down = ThankYouDialogState .set_value (False ),
355+
356+ ),
357+ open = ThankYouDialogState .value
358+ )
359+
360+
361+
362+
311363def custom_quote_form () -> rx .Component :
312364 """Custom quote form component with clean, maintainable structure."""
313365 return rx .box (
@@ -394,35 +446,36 @@ def custom_quote_form() -> rx.Component:
394446 ),
395447 class_name = "flex-row flex gap-x-2 mb-6" ,
396448 ),
449+ # LinkedIn field (replacing phone number)
397450 rx .cond (
398- QuoteFormState .banned_phone ,
451+ QuoteFormState .banned_linkedin ,
399452 rx .box (
400453 rx .el .div (
401454 rx .text (
402- "Phone number " ,
455+ "LinkedIn profile URL " ,
403456 class_name = "text-slate-11 text-sm font-medium mb-2" ,
404457 ),
405458 rx .text (
406- "Invalid phone number format!" ,
459+ "Invalid LinkedIn URL format!" ,
407460 class_name = "text-red-8 text-sm font-medium mb-2" ,
408461 ),
409462 class_name = "flex flex-row items-center justify-between w-full" ,
410463 ),
411464 rx .el .input (
412- placeholder = "Please enter a valid phone number " ,
413- name = "phone_number " ,
414- type = "tel " ,
465+ placeholder = "Please enter a valid LinkedIn URL " ,
466+ name = "linkedin_url " ,
467+ type = "url " ,
415468 required = True ,
416469 class_name = "box-border w-full border-2 border-red-5 bg-slate-1 px-6 pr-8 border rounded-[0.625rem] h-[2.25rem] font-medium text-slate-12 text-sm placeholder:text-slate-9 outline-none focus:outline-none caret-slate-12 peer pl-2.5 disabled:cursor-not-allowed disabled:border disabled:border-slate-5 disabled:!bg-slate-3 disabled:text-slate-8 disabled:placeholder:text-slate-8" ,
417470 ),
418471 class_name = "mb-6" ,
419472 ),
420473 text_input_field (
421- "Phone number " ,
422- "phone_number " ,
423- "(555) 123-4567 " ,
474+ "LinkedIn profile URL " ,
475+ "linkedin_url " ,
476+ "https://linkedin.com/in/your-profile " ,
424477 required = True ,
425- input_type = "tel " ,
478+ input_type = "url " ,
426479 ),
427480 ),
428481 # Project Details
@@ -437,7 +490,7 @@ def custom_quote_form() -> rx.Component:
437490 select_field (
438491 "Number of employees" ,
439492 "num_employees" ,
440- ["1- 10" , "11-50" , "51-100" , "101-500" , "500+" ],
493+ ["1" , "2-5" , "6- 10" , "11-50" , "51-100" , "101-500" , "500+" ], # Updated options
441494 "500+" ,
442495 required = True ,
443496 state_var = "num_employees" ,
@@ -469,26 +522,21 @@ def custom_quote_form() -> rx.Component:
469522 ),
470523 class_name = "w-full space-y-6" ,
471524 on_submit = QuoteFormState .submit ,
525+ reset_on_submit = True ,
472526 ),
473527 class_name = "relative bg-slate-1 p-6 sm:p-8 rounded-2xl border-2 border-[--violet-9] shadow-lg w-full max-w-md mx-auto lg:max-w-none lg:mx-0" ,
474528 ),
475529 class_name = "grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16 max-w-7xl mx-auto items-start" ,
476530 ),
531+ # Thank you modal
532+ thank_you_modal (),
477533 class_name = "py-12 sm:py-20 px-4 sm:px-8" ,
478534 )
479535
480536
481537def header () -> rx .Component :
482538 return rx .box (
483539 custom_quote_form (),
484- # rx.el.h1(
485- # "Get a custom quote",
486- # class_name="gradient-heading font-semibold text-3xl lg:text-5xl text-center",
487- # ),
488- # rx.el.p(
489- # "The complete platform for building and deploying your apps.",
490- # class_name="text-slate-9 text-md lg:text-xl font-semibold text-center",
491- # ),
492540 class_name = "flex flex-col gap-2 justify-center items-center max-w-[64.19rem] 2xl:border-x border-slate-4 w-full -mb-10 "
493541 + rx .cond (HostingBannerState .show_banner , "pt-[11rem]" , "pt-[12rem]" ),
494542 )
0 commit comments