@@ -6,11 +6,14 @@ defmodule AlgoraWeb.Onboarding.DevLive do
66  import  Ecto.Changeset 
77  import  Ecto.Query 
88
9+   alias  Algora.Accounts.User 
910  alias  Algora.Github 
11+   alias  Algora.Organizations 
1012  alias  Algora.Payments.Transaction 
1113  alias  Algora.Repo 
1214  alias  AlgoraWeb.Components.Logos 
1315  alias  AlgoraWeb.LocalStore 
16+   alias  Swoosh.Email 
1417
1518  require  Logger 
1619
@@ -77,14 +80,18 @@ defmodule AlgoraWeb.Onboarding.DevLive do
7780          limit:  10 
7881      ) 
7982
83+     signup_form  =  to_form ( User . signup_changeset ( % User { } ,  % { } ) ) 
84+ 
8085    { :ok , 
8186     socket 
87+      |>  assign ( :secret_code ,  nil ) 
8288     |>  assign ( :step ,  Enum . at ( @ steps ,  0 ) ) 
8389     |>  assign ( :steps ,  @ steps ) 
8490     |>  assign ( :total_steps ,  length ( @ steps ) ) 
8591     |>  assign ( :context ,  context ) 
8692     |>  assign ( :transactions ,  transactions ) 
87-      |>  assign ( :info_form ,  InfoForm . init ( ) ) } 
93+      |>  assign ( :info_form ,  InfoForm . init ( ) ) 
94+      |>  assign ( :signup_form ,  signup_form ) } 
8895  end 
8996
9097  @ impl  true 
@@ -153,6 +160,87 @@ defmodule AlgoraWeb.Onboarding.DevLive do
153160    { :noreply ,  push_event ( socket ,  "open_popup" ,  % { url:  popup_url } ) } 
154161  end 
155162
163+   @ impl  true 
164+   def  handle_event ( "send_signup_code" ,  % { "user"  =>  % { "email"  =>  email } } ,  socket )  do 
165+     code  =  Nanoid . generate ( ) 
166+ 
167+     changeset  =  User . signup_changeset ( % User { } ,  % { } ) 
168+ 
169+     case  send_signup_code_to_email ( email ,  code )  do 
170+       { :ok ,  _id }  -> 
171+         { :noreply , 
172+          socket 
173+          |>  LocalStore . assign_cached ( :secret_code ,  code ) 
174+          |>  LocalStore . assign_cached ( :email ,  email ) 
175+          |>  assign ( :signup_form ,  to_form ( changeset ) ) } 
176+ 
177+       { :error ,  _reason }  -> 
178+         # capture_error reason 
179+         { :noreply , 
180+          put_flash ( 
181+            socket , 
182+            :error , 
183+            "We had trouble sending mail to #{ email }  . Please try again" 
184+          ) } 
185+     end 
186+ 
187+     # case Algora.Accounts.get_user_by_email(email) do 
188+     #   %User{} = user -> 
189+     #     {:noreply, socket} 
190+ 
191+     #   nil -> 
192+     #     throttle() 
193+     #     {:noreply, put_flash(socket, :error, "Email address not found.")} 
194+     # end 
195+   end 
196+ 
197+   @ impl  true 
198+   def  handle_event ( "send_signup_code" ,  % { "user"  =>  % { "signup_code"  =>  code } } ,  socket )  do 
199+     if  Plug.Crypto . secure_compare ( String . trim ( code ) ,  socket . assigns . secret_code )  do 
200+       user_handle  = 
201+         socket . assigns . email 
202+         |>  String . replace ( ~r/ [^a-zA-Z0-9]/  ,  "-" ) 
203+         |>  String . downcase ( ) 
204+ 
205+       email  =  socket . assigns . email 
206+ 
207+       tech_stack  =  get_field ( socket . assigns . info_form . source ,  :tech_stack )  ||  [ ] 
208+       intentions  =  get_field ( socket . assigns . info_form . source ,  :intentions )  ||  [ ] 
209+ 
210+       opts  =  [ 
211+         tech_stack:  tech_stack , 
212+         seeking_bounties:  "bounties"  in  intentions , 
213+         seeking_contracts:  "contracts"  in  intentions , 
214+         seeking_jobs:  "jobs"  in  intentions 
215+       ] 
216+ 
217+       { :ok ,  user }  = 
218+         case  Repo . get_by ( User ,  email:  email )  do 
219+           nil  -> 
220+             % User { 
221+               type:  :individual , 
222+               last_context:  "personal" , 
223+               handle:  Organizations . ensure_unique_handle ( user_handle ) , 
224+               avatar_url:  Algora.Util . get_gravatar_url ( email ) 
225+             } 
226+             |>  User . signup_changeset ( % { email:  email } ) 
227+             |>  User . generate_id ( ) 
228+             |>  change ( opts ) 
229+             |>  Repo . insert ( ) 
230+ 
231+           existing_user  -> 
232+             existing_user 
233+             |>  change ( opts ) 
234+             |>  Repo . update ( ) 
235+         end 
236+ 
237+       { :noreply ,  redirect ( socket ,  to:  AlgoraWeb.UserAuth . generate_login_path ( user . email ,  socket . assigns [ :return_to ] ) ) } 
238+     else 
239+       throttle ( ) 
240+       { :noreply ,  put_flash ( socket ,  :error ,  "Invalid signup code" ) } 
241+     end 
242+   end 
243+ 
156244  @ impl  true 
157245  def  handle_event ( "prev_step" ,  _params ,  socket )  do 
158246    current_step_index  =  Enum . find_index ( socket . assigns . steps ,  & ( & 1  ==  socket . assigns . step ) ) 
@@ -197,6 +285,8 @@ defmodule AlgoraWeb.Onboarding.DevLive do
197285    end 
198286  end 
199287
288+   defp  throttle ,  do:  :timer . sleep ( 1000 ) 
289+ 
200290  defp  main_content ( % { step:  :info }  =  assigns )  do 
201291    ~H"""  
202292    < div  class = "space-y-8 " >  
@@ -266,28 +356,78 @@ defmodule AlgoraWeb.Onboarding.DevLive do
266356
267357  defp  main_content ( % { step:  :oauth }  =  assigns )  do 
268358    ~H"""  
269-     < div  class = "space-y-8 " >  
270-       < div >  
271-         < h2  class = "mb-2 text-3xl sm:text-4xl font-semibold " >  
272-           Connect your GitHub 
273-         </ h2 >  
274-         < p  class = "mb-6 text-muted-foreground " >  
275-           Join our community and start earning bounties 
276-         </ p >  
277- 
278-         < p  class = "text-sm text-muted-foreground/75 " >  
279-           By continuing, you agree to Algora's 
280-           < . link  href = "/terms "  class = "text-foreground hover:underline " > terms</ . link >  
281-           and < . link  href = "/privacy "  class = "text-foreground hover:underline " > privacy</ . link > . 
282-         </ p >  
283-       </ div >  
284-       < div  class = "flex justify-between " >  
285-         < . button  phx-click = "prev_step "  variant = "secondary " >  
286-           Previous 
287-         </ . button >  
288-         < . button  phx-click = "sign_in_with_github "  class = "inline-flex items-center " >  
289-           < Logos . github  class = "mr-2 h-5 w-5 "  />  Sign in with GitHub 
359+     < div  class = "max-w-sm " >  
360+       < h2  class = "mb-2 text-3xl sm:text-4xl font-semibold " >  
361+         Complete your signup 
362+       </ h2 >  
363+       < p  class = "mb-6 text-muted-foreground " >  
364+         Join our community and start earning bounties 
365+       </ p >  
366+ 
367+       < div  class = "mt-8 " >  
368+         < . button  :if = { ! @ secret_code }  phx-click = "sign_in_with_github "  class = "w-full py-5 " >  
369+           < Logos . github  class = "size-5 mr-2 -ml-1 shrink-0 "  />  Continue with GitHub 
290370        </ . button >  
371+ 
372+         < div  :if = { ! @ secret_code }  class = "relative mt-6 " >  
373+           < div  class = "absolute inset-0 flex items-center "  aria-hidden = "true " >  
374+             < div  class = "w-full border-t border-muted-foreground/50 " > </ div >  
375+           </ div >  
376+           < div  class = "relative flex justify-center text-sm/6 font-medium " >  
377+             < span  class = "bg-background px-6 text-muted-foreground " > or</ span >  
378+           </ div >  
379+         </ div >  
380+ 
381+         < div  class = "mt-4 " >  
382+           < . simple_form  
383+             :if = { ! @ secret_code }  
384+             for = { @ signup_form }  
385+             id = "send_signup_code_form "  
386+             phx-submit = "send_signup_code "  
387+           >  
388+             < div  class = "space-y-4 " >  
389+               < . input  
390+                 field = { @ signup_form [ :email ] }  
391+                 type = "email "  
392+                 label = "Email "  
393+ 394+                 required  
395+               />  
396+               < . button  phx-disable-with = "Signing up... "  class = "w-full py-5 "  variant = "secondary " >  
397+                 Continue with email 
398+               </ . button >  
399+             </ div >  
400+           </ . simple_form >  
401+         </ div >  
402+ 
403+         < . simple_form  
404+           :if = { @ secret_code }  
405+           for = { @ signup_form }  
406+           id = "send_signup_code_form "  
407+           phx-submit = "send_signup_code "  
408+         >  
409+           < . input  field = { @ signup_form [ :signup_code ] }  type = "text "  label = "Signup code "  required  />  
410+           < . button  phx-disable-with = "Signing up... "  class = "w-full py-5 " >  
411+             Submit 
412+           </ . button >  
413+         </ . simple_form >  
414+       </ div >  
415+ 
416+       < div  class = "mt-4 text-xs sm:text-sm text-muted-foreground w-full " >  
417+         By continuing, you agree to our 
418+         < . link  
419+           href = { AlgoraWeb.Constants . get ( :terms_url ) }  
420+           class = "font-medium text-foreground/90 hover:text-foreground "  
421+         >  
422+           terms 
423+         </ . link >  
424+         { " " }  and 
425+         < . link  
426+           href = { AlgoraWeb.Constants . get ( :privacy_url ) }  
427+           class = "font-medium text-foreground/90 hover:text-foreground "  
428+         >  
429+           privacy policy. 
430+         </ . link >  
291431      </ div >  
292432    </ div >  
293433    """  
@@ -341,4 +481,30 @@ defmodule AlgoraWeb.Onboarding.DevLive do
341481    <%  end  %>  
342482    """  
343483  end 
484+ 
485+   @ from_name  "Algora" 
486+ 487+ 
488+   defp  send_signup_code_to_email ( email ,  code )  do 
489+     email  = 
490+       Email . new ( ) 
491+       |>  Email . to ( email ) 
492+       |>  Email . from ( { @ from_name ,  @ from_email } ) 
493+       |>  Email . subject ( "Signup code for Algora" ) 
494+       |>  Email . text_body ( """ 
495+       Here is your signup code for Algora! 
496+ 
497+        #{ code }  
498+ 
499+       If you didn't request this link, you can safely ignore this email. 
500+ 
501+       -------------------------------------------------------------------------------- 
502+ 
503+       For correspondence, please email the Algora founders at [email protected]  and [email protected]  504+ 
505+       © 2025 Algora PBC. 
506+       """ ) 
507+ 
508+     Algora.Mailer . deliver ( email ) 
509+   end 
344510end 
0 commit comments