mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
style(portal): add operator runbooks to overview and services
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "69bb383eb566fc7cd58d13fdd5aad0d3d5b2b43107d66fbebc9d700607af2d26",
|
||||
"source_hash": "0401fbaf26ce16d3985c1cca3d6116f9fc2f3cb72e756d1bb8bca91d0d8633e2",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+69
-7
@@ -720,6 +720,31 @@ header .logout-btn:hover,
|
||||
.overview-side-card-primary {
|
||||
background: var(--panel-strong);
|
||||
}
|
||||
.overview-next-checklist {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.overview-next-check {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
}
|
||||
.overview-next-check strong {
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
color: var(--ink);
|
||||
}
|
||||
.overview-next-check span {
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.overview-next-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -1405,7 +1430,7 @@ header .logout-btn:hover,
|
||||
.service-shell-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
.service-shell-sidebar-head,
|
||||
.service-inline-support {
|
||||
@@ -1456,16 +1481,20 @@ header .logout-btn:hover,
|
||||
line-height: 1.6;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-empty-columns {
|
||||
.service-empty-command-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(280px, 0.92fr);
|
||||
grid-template-columns: minmax(220px, 0.82fr) minmax(0, 1.18fr);
|
||||
gap: 14px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.service-empty-column {
|
||||
.service-empty-command-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-muted);
|
||||
}
|
||||
.service-empty-column-title {
|
||||
font-size: 12px;
|
||||
@@ -1582,10 +1611,18 @@ header .logout-btn:hover,
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.service-empty-points-stack {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.service-empty-command-card-wide {
|
||||
grid-column: 2;
|
||||
background: #fff;
|
||||
}
|
||||
.service-empty-command-card-support {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.service-empty-support {
|
||||
margin-top: 16px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
@@ -1633,6 +1670,31 @@ header .logout-btn:hover,
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-empty-flow-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.service-empty-flow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-muted);
|
||||
}
|
||||
.service-empty-flow strong {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.service-empty-flow span {
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-section-auth {
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
@@ -179,7 +179,9 @@ describe('shell view', function() {
|
||||
expect(html).toContain('service-action-button');
|
||||
expect(html).toContain('id="service-panel-empty"');
|
||||
expect(html).toContain('Choose the next commercial action');
|
||||
expect(html).toContain('What to expect');
|
||||
expect(html).toContain('Task desk');
|
||||
expect(html).toContain('Available flows');
|
||||
expect(html).toContain('Escalate quickly');
|
||||
expect(html).toContain('id="open-retrieve-service"');
|
||||
expect(html).toContain('id="data-service-panel"');
|
||||
});
|
||||
@@ -195,7 +197,8 @@ describe('shell view', function() {
|
||||
|
||||
expect(html).toContain('Pulse Account');
|
||||
expect(html).toContain('Account console');
|
||||
expect(html).toContain('Use these tools for self-hosted commercial actions.');
|
||||
expect(html).toContain('Use these account tools for self-hosted licenses, billing, refunds, and privacy actions.');
|
||||
expect(html).toContain('Keep self-hosted commercial work here.');
|
||||
expect(html).toContain('Account services');
|
||||
expect(html).not.toContain('Self-hosted licenses and billing');
|
||||
});
|
||||
|
||||
@@ -358,6 +358,29 @@ function renderAccountOverviewSection(account: PortalAccountSummary): string {
|
||||
: checkingCount > 0
|
||||
? 'The fleet is mostly healthy, but there are still workspaces waiting on a completed health check.'
|
||||
: 'Hosted posture looks stable. Move into team or account services only if you need to change access or billing.';
|
||||
var nextStepChecklist = unhealthyCount > 0
|
||||
? (
|
||||
'<div class="overview-next-checklist">' +
|
||||
'<div class="overview-next-check"><strong>1. Review attention items</strong><span>Open the fleet and inspect any workspace marked as checking or needs attention.</span></div>' +
|
||||
'<div class="overview-next-check"><strong>2. Resolve operator blockers</strong><span>Use Team if the right people are not already attached to the hosted account.</span></div>' +
|
||||
'<div class="overview-next-check"><strong>3. Escalate billing separately</strong><span>Keep account billing or self-hosted license work out of the workspace review flow.</span></div>' +
|
||||
'</div>'
|
||||
)
|
||||
: checkingCount > 0
|
||||
? (
|
||||
'<div class="overview-next-checklist">' +
|
||||
'<div class="overview-next-check"><strong>1. Verify pending health checks</strong><span>Open the workspaces still settling and confirm they are safe to operate.</span></div>' +
|
||||
'<div class="overview-next-check"><strong>2. Keep the roster lean</strong><span>Review Team only if a pending workspace needs a different operator mix.</span></div>' +
|
||||
'<div class="overview-next-check"><strong>3. Leave billing as a separate action</strong><span>Use account services or billing only when the hosted fleet is already understood.</span></div>' +
|
||||
'</div>'
|
||||
)
|
||||
: (
|
||||
'<div class="overview-next-checklist">' +
|
||||
'<div class="overview-next-check"><strong>1. Use Workspaces for the next operational task</strong><span>Open a client workspace directly when you are ready to do hosted work.</span></div>' +
|
||||
'<div class="overview-next-check"><strong>2. Use Team for access changes only</strong><span>Keep roster changes explicit instead of mixing them into routine workspace work.</span></div>' +
|
||||
'<div class="overview-next-check"><strong>3. Use account services when the task is commercial</strong><span>Licenses, refunds, privacy, and self-hosted billing stay in their own section.</span></div>' +
|
||||
'</div>'
|
||||
);
|
||||
var nextStepActions =
|
||||
'<div class="overview-next-actions">' +
|
||||
'<button class="btn-primary btn-compact" type="button" data-shell-action="activate-section" data-shell-section="workspaces">Open workspaces</button>' +
|
||||
@@ -429,6 +452,7 @@ function renderAccountOverviewSection(account: PortalAccountSummary): string {
|
||||
'<div class="account-panel-kicker">Next move</div>' +
|
||||
'<h4>' + escapeHTML(nextStepTitle) + '</h4>' +
|
||||
'<p>' + escapeHTML(nextStepCopy) + '</p>' +
|
||||
nextStepChecklist +
|
||||
nextStepActions +
|
||||
'</div>' +
|
||||
renderAttentionPanel(workspaces) +
|
||||
@@ -765,7 +789,7 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
'<div class="service-shell-sidebar-head">' +
|
||||
'<div class="account-panel-kicker">Commercial actions</div>' +
|
||||
'<h3>Billing, licenses, refunds, and privacy</h3>' +
|
||||
'<p>Use these tools for self-hosted commercial actions. Hosted workspace operations stay in Workspaces and Team.</p>' +
|
||||
'<p>Keep self-hosted commercial work here. Hosted workspace operations stay in Workspaces and Team.</p>' +
|
||||
'</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', ['Invoices and plan changes', 'Subscription self-service']) +
|
||||
@@ -777,33 +801,38 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
'<div class="service-shell-main">' +
|
||||
'<div class="service-detail-shell">' +
|
||||
'<div class="service-panel service-panel-empty visible" id="service-panel-empty">' +
|
||||
'<div class="account-panel-kicker">Select a service</div>' +
|
||||
'<div class="account-panel-kicker">Task desk</div>' +
|
||||
'<h3>Choose the next commercial action</h3>' +
|
||||
'<p>Open a billing, license, refund, or privacy flow from the service navigator. The active request stays here so the account-services area behaves like one operating desk instead of a list of disconnected tools.</p>' +
|
||||
'<div class="service-empty-columns">' +
|
||||
'<div class="service-empty-column">' +
|
||||
'<p>Open a billing, license, refund, or privacy flow from the service navigator. The active request stays here so this area behaves like one commercial operating desk.</p>' +
|
||||
'<div class="service-empty-command-grid">' +
|
||||
'<div class="service-empty-command-card">' +
|
||||
'<div class="service-empty-column-title">Start here</div>' +
|
||||
'<div class="service-empty-points">' +
|
||||
'<div class="service-empty-point"><strong>Billing</strong><span>Open Stripe customer portal access after verification.</span></div>' +
|
||||
'<div class="service-empty-point"><strong>Licenses</strong><span>Recover the latest active self-hosted license and invoice link.</span></div>' +
|
||||
'<div class="service-empty-point"><strong>Refunds</strong><span>Confirm eligibility before revoking active commercial access.</span></div>' +
|
||||
'<div class="service-empty-point"><strong>Privacy</strong><span>Request export or deletion without leaving Pulse Account.</span></div>' +
|
||||
'<div class="service-empty-points service-empty-points-stack">' +
|
||||
'<div class="service-empty-point"><strong>Verify first</strong><span>Each flow confirms the commercial email before opening sensitive actions.</span></div>' +
|
||||
'<div class="service-empty-point"><strong>One task at a time</strong><span>Keep the active request in this desk until you finish or switch tools.</span></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="service-empty-column">' +
|
||||
'<div class="service-empty-column-title">What to expect</div>' +
|
||||
'<div class="service-empty-checklist">' +
|
||||
'<div class="service-empty-check"><strong>Verification first</strong><span>Each flow confirms the commercial email before opening sensitive account actions.</span></div>' +
|
||||
'<div class="service-empty-check"><strong>One task at a time</strong><span>The active commercial request stays in this panel until you finish or switch tools.</span></div>' +
|
||||
'<div class="service-empty-check"><strong>Support stays close</strong><span>If billing, licenses, refunds, or privacy behave unexpectedly, escalate from this surface.</span></div>' +
|
||||
'<div class="service-empty-command-card service-empty-command-card-wide">' +
|
||||
'<div class="service-empty-column-title">Available flows</div>' +
|
||||
'<div class="service-empty-flow-list">' +
|
||||
'<div class="service-empty-flow"><strong>Billing</strong><span>Stripe customer portal access after verification.</span></div>' +
|
||||
'<div class="service-empty-flow"><strong>Licenses</strong><span>Recover the latest active self-hosted license and invoice link.</span></div>' +
|
||||
'<div class="service-empty-flow"><strong>Refunds</strong><span>Confirm eligibility before revoking active commercial access.</span></div>' +
|
||||
'<div class="service-empty-flow"><strong>Privacy</strong><span>Request export or deletion without leaving Pulse Account.</span></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="service-empty-command-card service-empty-command-card-support">' +
|
||||
'<div class="service-empty-column-title">Support</div>' +
|
||||
'<div class="service-empty-checklist">' +
|
||||
'<div class="service-empty-check"><strong>Escalate quickly</strong><span>If billing, licenses, refunds, or privacy behave unexpectedly, escalate from this surface.</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="service-empty-support">Need help with billing, refund, privacy, or license actions? <a class="portal-support-link" href="mailto:' +
|
||||
escapeAttr(context.bootstrap.support_email || '') +
|
||||
'">' +
|
||||
escapeHTML(context.bootstrap.support_email || '') +
|
||||
'</a></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="service-empty-support">Need help with billing, refund, privacy, or license actions? <a class="portal-support-link" href="mailto:' +
|
||||
escapeAttr(context.bootstrap.support_email || '') +
|
||||
'">' +
|
||||
escapeHTML(context.bootstrap.support_email || '') +
|
||||
'</a></div>' +
|
||||
'</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>' +
|
||||
@@ -820,15 +849,6 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
'</a>.</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="service-inline-support">' +
|
||||
'<div class="account-panel-kicker">Support</div>' +
|
||||
'<p>Use support if a billing, refund, privacy, or license flow does 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>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</section>' +
|
||||
|
||||
@@ -795,6 +795,35 @@ header .logout-btn:hover,
|
||||
background: var(--panel-strong);
|
||||
}
|
||||
|
||||
.overview-next-checklist {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.overview-next-check {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
}
|
||||
|
||||
.overview-next-check strong {
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.overview-next-check span {
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.overview-next-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -1589,7 +1618,7 @@ header .logout-btn:hover,
|
||||
.service-shell-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.service-shell-sidebar-head,
|
||||
@@ -1649,17 +1678,21 @@ header .logout-btn:hover,
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.service-empty-columns {
|
||||
.service-empty-command-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(280px, 0.92fr);
|
||||
grid-template-columns: minmax(220px, 0.82fr) minmax(0, 1.18fr);
|
||||
gap: 14px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.service-empty-column {
|
||||
.service-empty-command-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-muted);
|
||||
}
|
||||
|
||||
.service-empty-column-title {
|
||||
@@ -1793,10 +1826,21 @@ header .logout-btn:hover,
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.service-empty-points-stack {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.service-empty-command-card-wide {
|
||||
grid-column: 2;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.service-empty-command-card-support {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.service-empty-support {
|
||||
margin-top: 16px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
@@ -1852,6 +1896,35 @@ header .logout-btn:hover,
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.service-empty-flow-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.service-empty-flow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-muted);
|
||||
}
|
||||
|
||||
.service-empty-flow strong {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.service-empty-flow span {
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.service-section-auth {
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user