+5
-3
appview/pages/pages.go
+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
+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
+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
+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 · tangled</title>
11
+
<link rel="stylesheet" href="/static/tw.css?{{ cssContentHash }}" type="text/css" />
12
+
<title>login · 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
+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 · 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
+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")