rastrillo / idear Public

Clone
git clone https://amadan.net/rastrillo/idear

Plain git — no account needed to clone.

Download

Download this file

1{{define "content"}}
2{{$viewer := .Content.Viewer}}
3{{$grantable := .Content.Grantable}}
4<h1>Members</h1>
5
6<table>
7<thead><tr><th>Member</th><th>Role</th><th>Status</th><th>Actions</th></tr></thead>
8<tbody>
9{{range .Content.Members}}
10<tr>
11<td>{{.Email}}{{if .Name}} ({{.Name}}){{end}}</td>
12<td>{{.Role.Title}}</td>
13<td>{{if .Active}}Active{{else}}Removed{{end}}</td>
14<td>
15{{if mayActOn $viewer .}}
16{{/* The role selector is built from Grantable, never from the three
17 role constants. An Admin may grant only Member — idear refuses a
18 grant that is not strictly below the granter's own rank — so a
19 selector offering Admin to an Admin would 403 on every submit,
20 and one offering Owner would 403 for everybody including the
21 Owner. Hiding a control is never the enforcement; the store
22 refuses either way. */}}
23{{if $grantable}}
24<form method="post" action="/members/{{.ID}}/role">
25<select name="role" aria-label="Role">
26{{range $grantable}}<option value="{{.}}">{{.Title}}</option>{{end}}
27</select>
28<button type="submit">Set role</button>
29</form>
30{{end}}
31{{if .Active}}
32<form method="post" action="/members/{{.ID}}/remove"><button type="submit">Remove</button></form>
33{{else}}
34<form method="post" action="/members/{{.ID}}/restore"><button type="submit">Restore</button></form>
35{{end}}
36{{end}}
37</td>
38</tr>
39{{end}}
40</tbody>
41</table>
42
43{{if $grantable}}
44<h2>Invite someone</h2>
45<form method="post" action="/members/invitations">
46<label>Email <input type="email" name="email" required></label>
47<select name="role" aria-label="Role">
48{{range $grantable}}<option value="{{.}}">{{.Title}}</option>{{end}}
49</select>
50<button type="submit">Invite</button>
51</form>
52{{end}}
53
54{{with .Content.Invitations}}
55<h2>Pending invitations</h2>
56<ul>
57{{range .}}
58<li>{{.Email}} — {{.Role.Title}}
59<form method="post" action="/members/invitations/{{.ID}}/revoke"><button type="submit">Revoke</button></form>
60</li>
61{{end}}
62</ul>
63{{end}}
64
65{{if .Content.IsOwner}}
66<h2>Transfer ownership</h2>
67{{/* The only path to Owner. idear demotes the outgoing Owner and
68 promotes the incoming one in one transaction, so the instance is
69 never left with two Owners or none — and role=owner is refused on
70 every other route, for every actor. */}}
71<form method="post" action="/members/transfer">
72<select name="member" aria-label="New owner">
73{{range .Content.Members}}{{if and .Active (ne .ID $viewer.ID)}}<option value="{{.ID}}">{{.Email}}</option>{{end}}{{end}}
74</select>
75<button type="submit">Transfer ownership</button>
76</form>
77{{end}}
78{{end}}
79