rin
Public Open-source lightweight git hosting.
settings.html · 184 lines · 8.6 KB
Raw
1 {{define "content"}}
2 <div class="container">
3 <div class="flex items-center justify-between mb-4">
4 <h1 class="text-lg font-semibold" style="margin:0;">{{t .Lang "settings.title"}}</h1>
5 <a href="/{{.CurrentUser.Username}}" class="btn btn-outline btn-sm">
6 {{icon "user" "size-3.5"}}
7 <span>@{{.CurrentUser.Username}}</span>
8 </a>
9 </div>
10
11 <!-- Avatar & Profile -->
12 <section class="card mb-4">
13 <div class="card-header">
14 <span class="font-semibold flex items-center gap-2">{{icon "user" "size-4"}} {{t .Lang "settings.profile"}}</span>
15 </div>
16 <form method="post" action="/settings/profile" enctype="multipart/form-data" class="card-body flex flex-col gap-4">
17 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
18 <input type="hidden" id="avatar-data-input" name="avatar_data" value="">
19 <input type="hidden" id="remove-avatar-input" name="remove_avatar" value="false">
20
21 <!-- Avatar Picker Row -->
22 <div class="flex items-center gap-4 flex-wrap pb-3" style="border-bottom:1px solid hsl(var(--border));">
23 <div id="avatar-preview-wrap">
24 {{avatar .CurrentUser.Username .CurrentUser.AvatarURL "avatar-xl"}}
25 </div>
26 <div class="flex flex-col gap-1.5">
27 <div class="text-sm font-medium">{{t .Lang "settings.avatar"}}</div>
28 <div class="flex items-center gap-2 flex-wrap">
29 <label class="btn btn-outline btn-sm" style="cursor:pointer;">
30 {{icon "image" "size-3.5"}}
31 <span>{{t .Lang "settings.avatar_upload"}}</span>
32 <input id="avatar-file-input" name="avatar_file" type="file" accept="image/png,image/jpeg,image/webp,image/gif" style="display:none;">
33 </label>
34 {{if .CurrentUser.AvatarURL}}
35 <button type="button" id="remove-avatar-btn" class="btn btn-ghost btn-sm text-destructive">
36 {{icon "trash" "size-3.5"}}
37 <span>{{t .Lang "settings.avatar_remove"}}</span>
38 </button>
39 {{end}}
40 </div>
41 <div class="field-hint" style="margin:0;">{{t .Lang "settings.avatar_hint"}}</div>
42 </div>
43 </div>
44
45 <div class="grid-2">
46 <div>
47 <label class="label">{{t .Lang "auth.username"}}</label>
48 <input type="text" class="input font-mono" value="{{.CurrentUser.Username}}" disabled>
49 </div>
50 <div>
51 <label class="label" for="full_name">{{t .Lang "settings.full_name"}}</label>
52 <input id="full_name" name="full_name" type="text" class="input" value="{{.CurrentUser.FullName}}" maxlength="80" placeholder="{{t .Lang "settings.full_name_ph"}}">
53 </div>
54 </div>
55
56 <div class="grid-2">
57 <div>
58 <label class="label" for="email">{{t .Lang "auth.email"}}</label>
59 <input id="email" name="email" type="email" class="input" value="{{.CurrentUser.Email}}" required>
60 </div>
61 <div>
62 <label class="label" for="location">{{t .Lang "settings.location"}}</label>
63 <input id="location" name="location" type="text" class="input" value="{{.CurrentUser.Location}}" maxlength="80" placeholder="{{t .Lang "settings.location_ph"}}">
64 </div>
65 </div>
66
67 <div class="grid-2">
68 <div>
69 <label class="label" for="bio">{{t .Lang "settings.bio"}}</label>
70 <input id="bio" name="bio" type="text" class="input" value="{{.CurrentUser.Bio}}" maxlength="280" placeholder="{{t .Lang "settings.bio_ph"}}">
71 </div>
72 <div>
73 <label class="label" for="website">{{t .Lang "settings.website"}}</label>
74 <input id="website" name="website" type="text" class="input" value="{{.CurrentUser.Website}}" maxlength="200" placeholder="{{t .Lang "settings.website_ph"}}">
75 </div>
76 </div>
77
78 <div class="flex items-center justify-between flex-wrap gap-2 pt-2">
79 <div class="flex items-center gap-1.5 flex-wrap">
80 <span class="text-xs text-muted">{{t .Lang "nav.theme"}}:</span>
81 <button type="button" class="btn btn-outline btn-sm" data-set-theme="light">{{t .Lang "theme.light"}}</button>
82 <button type="button" class="btn btn-outline btn-sm" data-set-theme="dark">{{t .Lang "theme.dark"}}</button>
83 <button type="button" class="btn btn-outline btn-sm" data-set-theme="system">{{t .Lang "theme.system"}}</button>
84 </div>
85 <button type="submit" class="btn btn-primary btn-sm">{{t .Lang "repo.save"}}</button>
86 </div>
87 </form>
88 </section>
89
90 <!-- Personal Access Tokens -->
91 <section id="tokens" class="card mb-4">
92 <div class="card-header">
93 <span class="font-semibold flex items-center gap-2">{{icon "key" "size-4"}} {{t .Lang "settings.tokens"}}</span>
94 </div>
95 <div class="card-body flex flex-col gap-3">
96 <p class="text-xs text-muted" style="margin:0;">{{t .Lang "settings.tokens.desc"}}</p>
97
98 {{if .NewRawToken}}
99 <div class="card p-3" style="border-color:hsl(var(--primary));background:hsl(var(--muted)/0.5);">
100 <div class="text-xs font-medium mb-2">{{t .Lang "settings.token.created_once"}}</div>
101 <div class="flex items-center justify-between gap-2">
102 <code class="font-mono text-sm select-all" id="created-token-val">{{.NewRawToken}}</code>
103 <button type="button" class="btn btn-secondary btn-sm" data-copy="{{.NewRawToken}}">
104 {{icon "copy" "size-3.5"}}
105 <span>{{t .Lang "common.copy"}}</span>
106 </button>
107 </div>
108 </div>
109 {{end}}
110
111 <form method="post" action="/settings/tokens" class="flex items-center gap-2">
112 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
113 <input name="name" type="text" class="input" placeholder="{{t .Lang "settings.token.name"}}" required maxlength="64">
114 <button type="submit" class="btn btn-primary">
115 {{icon "plus" "size-4"}}
116 <span>{{t .Lang "settings.token.create"}}</span>
117 </button>
118 </form>
119
120 {{if .Tokens}}
121 <div class="table-wrap">
122 <table class="table">
123 <thead>
124 <tr>
125 <th>{{t .Lang "settings.token.col_name"}}</th>
126 <th>{{t .Lang "settings.token.col_prefix"}}</th>
127 <th>{{t .Lang "settings.token.col_created"}}</th>
128 <th>{{t .Lang "settings.token.col_used"}}</th>
129 <th style="text-align:right;"></th>
130 </tr>
131 </thead>
132 <tbody>
133 {{range .Tokens}}
134 <tr>
135 <td class="font-medium">{{.Name}}</td>
136 <td><code class="font-mono text-xs text-muted">{{.TokenPrefix}}…</code></td>
137 <td class="text-xs text-muted">{{timeAgo .CreatedAt $.Lang}}</td>
138 <td class="text-xs text-muted">
139 {{if .LastUsedAt}}{{timeAgo .LastUsedAt $.Lang}}{{else}}{{t $.Lang "common.never"}}{{end}}
140 </td>
141 <td style="text-align:right;">
142 <form method="post" action="/settings/tokens/{{.ID}}/revoke" style="display:inline;">
143 <input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
144 <button type="submit" class="btn btn-destructive btn-sm">
145 {{icon "trash" "size-3.5"}}
146 <span>{{t $.Lang "settings.token.revoke"}}</span>
147 </button>
148 </form>
149 </td>
150 </tr>
151 {{end}}
152 </tbody>
153 </table>
154 </div>
155 {{else}}
156 <div class="text-xs text-muted">{{t .Lang "settings.token.empty"}}</div>
157 {{end}}
158 </div>
159 </section>
160
161 <!-- Change Password -->
162 <section class="card">
163 <div class="card-header">
164 <span class="font-semibold flex items-center gap-2">{{icon "lock" "size-4"}} {{t .Lang "auth.password"}}</span>
165 </div>
166 <form method="post" action="/settings/password" class="card-body flex flex-col gap-3">
167 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
168 <div class="grid-2">
169 <div>
170 <label class="label" for="current_password">{{t .Lang "settings.password.current"}}</label>
171 <input id="current_password" name="current_password" type="password" class="input" required>
172 </div>
173 <div>
174 <label class="label" for="new_password">{{t .Lang "settings.password.new"}}</label>
175 <input id="new_password" name="new_password" type="password" class="input" minlength="8" required>
176 </div>
177 </div>
178 <div class="flex items-center justify-end">
179 <button type="submit" class="btn btn-outline btn-sm">{{t .Lang "settings.password.update"}}</button>
180 </div>
181 </form>
182 </section>
183 </div>
184 {{end}}