77import urllib .parse
88from datetime import datetime
99from reflex .event import EventType
10-
10+ from pcweb . telemetry . postog_metrics import DemoEvent , send_data_to_posthog
1111
1212SelectVariant = Literal ["primary" , "secondary" , "outline" , "transparent" ]
1313SelectSize = Literal ["sm" , "md" , "lg" ]
2929 "lg" : "text-sm px-3 h-10 rounded-xl gap-3" ,
3030}
3131
32+
3233def select_item (
3334 content : tuple [str | rx .Component , EventType [()]],
3435 is_selected : bool = False ,
@@ -62,6 +63,7 @@ def select_item(
6263 )
6364 # return rx.el.button(text, **common_props)
6465
66+
6567def select (
6668 content : rx .Component ,
6769 variant : SelectVariant = "primary" ,
@@ -104,13 +106,14 @@ def select(
104106 ** props ,
105107 )
106108
109+
107110class QuoteFormState (rx .State ):
108111 """State management for the quote form."""
112+
109113 num_employees : str = "500+"
110114 referral_source : str = "Google Search"
111115 banned_email : bool = False
112116
113-
114117 def set_select_value (self , field : str , value : str ):
115118 """Update the selected value for a given field."""
116119 setattr (self , field , value )
@@ -119,27 +122,27 @@ def set_select_value(self, field: str, value: str):
119122 def submit (self , form_data : dict [str , Any ]):
120123 # Email domain validation
121124 banned_domains = [
122- ' gmail.com' ,
123- ' outlook.com' ,
124- ' hotmail.com' ,
125- ' yahoo.com' ,
126- ' icloud.com' ,
127- ' aol.com' ,
128- ' protonmail.com' ,
129- ' proton.me' ,
130- ' mail.com' ,
131- ' yandex.com' ,
132- ' zoho.com' ,
133- ' live.com' ,
134- ' msn.com' ,
135- ' me.com' ,
136- ' mac.com' ,
137- ' googlemail.com' ,
138- ' yahoo.co.uk' ,
139- ' yahoo.ca' ,
140- ' yahoo.co.in' ,
141- ' outlook.co.uk' ,
142- ' hotmail.co.uk'
125+ " gmail.com" ,
126+ " outlook.com" ,
127+ " hotmail.com" ,
128+ " yahoo.com" ,
129+ " icloud.com" ,
130+ " aol.com" ,
131+ " protonmail.com" ,
132+ " proton.me" ,
133+ " mail.com" ,
134+ " yandex.com" ,
135+ " zoho.com" ,
136+ " live.com" ,
137+ " msn.com" ,
138+ " me.com" ,
139+ " mac.com" ,
140+ " googlemail.com" ,
141+ " yahoo.co.uk" ,
142+ " yahoo.ca" ,
143+ " yahoo.co.in" ,
144+ " outlook.co.uk" ,
145+ " hotmail.co.uk" ,
143146 ]
144147
145148 email = form_data .get ("email" , "" ).lower ()
@@ -150,7 +153,6 @@ def submit(self, form_data: dict[str, Any]):
150153 yield rx .set_focus ("email" )
151154 return
152155
153-
154156 self .banned_email = False
155157 now = datetime .now ()
156158 current_month = now .strftime ("%Y-%m" )
@@ -164,17 +166,43 @@ def submit(self, form_data: dict[str, Any]):
164166 "Company name" : form_data .get ("company_name" , "" ),
165167 "Phone Number" : form_data .get ("phone_number" , "" ),
166168 "Number of Employees" : self .num_employees ,
167- "What internal tools are you looking to build?" : form_data .get ("internal_tools" , "" ),
169+ "What internal tools are you looking to build?" : form_data .get (
170+ "internal_tools" , ""
171+ ),
168172 "Where did you first hear about Reflex?" : self .referral_source ,
169173 "month" : current_month ,
170174 "date" : current_date ,
171175 }
172176
173177 query_string = urllib .parse .urlencode (params )
174- cal_url = f"https://cal.com/team/reflex/talk-to-a-reflex-expert?{ query_string } "
178+ cal_url = (
179+ f"https://cal.com/team/reflex/talk-to-a-reflex-expert?{ query_string } "
180+ )
181+
182+ yield QuoteFormState .send_demo_event (form_data )
175183
176184 return rx .redirect (cal_url )
177185
186+ @rx .event (background = True )
187+ async def send_demo_event (self , form_data : dict [str , Any ]):
188+ first_name = form_data .get ("first_name" , "" )
189+ last_name = form_data .get ("last_name" , "" )
190+ await send_data_to_posthog (
191+ DemoEvent (
192+ distinct_id = f"{ first_name } { last_name } " ,
193+ first_name = first_name ,
194+ last_name = last_name ,
195+ company_email = form_data .get ("email" , "" ),
196+ phone_number = form_data .get ("phone_number" , "" ),
197+ job_title = form_data .get ("job_title" , "" ),
198+ company_name = form_data .get ("company_name" , "" ),
199+ num_employees = self .num_employees ,
200+ internal_tools = form_data .get ("internal_tools" , "" ),
201+ referral_source = self .referral_source ,
202+ )
203+ )
204+
205+
178206def quote_input (placeholder : str , name : str , ** props ):
179207 return rx .el .input (
180208 placeholder = placeholder ,
@@ -183,7 +211,10 @@ def quote_input(placeholder: str, name: str, **props):
183211 ** props ,
184212 )
185213
186- def form_field (label : str , input_component , required : bool = False , class_name : str = "mb-6" ):
214+
215+ def form_field (
216+ label : str , input_component , required : bool = False , class_name : str = "mb-6"
217+ ):
187218 """Reusable form field component with label and input."""
188219 label_text = f"{ label } { '*' if required else '' } "
189220 return rx .box (
@@ -192,7 +223,15 @@ def form_field(label: str, input_component, required: bool = False, class_name:
192223 class_name = class_name ,
193224 )
194225
195- def text_input_field (label : str , name : str , placeholder : str , required : bool = False , input_type : str = "text" , class_name : str = "mb-6" ):
226+
227+ def text_input_field (
228+ label : str ,
229+ name : str ,
230+ placeholder : str ,
231+ required : bool = False ,
232+ input_type : str = "text" ,
233+ class_name : str = "mb-6" ,
234+ ):
196235 """Helper for creating text input fields."""
197236 input_component = quote_input (
198237 name = name ,
@@ -202,13 +241,26 @@ def text_input_field(label: str, name: str, placeholder: str, required: bool = F
202241 )
203242 return form_field (label , input_component , required , class_name )
204243
205- def select_field (label : str , name : str , options : list , placeholder : str , required : bool = False , state_var : str = "" ):
244+
245+ def select_field (
246+ label : str ,
247+ name : str ,
248+ options : list ,
249+ placeholder : str ,
250+ required : bool = False ,
251+ state_var : str = "" ,
252+ ):
206253 """Helper for creating custom select fields."""
207254 # Create scroll area with selectable options
208255 scroll_content = rx .box (
209256 * [
210257 select_item (
211- content = (option , lambda opt = option , var = state_var : QuoteFormState .set_select_value (var , opt )),
258+ content = (
259+ option ,
260+ lambda opt = option , var = state_var : QuoteFormState .set_select_value (
261+ var , opt
262+ ),
263+ ),
212264 size = "sm" ,
213265 variant = "selectable" ,
214266 class_name = "w-full justify-start px-4 py-2 hover:bg-slate-2 rounded-md" ,
@@ -231,6 +283,7 @@ def select_field(label: str, name: str, options: list, placeholder: str, require
231283 )
232284 return form_field (label , select_component , required )
233285
286+
234287def textarea_field (label : str , name : str , placeholder : str , required : bool = False ):
235288 """Helper for creating textarea fields."""
236289 textarea_component = rx .el .textarea (
@@ -241,6 +294,7 @@ def textarea_field(label: str, name: str, placeholder: str, required: bool = Fal
241294 )
242295 return form_field (label , textarea_component , required )
243296
297+
244298def custom_quote_form () -> rx .Component :
245299 """Custom quote form component with clean, maintainable structure."""
246300 return rx .box (
@@ -266,44 +320,108 @@ def custom_quote_form() -> rx.Component:
266320 rx .el .form (
267321 # Personal Information
268322 rx .el .div (
269- text_input_field ("First name" , "first_name" , "John" , required = True , class_name = "mb-0" ),
270- text_input_field ("Last name" , "last_name" , "Smith" , required = True , class_name = "mb-0" ),
323+ text_input_field (
324+ "First name" ,
325+ "first_name" ,
326+ "John" ,
327+ required = True ,
328+ class_name = "mb-0" ,
329+ ),
330+ text_input_field (
331+ "Last name" ,
332+ "last_name" ,
333+ "Smith" ,
334+ required = True ,
335+ class_name = "mb-0" ,
336+ ),
271337 class_name = "flex-row flex gap-x-2 mb-6" ,
272338 ),
273339 rx .cond (
274340 QuoteFormState .banned_email ,
275341 rx .box (
276342 rx .el .div (
277- rx .text ("Business email" , class_name = "text-slate-11 text-sm font-medium mb-2" ),
278- rx .text ("Personal emails not allowed!" , class_name = "text-red-8 text-sm font-medium mb-2" ),
279- class_name = "flex flex-row items-center justify-between w-full" ,
343+ rx .text (
344+ "Business email" ,
345+ class_name = "text-slate-11 text-sm font-medium mb-2" ,
346+ ),
347+ rx .text (
348+ "Personal emails not allowed!" ,
349+ class_name = "text-red-8 text-sm font-medium mb-2" ,
350+ ),
351+ class_name = "flex flex-row items-center justify-between w-full" ,
280352 ),
281353 rx .el .input (
282354 placeholder = "Personal emails not allowed!" ,
283355 name = "email" ,
284356 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" ,
285- )
357+ ),
358+ ),
359+ text_input_field (
360+ "Business email" ,
361+ "email" ,
362+ 363+ required = True ,
364+ input_type = "email" ,
286365 ),
287- text_input_field (
"Business email" ,
"email" ,
"[email protected] " ,
required = True ,
input_type = "email" ),
288366 ),
289367 rx .el .div (
290- text_input_field ("Job title" , "job_title" , "CTO" , required = True , class_name = "mb-0" ),
291- text_input_field ("Company name" , "company_name" , "Pynecone, Inc." , required = True , class_name = "mb-0" ),
368+ text_input_field (
369+ "Job title" ,
370+ "job_title" ,
371+ "CTO" ,
372+ required = True ,
373+ class_name = "mb-0" ,
374+ ),
375+ text_input_field (
376+ "Company name" ,
377+ "company_name" ,
378+ "Pynecone, Inc." ,
379+ required = True ,
380+ class_name = "mb-0" ,
381+ ),
292382 class_name = "flex-row flex gap-x-2 mb-6" ,
293383 ),
294-
295- text_input_field ("Phone number" , "phone_number" , "(555) 123-4567" , required = True , input_type = "tel" ),
296-
384+ text_input_field (
385+ "Phone number" ,
386+ "phone_number" ,
387+ "(555) 123-4567" ,
388+ required = True ,
389+ input_type = "tel" ,
390+ ),
297391 # Project Details
298- textarea_field ("What are you looking to build?" , "internal_tools" , "Please list any apps, requirements, or data sources you plan on using" , required = True ),
299-
392+ textarea_field (
393+ "What are you looking to build?" ,
394+ "internal_tools" ,
395+ "Please list any apps, requirements, or data sources you plan on using" ,
396+ required = True ,
397+ ),
300398 # Company Size and Referral
301399 rx .el .div (
302- select_field ("Number of employees" , "num_employees" , ["1-10" , "11-50" , "51-100" , "101-500" , "500+" ], "500+" , required = True , state_var = "num_employees" ),
303- select_field ("How did you hear about us?" , "referral_source" , ["Google Search" , "Social Media" , "Word of Mouth" , "Blog" , "Conference" , "Other" ], "Google Search" , required = True , state_var = "referral_source" ),
400+ select_field (
401+ "Number of employees" ,
402+ "num_employees" ,
403+ ["1-10" , "11-50" , "51-100" , "101-500" , "500+" ],
404+ "500+" ,
405+ required = True ,
406+ state_var = "num_employees" ,
407+ ),
408+ select_field (
409+ "How did you hear about us?" ,
410+ "referral_source" ,
411+ [
412+ "Google Search" ,
413+ "Social Media" ,
414+ "Word of Mouth" ,
415+ "Blog" ,
416+ "Conference" ,
417+ "Other" ,
418+ ],
419+ "Google Search" ,
420+ required = True ,
421+ state_var = "referral_source" ,
422+ ),
304423 class_name = "w-full flex-row flex flex-wrap justify-between mb-6" ,
305424 ),
306-
307425 # Submit button
308426 button (
309427 "Submit" ,
@@ -326,6 +444,7 @@ def custom_quote_form() -> rx.Component:
326444 class_name = "py-12 sm:py-20 px-4 sm:px-8" ,
327445 )
328446
447+
329448def header () -> rx .Component :
330449 return rx .box (
331450 custom_quote_form (),
0 commit comments