Monorepo for Tangled tangled.org

appview/pages: rework signup pages

- new /signup page to enter email ID
- login page has a link to go signup
- signup page has a link to go back to login

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 564cfdd2 fd66928f

verified
Changed files
+134 -136
appview
+5 -3
appview/pages/pages.go
··· 262 262 return p.executePlain("user/login", w, params) 263 263 } 264 264 265 - type SignupParams struct{} 265 + func (p *Pages) Signup(w io.Writer) error { 266 + return p.executePlain("user/signup", w, nil) 267 + } 266 268 267 - func (p *Pages) CompleteSignup(w io.Writer, params SignupParams) error { 268 - return p.executePlain("user/completeSignup", w, params) 269 + func (p *Pages) CompleteSignup(w io.Writer) error { 270 + return p.executePlain("user/completeSignup", w, nil) 269 271 } 270 272 271 273 type TermsOfServiceParams struct {
+4
appview/pages/templates/layouts/topbar.html
··· 15 15 {{ block "dropDown" . }} {{ end }} 16 16 {{ else }} 17 17 <a href="/login">login</a> 18 + <span class="text-gray-500 dark:text-gray-400">or</span> 19 + <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2"> 20 + join now {{ i "arrow-right" "size-4" }} 21 + </a> 18 22 {{ end }} 19 23 </div> 20 24 </div>
+5 -5
appview/pages/templates/user/completeSignup.html
··· 38 38 tightly-knit social coding. 39 39 </h2> 40 40 <form 41 - class="mt-4 max-w-sm mx-auto" 41 + class="mt-4 max-w-sm mx-auto flex flex-col gap-4" 42 42 hx-post="/signup/complete" 43 43 hx-swap="none" 44 44 hx-disabled-elt="#complete-signup-button" ··· 58 58 </span> 59 59 </div> 60 60 61 - <div class="flex flex-col mt-4"> 62 - <label for="username">desired username</label> 61 + <div class="flex flex-col"> 62 + <label for="username">username</label> 63 63 <input 64 64 type="text" 65 65 id="username" ··· 73 73 </span> 74 74 </div> 75 75 76 - <div class="flex flex-col mt-4"> 76 + <div class="flex flex-col"> 77 77 <label for="password">password</label> 78 78 <input 79 79 type="password" ··· 88 88 </div> 89 89 90 90 <button 91 - class="btn-create w-full my-2 mt-6" 91 + class="btn-create w-full my-2 mt-6 text-base" 92 92 type="submit" 93 93 id="complete-signup-button" 94 94 tabindex="4"
+11 -79
appview/pages/templates/user/login.html
··· 3 3 <html lang="en" class="dark:bg-gray-900"> 4 4 <head> 5 5 <meta charset="UTF-8" /> 6 - <meta 7 - name="viewport" 8 - content="width=device-width, initial-scale=1.0" 9 - /> 10 - <meta 11 - property="og:title" 12 - content="login · tangled" 13 - /> 14 - <meta 15 - property="og:url" 16 - content="https://tangled.sh/login" 17 - /> 18 - <meta 19 - property="og:description" 20 - content="login to or sign up for tangled" 21 - /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <meta property="og:title" content="login · tangled" /> 8 + <meta property="og:url" content="https://tangled.sh/login" /> 9 + <meta property="og:description" content="login to for tangled" /> 22 10 <script src="/static/htmx.min.js"></script> 23 - <link 24 - rel="stylesheet" 25 - href="/static/tw.css?{{ cssContentHash }}" 26 - type="text/css" 27 - /> 28 - <title>login or sign up &middot; tangled</title> 11 + <link rel="stylesheet" href="/static/tw.css?{{ cssContentHash }}" type="text/css" /> 12 + <title>login &middot; tangled</title> 29 13 </head> 30 14 <body class="flex items-center justify-center min-h-screen"> 31 15 <main class="max-w-md px-6 -mt-4"> 32 - <h1 33 - class="text-center text-2xl font-semibold italic dark:text-white" 34 - > 16 + <h1 class="text-center text-2xl font-semibold italic dark:text-white" > 35 17 tangled 36 18 </h1> 37 19 <h2 class="text-center text-xl italic dark:text-white"> ··· 51 33 name="handle" 52 34 tabindex="1" 53 35 required 54 - placeholder="foo.tngl.sh" 36 + placeholder="akshay.tngl.sh" 55 37 /> 56 38 <span class="text-sm text-gray-500 mt-1"> 57 39 Use your <a href="https://atproto.com">ATProto</a> ··· 61 43 </div> 62 44 63 45 <button 64 - class="btn w-full my-2 mt-6" 46 + class="btn w-full my-2 mt-6 text-base " 65 47 type="submit" 66 48 id="login-button" 67 49 tabindex="3" ··· 69 51 <span>login</span> 70 52 </button> 71 53 </form> 72 - <hr class="my-4"> 73 - <p class="text-sm text-gray-500 mt-4"> 74 - Alternatively, you may create an account on Tangled below. You will 75 - get a <code>user.tngl.sh</code> handle. 54 + <p class="text-sm text-gray-500"> 55 + Don't have an account? <a href="/signup" class="underline">Create an account</a> on Tangled now! 76 56 </p> 77 57 78 - <details class="group"> 79 - 80 - <summary 81 - class="btn cursor-pointer w-full mt-4 flex items-center justify-center gap-2" 82 - > 83 - create an account 84 - 85 - <div class="group-open:hidden flex">{{ i "arrow-right" "w-4 h-4" }}</div> 86 - <div class="hidden group-open:flex">{{ i "arrow-down" "w-4 h-4" }}</div> 87 - </summary> 88 - <form 89 - class="mt-4 max-w-sm mx-auto" 90 - hx-post="/signup" 91 - hx-swap="none" 92 - hx-disabled-elt="#signup-button" 93 - > 94 - <div class="flex flex-col mt-2"> 95 - <label for="email">email</label> 96 - <input 97 - type="email" 98 - id="email" 99 - name="email" 100 - tabindex="4" 101 - required 102 - placeholder="jason@bourne.co" 103 - /> 104 - </div> 105 - <span class="text-sm text-gray-500 mt-1"> 106 - You will receive an email with a code. Enter that, along with your 107 - desired username and password in the next page to complete your registration. 108 - </span> 109 - <button 110 - class="btn w-full my-2 mt-6" 111 - type="submit" 112 - id="signup-button" 113 - tabindex="7" 114 - > 115 - <span>sign up</span> 116 - </button> 117 - </form> 118 - </details> 119 - <p class="text-sm text-gray-500 mt-6"> 120 - Join our <a href="https://chat.tangled.sh">Discord</a> or 121 - IRC channel: 122 - <a href="https://web.libera.chat/#tangled" 123 - ><code>#tangled</code> on Libera Chat</a 124 - >. 125 - </p> 126 58 <p id="login-msg" class="error w-full"></p> 127 59 </main> 128 60 </body>
+53
appview/pages/templates/user/signup.html
··· 1 + {{ define "user/signup" }} 2 + <!doctype html> 3 + <html lang="en" class="dark:bg-gray-900"> 4 + <head> 5 + <meta charset="UTF-8" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <meta property="og:title" content="signup · tangled" /> 8 + <meta property="og:url" content="https://tangled.sh/signup" /> 9 + <meta property="og:description" content="sign up for tangled" /> 10 + <script src="/static/htmx.min.js"></script> 11 + <link rel="stylesheet" href="/static/tw.css?{{ cssContentHash }}" type="text/css" /> 12 + <title>sign up &middot; tangled</title> 13 + </head> 14 + <body class="flex items-center justify-center min-h-screen"> 15 + <main class="max-w-md px-6 -mt-4"> 16 + <h1 class="text-center text-2xl font-semibold italic dark:text-white" >tangled</h1> 17 + <h2 class="text-center text-xl italic dark:text-white">tightly-knit social coding.</h2> 18 + <form 19 + class="mt-4 max-w-sm mx-auto" 20 + hx-post="/signup" 21 + hx-swap="none" 22 + hx-disabled-elt="#signup-button" 23 + > 24 + <div class="flex flex-col mt-2"> 25 + <label for="email">email</label> 26 + <input 27 + type="email" 28 + id="email" 29 + name="email" 30 + tabindex="4" 31 + required 32 + placeholder="jason@bourne.co" 33 + /> 34 + </div> 35 + <span class="text-sm text-gray-500 mt-1"> 36 + You will receive an email with an invite code. Enter your 37 + invite code, desired username, and password in the next 38 + page to complete your registration. 39 + </span> 40 + <button class="btn text-base w-full my-2 mt-6" type="submit" id="signup-button" tabindex="7" > 41 + <span>join now</span> 42 + </button> 43 + </form> 44 + <p class="text-sm text-gray-500"> 45 + Already have an account? <a href="/login" class="underline">Login to Tangled</a>. 46 + </p> 47 + 48 + <p id="signup-msg" class="error w-full"></p> 49 + </main> 50 + </body> 51 + </html> 52 + {{ end }} 53 +
+56 -49
appview/signup/signup.go
··· 104 104 105 105 func (s *Signup) Router() http.Handler { 106 106 r := chi.NewRouter() 107 + r.Get("/", s.signup) 107 108 r.Post("/", s.signup) 108 109 r.Get("/complete", s.complete) 109 110 r.Post("/complete", s.complete) ··· 112 113 } 113 114 114 115 func (s *Signup) signup(w http.ResponseWriter, r *http.Request) { 115 - if s.cf == nil { 116 - http.Error(w, "signup is disabled", http.StatusFailedDependency) 117 - } 118 - emailId := r.FormValue("email") 116 + switch r.Method { 117 + case http.MethodGet: 118 + s.pages.Signup(w) 119 + case http.MethodPost: 120 + if s.cf == nil { 121 + http.Error(w, "signup is disabled", http.StatusFailedDependency) 122 + } 123 + emailId := r.FormValue("email") 119 124 120 - if !email.IsValidEmail(emailId) { 121 - s.pages.Notice(w, "login-msg", "Invalid email address.") 122 - return 123 - } 125 + noticeId := "signup-msg" 126 + if !email.IsValidEmail(emailId) { 127 + s.pages.Notice(w, noticeId, "Invalid email address.") 128 + return 129 + } 124 130 125 - exists, err := db.CheckEmailExistsAtAll(s.db, emailId) 126 - if err != nil { 127 - s.l.Error("failed to check email existence", "error", err) 128 - s.pages.Notice(w, "login-msg", "Failed to complete signup. Try again later.") 129 - return 130 - } 131 - if exists { 132 - s.pages.Notice(w, "login-msg", "Email already exists.") 133 - return 134 - } 131 + exists, err := db.CheckEmailExistsAtAll(s.db, emailId) 132 + if err != nil { 133 + s.l.Error("failed to check email existence", "error", err) 134 + s.pages.Notice(w, noticeId, "Failed to complete signup. Try again later.") 135 + return 136 + } 137 + if exists { 138 + s.pages.Notice(w, noticeId, "Email already exists.") 139 + return 140 + } 135 141 136 - code, err := s.inviteCodeRequest() 137 - if err != nil { 138 - s.l.Error("failed to create invite code", "error", err) 139 - s.pages.Notice(w, "login-msg", "Failed to create invite code.") 140 - return 141 - } 142 + code, err := s.inviteCodeRequest() 143 + if err != nil { 144 + s.l.Error("failed to create invite code", "error", err) 145 + s.pages.Notice(w, noticeId, "Failed to create invite code.") 146 + return 147 + } 142 148 143 - em := email.Email{ 144 - APIKey: s.config.Resend.ApiKey, 145 - From: s.config.Resend.SentFrom, 146 - To: emailId, 147 - Subject: "Verify your Tangled account", 148 - Text: `Copy and paste this code below to verify your account on Tangled. 149 + em := email.Email{ 150 + APIKey: s.config.Resend.ApiKey, 151 + From: s.config.Resend.SentFrom, 152 + To: emailId, 153 + Subject: "Verify your Tangled account", 154 + Text: `Copy and paste this code below to verify your account on Tangled. 149 155 ` + code, 150 - Html: `<p>Copy and paste this code below to verify your account on Tangled.</p> 156 + Html: `<p>Copy and paste this code below to verify your account on Tangled.</p> 151 157 <p><code>` + code + `</code></p>`, 152 - } 158 + } 153 159 154 - err = email.SendEmail(em) 155 - if err != nil { 156 - s.l.Error("failed to send email", "error", err) 157 - s.pages.Notice(w, "login-msg", "Failed to send email.") 158 - return 159 - } 160 - err = db.AddInflightSignup(s.db, db.InflightSignup{ 161 - Email: emailId, 162 - InviteCode: code, 163 - }) 164 - if err != nil { 165 - s.l.Error("failed to add inflight signup", "error", err) 166 - s.pages.Notice(w, "login-msg", "Failed to complete sign up. Try again later.") 167 - return 168 - } 160 + err = email.SendEmail(em) 161 + if err != nil { 162 + s.l.Error("failed to send email", "error", err) 163 + s.pages.Notice(w, noticeId, "Failed to send email.") 164 + return 165 + } 166 + err = db.AddInflightSignup(s.db, db.InflightSignup{ 167 + Email: emailId, 168 + InviteCode: code, 169 + }) 170 + if err != nil { 171 + s.l.Error("failed to add inflight signup", "error", err) 172 + s.pages.Notice(w, noticeId, "Failed to complete sign up. Try again later.") 173 + return 174 + } 169 175 170 - s.pages.HxRedirect(w, "/signup/complete") 176 + s.pages.HxRedirect(w, "/signup/complete") 177 + } 171 178 } 172 179 173 180 func (s *Signup) complete(w http.ResponseWriter, r *http.Request) { 174 181 switch r.Method { 175 182 case http.MethodGet: 176 - s.pages.CompleteSignup(w, pages.SignupParams{}) 183 + s.pages.CompleteSignup(w) 177 184 case http.MethodPost: 178 185 username := r.FormValue("username") 179 186 password := r.FormValue("password")