diff --git a/frontend/js/components/workspace-members-add-user-modal.js b/frontend/js/components/workspace-members-add-user-modal.js index af88004..f783259 100644 --- a/frontend/js/components/workspace-members-add-user-modal.js +++ b/frontend/js/components/workspace-members-add-user-modal.js @@ -1,10 +1,13 @@ -// Add-User modal (#10). Sits next to the invite modal in the members view, but -// instead of emailing an invite it creates a user account directly with an -// admin-set password and assigns them to this workspace + role. For -// admin-provisioning on instances with no outbound email (where invites never -// deliver). Mirrors workspace-members-invite-modal.js's structure. +// Add-User modal (#10). Creates a user account directly with an admin-set +// password and assigns them to a workspace + role (admin-provisioning for +// instances with no outbound email). Two open modes, ONE shared form: +// +// openAddUserModal({ id, name }, opts) -> fixed-workspace mode (members view). +// No picker; assigns into that workspace. +// openAddUserModal(null, opts) -> picker mode (platform Users admin page). +// Shows an Org/Workspace picker; the admin +// chooses the target workspace. // -// workspace: { id, name } // opts.onSuccess: (result) => void - fires on 201 (server response body) // opts.mapError: (err) => string - translates server error to display text import { api } from '../api.js'; @@ -12,6 +15,13 @@ import { t } from '../i18n.js'; const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +// Roles the picker offers. This is the SET POST /api/admin/users accepts +// (server: routes/admin.js WORKSPACE_ROLES) - keep them in sync so we never +// offer a value the endpoint 400s (the platform_operator dropdown/endpoint +// mismatch we already hit). Order here is display order (least-privilege first +// = the default selection); the server validates set membership, not order. +const WORKSPACE_ROLES = ['workspace_viewer', 'workspace_editor', 'workspace_admin']; + // Crockford-ish readable random password: avoids ambiguous chars (0/O, 1/l/I). function generatePassword(len = 16) { const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'; @@ -22,14 +32,40 @@ function generatePassword(len = 16) { return out; } +function wsLabel(w) { + return `${w.organization_name || '—'} / ${w.name}`; +} + export function openAddUserModal(workspace, opts = {}) { const { onSuccess, mapError } = opts; + // Picker mode whenever no concrete target workspace was supplied. + const pickerMode = !(workspace && workspace.id); + + const title = pickerMode + ? t('members.modal.add_user_title_generic') + : t('members.modal.add_user_title', { workspace: esc(workspace.name) }); + + const roleOptions = WORKSPACE_ROLES + .map(r => ``) + .join(''); + + // Workspace picker block — only rendered in picker mode. A filter input above + // a + + ` : ''; + const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = ` + ${workspaceGroup}