mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 12:15:42 +00:00
style(portal): integrate account console shell
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "f5ca96d0cb8760a1108b02f8b7f81a91bf85aa857f46f9317c753d304032ad63",
|
||||
"source_hash": "74ac8f2899e8cddd8834f1a02166bdcc9971a26141ccb23c0213d2948a619e79",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+193
-36
@@ -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;
|
||||
}
|
||||
|
||||
+25
-2
@@ -1660,12 +1660,35 @@
|
||||
function renderServiceActionRow(id, kicker, title, description, panelID, focusID) {
|
||||
return '<div class="service-action-row"><div class="service-action-copy"><span class="service-card-kicker">' + kicker + "</span><h3>" + title + "</h3><p>" + description + '</p></div><div class="service-action-cta"><button class="btn-secondary service-action-button" type="button" id="' + id + '" data-account-service-action="open-service-panel" data-account-service-panel="' + panelID + '" data-account-service-focus="' + focusID + '" data-shell-target="services">Open</button></div></div>';
|
||||
}
|
||||
function renderOverviewQuickAction(section, title, copy) {
|
||||
return '<button class="overview-quick-action" type="button" data-shell-action="activate-section" data-shell-section="' + section + '"><span class="overview-quick-action-title">' + title + '</span><span class="overview-quick-action-copy">' + copy + "</span></button>";
|
||||
}
|
||||
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 '<div class="overview-side-card"><div class="account-panel-kicker">Attention</div><h4>Fleet is stable</h4><p>Every visible hosted workspace currently reports a healthy posture.</p></div>';
|
||||
}
|
||||
var items = attention.slice(0, 3).map(function(workspace) {
|
||||
return '<div class="overview-alert-row"><div class="overview-alert-main"><strong>' + escapeHTML(workspace.display_name) + "</strong><span>" + escapeHTML(workspaceStatusCopy(workspace)) + "</span></div>" + healthBadgeHTML(workspace) + "</div>";
|
||||
}).join("");
|
||||
return '<div class="overview-side-card"><div class="account-panel-kicker">Attention</div><h4>Needs review</h4><p>These workspaces should be checked before you treat the hosted fleet as fully healthy.</p><div class="overview-alert-list">' + items + "</div></div>";
|
||||
}
|
||||
function renderOverviewBand(accounts) {
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
var workspaceTotal = countWorkspaces(accounts);
|
||||
@@ -1723,7 +1746,7 @@
|
||||
}
|
||||
return '<section class="account-content-panel account-content-panel-overview"><div class="account-command-deck"><div class="account-overview-card"><div class="account-panel-kicker">Hosted posture</div><h3>' + escapeHTML(account.name) + "</h3><p>" + escapeHTML(summaryText) + '</p><div class="account-overview-callout">' + 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."
|
||||
) + '</div></div><div class="account-metric-strip"><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Active workspaces</span><span class="account-stat-value">' + String(activeCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Healthy</span><span class="account-stat-value account-stat-healthy">' + String(healthyCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Checking</span><span class="account-stat-value account-stat-checking">' + String(checkingCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Needs attention</span><span class="account-stat-value account-stat-unhealthy">' + String(unhealthyCount) + "</span></div></div>" + actions + addWorkspaceForm + "</div></section>";
|
||||
) + '</div></div><div class="account-metric-strip"><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Active workspaces</span><span class="account-stat-value">' + String(activeCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Healthy</span><span class="account-stat-value account-stat-healthy">' + String(healthyCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Checking</span><span class="account-stat-value account-stat-checking">' + String(checkingCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Needs attention</span><span class="account-stat-value account-stat-unhealthy">' + String(unhealthyCount) + "</span></div></div>" + actions + addWorkspaceForm + '<div class="account-overview-secondary"><div class="overview-side-card overview-side-card-primary"><div class="account-panel-kicker">Operator overview</div><h4>Start from the next action, not the whole account</h4><p>Use the overview for posture, then move into workspaces, team, or account services depending on what needs attention.</p><div class="overview-quick-actions">' + 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") + "</div></div>" + renderAttentionPanel(workspaces) + "</div></div></section>";
|
||||
}
|
||||
function renderAccountWorkspaceSection(account, accountAPIBasePath) {
|
||||
var workspaces = Array.isArray(account.workspaces) ? account.workspaces : [];
|
||||
@@ -1760,7 +1783,7 @@
|
||||
var hostedContent = accounts.map(function(account) {
|
||||
return '<section class="account-surface"><div class="account-surface-header"><div class="account-heading"><div class="account-eyebrow">' + escapeHTML(accountKindLabel(account)) + "</div><h2>" + escapeHTML(account.name) + '</h2><div class="account-summary">' + escapeHTML(accountKindLabel(account) + " \xB7 " + titleCase(account.role) + " \xB7 " + workspaceCountLabel((account.workspaces || []).length)) + '</div></div><div class="account-badges"><span class="badge badge-' + escapeHTML(account.kind) + '">' + escapeHTML(account.kind_label) + "</span>" + roleBadgeHTML(account.role) + '</div></div><div class="account-surface-body">' + renderAccountOverviewSection(account) + renderAccountWorkspaceSection(account, context.accountAPIBasePath) + renderAccountTeamSection(account) + "</div></section>";
|
||||
}).join("");
|
||||
return '<div class="portal-shell" data-shell-section="' + activeSection + '">' + renderOverviewBand(accounts) + '<div class="portal-shell-layout">' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '<div class="portal-shell-main"><section class="portal-content-panel portal-content-panel-overview"><div class="portal-top-section-header"><div class="account-panel-kicker">' + (hosted ? "Hosted operations" : "Hosted access") + "</div><h2>" + (hosted ? "Hosted operations" : "No hosted workspaces are attached yet") + "</h2><p>" + (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.") + '</p></div><div id="accounts-root">' + hostedContent + '</div></section><section class="portal-content-panel portal-content-panel-services service-section" id="account-services-section"><div class="service-header"><div><div class="account-panel-kicker">Account services</div><h2>' + serviceHeading + '</h2></div><div class="service-note">' + serviceNote + '</div></div><div class="service-action-list">' + 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") + '</div><div class="service-panel" id="manage-service-panel"><div id="manage-service-root"></div></div><div class="service-panel" id="retrieve-service-panel"><div id="retrieve-service-root"></div></div><div class="service-panel" id="refund-service-panel"><div id="refund-service-root"></div></div><div class="service-panel" id="data-service-panel"><h3>Data and privacy</h3><p>Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.</p><div class="subsection"><div id="data-export-root"></div></div><div class="subsection"><div id="data-delete-root"></div></div><div class="helper-text">Payment-card data stays with Stripe. For Stripe deletion support, contact <a href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a>.</div></div></section><section class="portal-content-panel portal-content-panel-support">' + renderSupportSection(context) + "</section></div></div></div>";
|
||||
return '<div class="portal-shell" data-shell-section="' + activeSection + '">' + renderOverviewBand(accounts) + '<div class="portal-shell-layout">' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '<div class="portal-shell-main"><section class="portal-content-panel portal-content-panel-overview"><div class="portal-top-section-header"><div class="account-panel-kicker">' + (hosted ? "Hosted operations" : "Hosted access") + "</div><h2>" + (hosted ? "Hosted operations" : "No hosted workspaces are attached yet") + "</h2><p>" + (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.") + '</p></div><div id="accounts-root">' + hostedContent + '</div></section><section class="portal-content-panel portal-content-panel-services service-section" id="account-services-section"><div class="service-header"><div><div class="account-panel-kicker">Account services</div><h2>' + serviceHeading + '</h2></div><div class="service-note">' + serviceNote + '</div></div><div class="service-shell"><div class="service-shell-main"><div class="service-action-list">' + 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") + '</div><div class="service-panel" id="manage-service-panel"><div id="manage-service-root"></div></div><div class="service-panel" id="retrieve-service-panel"><div id="retrieve-service-root"></div></div><div class="service-panel" id="refund-service-panel"><div id="refund-service-root"></div></div><div class="service-panel" id="data-service-panel"><h3>Data and privacy</h3><p>Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.</p><div class="subsection"><div id="data-export-root"></div></div><div class="subsection"><div id="data-delete-root"></div></div><div class="helper-text">Payment-card data stays with Stripe. For Stripe deletion support, contact <a href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a>.</div></div></div><aside class="service-shell-sidebar"><div class="service-brief-card"><div class="account-panel-kicker">Scope</div><h3>Commercial account actions</h3><p>Use this area for self-hosted billing, license recovery, refunds, and privacy requests. Hosted workspace operations stay in the Workspaces and Team sections.</p></div><div class="service-brief-card"><div class="account-panel-kicker">Support</div><h3>Need help with a commercial request?</h3><p>Use support if billing, refund, privacy, or license actions do not behave as expected for this account.</p><a class="portal-support-link" href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a></div></aside></div></section><section class="portal-content-panel portal-content-panel-support">' + renderSupportSection(context) + "</section></div></div></div>";
|
||||
}
|
||||
function renderSignedOutPortalHTML(context) {
|
||||
var statusHTML = "";
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -124,6 +124,15 @@ function renderServiceActionRow(
|
||||
);
|
||||
}
|
||||
|
||||
function renderOverviewQuickAction(section: PortalShellSection, title: string, copy: string): string {
|
||||
return (
|
||||
'<button class="overview-quick-action" type="button" data-shell-action="activate-section" data-shell-section="' + section + '">' +
|
||||
'<span class="overview-quick-action-title">' + title + '</span>' +
|
||||
'<span class="overview-quick-action-copy">' + copy + '</span>' +
|
||||
'</button>'
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
'<div class="overview-side-card">' +
|
||||
'<div class="account-panel-kicker">Attention</div>' +
|
||||
'<h4>Fleet is stable</h4>' +
|
||||
'<p>Every visible hosted workspace currently reports a healthy posture.</p>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
var items = attention.slice(0, 3).map(function(workspace) {
|
||||
return (
|
||||
'<div class="overview-alert-row">' +
|
||||
'<div class="overview-alert-main">' +
|
||||
'<strong>' + escapeHTML(workspace.display_name) + '</strong>' +
|
||||
'<span>' + escapeHTML(workspaceStatusCopy(workspace)) + '</span>' +
|
||||
'</div>' +
|
||||
healthBadgeHTML(workspace) +
|
||||
'</div>'
|
||||
);
|
||||
}).join('');
|
||||
|
||||
return (
|
||||
'<div class="overview-side-card">' +
|
||||
'<div class="account-panel-kicker">Attention</div>' +
|
||||
'<h4>Needs review</h4>' +
|
||||
'<p>These workspaces should be checked before you treat the hosted fleet as fully healthy.</p>' +
|
||||
'<div class="overview-alert-list">' + items + '</div>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
function renderOverviewBand(accounts: PortalAccountSummary[]): string {
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
var workspaceTotal = countWorkspaces(accounts);
|
||||
@@ -357,6 +411,19 @@ function renderAccountOverviewSection(account: PortalAccountSummary): string {
|
||||
'</div>' +
|
||||
actions +
|
||||
addWorkspaceForm +
|
||||
'<div class="account-overview-secondary">' +
|
||||
'<div class="overview-side-card overview-side-card-primary">' +
|
||||
'<div class="account-panel-kicker">Operator overview</div>' +
|
||||
'<h4>Start from the next action, not the whole account</h4>' +
|
||||
'<p>Use the overview for posture, then move into workspaces, team, or account services depending on what needs attention.</p>' +
|
||||
'<div class="overview-quick-actions">' +
|
||||
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') +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
renderAttentionPanel(workspaces) +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</section>'
|
||||
);
|
||||
@@ -619,25 +686,46 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
'</div>' +
|
||||
'<div class="service-note">' + serviceNote + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="service-action-list">' +
|
||||
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') +
|
||||
'</div>' +
|
||||
'<div class="service-panel" id="manage-service-panel"><div id="manage-service-root"></div></div>' +
|
||||
'<div class="service-panel" id="retrieve-service-panel"><div id="retrieve-service-root"></div></div>' +
|
||||
'<div class="service-panel" id="refund-service-panel"><div id="refund-service-root"></div></div>' +
|
||||
'<div class="service-panel" id="data-service-panel">' +
|
||||
'<h3>Data and privacy</h3>' +
|
||||
'<p>Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.</p>' +
|
||||
'<div class="subsection"><div id="data-export-root"></div></div>' +
|
||||
'<div class="subsection"><div id="data-delete-root"></div></div>' +
|
||||
'<div class="helper-text">Payment-card data stays with Stripe. For Stripe deletion support, contact <a href="mailto:' +
|
||||
escapeAttr(context.bootstrap.support_email || '') +
|
||||
'">' +
|
||||
escapeHTML(context.bootstrap.support_email || '') +
|
||||
'</a>.</div>' +
|
||||
'<div class="service-shell">' +
|
||||
'<div class="service-shell-main">' +
|
||||
'<div class="service-action-list">' +
|
||||
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') +
|
||||
'</div>' +
|
||||
'<div class="service-panel" id="manage-service-panel"><div id="manage-service-root"></div></div>' +
|
||||
'<div class="service-panel" id="retrieve-service-panel"><div id="retrieve-service-root"></div></div>' +
|
||||
'<div class="service-panel" id="refund-service-panel"><div id="refund-service-root"></div></div>' +
|
||||
'<div class="service-panel" id="data-service-panel">' +
|
||||
'<h3>Data and privacy</h3>' +
|
||||
'<p>Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.</p>' +
|
||||
'<div class="subsection"><div id="data-export-root"></div></div>' +
|
||||
'<div class="subsection"><div id="data-delete-root"></div></div>' +
|
||||
'<div class="helper-text">Payment-card data stays with Stripe. For Stripe deletion support, contact <a href="mailto:' +
|
||||
escapeAttr(context.bootstrap.support_email || '') +
|
||||
'">' +
|
||||
escapeHTML(context.bootstrap.support_email || '') +
|
||||
'</a>.</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<aside class="service-shell-sidebar">' +
|
||||
'<div class="service-brief-card">' +
|
||||
'<div class="account-panel-kicker">Scope</div>' +
|
||||
'<h3>Commercial account actions</h3>' +
|
||||
'<p>Use this area for self-hosted billing, license recovery, refunds, and privacy requests. Hosted workspace operations stay in the Workspaces and Team sections.</p>' +
|
||||
'</div>' +
|
||||
'<div class="service-brief-card">' +
|
||||
'<div class="account-panel-kicker">Support</div>' +
|
||||
'<h3>Need help with a commercial request?</h3>' +
|
||||
'<p>Use support if billing, refund, privacy, or license actions do not behave as expected for this account.</p>' +
|
||||
'<a class="portal-support-link" href="mailto:' +
|
||||
escapeAttr(context.bootstrap.support_email || '') +
|
||||
'">' +
|
||||
escapeHTML(context.bootstrap.support_email || '') +
|
||||
'</a>' +
|
||||
'</div>' +
|
||||
'</aside>' +
|
||||
'</div>' +
|
||||
'</section>' +
|
||||
'<section class="portal-content-panel portal-content-panel-support">' +
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user