diff --git a/api/handlers.go b/api/handlers.go index f8d65f1..e13c384 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -1299,11 +1299,8 @@ func (h *Handlers) CreateOrg(w http.ResponseWriter, r *http.Request) { ErrorResponse(w, http.StatusBadRequest, "missing_fields", "Organization name required") return } - if len(req.Domains) == 0 { - ErrorResponse(w, http.StatusBadRequest, "missing_fields", "At least one domain required") - return - } - // Validate domains are not empty strings + // Domains are optional — used for invite validation only + // Validate domains are not empty strings (if provided) for _, d := range req.Domains { if strings.TrimSpace(d) == "" { ErrorResponse(w, http.StatusBadRequest, "invalid_domains", "Empty domain not allowed") diff --git a/dealspace b/dealspace index 4f9d471..9084e67 100755 Binary files a/dealspace and b/dealspace differ diff --git a/portal/templates/app/project.html b/portal/templates/app/project.html index be9b683..eac6732 100644 --- a/portal/templates/app/project.html +++ b/portal/templates/app/project.html @@ -162,7 +162,7 @@
@@ -862,17 +862,16 @@ // ---- Add Org Modal ---- let orgSearchTimeout; - let memberCount = 0; + let pendingMembers = []; // array of {name, email, title} function openAddOrgModal() { document.getElementById('addOrgModal').classList.remove('hidden'); document.getElementById('addOrgError').classList.add('hidden'); document.getElementById('addOrgName').value = ''; document.getElementById('addOrgId').value = ''; - document.getElementById('memberRows').innerHTML = ''; - memberCount = 0; + pendingMembers = []; + renderMemberBubbles(); setOrgType('buyer', document.querySelector('[data-type="buyer"]')); - addMemberRow(); loadGlobalOrgs(); } @@ -904,8 +903,7 @@ function orgNameSearch(q) { clearTimeout(orgSearchTimeout); const dd = document.getElementById('orgNameDropdown'); - const orgId = document.getElementById('addOrgId'); - orgId.value = ''; + document.getElementById('addOrgId').value = ''; if (!q) { dd.classList.add('hidden'); return; } orgSearchTimeout = setTimeout(() => { const matches = allGlobalOrgs.filter(o => { @@ -913,43 +911,122 @@ return (d.name || '').toLowerCase().includes(q.toLowerCase()); }); if (!matches.length) { - dd.innerHTML = `
No match — will create "${escHtml(q)}" as new org
`; - } else { - dd.innerHTML = matches.map(o => { - const d = parseData(o.data_text); - const name = d.name || 'Unnamed'; - return `
- ${escHtml(name)} - ${d.role || ''} -
`; - }).join(''); + // No match — just hide, the typed name will be used as-is on submit + dd.classList.add('hidden'); + return; } + dd.innerHTML = matches.map(o => { + const d = parseData(o.data_text); + const name = d.name || 'Unnamed'; + return `
+ ${escHtml(name)} + ${d.role || ''} +
`; + }).join(''); dd.classList.remove('hidden'); }, 200); } - function selectOrg(orgId, orgName, role, el) { + function orgNameBlur() { + // Hide dropdown shortly after blur (allow click to register first) + setTimeout(() => document.getElementById('orgNameDropdown').classList.add('hidden'), 200); + } + + function selectOrg(orgId, orgName, role) { document.getElementById('addOrgId').value = orgId; document.getElementById('addOrgName').value = orgName; if (role) setOrgType(role, document.querySelector(`[data-type="${role}"]`)); document.getElementById('orgNameDropdown').classList.add('hidden'); + // Load members of this org for suggestions + loadOrgMemberSuggestions(orgId); } - function addMemberRow() { - const idx = memberCount++; - const row = document.createElement('div'); - row.className = 'grid grid-cols-[1fr_1fr_1fr_auto] gap-2 items-center'; - row.id = 'member-row-' + idx; - row.innerHTML = ` - - - - `; - document.getElementById('memberRows').appendChild(row); + let orgMemberSuggestions = []; + async function loadOrgMemberSuggestions(orgId) { + orgMemberSuggestions = []; + if (!orgId) return; + // Get members from existing deal_org entries that reference this org + try { + const res = await fetchAPI('/api/orgs/' + orgId); + const org = await res.json(); + if (org && org.members) orgMemberSuggestions = org.members; + } catch(e) {} + } + + // ---- Member bubble system ---- + function renderMemberBubbles() { + const container = document.getElementById('memberRows'); + const bubbles = pendingMembers.map((m, i) => + `
+ ${(m.name||'?')[0].toUpperCase()} + ${escHtml(m.name)} + ${m.title ? `· ${escHtml(m.title)}` : ''} + +
` + ).join(''); + + container.innerHTML = ` +
${bubbles}
+
+
+ + +
+ + + +
`; + } + + function memberNameSearch(q) { + const dd = document.getElementById('memberNameDropdown'); + if (!q || !orgMemberSuggestions.length) { dd.classList.add('hidden'); return; } + const matches = orgMemberSuggestions.filter(m => + (m.name || '').toLowerCase().includes(q.toLowerCase()) || + (m.email || '').toLowerCase().includes(q.toLowerCase()) + ); + if (!matches.length) { dd.classList.add('hidden'); return; } + dd.innerHTML = matches.map(m => + `
+ ${escHtml(m.name)} + ${m.email ? `${escHtml(m.email)}` : ''} +
` + ).join(''); + dd.classList.remove('hidden'); + } + + function selectMemberSuggestion(mJson) { + const m = JSON.parse(mJson); + document.getElementById('new-mname').value = m.name || ''; + document.getElementById('new-memail').value = m.email || ''; + document.getElementById('new-mtitle').value = m.title || ''; + document.getElementById('memberNameDropdown').classList.add('hidden'); + } + + function commitMember() { + const name = (document.getElementById('new-mname')?.value || '').trim(); + const email = (document.getElementById('new-memail')?.value || '').trim(); + const title = (document.getElementById('new-mtitle')?.value || '').trim(); + if (!name && !email) return; + pendingMembers.push({ name: name || email, email, title }); + // Add to org suggestions for future use + if (!orgMemberSuggestions.find(m => m.email === email)) { + orgMemberSuggestions.push({ name, email, title }); + } + renderMemberBubbles(); + setTimeout(() => document.getElementById('new-mname')?.focus(), 50); + } + + function removeMember(idx) { + pendingMembers.splice(idx, 1); + renderMemberBubbles(); } async function submitAddOrg() { @@ -960,15 +1037,12 @@ const btn = document.getElementById('addOrgSubmitBtn'); if (!name) { errEl.textContent = 'Organization name is required'; errEl.classList.remove('hidden'); return; } - // Collect members - const members = []; - document.querySelectorAll('#memberRows > div').forEach(row => { - const idx = row.id.replace('member-row-', ''); - const n = document.getElementById('mname-' + idx)?.value.trim(); - const e = document.getElementById('memail-' + idx)?.value.trim(); - const t = document.getElementById('mtitle-' + idx)?.value.trim(); - if (n || e) members.push({ name: n || '', email: e || '', title: t || '' }); - }); + // Collect members — commit any in-progress input row first + const inProgName = document.getElementById('new-mname')?.value.trim(); + const inProgEmail = document.getElementById('new-memail')?.value.trim(); + const inProgTitle = document.getElementById('new-mtitle')?.value.trim(); + if (inProgName || inProgEmail) pendingMembers.push({ name: inProgName || inProgEmail, email: inProgEmail, title: inProgTitle }); + const members = [...pendingMembers]; const perms = { download: document.getElementById('permDownload').value,