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 + '
Open
';
}
+ function renderOverviewQuickAction(section, title, copy) {
+ return '' + title + ' ' + copy + " ";
+ }
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 'Attention
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 'Attention
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 'Hosted posture
' + escapeHTML(account.name) + " " + escapeHTML(summaryText) + '
' + escapeHTML(
account.kind === "msp" ? "This is the control surface for hosted clients, account billing, and operator access." : "This is the control surface for hosted workspaces, billing, and account access."
- ) + '
Active workspaces ' + String(activeCount) + '
Healthy ' + String(healthyCount) + '
Checking ' + String(checkingCount) + '
Needs attention ' + String(unhealthyCount) + "
" + actions + addWorkspaceForm + "
";
+ ) + 'Active workspaces ' + String(activeCount) + '
Healthy ' + String(healthyCount) + '
Checking ' + String(checkingCount) + '
Needs attention ' + String(unhealthyCount) + "
" + actions + addWorkspaceForm + 'Operator overview
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 '' + renderAccountOverviewSection(account) + renderAccountWorkspaceSection(account, context.accountAPIBasePath) + renderAccountTeamSection(account) + "
";
}).join("");
- return '' + renderOverviewBand(accounts) + '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '
' + 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) + '
' + 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 (
+ '' +
+ '' + title + ' ' +
+ '' + copy + ' ' +
+ ' '
+ );
+}
+
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 (
+ '' +
+ '
Attention
' +
+ '
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 (
+ '' +
+ '
Attention
' +
+ '
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 +
+ '' +
+ '
' +
+ '
Operator overview
' +
+ '
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;
}