feat: polish alpine shell feedback

This commit is contained in:
a.tolmachev
2026-03-31 12:32:05 +03:00
parent 84f4437ce0
commit 4d91ccf48f
16 changed files with 366 additions and 30 deletions
+6 -2
View File
@@ -282,7 +282,9 @@ document.addEventListener('alpine:init', function() {
await this.reload();
this.closeDrawer();
} catch (error) {
alert(error.message || 'Failed to save agent');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to save agent', 'Agent update failed');
}
this.saving = false;
}
},
@@ -294,7 +296,9 @@ document.addEventListener('alpine:init', function() {
await window.CrankApi.deleteAgent(this.workspaceId, id);
await this.reload();
} catch (error) {
alert(error.message || 'Failed to delete agent');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to delete agent', 'Delete failed');
}
}
},
+12 -4
View File
@@ -72,7 +72,9 @@ async function revokeKey(id) {
await window.CrankApi.revokePlatformApiKey(currentWorkspaceId, id);
await loadKeys();
} catch (error) {
alert(error.message || 'Failed to revoke key');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to revoke key', 'Revoke failed');
}
}
}
@@ -83,7 +85,9 @@ async function deleteKey(id) {
await window.CrankApi.deletePlatformApiKey(currentWorkspaceId, id);
await loadKeys();
} catch (error) {
alert(error.message || 'Failed to delete key');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to delete key', 'Delete failed');
}
}
}
@@ -231,7 +235,9 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun
return;
}
if (!selectedScopes.size) {
alert('Select at least one scope.');
if (window.CrankUi) {
window.CrankUi.info('Select at least one scope before creating a key.', 'Missing scope');
}
return;
}
@@ -245,7 +251,9 @@ document.getElementById('modal-confirm-btn').addEventListener('click', async fun
document.getElementById('modal-footer-done').style.display = '';
renderTable();
} catch (error) {
alert(error.message || 'Failed to create key');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to create key', 'Key creation failed');
}
}
});
+80 -3
View File
@@ -3,6 +3,20 @@
var sessionCache = null;
var sessionPromise = null;
function currentWorkspaceLabel() {
if (window.getCurrentWorkspace) {
var workspace = window.getCurrentWorkspace();
if (workspace && workspace.name) {
return workspace.name;
}
}
try {
return localStorage.getItem('crank_workspace_slug') || 'workspace';
} catch (_error) {
return 'workspace';
}
}
function isLoginPage() {
return /\/html\/login\.html$/.test(window.location.pathname);
}
@@ -21,12 +35,25 @@
try {
localStorage.removeItem(STORAGE_KEY);
} catch (_error) {}
renderShellIdentity(null);
}
function primaryMembership(session) {
return session && session.memberships && session.memberships.length
? session.memberships[0]
: null;
if (!(session && session.memberships && session.memberships.length)) {
return null;
}
if (window.getCurrentWorkspace) {
var currentWorkspace = window.getCurrentWorkspace();
if (currentWorkspace) {
var matched = session.memberships.find(function(item) {
return item.workspace && item.workspace.id === currentWorkspace.id;
});
if (matched) {
return matched;
}
}
}
return session.memberships[0];
}
function initials(displayName, email) {
@@ -56,9 +83,47 @@
} catch (_error) {}
}
function mirroredUser() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY));
} catch (_error) {
return null;
}
}
function renderShellIdentity(session) {
var membership = primaryMembership(session);
var fallbackUser = mirroredUser();
var displayName = session && session.user
? (session.user.display_name || session.user.email || 'Crank')
: (fallbackUser && fallbackUser.name) || 'Crank';
var email = session && session.user
? session.user.email
: fallbackUser && fallbackUser.email;
var role = membership
? String(membership.role || '').replace(/^\w/, function(char) { return char.toUpperCase(); })
: (fallbackUser && fallbackUser.role) || 'Viewer';
var workspace = membership && membership.workspace
? (membership.workspace.display_name || membership.workspace.slug || membership.workspace.id)
: currentWorkspaceLabel();
var avatarValue = initials(displayName, email);
document.querySelectorAll('.nav-avatar').forEach(function(element) {
element.textContent = avatarValue;
});
document.querySelectorAll('.user-dropdown-name').forEach(function(element) {
element.textContent = displayName;
});
document.querySelectorAll('.user-dropdown-role').forEach(function(element) {
element.textContent = role + ' · ' + workspace;
});
}
function replaceSession(session) {
sessionCache = session;
persistUserMirror(session);
renderShellIdentity(session);
window.dispatchEvent(new CustomEvent('crank:sessionchange', { detail: session }));
return session;
}
@@ -144,4 +209,16 @@
logout: logout,
handleUnauthorized: handleUnauthorized,
};
window.addEventListener('crank:workspacechange', function() {
renderShellIdentity(sessionCache);
});
window.addEventListener('crank:sessionchange', function(event) {
renderShellIdentity(event.detail || sessionCache);
});
document.addEventListener('DOMContentLoaded', function() {
renderShellIdentity(sessionCache);
});
}());
+3 -1
View File
@@ -369,7 +369,9 @@ document.addEventListener('alpine:init', function() {
}).filter(Boolean))).sort();
this.stats = computeStats(this.operations);
} catch (error) {
alert(error.message || 'Failed to delete operation');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to delete operation', 'Delete failed');
}
}
},
+72 -1
View File
@@ -1,5 +1,20 @@
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
@@ -255,7 +270,9 @@ async function loadWorkspaceSettings() {
saveButton.textContent = 'Saved ✓';
} catch (error) {
alert(error.message || 'Failed to save workspace settings');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to save workspace settings', 'Workspace save failed');
}
saveButton.textContent = original;
} finally {
setTimeout(function () {
@@ -268,11 +285,65 @@ async function loadWorkspaceSettings() {
}
}
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();
});
+70
View File
@@ -0,0 +1,70 @@
(function() {
var CONTAINER_ID = 'crank-toast-container';
var nextToastId = 0;
function ensureContainer() {
var container = document.getElementById(CONTAINER_ID);
if (container) return container;
container = document.createElement('div');
container.id = CONTAINER_ID;
container.className = 'toast-stack';
document.body.appendChild(container);
return container;
}
function dismiss(toast) {
if (!toast || !toast.parentNode) return;
toast.classList.add('closing');
window.setTimeout(function() {
if (toast.parentNode) toast.parentNode.removeChild(toast);
}, 180);
}
function notify(options) {
var config = options || {};
var duration = config.duration === 0 ? 0 : (config.duration || 3600);
var toast = document.createElement('div');
toast.id = 'toast_' + (++nextToastId);
toast.className = 'toast-card toast-' + (config.type || 'info');
toast.innerHTML =
'<div class="toast-copy">' +
(config.title ? '<div class="toast-title">' + config.title + '</div>' : '') +
(config.message ? '<div class="toast-message">' + config.message + '</div>' : '') +
'</div>' +
'<button class="toast-close" type="button" aria-label="Dismiss">' +
'<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">' +
'<line x1="1" y1="1" x2="11" y2="11"></line>' +
'<line x1="11" y1="1" x2="1" y2="11"></line>' +
'</svg>' +
'</button>';
toast.querySelector('.toast-close').addEventListener('click', function() {
dismiss(toast);
});
ensureContainer().appendChild(toast);
if (duration > 0) {
window.setTimeout(function() {
dismiss(toast);
}, duration);
}
return toast.id;
}
window.CrankUi = {
notify: notify,
success: function(message, title) {
return notify({ type: 'success', title: title || 'Done', message: message });
},
error: function(message, title) {
return notify({ type: 'error', title: title || 'Request failed', message: message });
},
info: function(message, title) {
return notify({ type: 'info', title: title || 'Info', message: message });
}
};
}());
+3 -1
View File
@@ -1179,7 +1179,9 @@ async function saveOperation(stayOnPage) {
try {
await persistCurrentDraft(stayOnPage);
} catch (error) {
alert(error.message || 'Failed to save operation');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to save operation', 'Save failed');
}
}
}
+24 -8
View File
@@ -342,7 +342,9 @@ async function submitForm() {
submit.disabled = false;
}, 1400);
} catch (error) {
alert(error.message || 'Failed to save workspace');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to save workspace', 'Save failed');
}
submit.disabled = false;
submit.textContent = originalLabel;
}
@@ -404,7 +406,9 @@ async function sendInvite() {
document.getElementById('invite-success').style.display = '';
await loadWorkspaceAccessData();
} catch (error) {
alert(error.message || 'Failed to send invite');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to send invite', 'Invite failed');
}
}
}
@@ -424,7 +428,9 @@ async function updateRole(select, userId) {
renderMembers();
} catch (error) {
select.value = previous;
alert(error.message || 'Failed to update role');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to update role', 'Role update failed');
}
} finally {
select.disabled = false;
}
@@ -442,7 +448,9 @@ async function removeMember(userId, name) {
await window.CrankApi.deleteMembership(workspaceFormState.workspaceId, userId);
await loadWorkspaceAccessData();
} catch (error) {
alert(error.message || 'Failed to remove member');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to remove member', 'Member removal failed');
}
}
}
@@ -455,7 +463,9 @@ async function revokeInviteById(invitationId) {
await window.CrankApi.deleteInvitation(workspaceFormState.workspaceId, invitationId);
await loadWorkspaceAccessData();
} catch (error) {
alert(error.message || 'Failed to revoke invite');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to revoke invite', 'Invite revoke failed');
}
}
}
@@ -489,7 +499,9 @@ async function exportWorkspaceSnapshot() {
: 'workspace';
downloadJsonFile(slug + '-snapshot.json', snapshot);
} catch (error) {
alert(error.message || 'Failed to export workspace');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to export workspace', 'Export failed');
}
}
}
@@ -505,7 +517,9 @@ async function deleteWorkspaceAction() {
await window.CrankApi.deleteWorkspace(workspaceFormState.workspaceId);
await window.CrankAuth.logout();
} catch (error) {
alert(error.message || 'Failed to delete workspace');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to delete workspace', 'Delete failed');
}
}
}
@@ -536,7 +550,9 @@ async function initPage() {
applyWorkspaceRecord(record);
await loadWorkspaceAccessData();
} catch (error) {
alert(error.message || 'Failed to load workspace');
if (window.CrankUi) {
window.CrankUi.error(error.message || 'Failed to load workspace', 'Workspace load failed');
}
}
}