7b85227fae
# Conflicts: # TASKS.md # apps/ui/js/auth.js
350 lines
12 KiB
JavaScript
350 lines
12 KiB
JavaScript
var settingsSession = null;
|
|
|
|
function settingsWorkspaceId() {
|
|
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
|
|
return workspace ? workspace.id : null;
|
|
}
|
|
|
|
function downloadSettingsJson(fileName, value) {
|
|
var blob = new Blob([JSON.stringify(value, null, 2)], { type: 'application/json;charset=utf-8' });
|
|
var url = URL.createObjectURL(blob);
|
|
var link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = fileName;
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
function initials(displayName, email) {
|
|
var source = displayName || email || 'Crank';
|
|
return source
|
|
.split(/\s+/)
|
|
.filter(Boolean)
|
|
.slice(0, 2)
|
|
.map(function(part) { return part.charAt(0).toUpperCase(); })
|
|
.join('') || 'CR';
|
|
}
|
|
|
|
function setStatus(id, text, isError) {
|
|
var node = document.getElementById(id);
|
|
if (!node) return;
|
|
node.textContent = text || '';
|
|
node.style.color = text
|
|
? (isError ? 'var(--red)' : 'var(--green)')
|
|
: 'var(--text-muted)';
|
|
}
|
|
|
|
function populateProfile(session) {
|
|
if (!session || !session.user) {
|
|
return;
|
|
}
|
|
|
|
var user = session.user;
|
|
document.getElementById('profile-avatar').textContent = initials(user.display_name, user.email);
|
|
document.getElementById('profile-display-name').textContent = user.display_name || 'Operator';
|
|
document.getElementById('profile-email').textContent = user.email || '';
|
|
|
|
var firstName = document.getElementById('field-firstname');
|
|
var email = document.getElementById('field-email');
|
|
if (firstName) {
|
|
firstName.value = user.display_name || '';
|
|
}
|
|
if (email) {
|
|
email.value = user.email || '';
|
|
}
|
|
}
|
|
|
|
async function loadProfile() {
|
|
if (!window.CrankAuth || !window.CrankApi) {
|
|
return;
|
|
}
|
|
|
|
settingsSession = await window.CrankAuth.fetchSession(true);
|
|
populateProfile(settingsSession);
|
|
}
|
|
|
|
function bindProfileSave() {
|
|
var button = document.getElementById('settings-profile-save-btn');
|
|
if (!button || button.dataset.bound === 'true') {
|
|
return;
|
|
}
|
|
button.dataset.bound = 'true';
|
|
|
|
button.addEventListener('click', async function() {
|
|
var original = button.textContent;
|
|
button.disabled = true;
|
|
button.textContent = 'Saving…';
|
|
setStatus('settings-profile-status', '', false);
|
|
|
|
try {
|
|
var session = await window.CrankApi.updateProfile({
|
|
display_name: document.getElementById('field-firstname').value.trim(),
|
|
email: document.getElementById('field-email').value.trim(),
|
|
});
|
|
settingsSession = session;
|
|
if (window.CrankAuth && typeof window.CrankAuth.replaceSession === 'function') {
|
|
window.CrankAuth.replaceSession(session);
|
|
}
|
|
populateProfile(session);
|
|
setStatus('settings-profile-status', 'Profile saved.', false);
|
|
button.textContent = 'Saved ✓';
|
|
} catch (error) {
|
|
setStatus('settings-profile-status', error.message || 'Failed to save profile', true);
|
|
button.textContent = original;
|
|
} finally {
|
|
setTimeout(function() {
|
|
button.disabled = false;
|
|
button.textContent = original;
|
|
}, 1200);
|
|
}
|
|
});
|
|
}
|
|
|
|
function bindPasswordSave() {
|
|
var button = document.getElementById('settings-password-save-btn');
|
|
if (!button || button.dataset.bound === 'true') {
|
|
return;
|
|
}
|
|
button.dataset.bound = 'true';
|
|
|
|
button.addEventListener('click', async function() {
|
|
var currentPassword = document.getElementById('security-current-password').value;
|
|
var newPassword = document.getElementById('security-new-password').value;
|
|
var confirmPassword = document.getElementById('security-confirm-password').value;
|
|
|
|
if (newPassword !== confirmPassword) {
|
|
setStatus('settings-password-status', 'New password and confirmation do not match.', true);
|
|
return;
|
|
}
|
|
|
|
var original = button.textContent;
|
|
button.disabled = true;
|
|
button.textContent = 'Saving…';
|
|
setStatus('settings-password-status', '', false);
|
|
|
|
try {
|
|
await window.CrankApi.changePassword({
|
|
current_password: currentPassword,
|
|
new_password: newPassword,
|
|
});
|
|
document.getElementById('security-current-password').value = '';
|
|
document.getElementById('security-new-password').value = '';
|
|
document.getElementById('security-confirm-password').value = '';
|
|
setStatus('settings-password-status', 'Password updated.', false);
|
|
button.textContent = 'Saved ✓';
|
|
} catch (error) {
|
|
setStatus('settings-password-status', error.message || 'Failed to change password', true);
|
|
button.textContent = original;
|
|
} finally {
|
|
setTimeout(function() {
|
|
button.disabled = false;
|
|
button.textContent = original;
|
|
}, 1200);
|
|
}
|
|
});
|
|
}
|
|
|
|
function injectLanguageSwitcher() {
|
|
var profileSection = document.getElementById('section-profile');
|
|
if (!profileSection) {
|
|
return;
|
|
}
|
|
|
|
var cardBody = profileSection.querySelector('.section-card-body');
|
|
if (!cardBody) {
|
|
return;
|
|
}
|
|
|
|
fetch('html/fragments/lang-switcher.html')
|
|
.then(function(response) { return response.text(); })
|
|
.then(function(html) {
|
|
var saveRow = cardBody.querySelector('div[style*="justify-content:flex-end"]');
|
|
if (saveRow) {
|
|
saveRow.insertAdjacentHTML('beforebegin', html);
|
|
} else {
|
|
cardBody.insertAdjacentHTML('beforeend', html);
|
|
}
|
|
if (typeof applyLang === 'function') {
|
|
applyLang();
|
|
}
|
|
});
|
|
}
|
|
|
|
function bindSectionNavigation() {
|
|
var navItems = document.querySelectorAll('.settings-nav-item[data-section]');
|
|
navItems.forEach(function (button) {
|
|
button.addEventListener('click', function () {
|
|
navItems.forEach(function (item) { item.classList.remove('active'); });
|
|
this.classList.add('active');
|
|
var target = document.getElementById('section-' + this.dataset.section);
|
|
if (target) {
|
|
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
});
|
|
});
|
|
|
|
var initialSection = window.location.hash ? window.location.hash.slice(1) : 'profile';
|
|
var initialButton = document.querySelector('.settings-nav-item[data-section="' + initialSection + '"]')
|
|
|| document.querySelector('.settings-nav-item[data-section="profile"]');
|
|
if (initialButton) {
|
|
setTimeout(function () { initialButton.click(); }, 50);
|
|
}
|
|
|
|
var observer = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (!entry.isIntersecting) {
|
|
return;
|
|
}
|
|
var section = entry.target.id.replace('section-', '');
|
|
document.querySelectorAll('.settings-nav-item[data-section]').forEach(function (button) {
|
|
button.classList.toggle('active', button.dataset.section === section);
|
|
});
|
|
});
|
|
}, { threshold: 0.3 });
|
|
|
|
document.querySelectorAll('[id^="section-"]').forEach(function (section) {
|
|
if (section.style.display !== 'none') {
|
|
observer.observe(section);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function loadWorkspaceSettings() {
|
|
if (!window.CrankApi || !window.whenWorkspacesReady) {
|
|
return;
|
|
}
|
|
|
|
await window.whenWorkspacesReady();
|
|
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
|
|
if (!workspace) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var record = await window.CrankApi.getWorkspace(workspace.id);
|
|
var item = record.workspace;
|
|
var settings = item.settings || {};
|
|
|
|
document.getElementById('settings-ws-slug').value = item.slug || '';
|
|
document.getElementById('settings-ws-display-name').value = item.display_name || '';
|
|
document.getElementById('settings-ws-description').value = settings.description || '';
|
|
document.getElementById('settings-ws-timezone').value = settings.timezone || 'UTC';
|
|
document.getElementById('settings-ws-protocol').value = settings.default_protocol || 'rest';
|
|
|
|
document.querySelectorAll('#section-workspace .lang-btn').forEach(function (button) {
|
|
button.classList.toggle('active', button.dataset.lang === (settings.language || (localStorage.getItem('crank_lang') || 'en')));
|
|
});
|
|
|
|
var saveButton = document.getElementById('settings-ws-save-btn');
|
|
if (saveButton.dataset.bound === 'true') {
|
|
return;
|
|
}
|
|
saveButton.dataset.bound = 'true';
|
|
|
|
saveButton.addEventListener('click', async function () {
|
|
var saveButton = this;
|
|
var original = saveButton.textContent;
|
|
saveButton.disabled = true;
|
|
saveButton.textContent = 'Saving…';
|
|
|
|
try {
|
|
await window.CrankApi.updateWorkspace(item.id, {
|
|
slug: document.getElementById('settings-ws-slug').value.trim(),
|
|
display_name: document.getElementById('settings-ws-display-name').value.trim(),
|
|
settings: {
|
|
description: document.getElementById('settings-ws-description').value.trim(),
|
|
timezone: document.getElementById('settings-ws-timezone').value,
|
|
default_protocol: document.getElementById('settings-ws-protocol').value,
|
|
language: localStorage.getItem('crank_lang') || 'en',
|
|
color: (workspace.settings && workspace.settings.color) || '#0d9488',
|
|
},
|
|
});
|
|
|
|
if (window.refreshWorkspaces) {
|
|
var workspaces = await window.refreshWorkspaces();
|
|
var updated = workspaces.find(function (entry) { return entry.id === item.id; });
|
|
if (updated && window.setCurrentWorkspace) {
|
|
await window.setCurrentWorkspace(updated);
|
|
}
|
|
}
|
|
|
|
saveButton.textContent = 'Saved ✓';
|
|
} catch (error) {
|
|
if (window.CrankUi) {
|
|
window.CrankUi.error(error.message || 'Failed to save workspace settings', 'Workspace save failed');
|
|
}
|
|
saveButton.textContent = original;
|
|
} finally {
|
|
setTimeout(function () {
|
|
saveButton.disabled = false;
|
|
saveButton.textContent = original;
|
|
}, 1200);
|
|
}
|
|
});
|
|
} catch (_error) {
|
|
}
|
|
}
|
|
|
|
function bindDangerZoneActions() {
|
|
var renameButton = document.getElementById('settings-ws-rename-btn');
|
|
var exportButton = document.getElementById('settings-ws-export-btn');
|
|
var deleteButton = document.getElementById('settings-ws-delete-btn');
|
|
|
|
if (renameButton && renameButton.dataset.bound !== 'true') {
|
|
renameButton.dataset.bound = 'true';
|
|
renameButton.addEventListener('click', function() {
|
|
document.getElementById('settings-ws-slug').focus();
|
|
if (window.CrankUi) {
|
|
window.CrankUi.info('Update the workspace slug above and save changes to rename it.', 'Rename workspace');
|
|
}
|
|
});
|
|
}
|
|
|
|
if (exportButton && exportButton.dataset.bound !== 'true') {
|
|
exportButton.dataset.bound = 'true';
|
|
exportButton.addEventListener('click', async function() {
|
|
var workspaceId = settingsWorkspaceId();
|
|
if (!workspaceId || !window.CrankApi) return;
|
|
try {
|
|
var snapshot = await window.CrankApi.exportWorkspace(workspaceId);
|
|
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
|
|
downloadSettingsJson((workspace ? workspace.slug : 'workspace') + '-snapshot.json', snapshot);
|
|
if (window.CrankUi) {
|
|
window.CrankUi.success('Workspace export downloaded.', 'Export complete');
|
|
}
|
|
} catch (error) {
|
|
if (window.CrankUi) {
|
|
window.CrankUi.error(error.message || 'Failed to export workspace', 'Export failed');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (deleteButton && deleteButton.dataset.bound !== 'true') {
|
|
deleteButton.dataset.bound = 'true';
|
|
deleteButton.addEventListener('click', async function() {
|
|
var workspaceId = settingsWorkspaceId();
|
|
if (!workspaceId || !window.CrankApi || !window.CrankAuth) return;
|
|
if (!window.confirm('Delete workspace? This cannot be undone.')) return;
|
|
try {
|
|
await window.CrankApi.deleteWorkspace(workspaceId);
|
|
await window.CrankAuth.logout();
|
|
} catch (error) {
|
|
if (window.CrankUi) {
|
|
window.CrankUi.error(error.message || 'Failed to delete workspace', 'Delete failed');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
injectLanguageSwitcher();
|
|
bindSectionNavigation();
|
|
loadProfile();
|
|
bindProfileSave();
|
|
bindPasswordSave();
|
|
bindDangerZoneActions();
|
|
loadWorkspaceSettings();
|
|
});
|