diff --git a/apps/ui/js/workspace-setup.js b/apps/ui/js/workspace-setup.js
index b32154d..df84efa 100644
--- a/apps/ui/js/workspace-setup.js
+++ b/apps/ui/js/workspace-setup.js
@@ -66,6 +66,35 @@ function formElements() {
};
}
+function createElement(tag, className, text) {
+ var node = document.createElement(tag);
+ if (className) {
+ node.className = className;
+ }
+ if (text !== undefined && text !== null) {
+ node.textContent = text;
+ }
+ return node;
+}
+
+function buildIconSvg(href, width, height, viewBox, attrs) {
+ var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ svg.setAttribute('width', String(width));
+ svg.setAttribute('height', String(height));
+ if (viewBox) {
+ svg.setAttribute('viewBox', viewBox);
+ }
+ if (attrs) {
+ Object.keys(attrs).forEach(function(key) {
+ svg.setAttribute(key, attrs[key]);
+ });
+ }
+ var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
+ use.setAttribute('href', href);
+ svg.appendChild(use);
+ return svg;
+}
+
function workspacePayload() {
var elements = formElements();
return {
@@ -111,30 +140,89 @@ function addMemberRow(container, membership) {
row.className = 'member-row';
var isCurrentUser = membership.user.id === workspaceFormState.currentUserId;
var canManage = membership.role !== 'owner';
- row.innerHTML =
- '
' +
- membership.user.display_name.slice(0, 2).toUpperCase() +
- '
' +
- '' +
- '
' + membership.user.display_name +
- (isCurrentUser ? ' (you)' : '') +
- '
' +
- '
' + membership.user.email + '
' +
- '
' +
- '—
' +
- (
- canManage
- ? '' +
- ''
- : '' + roleLabel(membership.role) + ''
+ var avatar = createElement(
+ 'div',
+ 'member-avatar',
+ membership.user.display_name.slice(0, 2).toUpperCase()
+ );
+ avatar.style.background =
+ 'linear-gradient(135deg,' + workspaceFormState.selectedColor + ',#6366f1)';
+ row.appendChild(avatar);
+
+ var info = createElement('div', 'member-info');
+ var name = createElement('div', 'member-name', membership.user.display_name);
+ if (isCurrentUser) {
+ name.appendChild(document.createTextNode(' '));
+ var currentUserLabel = createElement('span', null, '(you)');
+ currentUserLabel.style.fontSize = '11px';
+ currentUserLabel.style.fontWeight = '400';
+ currentUserLabel.style.color = 'var(--text-muted)';
+ name.appendChild(currentUserLabel);
+ }
+ info.appendChild(name);
+ info.appendChild(createElement('div', 'member-email', membership.user.email));
+ row.appendChild(info);
+
+ row.appendChild(createElement('div', 'member-last-active', '—'));
+
+ if (canManage) {
+ var select = createElement('select', 'member-role-select');
+ select.dataset.previous = membership.role;
+ [
+ ['owner', tKey('workspace_setup.role.owner')],
+ ['admin', tKey('workspace_setup.role.admin')],
+ ['operator', tKey('workspace_setup.role.operator')],
+ ['viewer', tKey('workspace_setup.role.viewer')]
+ ].forEach(function(optionData) {
+ var option = document.createElement('option');
+ option.value = optionData[0];
+ option.textContent = optionData[1];
+ option.selected = membership.role === optionData[0];
+ select.appendChild(option);
+ });
+ select.addEventListener('change', function() {
+ updateRole(select, membership.user.id);
+ });
+ row.appendChild(select);
+
+ var removeButton = createElement('button', 'member-remove-btn');
+ removeButton.type = 'button';
+ removeButton.title = tKey('workspace_setup.members.remove');
+ removeButton.appendChild(
+ buildIconSvg(
+ '',
+ 12,
+ 12,
+ '0 0 16 16',
+ {
+ fill: 'none',
+ stroke: 'currentColor',
+ 'stroke-width': '1.8',
+ 'stroke-linecap': 'round',
+ 'stroke-linejoin': 'round'
+ }
+ )
);
+ var removeSvg = removeButton.querySelector('svg');
+ var pathA = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ pathA.setAttribute('d', 'M2 4h12M5 4V3a1 1 0 011-1h4a1 1 0 011 1v1M10 8v4M6 8v4');
+ var pathB = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ pathB.setAttribute('d', 'M3 4l1 9a1 1 0 001 1h6a1 1 0 001-1l1-9');
+ removeSvg.appendChild(pathA);
+ removeSvg.appendChild(pathB);
+ removeButton.addEventListener('click', function() {
+ removeMember(membership.user.id, membership.user.display_name);
+ });
+ row.appendChild(removeButton);
+ } else {
+ row.appendChild(
+ createElement(
+ 'span',
+ 'member-role-badge ' + roleClass(membership.role),
+ roleLabel(membership.role)
+ )
+ );
+ }
container.appendChild(row);
}
@@ -174,26 +262,80 @@ function renderInvitations() {
}
pending.style.display = '';
- pending.innerHTML =
- '' +
- tKey('workspace_setup.members.pending') + ' ' + items.length + '' +
- '
';
+ pending.innerHTML = '';
+ var heading = createElement('div', null, tKey('workspace_setup.members.pending') + ' ');
+ heading.style.fontSize = '11px';
+ heading.style.fontWeight = '600';
+ heading.style.textTransform = 'uppercase';
+ heading.style.letterSpacing = '0.06em';
+ heading.style.color = 'var(--text-muted)';
+ heading.style.marginBottom = '10px';
+ var countPill = createElement('span', null, String(items.length));
+ countPill.id = 'pending-count';
+ countPill.style.background = 'rgba(139,148,158,0.15)';
+ countPill.style.borderRadius = '10px';
+ countPill.style.padding = '1px 7px';
+ countPill.style.fontSize = '10px';
+ heading.appendChild(countPill);
+ pending.appendChild(heading);
items.forEach(function (record) {
var invitation = record.invitation;
var row = document.createElement('div');
row.className = 'member-row';
- row.innerHTML =
- '?
' +
- '' +
- '
' + invitation.email + '
' +
- '
' + tfKey('workspace_setup.members.invited_pending', { date: invitation.created_at.slice(0, 10) }) + '
' +
- '
' +
- '—
' +
- '' + roleLabel(invitation.role) + '' +
- '';
+ var inviteAvatar = createElement('div', 'member-avatar', '?');
+ inviteAvatar.style.background = 'var(--bg-canvas)';
+ inviteAvatar.style.border = '1.5px dashed var(--border)';
+ inviteAvatar.style.color = 'var(--text-muted)';
+ inviteAvatar.style.fontSize = '16px';
+ row.appendChild(inviteAvatar);
+
+ var inviteInfo = createElement('div', 'member-info');
+ inviteInfo.appendChild(createElement('div', 'member-name', invitation.email));
+ inviteInfo.appendChild(
+ createElement(
+ 'div',
+ 'member-email',
+ tfKey('workspace_setup.members.invited_pending', { date: invitation.created_at.slice(0, 10) })
+ )
+ );
+ row.appendChild(inviteInfo);
+ row.appendChild(createElement('div', 'member-last-active', '—'));
+
+ var roleBadge = createElement('span', 'member-role-badge', roleLabel(invitation.role));
+ roleBadge.style.background = 'rgba(139,148,158,0.12)';
+ roleBadge.style.color = 'var(--text-muted)';
+ roleBadge.style.border = '1px solid var(--border)';
+ row.appendChild(roleBadge);
+
+ var revokeButton = createElement('button', 'member-remove-btn');
+ revokeButton.type = 'button';
+ revokeButton.title = tKey('workspace_setup.members.revoke');
+ var revokeSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ revokeSvg.setAttribute('width', '12');
+ revokeSvg.setAttribute('height', '12');
+ revokeSvg.setAttribute('viewBox', '0 0 16 16');
+ revokeSvg.setAttribute('fill', 'none');
+ revokeSvg.setAttribute('stroke', 'currentColor');
+ revokeSvg.setAttribute('stroke-width', '1.8');
+ revokeSvg.setAttribute('stroke-linecap', 'round');
+ var lineA = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ lineA.setAttribute('x1', '2');
+ lineA.setAttribute('y1', '2');
+ lineA.setAttribute('x2', '14');
+ lineA.setAttribute('y2', '14');
+ var lineB = document.createElementNS('http://www.w3.org/2000/svg', 'line');
+ lineB.setAttribute('x1', '14');
+ lineB.setAttribute('y1', '2');
+ lineB.setAttribute('x2', '2');
+ lineB.setAttribute('y2', '14');
+ revokeSvg.appendChild(lineA);
+ revokeSvg.appendChild(lineB);
+ revokeButton.appendChild(revokeSvg);
+ revokeButton.addEventListener('click', function() {
+ revokeInviteById(invitation.id);
+ });
+ row.appendChild(revokeButton);
pending.appendChild(row);
});
}
@@ -364,16 +506,44 @@ function addInviteRow() {
var rows = document.getElementById('invite-rows');
var node = document.createElement('div');
node.className = 'invite-row';
- node.innerHTML =
- '' +
- '' +
- '';
+ var emailInput = createElement('input', 'form-input');
+ emailInput.type = 'email';
+ emailInput.placeholder = 'colleague@company.com';
+ emailInput.autocomplete = 'off';
+ emailInput.style.flex = '1';
+ emailInput.style.marginBottom = '0';
+ node.appendChild(emailInput);
+
+ var roleSelect = createElement('select', 'form-input');
+ roleSelect.style.width = '130px';
+ roleSelect.style.marginBottom = '0';
+ [
+ ['admin', tKey('workspace_setup.role.admin')],
+ ['developer', tKey('workspace_setup.role.operator')],
+ ['viewer', tKey('workspace_setup.role.viewer')]
+ ].forEach(function(entry) {
+ var option = document.createElement('option');
+ option.value = entry[0];
+ option.textContent = entry[1];
+ option.selected = entry[0] === 'developer';
+ roleSelect.appendChild(option);
+ });
+ node.appendChild(roleSelect);
+
+ var removeButton = createElement('button', 'invite-row-remove');
+ removeButton.type = 'button';
+ removeButton.title = tKey('workspace_setup.members.remove');
+ removeButton.appendChild(
+ buildIconSvg(
+ (window.APP_BASE || '') + 'icons/general/close.svg#icon',
+ 12,
+ 12
+ )
+ );
+ removeButton.addEventListener('click', function() {
+ removeInviteRow(removeButton);
+ });
+ node.appendChild(removeButton);
rows.appendChild(node);
}