Implemented a retry mechanism for retrieving the SPA token

This commit is contained in:
2026-09-15 12:25:09 -03:00
parent cd6b60ddcd
commit 003035a3a4
7 changed files with 1393 additions and 74 deletions

View File

@@ -283,7 +283,7 @@
if (!sessionTab) {
try {
sessionTab = await openConsumerTokenSessionTab(sender);
sessionTab = await openConsumerTokenSessionTabWithRetry(sender, debugEntry);
if (debugEntry) {
debugEntry.metadata.sessionBootstrapUrl = CUSTOMER_TOKEN_SESSION_URL;
debugEntry.metadata.sessionBootstrapState = "opened-inactive-tab";
@@ -337,6 +337,36 @@
);
}
async function openConsumerTokenSessionTabWithRetry(sender, debugEntry) {
let attempt = 0;
// Opening failures must not consume the token service's five attempts.
while (true) {
attempt += 1;
if (debugEntry) {
debugEntry.metadata.sessionBootstrapUrl = CUSTOMER_TOKEN_SESSION_URL;
debugEntry.metadata.sessionTabOpenAttempt = String(attempt);
}
try {
const tab = await openConsumerTokenSessionTab(sender);
if (debugEntry) {
debugEntry.metadata.sessionBootstrapState = "opened-inactive-tab";
delete debugEntry.metadata.sessionTabNextRetryDelayMs;
}
return tab;
} catch (error) {
const retryDelayMs = getConsumerTokenRetryDelay(Math.min(attempt, 5));
if (debugEntry) {
debugEntry.metadata.sessionBootstrapState = "retrying-tab-open";
debugEntry.metadata.sessionTabLastOpenError = error.message || "unknown error";
debugEntry.metadata.sessionTabNextRetryDelayMs = String(retryDelayMs);
}
await wait(retryDelayMs);
}
}
}
function openConsumerTokenSessionTab(sender) {
const details = {
url: CUSTOMER_TOKEN_SESSION_URL,

View File

@@ -2132,6 +2132,8 @@
tabList.setAttribute("role", "tablist");
tabList.setAttribute("aria-label", "Account plans");
const tabsNavigation = createAccountPlansTabsNavigation(tabList);
const panels = document.createElement("div");
panels.className = "opportunities-extension-account-plans-panels";
@@ -2183,7 +2185,7 @@
panels.append(panel);
});
content.append(tabList, panels);
content.append(tabsNavigation, panels);
}
dialog.append(header, content);
@@ -2200,6 +2202,74 @@
}
}
function createAccountPlansTabsNavigation(tabList) {
const navigation = document.createElement("div");
navigation.className = "opportunities-extension-account-plans-tabs-navigation";
const previousButton = createAccountPlansTabNavigationButton("previous");
const nextButton = createAccountPlansTabNavigationButton("next");
const updateNavigation = () => {
const maximumScroll = Math.max(0, tabList.scrollWidth - tabList.clientWidth);
const tabs = Array.from(tabList.querySelectorAll("[role='tab']"));
const viewportStart = tabList.scrollLeft;
const viewportEnd = viewportStart + tabList.clientWidth;
const hasContentBefore = tabList.scrollLeft > 1
|| tabs.some((tab) => tab.offsetLeft < viewportStart - 1);
const hasContentAfter = tabList.scrollLeft < maximumScroll - 1
|| tabs.some((tab) => (tab.offsetLeft + tab.offsetWidth) > viewportEnd + 1);
previousButton.hidden = !hasContentBefore;
nextButton.hidden = !hasContentAfter;
};
const scrollTabs = (direction) => {
tabList.scrollBy({
left: direction * Math.max(160, Math.floor(tabList.clientWidth * .72)),
behavior: "smooth"
});
};
previousButton.addEventListener("click", () => scrollTabs(-1));
nextButton.addEventListener("click", () => scrollTabs(1));
tabList.addEventListener("scroll", updateNavigation, { passive: true });
const handleResize = () => {
if (!navigation.isConnected) {
window.removeEventListener("resize", handleResize);
return;
}
updateNavigation();
};
window.addEventListener("resize", handleResize, { passive: true });
requestAnimationFrame(() => requestAnimationFrame(updateNavigation));
setTimeout(updateNavigation, 0);
navigation.append(previousButton, tabList, nextButton);
return navigation;
}
function createAccountPlansTabNavigationButton(direction) {
const isPrevious = direction === "previous";
const button = document.createElement("button");
button.type = "button";
button.className = "opportunities-extension-account-plans-tab-navigation-button";
button.setAttribute("aria-label", isPrevious ? "Show previous account plans" : "Show next account plans");
button.title = isPrevious ? "Previous plans" : "Next plans";
const icon = createSvgElement("svg", {
viewBox: "0 0 24 24",
"aria-hidden": "true",
focusable: "false"
});
icon.append(createSvgElement("path", {
d: isPrevious ? "m14.5 5-7 7 7 7" : "m9.5 5 7 7-7 7",
fill: "none",
stroke: "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": "2"
}));
button.append(icon);
return button;
}
function createAccountPlansSkeleton() {
const skeleton = document.createElement("div");
skeleton.className = "opportunities-extension-account-plans-skeleton";
@@ -2211,12 +2281,15 @@
const tabs = document.createElement("div");
tabs.className = "opportunities-extension-account-plans-skeleton-tabs";
[96, 124, 108].forEach((width) => {
const tab = document.createElement("span");
tab.className = "opportunities-extension-account-plans-skeleton-bar";
tab.style.width = `${width}px`;
tabs.append(tab);
});
const tab = document.createElement("span");
tab.className = "opportunities-extension-account-plans-skeleton-tab";
const status = document.createElement("span");
status.className = "opportunities-extension-account-plans-skeleton-status";
const tabLabel = document.createElement("span");
tabLabel.className = "opportunities-extension-account-plans-skeleton-bar";
tabLabel.style.width = "104px";
tab.append(status, tabLabel);
tabs.append(tab);
const details = document.createElement("div");
details.className = "opportunities-extension-account-plans-skeleton-details";
@@ -2231,7 +2304,57 @@
details.append(row);
});
skeleton.append(announcement, tabs, details);
const trend = document.createElement("div");
trend.className = "opportunities-extension-account-plans-skeleton-trend";
const trendHeader = document.createElement("div");
trendHeader.className = "opportunities-extension-account-plans-skeleton-trend-header";
const periods = document.createElement("div");
periods.className = "opportunities-extension-account-plans-skeleton-periods";
[58, 52, 56, 38].forEach((width, index) => {
const period = document.createElement("span");
period.className = "opportunities-extension-account-plans-skeleton-bar";
period.style.width = `${width}px`;
if (index === 2) period.classList.add("opportunities-extension-account-plans-skeleton-period-active");
periods.append(period);
});
trendHeader.append(periods);
const navigation = document.createElement("div");
navigation.className = "opportunities-extension-account-plans-skeleton-navigation";
[32, 124, 32].forEach((width) => {
const item = document.createElement("span");
item.className = "opportunities-extension-account-plans-skeleton-bar";
item.style.width = `${width}px`;
navigation.append(item);
});
const chart = document.createElement("div");
chart.className = "opportunities-extension-account-plans-skeleton-chart";
const plot = document.createElement("div");
plot.className = "opportunities-extension-account-plans-skeleton-plot";
[34, 42, 38, 48, 44, 58, 64, 55, 76, 68, 86, 62].forEach((height) => {
const bar = document.createElement("span");
bar.className = "opportunities-extension-account-plans-skeleton-bar";
bar.style.height = `${height}%`;
plot.append(bar);
});
const legend = document.createElement("div");
legend.className = "opportunities-extension-account-plans-skeleton-legend";
[112, 68, 132].forEach((width) => {
const item = document.createElement("span");
item.className = "opportunities-extension-account-plans-skeleton-bar";
item.style.width = `${width}px`;
legend.append(item);
});
chart.append(plot, legend);
trend.append(trendHeader, navigation, chart);
const panel = document.createElement("div");
panel.className = "opportunities-extension-account-plan-panel opportunities-extension-account-plans-skeleton-panel";
panel.append(details, trend);
skeleton.append(announcement, tabs, panel);
return skeleton;
}
@@ -4706,7 +4829,7 @@
border-left: 5px solid transparent;
content: "";
pointer-events: none;
transform: translateY(-35%);
transform: translateY(-50%);
}
.opportunities-extension-customer-filter-trigger:disabled {
@@ -4866,7 +4989,7 @@
border-top: 6px solid #000000;
content: "";
pointer-events: none;
transform: translateY(-35%);
transform: translateY(-50%);
}
.opportunities-extension-field select {
@@ -5446,7 +5569,9 @@
display: grid;
width: min(1180px, 100%);
height: min(760px, calc(100vh - 24px));
min-width: 0;
grid-template-rows: auto minmax(0, 1fr);
grid-template-columns: minmax(0, 1fr);
overflow: hidden;
border: 1px solid #8f8a85;
border-radius: 4px;
@@ -5479,17 +5604,33 @@
.opportunities-extension-account-plans-content {
display: grid;
min-width: 0;
grid-template-rows: auto minmax(0, 1fr);
grid-template-columns: minmax(0, 1fr);
min-height: 0;
overflow: hidden;
}
.opportunities-extension-account-plans-content > .opportunities-extension-account-plans-skeleton {
grid-row: 1 / -1;
}
.opportunities-extension-account-plans-tabs-navigation {
position: relative;
min-width: 0;
border-bottom: 1px solid #dedbd7;
}
.opportunities-extension-account-plans-tabs-navigation .opportunities-extension-account-plans-tab-navigation-button[hidden] {
display: none;
}
.opportunities-extension-account-plans-tabs {
display: flex;
min-width: 0;
gap: 4px;
overflow-x: auto;
padding: 8px 20px 0;
border-bottom: 1px solid #dedbd7;
scrollbar-width: none;
}
@@ -5497,7 +5638,56 @@
display: none;
}
.opportunities-extension-account-plans-tab-navigation-button {
display: grid;
position: absolute;
z-index: 1;
top: 50%;
width: 32px;
height: 32px;
place-items: center;
padding: 0;
border: 1px solid #8f8a85;
border-radius: 4px;
background: #ffffff;
color: #312d2a;
box-shadow: 0 2px 6px rgba(0, 0, 0, .16);
transform: translateY(-50%);
cursor: pointer;
}
.opportunities-extension-account-plans-tab-navigation-button:first-child {
left: 8px;
}
.opportunities-extension-account-plans-tab-navigation-button:last-child {
right: 8px;
}
.opportunities-extension-account-plans-tab-navigation-button:hover:not(:disabled) {
border-color: #00758f;
background: #f0eeeb;
color: #005e73;
}
.opportunities-extension-account-plans-tab-navigation-button:disabled {
border-color: #dedbd7;
color: #b8b2ad;
cursor: default;
}
.opportunities-extension-account-plans-tab-navigation-button:focus-visible {
outline: 2px solid #00758f;
outline-offset: 2px;
}
.opportunities-extension-account-plans-tab-navigation-button svg {
width: 18px;
height: 18px;
}
.opportunities-extension-account-plans-panels {
min-width: 0;
min-height: 0;
overflow: hidden;
}
@@ -5923,26 +6113,58 @@
.opportunities-extension-account-plans-skeleton-tabs {
display: flex;
gap: 16px;
min-height: 51px;
min-height: 46px;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid #dedbd7;
}
.opportunities-extension-account-plans-skeleton {
display: grid;
height: 100%;
min-height: 0;
grid-template-rows: auto minmax(0, 1fr);
overflow: hidden;
}
.opportunities-extension-account-plans-skeleton-panel {
padding: 16px 20px;
}
.opportunities-extension-account-plans-skeleton-tab {
display: inline-flex;
align-items: center;
gap: 9px;
min-height: 38px;
padding: 0 12px;
border-bottom: 3px solid #00758f;
}
.opportunities-extension-account-plans-skeleton-status {
display: block;
width: 10px;
height: 10px;
flex: 0 0 auto;
border-radius: 50%;
background: #dedbd7;
box-shadow: 0 0 0 3px #efedeb;
animation: opportunities-extension-skeleton-pulse 1.15s ease-in-out infinite alternate;
}
.opportunities-extension-account-plans-skeleton-details {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0 28px;
padding: 11px 20px 28px;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0 24px;
padding: 0;
}
.opportunities-extension-account-plans-skeleton-details > div {
display: grid;
grid-template-columns: minmax(110px, 35%) minmax(0, 1fr);
grid-template-columns: minmax(104px, 38%) minmax(0, 1fr);
align-items: center;
gap: 14px;
min-height: 52px;
gap: 10px;
min-height: 42px;
padding: 7px 0;
border-bottom: 1px solid #ebe8e5;
}
@@ -5963,6 +6185,111 @@
height: 10px;
}
.opportunities-extension-account-plans-skeleton-trend {
display: grid;
min-height: 0;
grid-template-rows: auto auto minmax(0, 1fr);
margin: 14px 0 0;
padding-top: 12px;
border-top: 1px solid #dedbd7;
}
.opportunities-extension-account-plans-skeleton-trend-header {
display: flex;
justify-content: flex-end;
min-height: 38px;
}
.opportunities-extension-account-plans-skeleton-periods {
display: flex;
align-items: center;
gap: 16px;
}
.opportunities-extension-account-plans-skeleton-periods .opportunities-extension-account-plans-skeleton-bar {
height: 12px;
}
.opportunities-extension-account-plans-skeleton-period-active {
height: 38px !important;
border: 1px solid #8cc8d3;
background: #e7f6f8;
}
.opportunities-extension-account-plans-skeleton-navigation {
display: grid;
grid-template-columns: 32px 124px 32px;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 54px;
}
.opportunities-extension-account-plans-skeleton-navigation .opportunities-extension-account-plans-skeleton-bar {
justify-self: center;
}
.opportunities-extension-account-plans-skeleton-chart {
display: grid;
min-height: 0;
grid-template-columns: minmax(0, 1fr) 150px;
align-items: center;
gap: 28px;
padding: 12px 14px 0 62px;
}
.opportunities-extension-account-plans-skeleton-plot {
display: flex;
position: relative;
height: 300px;
min-height: 0;
box-sizing: border-box;
align-items: flex-end;
justify-content: space-around;
gap: 12px;
padding: 0 14px 64px;
background: repeating-linear-gradient(to bottom, transparent 0, transparent 58px, #ebe8e5 58px, #ebe8e5 59px) left top / 100% 236px no-repeat;
}
.opportunities-extension-account-plans-skeleton-plot::before,
.opportunities-extension-account-plans-skeleton-plot::after {
position: absolute;
display: block;
content: "";
background: #dedbd7;
}
.opportunities-extension-account-plans-skeleton-plot::before {
top: 0;
bottom: 64px;
left: 0;
width: 1px;
}
.opportunities-extension-account-plans-skeleton-plot::after {
right: 0;
bottom: 64px;
left: 0;
height: 1px;
}
.opportunities-extension-account-plans-skeleton-plot .opportunities-extension-account-plans-skeleton-bar {
width: min(38px, 7%);
flex: 0 1 38px;
border-radius: 2px 2px 0 0;
}
.opportunities-extension-account-plans-skeleton-legend {
display: grid;
align-content: center;
gap: 18px;
min-height: 0;
}
.opportunities-extension-account-plans-skeleton-legend .opportunities-extension-account-plans-skeleton-bar {
height: 11px;
}
@keyframes opportunities-extension-skeleton-pulse {
from { opacity: .48; }
to { opacity: 1; }
@@ -6364,6 +6691,16 @@
background: #5b5753;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-status {
background: #5b5753;
box-shadow: 0 0 0 3px #3b3937;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-period-active {
border-color: #3f7580;
background: #23474e;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-chart-skeleton-plot span {
background: #5b5753;
}
@@ -6452,6 +6789,7 @@
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-preferences-header,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-preferences-footer,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-header,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tabs-navigation,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tabs,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plan-details > div,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-tabs,
@@ -6460,10 +6798,21 @@
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-trend,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-chart-skeleton-plot {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-chart-skeleton-plot,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-trend,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot {
border-color: #4e4a46;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot {
background: repeating-linear-gradient(to bottom, transparent 0, transparent 58px, #4e4a46 58px, #4e4a46 59px) left top / 100% 236px no-repeat;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot::before,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot::after {
background: #4e4a46;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plan-tab {
color: #c9c5c1;
}
@@ -6492,19 +6841,22 @@
color: #91e4ed;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tab-navigation-button {
border-color: #6a6560;
background: #333130;
color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:hover:not(:disabled) {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:hover:not(:disabled),
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tab-navigation-button:hover:not(:disabled) {
border-color: #43c4d5;
background: #3b3937;
color: #91e4ed;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:disabled {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:disabled,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tab-navigation-button:disabled {
border-color: #4e4a46;
color: #746f6a;
}
@@ -6940,7 +7292,46 @@
}
.opportunities-extension-account-plans-skeleton-details {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0 14px;
padding: 0;
}
.opportunities-extension-account-plans-skeleton-details > div {
grid-template-columns: 1fr;
align-content: center;
gap: 5px;
min-height: 54px;
padding: 5px 0;
}
.opportunities-extension-account-plans-skeleton-trend {
margin: 14px 0 0;
}
.opportunities-extension-account-plans-skeleton-periods {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
align-items: center;
gap: 6px;
}
.opportunities-extension-account-plans-skeleton-periods .opportunities-extension-account-plans-skeleton-bar {
width: 100% !important;
}
.opportunities-extension-account-plans-skeleton-chart {
grid-template-columns: minmax(0, 1fr);
gap: 12px;
padding: 12px 8px 0 34px;
}
.opportunities-extension-account-plans-skeleton-legend {
display: flex;
min-height: 24px;
align-items: center;
justify-content: center;
gap: 12px;
}
.opportunities-extension-consumption-trend-header {

View File

@@ -283,7 +283,7 @@
if (!sessionTab) {
try {
sessionTab = await openConsumerTokenSessionTab(sender);
sessionTab = await openConsumerTokenSessionTabWithRetry(sender, debugEntry);
if (debugEntry) {
debugEntry.metadata.sessionBootstrapUrl = CUSTOMER_TOKEN_SESSION_URL;
debugEntry.metadata.sessionBootstrapState = "opened-inactive-tab";
@@ -337,6 +337,36 @@
);
}
async function openConsumerTokenSessionTabWithRetry(sender, debugEntry) {
let attempt = 0;
// Opening failures must not consume the token service's five attempts.
while (true) {
attempt += 1;
if (debugEntry) {
debugEntry.metadata.sessionBootstrapUrl = CUSTOMER_TOKEN_SESSION_URL;
debugEntry.metadata.sessionTabOpenAttempt = String(attempt);
}
try {
const tab = await openConsumerTokenSessionTab(sender);
if (debugEntry) {
debugEntry.metadata.sessionBootstrapState = "opened-inactive-tab";
delete debugEntry.metadata.sessionTabNextRetryDelayMs;
}
return tab;
} catch (error) {
const retryDelayMs = getConsumerTokenRetryDelay(Math.min(attempt, 5));
if (debugEntry) {
debugEntry.metadata.sessionBootstrapState = "retrying-tab-open";
debugEntry.metadata.sessionTabLastOpenError = error.message || "unknown error";
debugEntry.metadata.sessionTabNextRetryDelayMs = String(retryDelayMs);
}
await wait(retryDelayMs);
}
}
}
function openConsumerTokenSessionTab(sender) {
const details = {
url: CUSTOMER_TOKEN_SESSION_URL,

View File

@@ -2132,6 +2132,8 @@
tabList.setAttribute("role", "tablist");
tabList.setAttribute("aria-label", "Account plans");
const tabsNavigation = createAccountPlansTabsNavigation(tabList);
const panels = document.createElement("div");
panels.className = "opportunities-extension-account-plans-panels";
@@ -2183,7 +2185,7 @@
panels.append(panel);
});
content.append(tabList, panels);
content.append(tabsNavigation, panels);
}
dialog.append(header, content);
@@ -2200,6 +2202,74 @@
}
}
function createAccountPlansTabsNavigation(tabList) {
const navigation = document.createElement("div");
navigation.className = "opportunities-extension-account-plans-tabs-navigation";
const previousButton = createAccountPlansTabNavigationButton("previous");
const nextButton = createAccountPlansTabNavigationButton("next");
const updateNavigation = () => {
const maximumScroll = Math.max(0, tabList.scrollWidth - tabList.clientWidth);
const tabs = Array.from(tabList.querySelectorAll("[role='tab']"));
const viewportStart = tabList.scrollLeft;
const viewportEnd = viewportStart + tabList.clientWidth;
const hasContentBefore = tabList.scrollLeft > 1
|| tabs.some((tab) => tab.offsetLeft < viewportStart - 1);
const hasContentAfter = tabList.scrollLeft < maximumScroll - 1
|| tabs.some((tab) => (tab.offsetLeft + tab.offsetWidth) > viewportEnd + 1);
previousButton.hidden = !hasContentBefore;
nextButton.hidden = !hasContentAfter;
};
const scrollTabs = (direction) => {
tabList.scrollBy({
left: direction * Math.max(160, Math.floor(tabList.clientWidth * .72)),
behavior: "smooth"
});
};
previousButton.addEventListener("click", () => scrollTabs(-1));
nextButton.addEventListener("click", () => scrollTabs(1));
tabList.addEventListener("scroll", updateNavigation, { passive: true });
const handleResize = () => {
if (!navigation.isConnected) {
window.removeEventListener("resize", handleResize);
return;
}
updateNavigation();
};
window.addEventListener("resize", handleResize, { passive: true });
requestAnimationFrame(() => requestAnimationFrame(updateNavigation));
setTimeout(updateNavigation, 0);
navigation.append(previousButton, tabList, nextButton);
return navigation;
}
function createAccountPlansTabNavigationButton(direction) {
const isPrevious = direction === "previous";
const button = document.createElement("button");
button.type = "button";
button.className = "opportunities-extension-account-plans-tab-navigation-button";
button.setAttribute("aria-label", isPrevious ? "Show previous account plans" : "Show next account plans");
button.title = isPrevious ? "Previous plans" : "Next plans";
const icon = createSvgElement("svg", {
viewBox: "0 0 24 24",
"aria-hidden": "true",
focusable: "false"
});
icon.append(createSvgElement("path", {
d: isPrevious ? "m14.5 5-7 7 7 7" : "m9.5 5 7 7-7 7",
fill: "none",
stroke: "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": "2"
}));
button.append(icon);
return button;
}
function createAccountPlansSkeleton() {
const skeleton = document.createElement("div");
skeleton.className = "opportunities-extension-account-plans-skeleton";
@@ -2211,12 +2281,15 @@
const tabs = document.createElement("div");
tabs.className = "opportunities-extension-account-plans-skeleton-tabs";
[96, 124, 108].forEach((width) => {
const tab = document.createElement("span");
tab.className = "opportunities-extension-account-plans-skeleton-bar";
tab.style.width = `${width}px`;
tabs.append(tab);
});
const tab = document.createElement("span");
tab.className = "opportunities-extension-account-plans-skeleton-tab";
const status = document.createElement("span");
status.className = "opportunities-extension-account-plans-skeleton-status";
const tabLabel = document.createElement("span");
tabLabel.className = "opportunities-extension-account-plans-skeleton-bar";
tabLabel.style.width = "104px";
tab.append(status, tabLabel);
tabs.append(tab);
const details = document.createElement("div");
details.className = "opportunities-extension-account-plans-skeleton-details";
@@ -2231,7 +2304,57 @@
details.append(row);
});
skeleton.append(announcement, tabs, details);
const trend = document.createElement("div");
trend.className = "opportunities-extension-account-plans-skeleton-trend";
const trendHeader = document.createElement("div");
trendHeader.className = "opportunities-extension-account-plans-skeleton-trend-header";
const periods = document.createElement("div");
periods.className = "opportunities-extension-account-plans-skeleton-periods";
[58, 52, 56, 38].forEach((width, index) => {
const period = document.createElement("span");
period.className = "opportunities-extension-account-plans-skeleton-bar";
period.style.width = `${width}px`;
if (index === 2) period.classList.add("opportunities-extension-account-plans-skeleton-period-active");
periods.append(period);
});
trendHeader.append(periods);
const navigation = document.createElement("div");
navigation.className = "opportunities-extension-account-plans-skeleton-navigation";
[32, 124, 32].forEach((width) => {
const item = document.createElement("span");
item.className = "opportunities-extension-account-plans-skeleton-bar";
item.style.width = `${width}px`;
navigation.append(item);
});
const chart = document.createElement("div");
chart.className = "opportunities-extension-account-plans-skeleton-chart";
const plot = document.createElement("div");
plot.className = "opportunities-extension-account-plans-skeleton-plot";
[34, 42, 38, 48, 44, 58, 64, 55, 76, 68, 86, 62].forEach((height) => {
const bar = document.createElement("span");
bar.className = "opportunities-extension-account-plans-skeleton-bar";
bar.style.height = `${height}%`;
plot.append(bar);
});
const legend = document.createElement("div");
legend.className = "opportunities-extension-account-plans-skeleton-legend";
[112, 68, 132].forEach((width) => {
const item = document.createElement("span");
item.className = "opportunities-extension-account-plans-skeleton-bar";
item.style.width = `${width}px`;
legend.append(item);
});
chart.append(plot, legend);
trend.append(trendHeader, navigation, chart);
const panel = document.createElement("div");
panel.className = "opportunities-extension-account-plan-panel opportunities-extension-account-plans-skeleton-panel";
panel.append(details, trend);
skeleton.append(announcement, tabs, panel);
return skeleton;
}
@@ -4706,7 +4829,7 @@
border-left: 5px solid transparent;
content: "";
pointer-events: none;
transform: translateY(-35%);
transform: translateY(-50%);
}
.opportunities-extension-customer-filter-trigger:disabled {
@@ -4866,7 +4989,7 @@
border-top: 6px solid #000000;
content: "";
pointer-events: none;
transform: translateY(-35%);
transform: translateY(-50%);
}
.opportunities-extension-field select {
@@ -5446,7 +5569,9 @@
display: grid;
width: min(1180px, 100%);
height: min(760px, calc(100vh - 24px));
min-width: 0;
grid-template-rows: auto minmax(0, 1fr);
grid-template-columns: minmax(0, 1fr);
overflow: hidden;
border: 1px solid #8f8a85;
border-radius: 4px;
@@ -5479,17 +5604,33 @@
.opportunities-extension-account-plans-content {
display: grid;
min-width: 0;
grid-template-rows: auto minmax(0, 1fr);
grid-template-columns: minmax(0, 1fr);
min-height: 0;
overflow: hidden;
}
.opportunities-extension-account-plans-content > .opportunities-extension-account-plans-skeleton {
grid-row: 1 / -1;
}
.opportunities-extension-account-plans-tabs-navigation {
position: relative;
min-width: 0;
border-bottom: 1px solid #dedbd7;
}
.opportunities-extension-account-plans-tabs-navigation .opportunities-extension-account-plans-tab-navigation-button[hidden] {
display: none;
}
.opportunities-extension-account-plans-tabs {
display: flex;
min-width: 0;
gap: 4px;
overflow-x: auto;
padding: 8px 20px 0;
border-bottom: 1px solid #dedbd7;
scrollbar-width: none;
}
@@ -5497,7 +5638,56 @@
display: none;
}
.opportunities-extension-account-plans-tab-navigation-button {
display: grid;
position: absolute;
z-index: 1;
top: 50%;
width: 32px;
height: 32px;
place-items: center;
padding: 0;
border: 1px solid #8f8a85;
border-radius: 4px;
background: #ffffff;
color: #312d2a;
box-shadow: 0 2px 6px rgba(0, 0, 0, .16);
transform: translateY(-50%);
cursor: pointer;
}
.opportunities-extension-account-plans-tab-navigation-button:first-child {
left: 8px;
}
.opportunities-extension-account-plans-tab-navigation-button:last-child {
right: 8px;
}
.opportunities-extension-account-plans-tab-navigation-button:hover:not(:disabled) {
border-color: #00758f;
background: #f0eeeb;
color: #005e73;
}
.opportunities-extension-account-plans-tab-navigation-button:disabled {
border-color: #dedbd7;
color: #b8b2ad;
cursor: default;
}
.opportunities-extension-account-plans-tab-navigation-button:focus-visible {
outline: 2px solid #00758f;
outline-offset: 2px;
}
.opportunities-extension-account-plans-tab-navigation-button svg {
width: 18px;
height: 18px;
}
.opportunities-extension-account-plans-panels {
min-width: 0;
min-height: 0;
overflow: hidden;
}
@@ -5923,26 +6113,58 @@
.opportunities-extension-account-plans-skeleton-tabs {
display: flex;
gap: 16px;
min-height: 51px;
min-height: 46px;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid #dedbd7;
}
.opportunities-extension-account-plans-skeleton {
display: grid;
height: 100%;
min-height: 0;
grid-template-rows: auto minmax(0, 1fr);
overflow: hidden;
}
.opportunities-extension-account-plans-skeleton-panel {
padding: 16px 20px;
}
.opportunities-extension-account-plans-skeleton-tab {
display: inline-flex;
align-items: center;
gap: 9px;
min-height: 38px;
padding: 0 12px;
border-bottom: 3px solid #00758f;
}
.opportunities-extension-account-plans-skeleton-status {
display: block;
width: 10px;
height: 10px;
flex: 0 0 auto;
border-radius: 50%;
background: #dedbd7;
box-shadow: 0 0 0 3px #efedeb;
animation: opportunities-extension-skeleton-pulse 1.15s ease-in-out infinite alternate;
}
.opportunities-extension-account-plans-skeleton-details {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0 28px;
padding: 11px 20px 28px;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0 24px;
padding: 0;
}
.opportunities-extension-account-plans-skeleton-details > div {
display: grid;
grid-template-columns: minmax(110px, 35%) minmax(0, 1fr);
grid-template-columns: minmax(104px, 38%) minmax(0, 1fr);
align-items: center;
gap: 14px;
min-height: 52px;
gap: 10px;
min-height: 42px;
padding: 7px 0;
border-bottom: 1px solid #ebe8e5;
}
@@ -5963,6 +6185,111 @@
height: 10px;
}
.opportunities-extension-account-plans-skeleton-trend {
display: grid;
min-height: 0;
grid-template-rows: auto auto minmax(0, 1fr);
margin: 14px 0 0;
padding-top: 12px;
border-top: 1px solid #dedbd7;
}
.opportunities-extension-account-plans-skeleton-trend-header {
display: flex;
justify-content: flex-end;
min-height: 38px;
}
.opportunities-extension-account-plans-skeleton-periods {
display: flex;
align-items: center;
gap: 16px;
}
.opportunities-extension-account-plans-skeleton-periods .opportunities-extension-account-plans-skeleton-bar {
height: 12px;
}
.opportunities-extension-account-plans-skeleton-period-active {
height: 38px !important;
border: 1px solid #8cc8d3;
background: #e7f6f8;
}
.opportunities-extension-account-plans-skeleton-navigation {
display: grid;
grid-template-columns: 32px 124px 32px;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 54px;
}
.opportunities-extension-account-plans-skeleton-navigation .opportunities-extension-account-plans-skeleton-bar {
justify-self: center;
}
.opportunities-extension-account-plans-skeleton-chart {
display: grid;
min-height: 0;
grid-template-columns: minmax(0, 1fr) 150px;
align-items: center;
gap: 28px;
padding: 12px 14px 0 62px;
}
.opportunities-extension-account-plans-skeleton-plot {
display: flex;
position: relative;
height: 300px;
min-height: 0;
box-sizing: border-box;
align-items: flex-end;
justify-content: space-around;
gap: 12px;
padding: 0 14px 64px;
background: repeating-linear-gradient(to bottom, transparent 0, transparent 58px, #ebe8e5 58px, #ebe8e5 59px) left top / 100% 236px no-repeat;
}
.opportunities-extension-account-plans-skeleton-plot::before,
.opportunities-extension-account-plans-skeleton-plot::after {
position: absolute;
display: block;
content: "";
background: #dedbd7;
}
.opportunities-extension-account-plans-skeleton-plot::before {
top: 0;
bottom: 64px;
left: 0;
width: 1px;
}
.opportunities-extension-account-plans-skeleton-plot::after {
right: 0;
bottom: 64px;
left: 0;
height: 1px;
}
.opportunities-extension-account-plans-skeleton-plot .opportunities-extension-account-plans-skeleton-bar {
width: min(38px, 7%);
flex: 0 1 38px;
border-radius: 2px 2px 0 0;
}
.opportunities-extension-account-plans-skeleton-legend {
display: grid;
align-content: center;
gap: 18px;
min-height: 0;
}
.opportunities-extension-account-plans-skeleton-legend .opportunities-extension-account-plans-skeleton-bar {
height: 11px;
}
@keyframes opportunities-extension-skeleton-pulse {
from { opacity: .48; }
to { opacity: 1; }
@@ -6364,6 +6691,16 @@
background: #5b5753;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-status {
background: #5b5753;
box-shadow: 0 0 0 3px #3b3937;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-period-active {
border-color: #3f7580;
background: #23474e;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-chart-skeleton-plot span {
background: #5b5753;
}
@@ -6452,6 +6789,7 @@
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-preferences-header,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-preferences-footer,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-header,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tabs-navigation,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tabs,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plan-details > div,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-tabs,
@@ -6460,10 +6798,21 @@
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-trend,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-chart-skeleton-plot {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-chart-skeleton-plot,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-trend,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot {
border-color: #4e4a46;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot {
background: repeating-linear-gradient(to bottom, transparent 0, transparent 58px, #4e4a46 58px, #4e4a46 59px) left top / 100% 236px no-repeat;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot::before,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-skeleton-plot::after {
background: #4e4a46;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plan-tab {
color: #c9c5c1;
}
@@ -6492,19 +6841,22 @@
color: #91e4ed;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tab-navigation-button {
border-color: #6a6560;
background: #333130;
color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:hover:not(:disabled) {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:hover:not(:disabled),
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tab-navigation-button:hover:not(:disabled) {
border-color: #43c4d5;
background: #3b3937;
color: #91e4ed;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:disabled {
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-consumption-navigation-button:disabled,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-account-plans-tab-navigation-button:disabled {
border-color: #4e4a46;
color: #746f6a;
}
@@ -6940,7 +7292,46 @@
}
.opportunities-extension-account-plans-skeleton-details {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0 14px;
padding: 0;
}
.opportunities-extension-account-plans-skeleton-details > div {
grid-template-columns: 1fr;
align-content: center;
gap: 5px;
min-height: 54px;
padding: 5px 0;
}
.opportunities-extension-account-plans-skeleton-trend {
margin: 14px 0 0;
}
.opportunities-extension-account-plans-skeleton-periods {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
align-items: center;
gap: 6px;
}
.opportunities-extension-account-plans-skeleton-periods .opportunities-extension-account-plans-skeleton-bar {
width: 100% !important;
}
.opportunities-extension-account-plans-skeleton-chart {
grid-template-columns: minmax(0, 1fr);
gap: 12px;
padding: 12px 8px 0 34px;
}
.opportunities-extension-account-plans-skeleton-legend {
display: flex;
min-height: 24px;
align-items: center;
justify-content: center;
gap: 12px;
}
.opportunities-extension-consumption-trend-header {