diff --git a/internal/cloudcp/portal/assets/portal_services.js b/internal/cloudcp/portal/assets/portal_services.js index 3e909b7763..5a1eeca81c 100644 --- a/internal/cloudcp/portal/assets/portal_services.js +++ b/internal/cloudcp/portal/assets/portal_services.js @@ -3,8 +3,8 @@ var serviceState = { openPanelID: '', flows: { - manage: newVerificationFlowState(), - retrieve: newVerificationFlowState(), + manage: newVerificationFlowState(), + retrieve: newVerificationFlowState(), export: newVerificationFlowState(), delete: newVerificationFlowState(), }, @@ -22,6 +22,8 @@ step2Visible: false, status: emptyStatus(), result: null, + emailValue: '', + codeValue: '', }; } @@ -41,6 +43,19 @@ return document.getElementById(id); } + function escapeText(value) { + return String(value || '') + .replace(/&/g, '&') + .replace(//g, '>'); + } + + function escapeAttribute(value) { + return escapeText(value) + .replace(/"/g, '"') + .replace(/'/g, '''); + } + function readValue(id) { var el = getElement(id); return el ? el.value.trim() : ''; @@ -137,6 +152,9 @@ var flow = verificationFlows[flowID]; if (!flow) return; var flowState = serviceState.flows[flowID]; + if (flow.renderPanel) { + flow.renderPanel(flowState); + } renderButton(flow.requestButtonID, flowState.requesting, flowState.requesting ? flow.requestPendingLabel : flow.requestLabel); renderButton(flow.confirmButtonID, flowState.confirming, flowState.confirming ? flow.confirmPendingLabel : flow.confirmLabel); renderStatus(flow.statusID, flowState.status); @@ -164,7 +182,9 @@ function resetVerificationFlow(flowID) { var flow = verificationFlows[flowID]; if (!flow) return; + var previous = serviceState.flows[flowID]; serviceState.flows[flowID] = newVerificationFlowState(); + serviceState.flows[flowID].emailValue = previous.emailValue; if (flow.codeInputID) { setValue(flow.codeInputID, ''); } @@ -212,51 +232,66 @@ resendSuccessMessage: 'New verification code sent.', requestErrorMessage: 'Failed to send verification code', confirmErrorMessage: 'Failed to retrieve license', + readEmailValue: function() { + return serviceState.flows.retrieve.emailValue; + }, + readCodeValue: function() { + return serviceState.flows.retrieve.codeValue; + }, onRequestStart: function() { serviceState.flows.retrieve.result = null; }, onConfirmSuccess: function(data) { serviceState.flows.retrieve.result = data.license; + serviceState.flows.retrieve.codeValue = ''; setFlowStatus('retrieve', 'License retrieved successfully.', false); }, + renderPanel: function(flowState) { + var root = getElement('retrieve-service-root'); + if (!root) return; + var result = flowState.result; + var invoiceURL = result && result.invoice_url ? result.invoice_url : '#'; + var invoiceDisplay = result && result.invoice_url ? 'inline-block' : 'none'; + var copyDisplay = result ? 'inline-block' : 'none'; + var resultDisplay = result ? 'block' : 'none'; + root.innerHTML = '' + + '

Retrieve licenses

' + + '

Request a verification code for the commercial email, then reveal the current active self-hosted license without leaving Pulse Account.

' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + '
' + + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + '' + + 'View Invoice' + + '
' + + '
Use the latest active self-hosted license for this commercial email.
' + + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '
Plan
' + escapeText(result ? result.tier : '') + '
' + + '
Issued
' + escapeText(result ? new Date(result.issued_at).toLocaleString() : '') + '
' + + '
Expires
' + escapeText(result ? (result.expires_at ? new Date(result.expires_at).toLocaleString() : 'Does not expire') : '') + '
' + + '
Purchase Email
' + escapeText(result ? result.email : '') + '
' + + '
' + + '
'; + }, renderResult: function(result) { - setVisible('retrieve-inline-result', !!result); - if (!result) { - setValue('retrieve-inline-token', ''); - setText('retrieve-inline-tier', ''); - setText('retrieve-inline-issued', ''); - setText('retrieve-inline-expires', ''); - setText('retrieve-inline-email-value', ''); - var emptyInvoice = getElement('retrieve-inline-invoice'); - if (emptyInvoice) { - emptyInvoice.href = '#'; - emptyInvoice.style.display = 'none'; - } - var emptyCopy = getElement('retrieve-inline-copy'); - if (emptyCopy) { - emptyCopy.style.display = 'none'; - } - return; - } - setValue('retrieve-inline-token', result.token); - setText('retrieve-inline-tier', result.tier); - setText('retrieve-inline-issued', new Date(result.issued_at).toLocaleString()); - setText('retrieve-inline-expires', result.expires_at ? new Date(result.expires_at).toLocaleString() : 'Does not expire'); - setText('retrieve-inline-email-value', result.email); - var invoice = getElement('retrieve-inline-invoice'); - if (invoice) { - if (result.invoice_url) { - invoice.href = result.invoice_url; - invoice.style.display = 'inline-block'; - } else { - invoice.href = '#'; - invoice.style.display = 'none'; - } - } - var copy = getElement('retrieve-inline-copy'); - if (copy) { - copy.style.display = 'inline-block'; - } + void result; } }, export: { @@ -328,7 +363,7 @@ async function requestVerificationCode(flowID) { var flow = verificationFlows[flowID]; if (!flow) return; - var email = readValue(flow.emailInputID); + var email = flow.readEmailValue ? flow.readEmailValue() : readValue(flow.emailInputID); if (!email) { focusElement(flow.emailInputID); return; @@ -373,7 +408,7 @@ var flow = verificationFlows[flowID]; if (!flow) return; var email = serviceState.flows[flowID].pendingEmail; - var code = readValue(flow.codeInputID); + var code = flow.readCodeValue ? flow.readCodeValue() : readValue(flow.codeInputID); if (!email || !code) return; if (flow.beforeConfirm && flow.beforeConfirm() === false) { return; @@ -394,7 +429,7 @@ } async function copyRetrievedLicense() { - var token = getElement('retrieve-inline-token').value; + var token = serviceState.flows.retrieve.result ? serviceState.flows.retrieve.result.token : ''; if (!token) return; try { await navigator.clipboard.writeText(token); @@ -428,6 +463,7 @@ } renderOpenPanels(); + serviceState.flows.retrieve.emailValue = (runtime.getBootstrap ? ((runtime.getBootstrap() || {}).email || '') : ''); renderAllFlows(); document.addEventListener('click', function(event) { @@ -496,4 +532,20 @@ return; } }); + + document.addEventListener('input', function(event) { + var target = event.target; + if (!target) return; + var inputKind = target.getAttribute('data-account-service-input') || ''; + switch (inputKind) { + case 'retrieve-email': + serviceState.flows.retrieve.emailValue = target.value; + return; + case 'retrieve-code': + serviceState.flows.retrieve.codeValue = target.value; + return; + default: + return; + } + }); })(); diff --git a/internal/cloudcp/portal/handlers_test.go b/internal/cloudcp/portal/handlers_test.go index 6df28ea9f8..ebde40e675 100644 --- a/internal/cloudcp/portal/handlers_test.go +++ b/internal/cloudcp/portal/handlers_test.go @@ -806,12 +806,16 @@ func TestPortalPageTemplate_AccountServicesRendered(t *testing.T) { `data-account-service-action="data-delete-confirm"`, `id="manage-service-panel"`, `id="retrieve-service-panel"`, + `id="retrieve-service-root"`, `id="refund-service-panel"`, `id="data-service-panel"`, `var verificationFlows = {`, `var serviceState = {`, `renderOpenPanels();`, `renderAllFlows();`, + `data-account-service-input="retrieve-email"`, + `data-account-service-input="retrieve-code"`, + `renderPanel: function(flowState) {`, `requestVerificationCode('manage')`, `confirmVerificationCode('retrieve')`, `resendVerificationCode('export', event)`, diff --git a/internal/cloudcp/portal/templates/portal.html b/internal/cloudcp/portal/templates/portal.html index af32a44ddb..f07fc56285 100644 --- a/internal/cloudcp/portal/templates/portal.html +++ b/internal/cloudcp/portal/templates/portal.html @@ -166,49 +166,51 @@
-

Retrieve licenses

-

Request a verification code for the commercial email, then reveal the current active self-hosted license without leaving Pulse Account.

-
-
- - -
-
- -
-
- -
-