From bf515ebc10cd6c2e0f7707221c1eaa052d40276f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 25 Mar 2026 22:33:09 +0000 Subject: [PATCH] feat(cloudcp): promote portal to Pulse Account Rebrand the hosted control-plane portal as Pulse Account and expose transitional self-hosted commercial services there so the account story stops reading as Cloud-only. --- internal/cloudcp/portal/handlers_test.go | 58 +++++++++++++++++++ internal/cloudcp/portal/page.go | 74 ++++++++++++++++++++---- 2 files changed, 120 insertions(+), 12 deletions(-) diff --git a/internal/cloudcp/portal/handlers_test.go b/internal/cloudcp/portal/handlers_test.go index 1240a784a..1429f305f 100644 --- a/internal/cloudcp/portal/handlers_test.go +++ b/internal/cloudcp/portal/handlers_test.go @@ -697,6 +697,64 @@ func TestPortalPageTemplate_ActorRolePassedToSection(t *testing.T) { } } +func TestPortalPageTemplate_AccountServicesRendered(t *testing.T) { + data := portalPageData{ + Nonce: "test-nonce", + Email: "owner@example.com", + PublicSiteURL: "https://pulserelay.pro", + SupportEmail: "support@pulserelay.pro", + Accounts: []portalPageAccount{ + { + ID: "a_test", + Kind: "cloud", + KindLabel: "Cloud", + Name: "Test Account", + }, + }, + } + + var buf strings.Builder + if err := portalPageTmpl.Execute(&buf, data); err != nil { + t.Fatalf("template execute: %v", err) + } + html := buf.String() + + mustContain := []string{ + "Pulse Account", + "Pulse Account", + "Other account services", + `href="https://pulserelay.pro/manage.html"`, + `href="https://pulserelay.pro/retrieve-license.html"`, + `href="https://pulserelay.pro/refund.html"`, + `href="https://pulserelay.pro/data.html"`, + "self-hosted commercial tools still live on the public Pulse surface today", + } + for _, needle := range mustContain { + if !strings.Contains(html, needle) { + t.Errorf("expected %q in rendered HTML", needle) + } + } +} + +func TestPortalLoginTemplate_UsesPulseAccountBranding(t *testing.T) { + var buf strings.Builder + if err := loginPageTmpl.Execute(&buf, struct{ Nonce string }{Nonce: "test-nonce"}); err != nil { + t.Fatalf("template execute: %v", err) + } + html := buf.String() + + mustContain := []string{ + "Pulse Account — Sign In", + "Pulse Account", + "Sign in to manage Cloud workspaces, MSP access, and commercial account services.", + } + for _, needle := range mustContain { + if !strings.Contains(html, needle) { + t.Errorf("expected %q in rendered HTML", needle) + } + } +} + func TestBillingPortalRedirect_NoReturnURL(t *testing.T) { reg := newTestRegistry(t) accountID, err := registry.GenerateAccountID() diff --git a/internal/cloudcp/portal/page.go b/internal/cloudcp/portal/page.go index 31401ebb4..efad0b8fe 100644 --- a/internal/cloudcp/portal/page.go +++ b/internal/cloudcp/portal/page.go @@ -34,17 +34,24 @@ type portalPageAccount struct { // portalPageData is passed to the portal HTML template. type portalPageData struct { - Nonce string - Email string - Accounts []portalPageAccount + Nonce string + Email string + PublicSiteURL string + SupportEmail string + Accounts []portalPageAccount } +const ( + defaultPublicSiteURL = "https://pulserelay.pro" + defaultSupportEmail = "support@pulserelay.pro" +) + var portalPageTmpl = template.Must(template.New("portal").Parse(` - Pulse Cloud — Portal + Pulse Account