From c8e4a7a680a00d19e916ad2e8ce7a28c30733217 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 27 Mar 2026 15:48:14 +0000 Subject: [PATCH] feat(portal): make management surfaces explicit --- .../cloudcp/portal/dist/build_manifest.json | 2 +- internal/cloudcp/portal/dist/portal_app.css | 50 ++++++++++++++--- internal/cloudcp/portal/dist/portal_app.js | 12 +++++ .../frontend/src/account_runtime.test.ts | 30 +++++++++++ .../portal/frontend/src/account_runtime.ts | 12 +++++ .../cloudcp/portal/frontend/src/styles.css | 54 ++++++++++++++++--- 6 files changed, 143 insertions(+), 17 deletions(-) diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json index 94bcb1005b..5f37d47e48 100644 --- a/internal/cloudcp/portal/dist/build_manifest.json +++ b/internal/cloudcp/portal/dist/build_manifest.json @@ -1,5 +1,5 @@ { - "source_hash": "3e262c9fe2687c5e0d4ff9d11cd6f313d2f7b1e9fdb2a3f6be274246cd491d25", + "source_hash": "3b31c205a79f7c867444f852e0c63518ac751ef055d7c7679830912af99327cf", "build_inputs": [ "package.json", "tsconfig.json", diff --git a/internal/cloudcp/portal/dist/portal_app.css b/internal/cloudcp/portal/dist/portal_app.css index b234d8277a..5a28639288 100644 --- a/internal/cloudcp/portal/dist/portal_app.css +++ b/internal/cloudcp/portal/dist/portal_app.css @@ -247,7 +247,7 @@ header .logout-btn:hover, } .portal-frame { display: grid; - grid-template-columns: minmax(250px, 290px) minmax(0, 1fr); + grid-template-columns: minmax(220px, 250px) minmax(0, 1fr); gap: 22px; align-items: start; } @@ -397,14 +397,15 @@ header .logout-btn:hover, } .account-surface-body { display: grid; - grid-template-columns: minmax(260px, 320px) minmax(0, 1fr); - gap: 24px; + grid-template-columns: 1fr; + gap: 18px; margin-top: 22px; } .account-overview-rail { - display: flex; - flex-direction: column; + display: grid; + grid-template-columns: minmax(280px, 0.95fr) minmax(0, 1.15fr); gap: 14px; + align-items: start; } .account-overview-card, .account-stat-card, @@ -464,6 +465,13 @@ header .logout-btn:hover, grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } +.account-overview-card { + grid-column: 1; +} +.account-status-grid { + grid-column: 2; + grid-row: 1 / span 2; +} .account-stat-card { min-height: 108px; } @@ -503,6 +511,10 @@ header .logout-btn:hover, 180deg, var(--panel), var(--panel-strong)); + grid-column: 1; +} +.add-workspace-form { + grid-column: 1 / -1; } .account-main-stage { display: flex; @@ -577,7 +589,7 @@ header .logout-btn:hover, } .account-management-grid { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: 1fr; gap: 14px; } .workspace-management-panel, @@ -588,6 +600,19 @@ header .logout-btn:hover, .team-section.visible .team-management-panel { box-shadow: var(--shadow-md); } +.workspace-management-panel { + background: + linear-gradient( + 180deg, + rgba(255, 253, 248, 0.98), + rgba(245, 238, 226, 0.98)); +} +.workspace-management-content { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 16px; + align-items: end; +} .workspace-management-header, .team-management-header { display: flex; @@ -666,7 +691,7 @@ header .logout-btn:hover, } .team-management-grid { display: grid; - grid-template-columns: minmax(0, 1.45fr) minmax(280px, 1fr); + grid-template-columns: minmax(0, 1.6fr) minmax(320px, 0.95fr); gap: 14px; } .team-panel-heading { @@ -893,7 +918,6 @@ header .logout-btn:hover, } @media (max-width: 1180px) { .portal-frame, - .account-surface-body, .account-management-grid, .team-management-grid { grid-template-columns: 1fr; @@ -905,6 +929,16 @@ header .logout-btn:hover, display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } + .account-overview-rail { + grid-template-columns: 1fr; + } + .account-status-grid { + grid-column: auto; + grid-row: auto; + } + .workspace-management-content { + grid-template-columns: 1fr; + } } @media (max-width: 920px) { .portal-hero { diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js index b5432a665a..c7b243c08a 100644 --- a/internal/cloudcp/portal/dist/portal_app.js +++ b/internal/cloudcp/portal/dist/portal_app.js @@ -662,6 +662,10 @@ deps.store.updateAccountState(function(accountState) { var entry = ensurePortalAccountUIEntry(accountState, accountID); entry.addWorkspaceOpen = !entry.addWorkspaceOpen; + if (entry.addWorkspaceOpen) { + entry.teamVisible = false; + entry.selectedWorkspaceID = ""; + } shouldFocus = entry.addWorkspaceOpen; }); if (shouldFocus) { @@ -672,6 +676,10 @@ deps.store.updateAccountState(function(accountState) { var entry = ensurePortalAccountUIEntry(accountState, accountID); entry.selectedWorkspaceID = entry.selectedWorkspaceID === workspaceID ? "" : workspaceID; + if (entry.selectedWorkspaceID) { + entry.teamVisible = false; + entry.addWorkspaceOpen = false; + } }); }; var clearWorkspaceSelection = function(accountID) { @@ -768,6 +776,10 @@ deps.store.updateAccountState(function(accountState) { var entry = ensurePortalAccountUIEntry(accountState, accountID); entry.teamVisible = !entry.teamVisible; + if (entry.teamVisible) { + entry.selectedWorkspaceID = ""; + entry.addWorkspaceOpen = false; + } nextVisible = entry.teamVisible; }); if (nextVisible) { diff --git a/internal/cloudcp/portal/frontend/src/account_runtime.test.ts b/internal/cloudcp/portal/frontend/src/account_runtime.test.ts index 5f310d70b2..df6abff031 100644 --- a/internal/cloudcp/portal/frontend/src/account_runtime.test.ts +++ b/internal/cloudcp/portal/frontend/src/account_runtime.test.ts @@ -143,8 +143,23 @@ describe('account runtime', function() { '' + ''; + deps.store.updateAccountState(function(accountState) { + var entry = accountState.byAccountID.acct_1 || (accountState.byAccountID.acct_1 = { + addWorkspaceOpen: true, + createWorkspace: { pending: false, error: '' }, + selectedWorkspaceID: '', + manageWorkspace: { pending: false, error: '' }, + teamVisible: true, + teamQuery: { status: 'idle', error: '', data: [] }, + }); + entry.addWorkspaceOpen = true; + entry.teamVisible = true; + }, { notify: false }); + runtime.selectWorkspace('acct_1', 'ws_2'); expect(deps.store.getAccountState().byAccountID.acct_1.selectedWorkspaceID).toBe('ws_2'); + expect(deps.store.getAccountState().byAccountID.acct_1.teamVisible).toBe(false); + expect(deps.store.getAccountState().byAccountID.acct_1.addWorkspaceOpen).toBe(false); expect(document.getElementById('workspace-management-title-acct_1')?.textContent).toContain('Alpha Workspace'); expect(document.getElementById('workspace-management-action-acct_1')?.textContent).toContain('Suspend workspace'); @@ -182,10 +197,25 @@ describe('account runtime', function() { '' + ''; + deps.store.updateAccountState(function(accountState) { + var entry = accountState.byAccountID.acct_1 || (accountState.byAccountID.acct_1 = { + addWorkspaceOpen: true, + createWorkspace: { pending: false, error: '' }, + selectedWorkspaceID: 'ws_1', + manageWorkspace: { pending: false, error: '' }, + teamVisible: false, + teamQuery: { status: 'idle', error: '', data: [] }, + }); + entry.addWorkspaceOpen = true; + entry.selectedWorkspaceID = 'ws_1'; + }, { notify: false }); + runtime.toggleTeam('acct_1'); await flushAsync(); expect(deps.store.getAccountState().byAccountID.acct_1.teamVisible).toBe(true); + expect(deps.store.getAccountState().byAccountID.acct_1.selectedWorkspaceID).toBe(''); + expect(deps.store.getAccountState().byAccountID.acct_1.addWorkspaceOpen).toBe(false); expect(deps.store.getAccountState().byAccountID.acct_1.teamQuery.status).toBe('ready'); expect(deps.store.getAccountState().byAccountID.acct_1.teamQuery.data).toHaveLength(1); expect(document.getElementById('team-stats-acct_1')?.textContent).toContain('Members'); diff --git a/internal/cloudcp/portal/frontend/src/account_runtime.ts b/internal/cloudcp/portal/frontend/src/account_runtime.ts index 3a455061bb..02f419f2bc 100644 --- a/internal/cloudcp/portal/frontend/src/account_runtime.ts +++ b/internal/cloudcp/portal/frontend/src/account_runtime.ts @@ -86,6 +86,10 @@ export function installAccountRuntime(deps: AccountRuntimeDeps): AccountRuntime deps.store.updateAccountState(function(accountState) { var entry = ensurePortalAccountUIEntry(accountState, accountID); entry.addWorkspaceOpen = !entry.addWorkspaceOpen; + if (entry.addWorkspaceOpen) { + entry.teamVisible = false; + entry.selectedWorkspaceID = ''; + } shouldFocus = entry.addWorkspaceOpen; }); if (shouldFocus) { @@ -97,6 +101,10 @@ export function installAccountRuntime(deps: AccountRuntimeDeps): AccountRuntime deps.store.updateAccountState(function(accountState) { var entry = ensurePortalAccountUIEntry(accountState, accountID); entry.selectedWorkspaceID = entry.selectedWorkspaceID === workspaceID ? '' : workspaceID; + if (entry.selectedWorkspaceID) { + entry.teamVisible = false; + entry.addWorkspaceOpen = false; + } }); }; @@ -198,6 +206,10 @@ export function installAccountRuntime(deps: AccountRuntimeDeps): AccountRuntime deps.store.updateAccountState(function(accountState) { var entry = ensurePortalAccountUIEntry(accountState, accountID); entry.teamVisible = !entry.teamVisible; + if (entry.teamVisible) { + entry.selectedWorkspaceID = ''; + entry.addWorkspaceOpen = false; + } nextVisible = entry.teamVisible; }); if (nextVisible) { diff --git a/internal/cloudcp/portal/frontend/src/styles.css b/internal/cloudcp/portal/frontend/src/styles.css index 15baf3dcce..5de5ab4f35 100644 --- a/internal/cloudcp/portal/frontend/src/styles.css +++ b/internal/cloudcp/portal/frontend/src/styles.css @@ -249,7 +249,7 @@ header .logout-btn:hover, .portal-frame { display: grid; - grid-template-columns: minmax(250px, 290px) minmax(0, 1fr); + grid-template-columns: minmax(220px, 250px) minmax(0, 1fr); gap: 22px; align-items: start; } @@ -420,15 +420,16 @@ header .logout-btn:hover, .account-surface-body { display: grid; - grid-template-columns: minmax(260px, 320px) minmax(0, 1fr); - gap: 24px; + grid-template-columns: 1fr; + gap: 18px; margin-top: 22px; } .account-overview-rail { - display: flex; - flex-direction: column; + display: grid; + grid-template-columns: minmax(280px, 0.95fr) minmax(0, 1.15fr); gap: 14px; + align-items: start; } .account-overview-card, @@ -494,6 +495,15 @@ header .logout-btn:hover, gap: 12px; } +.account-overview-card { + grid-column: 1; +} + +.account-status-grid { + grid-column: 2; + grid-row: 1 / span 2; +} + .account-stat-card { min-height: 108px; } @@ -527,6 +537,11 @@ header .logout-btn:hover, flex-direction: column; gap: 16px; background: linear-gradient(180deg, var(--panel), var(--panel-strong)); + grid-column: 1; +} + +.add-workspace-form { + grid-column: 1 / -1; } .account-main-stage { @@ -614,7 +629,7 @@ header .logout-btn:hover, .account-management-grid { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: 1fr; gap: 14px; } @@ -628,6 +643,17 @@ header .logout-btn:hover, box-shadow: var(--shadow-md); } +.workspace-management-panel { + background: linear-gradient(180deg, rgba(255, 253, 248, 0.98), rgba(245, 238, 226, 0.98)); +} + +.workspace-management-content { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 16px; + align-items: end; +} + .workspace-management-header, .team-management-header { display: flex; @@ -717,7 +743,7 @@ header .logout-btn:hover, .team-management-grid { display: grid; - grid-template-columns: minmax(0, 1.45fr) minmax(280px, 1fr); + grid-template-columns: minmax(0, 1.6fr) minmax(320px, 0.95fr); gap: 14px; } @@ -981,7 +1007,6 @@ header .logout-btn:hover, @media (max-width: 1180px) { .portal-frame, - .account-surface-body, .account-management-grid, .team-management-grid { grid-template-columns: 1fr; @@ -995,6 +1020,19 @@ header .logout-btn:hover, display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } + + .account-overview-rail { + grid-template-columns: 1fr; + } + + .account-status-grid { + grid-column: auto; + grid-row: auto; + } + + .workspace-management-content { + grid-template-columns: 1fr; + } } @media (max-width: 920px) {