From cf307f390665953e5ae8f4dbe9752e12b1d3ec2e Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 27 Mar 2026 17:29:56 +0000 Subject: [PATCH] style(portal): integrate account console shell --- .../cloudcp/portal/dist/build_manifest.json | 2 +- internal/cloudcp/portal/dist/portal_app.css | 229 +++++++++++++--- internal/cloudcp/portal/dist/portal_app.js | 27 +- .../portal/frontend/src/shell_view.test.ts | 5 + .../cloudcp/portal/frontend/src/shell_view.ts | 126 +++++++-- .../cloudcp/portal/frontend/src/styles.css | 253 +++++++++++++++--- 6 files changed, 548 insertions(+), 94 deletions(-) diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json index 677ac339ee..a6069931f1 100644 --- a/internal/cloudcp/portal/dist/build_manifest.json +++ b/internal/cloudcp/portal/dist/build_manifest.json @@ -1,5 +1,5 @@ { - "source_hash": "f5ca96d0cb8760a1108b02f8b7f81a91bf85aa857f46f9317c753d304032ad63", + "source_hash": "74ac8f2899e8cddd8834f1a02166bdcc9971a26141ccb23c0213d2948a619e79", "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 a2a0894c77..37488709f1 100644 --- a/internal/cloudcp/portal/dist/portal_app.css +++ b/internal/cloudcp/portal/dist/portal_app.css @@ -160,16 +160,16 @@ header .logout-btn:hover, .portal-shell { display: flex; flex-direction: column; - gap: 18px; + gap: 16px; } .portal-hero { display: grid; grid-template-columns: minmax(0, 1.8fr) minmax(360px, 0.92fr); gap: 16px; - padding: 22px 24px; + padding: 18px 20px; border: 1px solid var(--line); border-radius: var(--radius-lg); - background: var(--panel); + background: #fbfcfd; box-shadow: var(--shadow-sm); } .portal-hero-copy { @@ -219,11 +219,11 @@ header .logout-btn:hover, display: flex; flex-direction: column; justify-content: space-between; - min-height: 92px; - padding: 14px; + min-height: 80px; + padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); - background: var(--panel-strong); + background: #fcfdff; } .portal-hero-stat-label { font-size: 12px; @@ -250,23 +250,29 @@ header .logout-btn:hover, .portal-shell-layout { display: grid; grid-template-columns: 240px minmax(0, 1fr); - gap: 18px; + gap: 0; align-items: start; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: #f7fafc; + box-shadow: var(--shadow-sm); + overflow: hidden; } .portal-shell-nav { - position: sticky; - top: 88px; - border: 1px solid var(--nav-line); - border-radius: var(--radius-md); + position: static; + min-height: 100%; + border: none; + border-right: 1px solid var(--nav-line); + border-radius: 0; background: var(--nav-bg); - box-shadow: var(--shadow-sm); + box-shadow: none; overflow: hidden; } .portal-shell-nav-header { display: flex; flex-direction: column; gap: 6px; - padding: 18px 18px 16px; + padding: 20px 20px 18px; border-bottom: 1px solid var(--nav-line); } .portal-shell-nav-eyebrow { @@ -291,22 +297,22 @@ header .logout-btn:hover, .portal-shell-nav-group { display: flex; flex-direction: column; + padding: 8px 0; } .portal-shell-nav-link { display: flex; flex-direction: column; gap: 6px; width: 100%; - padding: 14px 18px; + padding: 15px 20px; border: none; - border-bottom: 1px solid var(--nav-line); background: transparent; text-align: left; cursor: pointer; transition: background 140ms ease, color 140ms ease; } -.portal-shell-nav-link:last-child { - border-bottom: none; +.portal-shell-nav-link + .portal-shell-nav-link { + border-top: 1px solid rgba(255, 255, 255, 0.04); } .portal-shell-nav-link:hover { background: rgba(255, 255, 255, 0.04); @@ -329,6 +335,9 @@ header .logout-btn:hover, } .portal-shell-main { min-width: 0; + min-height: 760px; + padding: 24px 26px 30px; + background: #f7fafc; } .portal-shell-main > .portal-content-panel { display: none !important; @@ -393,19 +402,24 @@ header .logout-btn:hover, flex-wrap: wrap; } .account-surface { - border-radius: var(--radius-lg); - padding: 22px; + border: none; + background: transparent; + box-shadow: none; + padding: 0; } .account-surface + .account-surface { - margin-top: 18px; + margin-top: 22px; } .account-surface-header { display: flex; align-items: start; justify-content: space-between; gap: 16px; - padding-bottom: 18px; - border-bottom: 1px solid var(--line); + padding: 18px 20px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel); + box-shadow: var(--shadow-sm); } .account-heading h2 { margin: 4px 0 0; @@ -432,12 +446,20 @@ header .logout-btn:hover, .account-surface-body { display: grid; grid-template-columns: 1fr; - gap: 18px; - margin-top: 22px; + gap: 14px; + margin-top: 14px; } .account-content-panel { display: block; } +.account-content-panel-workspaces, +.account-content-panel-team { + padding: 18px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel); + box-shadow: var(--shadow-sm); +} .account-command-deck, .account-overview-card, .account-stat-card, @@ -491,7 +513,7 @@ header .logout-btn:hover, .account-command-deck { display: grid; grid-template-columns: minmax(320px, 1.08fr) minmax(0, 1.3fr); - gap: 12px; + gap: 14px; align-items: stretch; } .account-metric-strip { @@ -537,8 +559,10 @@ header .logout-btn:hover, justify-content: space-between; gap: 18px; grid-column: 1 / -1; - padding: 14px 16px 0; + padding: 16px; border-top: 1px solid var(--line); + background: var(--panel); + border-radius: var(--radius-md); } .account-action-copy { max-width: 720px; @@ -549,6 +573,95 @@ header .logout-btn:hover, line-height: 1.6; color: var(--ink-soft); } +.account-overview-secondary { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr); + gap: 12px; + grid-column: 1 / -1; +} +.overview-side-card { + padding: 16px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel); +} +.overview-side-card h4 { + margin: 8px 0 0; + font-size: 18px; + line-height: 1.08; + letter-spacing: -0.03em; +} +.overview-side-card p { + margin: 10px 0 0; + font-size: 14px; + line-height: 1.6; + color: var(--ink-soft); +} +.overview-side-card-primary { + background: var(--panel-strong); +} +.overview-quick-actions { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + margin-top: 16px; +} +.overview-quick-action { + display: flex; + flex-direction: column; + gap: 6px; + padding: 14px; + border: 1px solid var(--line); + border-radius: 10px; + background: #fff; + text-align: left; + cursor: pointer; +} +.overview-quick-action:hover { + border-color: var(--accent); + box-shadow: var(--shadow-sm); +} +.overview-quick-action-title { + font-size: 14px; + font-weight: 700; + color: var(--ink); +} +.overview-quick-action-copy { + font-size: 12px; + line-height: 1.5; + color: var(--ink-soft); +} +.overview-alert-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 16px; +} +.overview-alert-row { + display: flex; + align-items: start; + justify-content: space-between; + gap: 12px; + padding: 12px 0; + border-top: 1px solid var(--line); +} +.overview-alert-row:first-child { + border-top: none; + padding-top: 0; +} +.overview-alert-main { + display: flex; + flex-direction: column; + gap: 4px; +} +.overview-alert-main strong { + font-size: 14px; +} +.overview-alert-main span { + font-size: 13px; + line-height: 1.55; + color: var(--ink-soft); +} .add-workspace-form { grid-column: 1 / -1; } @@ -561,7 +674,7 @@ header .logout-btn:hover, border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 22px; - background: rgba(255, 255, 255, 0.98); + background: var(--panel); box-shadow: var(--shadow-sm); } .portal-support-panel h2 { @@ -601,19 +714,22 @@ header .logout-btn:hover, color: var(--ink-soft); } .account-stage-header { - padding: 2px 4px 0; + padding: 0 0 12px; + border-bottom: 1px solid var(--line); + margin-bottom: 14px; } .workspace-list-wrap { display: flex; flex-direction: column; - gap: 8px; + gap: 10px; } .workspace-list-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; - padding: 4px 4px 10px; + padding: 2px 0 12px; + border-bottom: 1px solid var(--line); } .workspace-list-summary { font-size: 13px; @@ -648,7 +764,7 @@ header .logout-btn:hover, display: grid; grid-template-columns: minmax(0, 1.55fr) 120px 120px 180px; gap: 14px; - padding: 0 16px 6px; + padding: 2px 16px 6px; font-size: 11px; font-weight: 800; letter-spacing: 0.12em; @@ -666,6 +782,7 @@ header .logout-btn:hover, gap: 14px; align-items: center; padding: 16px; + background: var(--panel-muted); } .workspace-row-main { display: grid; @@ -726,7 +843,7 @@ header .logout-btn:hover, padding: 12px; border: 1px solid var(--line); border-radius: var(--radius-md); - background: var(--panel-muted); + background: #fff; } .workspace-row-status-label { font-size: 11px; @@ -754,7 +871,7 @@ header .logout-btn:hover, box-shadow: var(--shadow-md); } .workspace-management-panel { - background: var(--panel); + background: var(--panel-muted); } .workspace-management-content { display: grid; @@ -848,6 +965,11 @@ header .logout-btn:hover, grid-template-columns: minmax(0, 1.65fr) minmax(320px, 0.92fr); gap: 14px; } +.team-roster, +.team-invite-panel { + background: var(--panel-muted); + border-color: var(--line-strong); +} .team-panel-heading { margin-bottom: 12px; } @@ -875,7 +997,7 @@ header .logout-btn:hover, padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; - background: var(--panel-muted); + background: #fff; } .team-member-identity { display: flex; @@ -954,6 +1076,36 @@ header .logout-btn:hover, flex-direction: column; gap: 10px; } +.service-shell { + display: grid; + grid-template-columns: minmax(0, 1.45fr) minmax(280px, 0.7fr); + gap: 16px; + align-items: start; +} +.service-shell-main, +.service-shell-sidebar { + display: flex; + flex-direction: column; + gap: 12px; +} +.service-brief-card { + padding: 18px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel-muted); +} +.service-brief-card h3 { + margin: 8px 0 0; + font-size: 19px; + line-height: 1.08; + letter-spacing: -0.03em; +} +.service-brief-card p { + margin: 10px 0 0; + font-size: 14px; + line-height: 1.6; + color: var(--ink-soft); +} .service-action-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; @@ -962,7 +1114,7 @@ header .logout-btn:hover, padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--radius-md); - background: var(--panel); + background: var(--panel-muted); } .service-action-copy { display: flex; @@ -1157,7 +1309,9 @@ header .logout-btn:hover, } @media (max-width: 1180px) { .account-management-grid, - .team-management-grid { + .team-management-grid, + .account-overview-secondary, + .service-shell { grid-template-columns: 1fr; } .portal-shell-layout, @@ -1167,6 +1321,9 @@ header .logout-btn:hover, .account-metric-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .overview-quick-actions { + grid-template-columns: 1fr; + } .portal-shell-nav { position: static; } diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js index df220ddbff..68d10798c1 100644 --- a/internal/cloudcp/portal/dist/portal_app.js +++ b/internal/cloudcp/portal/dist/portal_app.js @@ -1660,12 +1660,35 @@ function renderServiceActionRow(id, kicker, title, description, panelID, focusID) { return '
' + kicker + "

" + title + "

" + description + '

'; } + function renderOverviewQuickAction(section, title, copy) { + return '"; + } function workspaceStatusCopy(workspace) { var status = workspaceHealthState(workspace); if (status === "healthy") return "Live updates and health checks are currently good."; if (status === "unhealthy") return "This workspace needs attention before it is trustworthy."; return "This workspace is still waiting on a completed health check."; } + function attentionWorkspaces(workspaces) { + var results = []; + for (var i = 0; i < workspaces.length; i += 1) { + var status = workspaceHealthState(workspaces[i]); + if (status === "unhealthy" || status === "checking") { + results.push(workspaces[i]); + } + } + return results; + } + function renderAttentionPanel(workspaces) { + var attention = attentionWorkspaces(workspaces); + if (!attention.length) { + return '

Fleet is stable

Every visible hosted workspace currently reports a healthy posture.

'; + } + var items = attention.slice(0, 3).map(function(workspace) { + return '
' + escapeHTML(workspace.display_name) + "" + escapeHTML(workspaceStatusCopy(workspace)) + "
" + healthBadgeHTML(workspace) + "
"; + }).join(""); + return '

Needs review

These workspaces should be checked before you treat the hosted fleet as fully healthy.

' + items + "
"; + } function renderOverviewBand(accounts) { var hosted = hasHostedAccounts(accounts); var workspaceTotal = countWorkspaces(accounts); @@ -1723,7 +1746,7 @@ } return '
"; + ) + '
" + actions + addWorkspaceForm + '

Start from the next action, not the whole account

Use the overview for posture, then move into workspaces, team, or account services depending on what needs attention.

' + renderOverviewQuickAction("workspaces", "Open workspaces", "Review the hosted fleet and move into a workspace") + renderOverviewQuickAction("team", "Review team access", "Check who can operate billing and hosted workspaces") + renderOverviewQuickAction("services", "Open account services", "Handle billing, licenses, refunds, and privacy actions") + "
" + renderAttentionPanel(workspaces) + "
"; } function renderAccountWorkspaceSection(account, accountAPIBasePath) { var workspaces = Array.isArray(account.workspaces) ? account.workspaces : []; @@ -1760,7 +1783,7 @@ var hostedContent = accounts.map(function(account) { return '
"; }).join(""); - return '
' + renderOverviewBand(accounts) + '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '

" + (hosted ? "Hosted operations" : "No hosted workspaces are attached yet") + "

" + (hosted ? "Use this area for workspace access, fleet operations, hosted billing, and team management." : "This account does not currently have hosted workspace access. If that is unexpected, contact support while using the commercial tools below.") + '

' + hostedContent + '

' + serviceHeading + '

' + serviceNote + '
' + renderServiceActionRow("open-manage-service", "Billing", "Manage subscriptions", "Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.", "manage-service-panel", "manage-inline-email") + renderServiceActionRow("open-retrieve-service", "Licenses", "Retrieve licenses", "Recover the latest active self-hosted license and invoice link for a commercial email address.", "retrieve-service-panel", "retrieve-inline-email") + renderServiceActionRow("open-refund-service", "Refunds", "Refund requests", "Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.", "refund-service-panel", "refund-inline-email") + renderServiceActionRow("open-data-service", "Privacy", "Data and privacy", "Request commercial data export or deletion without leaving the account shell.", "data-service-panel", "data-export-email") + '

Data and privacy

Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.

Payment-card data stays with Stripe. For Stripe deletion support, contact ' + escapeHTML(context.bootstrap.support_email || "") + '.
' + renderSupportSection(context) + "
"; + return '
' + renderOverviewBand(accounts) + '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '

" + (hosted ? "Hosted operations" : "No hosted workspaces are attached yet") + "

" + (hosted ? "Use this area for workspace access, fleet operations, hosted billing, and team management." : "This account does not currently have hosted workspace access. If that is unexpected, contact support while using the commercial tools below.") + '

' + hostedContent + '

' + serviceHeading + '

' + serviceNote + '
' + renderServiceActionRow("open-manage-service", "Billing", "Manage subscriptions", "Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.", "manage-service-panel", "manage-inline-email") + renderServiceActionRow("open-retrieve-service", "Licenses", "Retrieve licenses", "Recover the latest active self-hosted license and invoice link for a commercial email address.", "retrieve-service-panel", "retrieve-inline-email") + renderServiceActionRow("open-refund-service", "Refunds", "Refund requests", "Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.", "refund-service-panel", "refund-inline-email") + renderServiceActionRow("open-data-service", "Privacy", "Data and privacy", "Request commercial data export or deletion without leaving the account shell.", "data-service-panel", "data-export-email") + '

Data and privacy

Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.

Payment-card data stays with Stripe. For Stripe deletion support, contact ' + escapeHTML(context.bootstrap.support_email || "") + '.
' + renderSupportSection(context) + "
"; } function renderSignedOutPortalHTML(context) { var statusHTML = ""; diff --git a/internal/cloudcp/portal/frontend/src/shell_view.test.ts b/internal/cloudcp/portal/frontend/src/shell_view.test.ts index fce39d1961..9c79a06905 100644 --- a/internal/cloudcp/portal/frontend/src/shell_view.test.ts +++ b/internal/cloudcp/portal/frontend/src/shell_view.test.ts @@ -140,6 +140,11 @@ describe('shell view', function() { expect(html).toContain('Acme MSP'); expect(html).toContain('Account operations'); expect(html).toContain('Manage the client fleet from this account surface.'); + expect(html).toContain('Operator overview'); + expect(html).toContain('Start from the next action, not the whole account'); + expect(html).toContain('Open workspaces'); + expect(html).toContain('Review team access'); + expect(html).toContain('Needs review'); expect(html).toContain('Workspace fleet'); expect(html).toContain('Alpha Workspace'); expect(html).toContain('Beta Workspace'); diff --git a/internal/cloudcp/portal/frontend/src/shell_view.ts b/internal/cloudcp/portal/frontend/src/shell_view.ts index 94e8d560ef..8531026435 100644 --- a/internal/cloudcp/portal/frontend/src/shell_view.ts +++ b/internal/cloudcp/portal/frontend/src/shell_view.ts @@ -124,6 +124,15 @@ function renderServiceActionRow( ); } +function renderOverviewQuickAction(section: PortalShellSection, title: string, copy: string): string { + return ( + '' + ); +} + function workspaceStatusCopy(workspace: PortalWorkspaceSummary): string { var status = workspaceHealthState(workspace); if (status === 'healthy') return 'Live updates and health checks are currently good.'; @@ -131,6 +140,51 @@ function workspaceStatusCopy(workspace: PortalWorkspaceSummary): string { return 'This workspace is still waiting on a completed health check.'; } +function attentionWorkspaces(workspaces: PortalWorkspaceSummary[]): PortalWorkspaceSummary[] { + var results: PortalWorkspaceSummary[] = []; + for (var i = 0; i < workspaces.length; i += 1) { + var status = workspaceHealthState(workspaces[i]); + if (status === 'unhealthy' || status === 'checking') { + results.push(workspaces[i]); + } + } + return results; +} + +function renderAttentionPanel(workspaces: PortalWorkspaceSummary[]): string { + var attention = attentionWorkspaces(workspaces); + if (!attention.length) { + return ( + '
' + + '' + + '

Fleet is stable

' + + '

Every visible hosted workspace currently reports a healthy posture.

' + + '
' + ); + } + + var items = attention.slice(0, 3).map(function(workspace) { + return ( + '
' + + '
' + + '' + escapeHTML(workspace.display_name) + '' + + '' + escapeHTML(workspaceStatusCopy(workspace)) + '' + + '
' + + healthBadgeHTML(workspace) + + '
' + ); + }).join(''); + + return ( + '
' + + '' + + '

Needs review

' + + '

These workspaces should be checked before you treat the hosted fleet as fully healthy.

' + + '
' + items + '
' + + '
' + ); +} + function renderOverviewBand(accounts: PortalAccountSummary[]): string { var hosted = hasHostedAccounts(accounts); var workspaceTotal = countWorkspaces(accounts); @@ -357,6 +411,19 @@ function renderAccountOverviewSection(account: PortalAccountSummary): string { '' + actions + addWorkspaceForm + + '
' + + '
' + + '' + + '

Start from the next action, not the whole account

' + + '

Use the overview for posture, then move into workspaces, team, or account services depending on what needs attention.

' + + '
' + + renderOverviewQuickAction('workspaces', 'Open workspaces', 'Review the hosted fleet and move into a workspace') + + renderOverviewQuickAction('team', 'Review team access', 'Check who can operate billing and hosted workspaces') + + renderOverviewQuickAction('services', 'Open account services', 'Handle billing, licenses, refunds, and privacy actions') + + '
' + + '
' + + renderAttentionPanel(workspaces) + + '
' + '' + '' ); @@ -619,25 +686,46 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string '' + '
' + serviceNote + '
' + '' + - '
' + - renderServiceActionRow('open-manage-service', 'Billing', 'Manage subscriptions', 'Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.', 'manage-service-panel', 'manage-inline-email') + - renderServiceActionRow('open-retrieve-service', 'Licenses', 'Retrieve licenses', 'Recover the latest active self-hosted license and invoice link for a commercial email address.', 'retrieve-service-panel', 'retrieve-inline-email') + - renderServiceActionRow('open-refund-service', 'Refunds', 'Refund requests', 'Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.', 'refund-service-panel', 'refund-inline-email') + - renderServiceActionRow('open-data-service', 'Privacy', 'Data and privacy', 'Request commercial data export or deletion without leaving the account shell.', 'data-service-panel', 'data-export-email') + - '
' + - '
' + - '
' + - '
' + - '
' + - '

Data and privacy

' + - '

Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.

' + - '
' + - '
' + - '
Payment-card data stays with Stripe. For Stripe deletion support, contact ' + - escapeHTML(context.bootstrap.support_email || '') + - '.
' + + '
' + + '
' + + '
' + + renderServiceActionRow('open-manage-service', 'Billing', 'Manage subscriptions', 'Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.', 'manage-service-panel', 'manage-inline-email') + + renderServiceActionRow('open-retrieve-service', 'Licenses', 'Retrieve licenses', 'Recover the latest active self-hosted license and invoice link for a commercial email address.', 'retrieve-service-panel', 'retrieve-inline-email') + + renderServiceActionRow('open-refund-service', 'Refunds', 'Refund requests', 'Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.', 'refund-service-panel', 'refund-inline-email') + + renderServiceActionRow('open-data-service', 'Privacy', 'Data and privacy', 'Request commercial data export or deletion without leaving the account shell.', 'data-service-panel', 'data-export-email') + + '
' + + '
' + + '
' + + '
' + + '
' + + '

Data and privacy

' + + '

Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.

' + + '
' + + '
' + + '
Payment-card data stays with Stripe. For Stripe deletion support, contact ' + + escapeHTML(context.bootstrap.support_email || '') + + '.
' + + '
' + + '
' + + '' + '
' + '' + '
' + diff --git a/internal/cloudcp/portal/frontend/src/styles.css b/internal/cloudcp/portal/frontend/src/styles.css index da68c899be..80d75b12a4 100644 --- a/internal/cloudcp/portal/frontend/src/styles.css +++ b/internal/cloudcp/portal/frontend/src/styles.css @@ -157,17 +157,17 @@ header .logout-btn:hover, .portal-shell { display: flex; flex-direction: column; - gap: 18px; + gap: 16px; } .portal-hero { display: grid; grid-template-columns: minmax(0, 1.8fr) minmax(360px, 0.92fr); gap: 16px; - padding: 22px 24px; + padding: 18px 20px; border: 1px solid var(--line); border-radius: var(--radius-lg); - background: var(--panel); + background: #fbfcfd; box-shadow: var(--shadow-sm); } @@ -225,11 +225,11 @@ header .logout-btn:hover, display: flex; flex-direction: column; justify-content: space-between; - min-height: 92px; - padding: 14px; + min-height: 80px; + padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-md); - background: var(--panel-strong); + background: #fcfdff; } .portal-hero-stat-label { @@ -260,17 +260,23 @@ header .logout-btn:hover, .portal-shell-layout { display: grid; grid-template-columns: 240px minmax(0, 1fr); - gap: 18px; + gap: 0; align-items: start; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: #f7fafc; + box-shadow: var(--shadow-sm); + overflow: hidden; } .portal-shell-nav { - position: sticky; - top: 88px; - border: 1px solid var(--nav-line); - border-radius: var(--radius-md); + position: static; + min-height: 100%; + border: none; + border-right: 1px solid var(--nav-line); + border-radius: 0; background: var(--nav-bg); - box-shadow: var(--shadow-sm); + box-shadow: none; overflow: hidden; } @@ -278,7 +284,7 @@ header .logout-btn:hover, display: flex; flex-direction: column; gap: 6px; - padding: 18px 18px 16px; + padding: 20px 20px 18px; border-bottom: 1px solid var(--nav-line); } @@ -307,6 +313,7 @@ header .logout-btn:hover, .portal-shell-nav-group { display: flex; flex-direction: column; + padding: 8px 0; } .portal-shell-nav-link { @@ -314,17 +321,16 @@ header .logout-btn:hover, flex-direction: column; gap: 6px; width: 100%; - padding: 14px 18px; + padding: 15px 20px; border: none; - border-bottom: 1px solid var(--nav-line); background: transparent; text-align: left; cursor: pointer; transition: background 140ms ease, color 140ms ease; } -.portal-shell-nav-link:last-child { - border-bottom: none; +.portal-shell-nav-link + .portal-shell-nav-link { + border-top: 1px solid rgba(255, 255, 255, 0.04); } .portal-shell-nav-link:hover { @@ -352,6 +358,9 @@ header .logout-btn:hover, .portal-shell-main { min-width: 0; + min-height: 760px; + padding: 24px 26px 30px; + background: #f7fafc; } .portal-shell-main > .portal-content-panel { @@ -427,12 +436,14 @@ header .logout-btn:hover, } .account-surface { - border-radius: var(--radius-lg); - padding: 22px; + border: none; + background: transparent; + box-shadow: none; + padding: 0; } .account-surface + .account-surface { - margin-top: 18px; + margin-top: 22px; } .account-surface-header { @@ -440,8 +451,11 @@ header .logout-btn:hover, align-items: start; justify-content: space-between; gap: 16px; - padding-bottom: 18px; - border-bottom: 1px solid var(--line); + padding: 18px 20px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel); + box-shadow: var(--shadow-sm); } .account-heading h2 { @@ -472,14 +486,23 @@ header .logout-btn:hover, .account-surface-body { display: grid; grid-template-columns: 1fr; - gap: 18px; - margin-top: 22px; + gap: 14px; + margin-top: 14px; } .account-content-panel { display: block; } +.account-content-panel-workspaces, +.account-content-panel-team { + padding: 18px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel); + box-shadow: var(--shadow-sm); +} + .account-command-deck, .account-overview-card, .account-stat-card, @@ -537,7 +560,7 @@ header .logout-btn:hover, .account-command-deck { display: grid; grid-template-columns: minmax(320px, 1.08fr) minmax(0, 1.3fr); - gap: 12px; + gap: 14px; align-items: stretch; } @@ -583,8 +606,10 @@ header .logout-btn:hover, justify-content: space-between; gap: 18px; grid-column: 1 / -1; - padding: 14px 16px 0; + padding: 16px; border-top: 1px solid var(--line); + background: var(--panel); + border-radius: var(--radius-md); } .account-action-copy { @@ -598,6 +623,111 @@ header .logout-btn:hover, color: var(--ink-soft); } +.account-overview-secondary { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr); + gap: 12px; + grid-column: 1 / -1; +} + +.overview-side-card { + padding: 16px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel); +} + +.overview-side-card h4 { + margin: 8px 0 0; + font-size: 18px; + line-height: 1.08; + letter-spacing: -0.03em; +} + +.overview-side-card p { + margin: 10px 0 0; + font-size: 14px; + line-height: 1.6; + color: var(--ink-soft); +} + +.overview-side-card-primary { + background: var(--panel-strong); +} + +.overview-quick-actions { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; + margin-top: 16px; +} + +.overview-quick-action { + display: flex; + flex-direction: column; + gap: 6px; + padding: 14px; + border: 1px solid var(--line); + border-radius: 10px; + background: #fff; + text-align: left; + cursor: pointer; +} + +.overview-quick-action:hover { + border-color: var(--accent); + box-shadow: var(--shadow-sm); +} + +.overview-quick-action-title { + font-size: 14px; + font-weight: 700; + color: var(--ink); +} + +.overview-quick-action-copy { + font-size: 12px; + line-height: 1.5; + color: var(--ink-soft); +} + +.overview-alert-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 16px; +} + +.overview-alert-row { + display: flex; + align-items: start; + justify-content: space-between; + gap: 12px; + padding: 12px 0; + border-top: 1px solid var(--line); +} + +.overview-alert-row:first-child { + border-top: none; + padding-top: 0; +} + +.overview-alert-main { + display: flex; + flex-direction: column; + gap: 4px; +} + +.overview-alert-main strong { + font-size: 14px; +} + +.overview-alert-main span { + font-size: 13px; + line-height: 1.55; + color: var(--ink-soft); +} + .add-workspace-form { grid-column: 1 / -1; } @@ -612,7 +742,7 @@ header .logout-btn:hover, border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 22px; - background: rgba(255, 255, 255, 0.98); + background: var(--panel); box-shadow: var(--shadow-sm); } @@ -659,13 +789,15 @@ header .logout-btn:hover, } .account-stage-header { - padding: 2px 4px 0; + padding: 0 0 12px; + border-bottom: 1px solid var(--line); + margin-bottom: 14px; } .workspace-list-wrap { display: flex; flex-direction: column; - gap: 8px; + gap: 10px; } .workspace-list-toolbar { @@ -673,7 +805,8 @@ header .logout-btn:hover, align-items: center; justify-content: space-between; gap: 16px; - padding: 4px 4px 10px; + padding: 2px 0 12px; + border-bottom: 1px solid var(--line); } .workspace-list-summary { @@ -714,7 +847,7 @@ header .logout-btn:hover, display: grid; grid-template-columns: minmax(0, 1.55fr) 120px 120px 180px; gap: 14px; - padding: 0 16px 6px; + padding: 2px 16px 6px; font-size: 11px; font-weight: 800; letter-spacing: 0.12em; @@ -734,6 +867,7 @@ header .logout-btn:hover, gap: 14px; align-items: center; padding: 16px; + background: var(--panel-muted); } .workspace-row-main { @@ -803,7 +937,7 @@ header .logout-btn:hover, padding: 12px; border: 1px solid var(--line); border-radius: var(--radius-md); - background: var(--panel-muted); + background: #fff; } .workspace-row-status-label { @@ -837,7 +971,7 @@ header .logout-btn:hover, } .workspace-management-panel { - background: var(--panel); + background: var(--panel-muted); } .workspace-management-content { @@ -946,6 +1080,12 @@ header .logout-btn:hover, gap: 14px; } +.team-roster, +.team-invite-panel { + background: var(--panel-muted); + border-color: var(--line-strong); +} + .team-panel-heading { margin-bottom: 12px; } @@ -976,7 +1116,7 @@ header .logout-btn:hover, padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; - background: var(--panel-muted); + background: #fff; } .team-member-identity { @@ -1069,6 +1209,41 @@ header .logout-btn:hover, gap: 10px; } +.service-shell { + display: grid; + grid-template-columns: minmax(0, 1.45fr) minmax(280px, 0.7fr); + gap: 16px; + align-items: start; +} + +.service-shell-main, +.service-shell-sidebar { + display: flex; + flex-direction: column; + gap: 12px; +} + +.service-brief-card { + padding: 18px; + border: 1px solid var(--line); + border-radius: var(--radius-md); + background: var(--panel-muted); +} + +.service-brief-card h3 { + margin: 8px 0 0; + font-size: 19px; + line-height: 1.08; + letter-spacing: -0.03em; +} + +.service-brief-card p { + margin: 10px 0 0; + font-size: 14px; + line-height: 1.6; + color: var(--ink-soft); +} + .service-action-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; @@ -1077,7 +1252,7 @@ header .logout-btn:hover, padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--radius-md); - background: var(--panel); + background: var(--panel-muted); } .service-action-copy { @@ -1302,7 +1477,9 @@ header .logout-btn:hover, @media (max-width: 1180px) { .account-management-grid, - .team-management-grid { + .team-management-grid, + .account-overview-secondary, + .service-shell { grid-template-columns: 1fr; } @@ -1315,6 +1492,10 @@ header .logout-btn:hover, grid-template-columns: repeat(2, minmax(0, 1fr)); } + .overview-quick-actions { + grid-template-columns: 1fr; + } + .portal-shell-nav { position: static; }