From fe4b3cf671921dcb06c5721f068eb9330dfd30ed Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 8 Mar 2026 13:07:05 +0700 Subject: [PATCH] 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 --- login.go | 13 +++++----- static/profile.html | 60 +++++++++++++++++++++++++++++++++++++++++++-- static/style.css | 22 +++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/login.go b/login.go index 88767c9..60e3f8f 100644 --- a/login.go +++ b/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, }) } diff --git a/static/profile.html b/static/profile.html index 680c197..2353d21 100644 --- a/static/profile.html +++ b/static/profile.html @@ -3,7 +3,7 @@ @@ -22,7 +22,7 @@ {{end}} - {{if or .emails .aliases}} + {{if or .emails .aliases .alias_domains}} {{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); + }); + }); + } {{template "footer" .}} diff --git a/static/style.css b/static/style.css index 30250b5..93bca27 100644 --- a/static/style.css +++ b/static/style.css @@ -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 ============================================================ */