feat: add alias creation UI with domain selector on profile page
Users can now generate a random disposable alias by selecting an allowed domain from a dropdown and clicking "Generate alias", without specifying a custom local part. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
69c307e7d6
commit
ef29a0c7f1
3 changed files with 87 additions and 8 deletions
13
login.go
13
login.go
|
|
@ -165,12 +165,13 @@ func tryLogin(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
displayTmpl(w, "profile.html", map[string]any{
|
||||
"login": loginName,
|
||||
"fields": fields,
|
||||
"emails": emails,
|
||||
"aliases": aliases,
|
||||
"api_token": apiToken,
|
||||
"card_wide": true,
|
||||
"login": loginName,
|
||||
"fields": fields,
|
||||
"emails": emails,
|
||||
"aliases": aliases,
|
||||
"api_token": apiToken,
|
||||
"alias_domains": allowedAliasDomains,
|
||||
"card_wide": true,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<nav class="tabs">
|
||||
<button class="tab-btn active" onclick="showTab(this,'account')">Account</button>
|
||||
{{if or .emails .aliases}}<button class="tab-btn" onclick="showTab(this,'email')">Email & Aliases</button>{{end}}
|
||||
{{if or .emails .aliases .alias_domains}}<button class="tab-btn" onclick="showTab(this,'email')">Email & Aliases</button>{{end}}
|
||||
<button class="tab-btn" onclick="showTab(this,'api')">API</button>
|
||||
</nav>
|
||||
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if or .emails .aliases}}
|
||||
{{if or .emails .aliases .alias_domains}}
|
||||
<div id="tab-email" class="tab-panel hidden">
|
||||
{{if .emails}}
|
||||
<h3 class="section-title">Email addresses</h3>
|
||||
|
|
@ -47,6 +47,19 @@
|
|||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
{{if .alias_domains}}
|
||||
<div class="alias-create">
|
||||
<select id="alias-domain" class="alias-domain-select">
|
||||
{{range .alias_domains}}
|
||||
<option value="{{.}}">@{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<button class="btn btn-primary btn-sm"
|
||||
data-token="{{.api_token}}"
|
||||
onclick="createAlias(this)">Generate alias</button>
|
||||
<span id="alias-create-error" class="alias-create-error"></span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
|
@ -72,5 +85,48 @@
|
|||
if (r.ok) document.getElementById(btn.dataset.elem).remove();
|
||||
});
|
||||
}
|
||||
function createAlias(btn) {
|
||||
var domain = document.getElementById('alias-domain').value;
|
||||
var errEl = document.getElementById('alias-create-error');
|
||||
errEl.textContent = '';
|
||||
btn.disabled = true;
|
||||
fetch('/api/v1/aliases', {
|
||||
method: 'POST',
|
||||
headers: {'Authorization': 'Bearer ' + btn.dataset.token, 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({domain: domain})
|
||||
}).then(function(r) {
|
||||
btn.disabled = false;
|
||||
if (!r.ok) { return r.text().then(function(t) { errEl.textContent = t; }); }
|
||||
return r.json().then(function(data) {
|
||||
var list = document.querySelector('.alias-list');
|
||||
if (!list) {
|
||||
var h = document.createElement('h3');
|
||||
h.className = 'section-title';
|
||||
h.textContent = 'Disposable aliases';
|
||||
document.querySelector('.alias-create').before(h);
|
||||
list = document.createElement('ul');
|
||||
list.className = 'alias-list';
|
||||
document.querySelector('.alias-create').before(list);
|
||||
}
|
||||
var idx = list.children.length;
|
||||
var li = document.createElement('li');
|
||||
li.id = 'alias-' + idx;
|
||||
li.className = 'alias-item';
|
||||
var span = document.createElement('span');
|
||||
span.className = 'alias-value';
|
||||
span.textContent = data.data.email;
|
||||
var del = document.createElement('button');
|
||||
del.className = 'btn btn-danger btn-sm';
|
||||
del.dataset.alias = encodeURIComponent(data.data.email);
|
||||
del.dataset.token = btn.dataset.token;
|
||||
del.dataset.elem = li.id;
|
||||
del.textContent = 'Delete';
|
||||
del.onclick = function() { deleteAlias(del); };
|
||||
li.appendChild(span);
|
||||
li.appendChild(del);
|
||||
list.appendChild(li);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{{template "footer" .}}
|
||||
|
|
|
|||
|
|
@ -500,6 +500,28 @@ body {
|
|||
word-break: break-all;
|
||||
}
|
||||
|
||||
.alias-create {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.alias-domain-select {
|
||||
font-size: 0.875rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.alias-create-error {
|
||||
font-size: 0.8rem;
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Profile: API tab
|
||||
============================================================ */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue