primary email change functionality
Build LinkLog Development Image / development-image (push) Successful in 12s
Build LinkLog Development Image / development-image (push) Successful in 12s
This commit is contained in:
@@ -18,6 +18,9 @@ const otpEnabled = document.querySelector('#otp-enabled');
|
||||
const otpSecret = document.querySelector('#otp-secret');
|
||||
const otpUri = document.querySelector('#otp-uri');
|
||||
const otpStatus = document.querySelector('#otp-status');
|
||||
const emailAddressList = document.querySelector('#email-address-list');
|
||||
const additionalEmailForm = document.querySelector('#additional-email-form');
|
||||
const emailAddressStatus = document.querySelector('#email-address-status');
|
||||
|
||||
function authHeaders(includeJson = false) {
|
||||
return {
|
||||
@@ -37,6 +40,80 @@ function setOtpStatus(message, isError = false) {
|
||||
otpStatus.style.color = isError ? '#b91c1c' : '#166534';
|
||||
}
|
||||
|
||||
function setEmailAddressStatus(message, isError = false) {
|
||||
emailAddressStatus.textContent = message;
|
||||
emailAddressStatus.style.color = isError ? '#b91c1c' : '#166534';
|
||||
}
|
||||
|
||||
function renderEmailAddresses(addresses) {
|
||||
emailAddressList.replaceChildren(...addresses.map((address) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'email-address-row';
|
||||
const label = document.createElement('span');
|
||||
label.textContent = `${address.email} - ${address.verified ? 'validated' : 'not validated'}${address.primary ? ' (primary)' : ''}`;
|
||||
row.appendChild(label);
|
||||
if (!address.primary) {
|
||||
if (!address.verified) {
|
||||
const resend = document.createElement('button');
|
||||
resend.type = 'button';
|
||||
resend.textContent = 'Resend validation';
|
||||
resend.addEventListener('click', async () => {
|
||||
resend.disabled = true;
|
||||
const response = await fetch(`/api/user/emails/${encodeURIComponent(address.id)}/resend`, {method: 'POST', headers: authHeaders()});
|
||||
const result = await response.json();
|
||||
setEmailAddressStatus(response.ok ? result.message : (result.detail || 'Could not send validation email.'), !response.ok);
|
||||
if (response.ok && result.next_allowed_at) window.setTimeout(() => { resend.disabled = false; }, Math.max(0, Date.parse(result.next_allowed_at) - Date.now()));
|
||||
else if (!response.ok) resend.disabled = false;
|
||||
});
|
||||
row.appendChild(resend);
|
||||
}
|
||||
if (address.verified && address.can_be_primary) {
|
||||
const makePrimary = document.createElement('button');
|
||||
makePrimary.type = 'button';
|
||||
makePrimary.textContent = 'Make primary';
|
||||
makePrimary.addEventListener('click', async () => {
|
||||
const response = await fetch(`/api/user/emails/${encodeURIComponent(address.id)}/make-primary`, {method: 'POST', headers: authHeaders()});
|
||||
const result = await response.json();
|
||||
setEmailAddressStatus(response.ok ? `${result.email} is now the primary email address.` : (result.detail || 'Could not change primary email.'), !response.ok);
|
||||
if (response.ok) {
|
||||
await loadEmailAddresses();
|
||||
}
|
||||
});
|
||||
row.appendChild(makePrimary);
|
||||
}
|
||||
const remove = document.createElement('button');
|
||||
remove.type = 'button';
|
||||
remove.textContent = 'Remove';
|
||||
remove.addEventListener('click', async () => {
|
||||
const response = await fetch(`/api/user/emails/${encodeURIComponent(address.id)}`, {method: 'DELETE', headers: authHeaders()});
|
||||
if (response.ok) loadEmailAddresses();
|
||||
else setEmailAddressStatus('Could not remove email address.', true);
|
||||
});
|
||||
row.appendChild(remove);
|
||||
}
|
||||
return row;
|
||||
}));
|
||||
}
|
||||
|
||||
async function loadEmailAddresses() {
|
||||
const response = await fetch('/api/user/emails', {headers: authHeaders()});
|
||||
if (!response.ok) throw new Error('Could not load email addresses');
|
||||
renderEmailAddresses(await response.json());
|
||||
}
|
||||
|
||||
additionalEmailForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
const response = await fetch('/api/user/emails', {
|
||||
method: 'POST', headers: authHeaders(true), body: JSON.stringify({email: additionalEmailForm.elements.email.value.trim()}),
|
||||
});
|
||||
const result = await response.json();
|
||||
setEmailAddressStatus(response.ok ? 'Email address added. Check your inbox to validate it.' : (result.detail || 'Could not add email address.'), !response.ok);
|
||||
if (response.ok) {
|
||||
additionalEmailForm.reset();
|
||||
loadEmailAddresses();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadOtp() {
|
||||
const response = await fetch('/api/user/otp', {headers: authHeaders()});
|
||||
if (!response.ok) throw new Error('Could not load one-time password settings');
|
||||
@@ -95,7 +172,7 @@ async function loadProfile() {
|
||||
if (!response.ok) throw new Error('Could not load profile');
|
||||
const profile = await response.json();
|
||||
document.querySelector('#username').textContent = profile.username || '';
|
||||
document.querySelector('#email').value = profile.email || '';
|
||||
document.querySelector('#email').textContent = profile.email || '';
|
||||
document.querySelector('#bio').value = profile.bio || '';
|
||||
const avatarPreview = document.querySelector('#avatar-preview');
|
||||
if (profile.avatar_url) {
|
||||
@@ -211,7 +288,7 @@ passwordForm.addEventListener('submit', async (event) => {
|
||||
if (response.ok) passwordForm.reset();
|
||||
});
|
||||
|
||||
Promise.all([loadProfile(), loadMastodonConfig(), loadOtp()]).catch((error) => {
|
||||
Promise.all([loadProfile(), loadMastodonConfig(), loadOtp(), loadEmailAddresses()]).catch((error) => {
|
||||
setStatus('#profile-status', accessToken ? error.message : 'Please sign in first.', true);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -430,6 +430,27 @@ button:focus-visible {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.email-address-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.email-address-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.email-address-row button {
|
||||
min-width: 0;
|
||||
padding: 5px 9px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.settings-panel textarea {
|
||||
min-height: 110px;
|
||||
resize: vertical;
|
||||
|
||||
Reference in New Issue
Block a user