+8
appview/pages/funcmap.go
+8
appview/pages/funcmap.go
···
21
21
"github.com/go-enry/go-enry/v2"
22
22
"tangled.sh/tangled.sh/core/appview/filetree"
23
23
"tangled.sh/tangled.sh/core/appview/pages/markup"
24
+
"tangled.sh/tangled.sh/core/crypto"
24
25
)
25
26
26
27
func (p *Pages) funcMap() template.FuncMap {
···
281
282
"normalizeForHtmlId": func(s string) string {
282
283
// TODO: extend this to handle other cases?
283
284
return strings.ReplaceAll(s, ":", "_")
285
+
},
286
+
"sshFingerprint": func(pubKey string) string {
287
+
fp, err := crypto.SSHFingerprint(pubKey)
288
+
if err != nil {
289
+
return "error"
290
+
}
291
+
return fp
284
292
},
285
293
}
286
294
}
+25
-3
appview/pages/pages.go
+25
-3
appview/pages/pages.go
···
306
306
return p.execute("timeline/timeline", w, params)
307
307
}
308
308
309
-
type SettingsParams struct {
309
+
type UserProfileSettingsParams struct {
310
+
LoggedInUser *oauth.User
311
+
Tabs []map[string]any
312
+
Tab string
313
+
}
314
+
315
+
func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error {
316
+
return p.execute("user/settings/profile", w, params)
317
+
}
318
+
319
+
type UserKeysSettingsParams struct {
310
320
LoggedInUser *oauth.User
311
321
PubKeys []db.PublicKey
322
+
Tabs []map[string]any
323
+
Tab string
324
+
}
325
+
326
+
func (p *Pages) UserKeysSettings(w io.Writer, params UserKeysSettingsParams) error {
327
+
return p.execute("user/settings/keys", w, params)
328
+
}
329
+
330
+
type UserEmailsSettingsParams struct {
331
+
LoggedInUser *oauth.User
312
332
Emails []db.Email
333
+
Tabs []map[string]any
334
+
Tab string
313
335
}
314
336
315
-
func (p *Pages) Settings(w io.Writer, params SettingsParams) error {
316
-
return p.execute("settings", w, params)
337
+
func (p *Pages) UserEmailsSettings(w io.Writer, params UserEmailsSettingsParams) error {
338
+
return p.execute("user/settings/emails", w, params)
317
339
}
318
340
319
341
type KnotsParams struct {
-192
appview/pages/templates/settings.html
-192
appview/pages/templates/settings.html
···
1
-
{{ define "title" }}settings{{ end }}
2
-
3
-
{{ define "content" }}
4
-
<div class="p-6">
5
-
<p class="text-xl font-bold dark:text-white">Settings</p>
6
-
</div>
7
-
<div class="flex flex-col">
8
-
{{ block "profile" . }} {{ end }}
9
-
{{ block "keys" . }} {{ end }}
10
-
{{ block "emails" . }} {{ end }}
11
-
</div>
12
-
{{ end }}
13
-
14
-
{{ define "profile" }}
15
-
<h2 class="text-sm font-bold py-2 px-6 uppercase dark:text-gray-300">profile</h2>
16
-
<section class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">
17
-
<dl class="grid grid-cols-[auto_1fr] gap-x-4 dark:text-gray-200">
18
-
{{ if .LoggedInUser.Handle }}
19
-
<dt class="font-bold">handle</dt>
20
-
<dd>@{{ .LoggedInUser.Handle }}</dd>
21
-
{{ end }}
22
-
<dt class="font-bold">did</dt>
23
-
<dd>{{ .LoggedInUser.Did }}</dd>
24
-
<dt class="font-bold">pds</dt>
25
-
<dd>{{ .LoggedInUser.Pds }}</dd>
26
-
</dl>
27
-
</section>
28
-
{{ end }}
29
-
30
-
{{ define "keys" }}
31
-
<h2 class="text-sm font-bold py-2 px-6 uppercase dark:text-gray-300">ssh keys</h2>
32
-
<section class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">
33
-
<p class="mb-8 dark:text-gray-300">SSH public keys added here will be broadcasted to knots that you are a member of, <br> allowing you to push to repositories there.</p>
34
-
<div id="key-list" class="flex flex-col gap-6 mb-8">
35
-
{{ range $index, $key := .PubKeys }}
36
-
<div class="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
37
-
<div class="flex flex-col gap-1">
38
-
<div class="inline-flex items-center gap-4">
39
-
{{ i "key" "w-3 h-3 dark:text-gray-300" }}
40
-
<p class="font-bold dark:text-white">{{ .Name }}</p>
41
-
</div>
42
-
<p class="text-sm text-gray-500 dark:text-gray-400">added {{ template "repo/fragments/time" .Created }}</p>
43
-
<div class="overflow-x-auto whitespace-nowrap flex-1 max-w-full">
44
-
<code class="text-sm text-gray-500 dark:text-gray-400">{{ .Key }}</code>
45
-
</div>
46
-
</div>
47
-
<button
48
-
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
49
-
title="Delete key"
50
-
hx-delete="/settings/keys?name={{urlquery .Name}}&rkey={{urlquery .Rkey}}&key={{urlquery .Key}}"
51
-
hx-confirm="Are you sure you want to delete the key '{{ .Name }}'?"
52
-
>
53
-
{{ i "trash-2" "w-5 h-5" }}
54
-
<span class="hidden md:inline">delete</span>
55
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
56
-
</button>
57
-
</div>
58
-
{{ end }}
59
-
</div>
60
-
<form
61
-
hx-put="/settings/keys"
62
-
hx-indicator="#add-sshkey-spinner"
63
-
hx-swap="none"
64
-
class="max-w-2xl mb-8 space-y-4"
65
-
>
66
-
<input
67
-
type="text"
68
-
id="name"
69
-
name="name"
70
-
placeholder="key name"
71
-
required
72
-
class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"/>
73
-
74
-
<input
75
-
id="key"
76
-
name="key"
77
-
placeholder="ssh-rsa AAAAAA..."
78
-
required
79
-
class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"/>
80
-
81
-
<button class="btn dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 flex gap-2 items-center" type="submit">
82
-
<span>add key</span>
83
-
<span id="add-sshkey-spinner" class="group">
84
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
85
-
</span>
86
-
</button>
87
-
88
-
<div id="settings-keys" class="error dark:text-red-400"></div>
89
-
</form>
90
-
</section>
91
-
{{ end }}
92
-
93
-
{{ define "emails" }}
94
-
<h2 class="text-sm font-bold py-2 px-6 uppercase dark:text-gray-300">email addresses</h2>
95
-
<section class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">
96
-
<p class="mb-8 dark:text-gray-300">Commits authored using emails listed here will be associated with your Tangled profile.</p>
97
-
<div id="email-list" class="flex flex-col gap-6 mb-8">
98
-
{{ range $index, $email := .Emails }}
99
-
<div class="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
100
-
<div class="flex flex-col gap-2">
101
-
<div class="inline-flex items-center gap-4">
102
-
{{ i "mail" "w-3 h-3 dark:text-gray-300" }}
103
-
<p class="font-bold dark:text-white">{{ .Address }}</p>
104
-
<div class="inline-flex items-center gap-1">
105
-
{{ if .Verified }}
106
-
<span class="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded">verified</span>
107
-
{{ else }}
108
-
<span class="text-xs bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 px-2 py-1 rounded">unverified</span>
109
-
{{ end }}
110
-
{{ if .Primary }}
111
-
<span class="text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-2 py-1 rounded">primary</span>
112
-
{{ end }}
113
-
</div>
114
-
</div>
115
-
<p class="text-sm text-gray-500 dark:text-gray-400">added {{ template "repo/fragments/time" .CreatedAt }}</p>
116
-
</div>
117
-
<div class="flex gap-2 items-center">
118
-
{{ if not .Verified }}
119
-
<button
120
-
class="btn flex gap-2 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600"
121
-
hx-post="/settings/emails/verify/resend"
122
-
hx-swap="none"
123
-
href="#"
124
-
hx-vals='{"email": "{{ .Address }}"}'>
125
-
{{ i "rotate-cw" "w-5 h-5" }}
126
-
<span class="hidden md:inline">resend</span>
127
-
</button>
128
-
{{ end }}
129
-
{{ if and (not .Primary) .Verified }}
130
-
<a
131
-
class="text-sm dark:text-blue-400 dark:hover:text-blue-300"
132
-
hx-post="/settings/emails/primary"
133
-
hx-swap="none"
134
-
href="#"
135
-
hx-vals='{"email": "{{ .Address }}"}'>
136
-
set as primary
137
-
</a>
138
-
{{ end }}
139
-
{{ if not .Primary }}
140
-
<form
141
-
hx-delete="/settings/emails"
142
-
hx-confirm="Are you sure you wish to delete the email '{{ .Address }}'?"
143
-
hx-indicator="#delete-email-{{ $index }}-spinner"
144
-
>
145
-
<input type="hidden" name="email" value="{{ .Address }}">
146
-
<button
147
-
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 flex gap-2 items-center"
148
-
title="Delete email"
149
-
type="submit"
150
-
>
151
-
{{ i "trash-2" "w-5 h-5" }}
152
-
<span class="hidden md:inline">delete</span>
153
-
<span id="delete-email-{{ $index }}-spinner" class="group">
154
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
155
-
</span>
156
-
</button>
157
-
</form>
158
-
{{ end }}
159
-
</div>
160
-
</div>
161
-
{{ end }}
162
-
</div>
163
-
<form
164
-
hx-put="/settings/emails"
165
-
hx-swap="none"
166
-
class="max-w-2xl mb-8 space-y-4"
167
-
hx-indicator="#add-email-spinner"
168
-
>
169
-
<input
170
-
type="email"
171
-
id="email"
172
-
name="email"
173
-
placeholder="your@email.com"
174
-
required
175
-
class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"
176
-
>
177
-
178
-
<button
179
-
class="btn dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 flex gap-2 items-center"
180
-
type="submit"
181
-
>
182
-
<span>add email</span>
183
-
<span id="add-email-spinner" class="group">
184
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
185
-
</span>
186
-
</button>
187
-
188
-
<div id="settings-emails-error" class="error dark:text-red-400"></div>
189
-
<div id="settings-emails-success" class="success dark:text-green-400"></div>
190
-
</form>
191
-
</section>
192
-
{{ end }}
+94
appview/pages/templates/user/settings/emails.html
+94
appview/pages/templates/user/settings/emails.html
···
1
+
{{ define "title" }}{{ .Tab }} settings{{ end }}
2
+
3
+
{{ define "content" }}
4
+
<div class="p-6">
5
+
<p class="text-xl font-bold dark:text-white">Settings</p>
6
+
</div>
7
+
<div class="bg-white dark:bg-gray-800">
8
+
<section class="w-full grid grid-cols-1 md:grid-cols-4 gap-6 p-6">
9
+
<div class="col-span-1">
10
+
{{ template "user/settings/fragments/sidebar" . }}
11
+
</div>
12
+
<div class="col-span-1 md:col-span-3 flex flex-col gap-6">
13
+
{{ template "emailSettings" . }}
14
+
</div>
15
+
</section>
16
+
</div>
17
+
{{ end }}
18
+
19
+
{{ define "emailSettings" }}
20
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
21
+
<div class="col-span-1 md:col-span-2">
22
+
<h2 class="text-sm pb-2 uppercase font-bold">Email Addresses</h2>
23
+
<p class="text-gray-500 dark:text-gray-400">
24
+
Commits authored using emails listed here will be associated with your Tangled profile.
25
+
</p>
26
+
</div>
27
+
<div class="col-span-1 md:col-span-1 md:justify-self-end">
28
+
{{ template "addEmailButton" . }}
29
+
</div>
30
+
</div>
31
+
<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
32
+
{{ range .Emails }}
33
+
{{ template "user/settings/fragments/emailListing" (list $ .) }}
34
+
{{ else }}
35
+
<div class="flex items-center justify-center p-2 text-gray-500">
36
+
no emails added yet
37
+
</div>
38
+
{{ end }}
39
+
</div>
40
+
{{ end }}
41
+
42
+
{{ define "addEmailButton" }}
43
+
<button
44
+
class="btn flex items-center gap-2"
45
+
popovertarget="add-email-modal"
46
+
popovertargetaction="toggle">
47
+
{{ i "plus" "size-4" }}
48
+
add email
49
+
</button>
50
+
<div
51
+
id="add-email-modal"
52
+
popover
53
+
class="bg-white w-full md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">
54
+
{{ template "addEmailModal" . }}
55
+
</div>
56
+
{{ end}}
57
+
58
+
{{ define "addEmailModal" }}
59
+
<form
60
+
hx-put="/settings/emails"
61
+
hx-indicator="#spinner"
62
+
hx-swap="none"
63
+
class="flex flex-col gap-2"
64
+
>
65
+
<p class="uppercase p-0">ADD EMAIL</p>
66
+
<p class="text-sm text-gray-500 dark:text-gray-400">Commits using this email will be associated with your profile.</p>
67
+
<input
68
+
type="email"
69
+
id="email-address"
70
+
name="email"
71
+
required
72
+
placeholder="your@email.com"
73
+
class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"
74
+
/>
75
+
<div class="flex gap-2 pt-2">
76
+
<button
77
+
type="button"
78
+
popovertarget="add-email-modal"
79
+
popovertargetaction="hide"
80
+
class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
81
+
>
82
+
{{ i "x" "size-4" }} cancel
83
+
</button>
84
+
<button type="submit" class="btn w-1/2 flex items-center">
85
+
<span class="inline-flex gap-2 items-center">{{ i "plus" "size-4" }} add</span>
86
+
<span id="spinner" class="group">
87
+
{{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
88
+
</span>
89
+
</button>
90
+
</div>
91
+
<div id="settings-emails-error" class="text-red-500 dark:text-red-400"></div>
92
+
<div id="settings-emails-success" class="text-green-500 dark:text-green-400"></div>
93
+
</form>
94
+
{{ end }}
+62
appview/pages/templates/user/settings/fragments/emailListing.html
+62
appview/pages/templates/user/settings/fragments/emailListing.html
···
1
+
{{ define "user/settings/fragments/emailListing" }}
2
+
{{ $root := index . 0 }}
3
+
{{ $email := index . 1 }}
4
+
<div id="email-{{$email.Address}}" class="flex items-center justify-between p-2">
5
+
<div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[80%]">
6
+
<div class="flex items-center gap-2">
7
+
{{ i "mail" "w-4 h-4 text-gray-500 dark:text-gray-400" }}
8
+
<span class="font-bold">
9
+
{{ $email.Address }}
10
+
</span>
11
+
<div class="inline-flex items-center gap-1">
12
+
{{ if $email.Verified }}
13
+
<span class="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded">verified</span>
14
+
{{ else }}
15
+
<span class="text-xs bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 px-2 py-1 rounded">unverified</span>
16
+
{{ end }}
17
+
{{ if $email.Primary }}
18
+
<span class="text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-2 py-1 rounded">primary</span>
19
+
{{ end }}
20
+
</div>
21
+
</div>
22
+
<div class="flex text-sm flex-wrap text items-center gap-1 text-gray-500 dark:text-gray-400">
23
+
<span>added {{ template "repo/fragments/time" $email.CreatedAt }}</span>
24
+
</div>
25
+
</div>
26
+
<div class="flex gap-2 items-center">
27
+
{{ if not $email.Verified }}
28
+
<button
29
+
class="btn flex gap-2 text-sm px-2 py-1"
30
+
hx-post="/settings/emails/verify/resend"
31
+
hx-swap="none"
32
+
hx-vals='{"email": "{{ $email.Address }}"}'>
33
+
{{ i "rotate-cw" "w-4 h-4" }}
34
+
<span class="hidden md:inline">resend</span>
35
+
</button>
36
+
{{ end }}
37
+
{{ if and (not $email.Primary) $email.Verified }}
38
+
<button
39
+
class="btn text-sm px-2 py-1 text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
40
+
hx-post="/settings/emails/primary"
41
+
hx-swap="none"
42
+
hx-vals='{"email": "{{ $email.Address }}"}'>
43
+
set as primary
44
+
</button>
45
+
{{ end }}
46
+
{{ if not $email.Primary }}
47
+
<button
48
+
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
49
+
title="Delete email"
50
+
hx-delete="/settings/emails"
51
+
hx-swap="none"
52
+
hx-vals='{"email": "{{ $email.Address }}"}'
53
+
hx-confirm="Are you sure you want to delete the email {{ $email.Address }}?"
54
+
>
55
+
{{ i "trash-2" "w-5 h-5" }}
56
+
<span class="hidden md:inline">delete</span>
57
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
58
+
</button>
59
+
{{ end }}
60
+
</div>
61
+
</div>
62
+
{{ end }}
+31
appview/pages/templates/user/settings/fragments/keyListing.html
+31
appview/pages/templates/user/settings/fragments/keyListing.html
···
1
+
{{ define "user/settings/fragments/keyListing" }}
2
+
{{ $root := index . 0 }}
3
+
{{ $key := index . 1 }}
4
+
<div id="key-{{$key.Name}}" class="flex items-center justify-between p-2">
5
+
<div class="hover:no-underline flex flex-col gap-1 text min-w-0 max-w-[80%]">
6
+
<div class="flex items-center gap-2">
7
+
<span>{{ i "key" "w-4" "h-4" }}</span>
8
+
<span class="font-bold">
9
+
{{ $key.Name }}
10
+
</span>
11
+
</div>
12
+
<span class="font-mono text-sm text-gray-500 dark:text-gray-400">
13
+
{{ sshFingerprint $key.Key }}
14
+
</span>
15
+
<div class="flex flex-wrap text-sm items-center gap-1 text-gray-500 dark:text-gray-400">
16
+
<span>added {{ template "repo/fragments/time" $key.Created }}</span>
17
+
</div>
18
+
</div>
19
+
<button
20
+
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
21
+
title="Delete key"
22
+
hx-delete="/settings/keys?name={{urlquery $key.Name}}&rkey={{urlquery $key.Rkey}}&key={{urlquery $key.Key}}"
23
+
hx-swap="none"
24
+
hx-confirm="Are you sure you want to delete the key {{ $key.Name }}?"
25
+
>
26
+
{{ i "trash-2" "w-5 h-5" }}
27
+
<span class="hidden md:inline">delete</span>
28
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
29
+
</button>
30
+
</div>
31
+
{{ end }}
+101
appview/pages/templates/user/settings/keys.html
+101
appview/pages/templates/user/settings/keys.html
···
1
+
{{ define "title" }}{{ .Tab }} settings{{ end }}
2
+
3
+
{{ define "content" }}
4
+
<div class="p-6">
5
+
<p class="text-xl font-bold dark:text-white">Settings</p>
6
+
</div>
7
+
<div class="bg-white dark:bg-gray-800">
8
+
<section class="w-full grid grid-cols-1 md:grid-cols-4 gap-6 p-6">
9
+
<div class="col-span-1">
10
+
{{ template "user/settings/fragments/sidebar" . }}
11
+
</div>
12
+
<div class="col-span-1 md:col-span-3 flex flex-col gap-6">
13
+
{{ template "sshKeysSettings" . }}
14
+
</div>
15
+
</section>
16
+
</div>
17
+
{{ end }}
18
+
19
+
{{ define "sshKeysSettings" }}
20
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
21
+
<div class="col-span-1 md:col-span-2">
22
+
<h2 class="text-sm pb-2 uppercase font-bold">SSH Keys</h2>
23
+
<p class="text-gray-500 dark:text-gray-400">
24
+
SSH public keys added here will be broadcasted to knots that you are a member of,
25
+
allowing you to push to repositories there.
26
+
</p>
27
+
</div>
28
+
<div class="col-span-1 md:col-span-1 md:justify-self-end">
29
+
{{ template "addKeyButton" . }}
30
+
</div>
31
+
</div>
32
+
<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
33
+
{{ range .PubKeys }}
34
+
{{ template "user/settings/fragments/keyListing" (list $ .) }}
35
+
{{ else }}
36
+
<div class="flex items-center justify-center p-2 text-gray-500">
37
+
no keys added yet
38
+
</div>
39
+
{{ end }}
40
+
</div>
41
+
{{ end }}
42
+
43
+
{{ define "addKeyButton" }}
44
+
<button
45
+
class="btn flex items-center gap-2"
46
+
popovertarget="add-key-modal"
47
+
popovertargetaction="toggle">
48
+
{{ i "plus" "size-4" }}
49
+
add key
50
+
</button>
51
+
<div
52
+
id="add-key-modal"
53
+
popover
54
+
class="bg-white w-full md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">
55
+
{{ template "addKeyModal" . }}
56
+
</div>
57
+
{{ end}}
58
+
59
+
{{ define "addKeyModal" }}
60
+
<form
61
+
hx-put="/settings/keys"
62
+
hx-indicator="#spinner"
63
+
hx-swap="none"
64
+
class="flex flex-col gap-2"
65
+
>
66
+
<p class="uppercase p-0">ADD SSH KEY</p>
67
+
<p class="text-sm text-gray-500 dark:text-gray-400">SSH keys allow you to push to repositories in knots you're a member of.</p>
68
+
<input
69
+
type="text"
70
+
id="key-name"
71
+
name="name"
72
+
required
73
+
placeholder="key name"
74
+
class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"
75
+
/>
76
+
<textarea
77
+
type="text"
78
+
id="key-value"
79
+
name="key"
80
+
required
81
+
placeholder="ssh-rsa AAAAB3NzaC1yc2E..."
82
+
class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"></textarea>
83
+
<div class="flex gap-2 pt-2">
84
+
<button
85
+
type="button"
86
+
popovertarget="add-key-modal"
87
+
popovertargetaction="hide"
88
+
class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
89
+
>
90
+
{{ i "x" "size-4" }} cancel
91
+
</button>
92
+
<button type="submit" class="btn w-1/2 flex items-center">
93
+
<span class="inline-flex gap-2 items-center">{{ i "plus" "size-4" }} add</span>
94
+
<span id="spinner" class="group">
95
+
{{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
96
+
</span>
97
+
</button>
98
+
</div>
99
+
<div id="settings-keys" class="text-red-500 dark:text-red-400"></div>
100
+
</form>
101
+
{{ end }}
+64
appview/pages/templates/user/settings/profile.html
+64
appview/pages/templates/user/settings/profile.html
···
1
+
{{ define "title" }}{{ .Tab }} settings{{ end }}
2
+
3
+
{{ define "content" }}
4
+
<div class="p-6">
5
+
<p class="text-xl font-bold dark:text-white">Settings</p>
6
+
</div>
7
+
<div class="bg-white dark:bg-gray-800">
8
+
<section class="w-full grid grid-cols-1 md:grid-cols-4 gap-6 p-6">
9
+
<div class="col-span-1">
10
+
{{ template "user/settings/fragments/sidebar" . }}
11
+
</div>
12
+
<div class="col-span-1 md:col-span-3 flex flex-col gap-6">
13
+
{{ template "profileInfo" . }}
14
+
</div>
15
+
</section>
16
+
</div>
17
+
{{ end }}
18
+
19
+
{{ define "profileInfo" }}
20
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
21
+
<div class="col-span-1 md:col-span-2">
22
+
<h2 class="text-sm pb-2 uppercase font-bold">Profile</h2>
23
+
<p class="text-gray-500 dark:text-gray-400">
24
+
Your account information from your AT Protocol identity.
25
+
</p>
26
+
</div>
27
+
<div class="col-span-1 md:col-span-1 md:justify-self-end">
28
+
</div>
29
+
</div>
30
+
<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
31
+
<div class="flex items-center justify-between p-4">
32
+
<div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[80%]">
33
+
{{ if .LoggedInUser.Handle }}
34
+
<span class="font-bold">
35
+
@{{ .LoggedInUser.Handle }}
36
+
</span>
37
+
<div class="flex flex-wrap text items-center gap-1 text-gray-500 dark:text-gray-400">
38
+
<span>Handle</span>
39
+
</div>
40
+
{{ end }}
41
+
</div>
42
+
</div>
43
+
<div class="flex items-center justify-between p-4">
44
+
<div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[80%]">
45
+
<span class="font-mono text-xs">
46
+
{{ .LoggedInUser.Did }}
47
+
</span>
48
+
<div class="flex flex-wrap text items-center gap-1 text-gray-500 dark:text-gray-400">
49
+
<span>Decentralized Identifier (DID)</span>
50
+
</div>
51
+
</div>
52
+
</div>
53
+
<div class="flex items-center justify-between p-4">
54
+
<div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[80%]">
55
+
<span class="font-bold">
56
+
{{ .LoggedInUser.Pds }}
57
+
</span>
58
+
<div class="flex flex-wrap text items-center gap-1 text-gray-500 dark:text-gray-400">
59
+
<span>Personal Data Server (PDS)</span>
60
+
</div>
61
+
</div>
62
+
</div>
63
+
</div>
64
+
{{ end }}
+44
-9
appview/settings/settings.go
+44
-9
appview/settings/settings.go
···
33
33
Config *config.Config
34
34
}
35
35
36
+
type tab = map[string]any
37
+
38
+
var (
39
+
settingsTabs []tab = []tab{
40
+
{"Name": "profile", "Icon": "user"},
41
+
{"Name": "keys", "Icon": "key"},
42
+
{"Name": "emails", "Icon": "mail"},
43
+
}
44
+
)
45
+
36
46
func (s *Settings) Router() http.Handler {
37
47
r := chi.NewRouter()
38
48
39
49
r.Use(middleware.AuthMiddleware(s.OAuth))
40
50
41
-
r.Get("/", s.settings)
51
+
// settings pages
52
+
r.Get("/", s.profileSettings)
53
+
r.Get("/profile", s.profileSettings)
42
54
43
55
r.Route("/keys", func(r chi.Router) {
56
+
r.Get("/", s.keysSettings)
44
57
r.Put("/", s.keys)
45
58
r.Delete("/", s.keys)
46
59
})
47
60
48
61
r.Route("/emails", func(r chi.Router) {
62
+
r.Get("/", s.emailsSettings)
49
63
r.Put("/", s.emails)
50
64
r.Delete("/", s.emails)
51
65
r.Get("/verify", s.emailsVerify)
···
56
70
return r
57
71
}
58
72
59
-
func (s *Settings) settings(w http.ResponseWriter, r *http.Request) {
73
+
func (s *Settings) profileSettings(w http.ResponseWriter, r *http.Request) {
74
+
user := s.OAuth.GetUser(r)
75
+
76
+
s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{
77
+
LoggedInUser: user,
78
+
Tabs: settingsTabs,
79
+
Tab: "profile",
80
+
})
81
+
}
82
+
83
+
func (s *Settings) keysSettings(w http.ResponseWriter, r *http.Request) {
60
84
user := s.OAuth.GetUser(r)
61
85
pubKeys, err := db.GetPublicKeysForDid(s.Db, user.Did)
62
86
if err != nil {
63
87
log.Println(err)
64
88
}
65
89
90
+
s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{
91
+
LoggedInUser: user,
92
+
PubKeys: pubKeys,
93
+
Tabs: settingsTabs,
94
+
Tab: "keys",
95
+
})
96
+
}
97
+
98
+
func (s *Settings) emailsSettings(w http.ResponseWriter, r *http.Request) {
99
+
user := s.OAuth.GetUser(r)
66
100
emails, err := db.GetAllEmails(s.Db, user.Did)
67
101
if err != nil {
68
102
log.Println(err)
69
103
}
70
104
71
-
s.Pages.Settings(w, pages.SettingsParams{
105
+
s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{
72
106
LoggedInUser: user,
73
-
PubKeys: pubKeys,
74
107
Emails: emails,
108
+
Tabs: settingsTabs,
109
+
Tab: "emails",
75
110
})
76
111
}
77
112
···
201
236
return
202
237
}
203
238
204
-
s.Pages.HxLocation(w, "/settings")
239
+
s.Pages.HxLocation(w, "/settings/emails")
205
240
return
206
241
}
207
242
}
···
244
279
return
245
280
}
246
281
247
-
http.Redirect(w, r, "/settings", http.StatusSeeOther)
282
+
http.Redirect(w, r, "/settings/emails", http.StatusSeeOther)
248
283
}
249
284
250
285
func (s *Settings) emailsVerifyResend(w http.ResponseWriter, r *http.Request) {
···
339
374
return
340
375
}
341
376
342
-
s.Pages.HxLocation(w, "/settings")
377
+
s.Pages.HxLocation(w, "/settings/emails")
343
378
}
344
379
345
380
func (s *Settings) keys(w http.ResponseWriter, r *http.Request) {
···
410
445
return
411
446
}
412
447
413
-
s.Pages.HxLocation(w, "/settings")
448
+
s.Pages.HxLocation(w, "/settings/keys")
414
449
return
415
450
416
451
case http.MethodDelete:
···
455
490
}
456
491
log.Println("deleted successfully")
457
492
458
-
s.Pages.HxLocation(w, "/settings")
493
+
s.Pages.HxLocation(w, "/settings/keys")
459
494
return
460
495
}
461
496
}