diff --git a/apps/compliance-portal/src/lib/auth/continueUrl.ts b/apps/compliance-portal/src/lib/auth/continueUrl.ts index 46833aaea..5a23a5c7d 100644 --- a/apps/compliance-portal/src/lib/auth/continueUrl.ts +++ b/apps/compliance-portal/src/lib/auth/continueUrl.ts @@ -20,7 +20,6 @@ import { FullNameRequiredError, NDASignatureRequiredError } from "@probo/relay"; -import { getPathPrefix } from "#/lib/http/pathPrefix"; import { localizedPath, resolveUrlLocale, type UrlLocale } from "#/lib/i18n/locale"; // Markers appended to a post-auth `continue` URL so the portal fires the pending @@ -38,12 +37,11 @@ export const NEW_REQUEST_PARAM = "new-request"; export const SUBSCRIBE_PARAM = "subscribe"; // Validates a `continue` target before we navigate to it. Only same-origin URLs -// under the portal's path prefix are accepted; anything else falls back to the -// portal home, so a crafted `?continue=` can never bounce the user off-site. +// are accepted; anything else falls back to the portal home, so a crafted +// `?continue=` can never bounce the user off-site. export function getSafeContinueUrl(param: string | null | undefined): string { - const prefix = getPathPrefix(); const localeHome = localizedPath(resolveUrlLocale(), "/"); - const fallback = window.location.origin + (prefix || "") + localeHome; + const fallback = window.location.origin + localeHome; if (!param) { return fallback; @@ -51,10 +49,7 @@ export function getSafeContinueUrl(param: string | null | undefined): string { try { const url = new URL(param, window.location.origin); - const underPrefix = prefix === "" - ? url.pathname.startsWith("/") - : url.pathname === prefix || url.pathname.startsWith(`${prefix}/`); - if (url.origin === window.location.origin && underPrefix) { + if (url.origin === window.location.origin) { return window.location.origin + url.pathname + url.search; } } catch { diff --git a/apps/compliance-portal/src/lib/http/pathPrefix.ts b/apps/compliance-portal/src/lib/http/pathPrefix.ts deleted file mode 100644 index b7f8b6fef..000000000 --- a/apps/compliance-portal/src/lib/http/pathPrefix.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { matchPath } from "react-router"; - -export function getPathPrefix() { - const match = matchPath( - { path: "/trust/:id", caseSensitive: false, end: false }, - window.location.pathname, - ); - - let prefix = ""; - if (match) { - prefix = `/trust/${match.params.id}`; - } - - return prefix; -} diff --git a/apps/compliance-portal/src/routes.tsx b/apps/compliance-portal/src/routes.tsx index 2c7399e9f..5d8cbd228 100644 --- a/apps/compliance-portal/src/routes.tsx +++ b/apps/compliance-portal/src/routes.tsx @@ -25,7 +25,6 @@ import { createBrowserRouter, redirect } from "react-router"; import { PageErrorBoundary } from "#/components/errors/PageErrorBoundary"; import { RootErrorBoundary } from "#/components/errors/RootErrorBoundary"; -import { getPathPrefix } from "#/lib/http/pathPrefix"; import { localeLayoutLoader } from "#/lib/i18n/localeRedirect"; import { resolveUrlLocale } from "#/lib/i18n/locale"; import { authRoutes } from "#/pages/auth/routes"; @@ -89,7 +88,7 @@ const routes = [ ...ndaRoutes, ], }, - // Bare basename root (no locale segment) → guessed locale home. + // Bare root (no locale segment) → guessed locale home. { path: "/", loader: () => { @@ -100,8 +99,5 @@ const routes = [ }, ] satisfies AppRoute[]; -// The portal is served under a /trust/{slug} path prefix (or a bare custom -// domain). Match the router basename to that prefix so the routes resolve. -export const router = createBrowserRouter(routes.map(routeFromAppRoute), { - basename: getPathPrefix() || "/", -}); +// Host-routed portal (slug subdomain / custom domain); paths are site-root. +export const router = createBrowserRouter(routes.map(routeFromAppRoute)); diff --git a/pkg/server/api/complianceportal/v1/seo.go b/pkg/server/api/complianceportal/v1/seo.go index 236b5f128..bf41026ab 100644 --- a/pkg/server/api/complianceportal/v1/seo.go +++ b/pkg/server/api/complianceportal/v1/seo.go @@ -35,9 +35,14 @@ const defaultCompliancePortalLocale = "en" // SEOFromRequest derives html lang, a self-referencing canonical URL, and // hreflang alternates (including x-default → English) for the SPA shell. +// Portals are host-routed (slug subdomain / custom domain); the request path +// is already relative to the portal root. func SEOFromRequest(r *http.Request, pageBaseURL string) (htmlLang, canonical string, hreflang []HreflangLink) { - appPath := complianceAppPath(r.URL.Path) - locale, rest := splitLocaleFromAppPath(appPath) + pathname := r.URL.Path + if pathname == "" { + pathname = "/" + } + locale, rest := splitLocaleFromAppPath(pathname) htmlLang = locale canonical = localizedPageURL(pageBaseURL, locale, rest) @@ -57,26 +62,6 @@ func SEOFromRequest(r *http.Request, pageBaseURL string) (htmlLang, canonical st return htmlLang, canonical, hreflang } -// complianceAppPath returns the path relative to the portal root: under -// /trust/:slug it strips that prefix; on a custom domain it returns the path as-is. -func complianceAppPath(pathname string) string { - trimmed := strings.TrimPrefix(pathname, "/") - if strings.HasPrefix(trimmed, "trust/") { - parts := strings.SplitN(trimmed, "/", 3) - if len(parts) < 2 { - return "/" - } - if len(parts) == 2 { - return "/" - } - return "/" + parts[2] - } - if pathname == "" { - return "/" - } - return pathname -} - func splitLocaleFromAppPath(appPath string) (locale, rest string) { segments := strings.Split(strings.Trim(appPath, "/"), "/") if len(segments) == 0 || segments[0] == "" { @@ -91,7 +76,7 @@ func splitLocaleFromAppPath(appPath string) (locale, rest string) { return locale, "/" + strings.Join(segments[1:], "/") } - // Unprefixed legacy path — treat content path as-is; default lang for tags. + // Unprefixed path — treat content path as-is; default lang for tags. return defaultCompliancePortalLocale, appPath } diff --git a/pkg/server/api/complianceportal/v1/seo_test.go b/pkg/server/api/complianceportal/v1/seo_test.go index 2aebf4578..55d6ac346 100644 --- a/pkg/server/api/complianceportal/v1/seo_test.go +++ b/pkg/server/api/complianceportal/v1/seo_test.go @@ -32,12 +32,19 @@ import ( func TestSEOFromRequest(t *testing.T) { t.Parallel() - req, err := http.NewRequest(http.MethodGet, "https://app.example.com/trust/acme/fr/documents", nil) + req, err := http.NewRequest( + http.MethodGet, + "https://acme.probopage.localhost/fr/documents", + nil, + ) require.NoError(t, err) - lang, canonical, hreflang := complianceportal_v1.SEOFromRequest(req, "https://app.example.com/trust/acme") + lang, canonical, hreflang := complianceportal_v1.SEOFromRequest( + req, + "https://acme.probopage.localhost", + ) assert.Equal(t, "fr", lang) - assert.Equal(t, "https://app.example.com/trust/acme/fr/documents", canonical) + assert.Equal(t, "https://acme.probopage.localhost/fr/documents", canonical) require.NotEmpty(t, hreflang) var xDefault string @@ -50,6 +57,6 @@ func TestSEOFromRequest(t *testing.T) { enHref = link.Href } } - assert.Equal(t, "https://app.example.com/trust/acme/en/documents", enHref) + assert.Equal(t, "https://acme.probopage.localhost/en/documents", enHref) assert.Equal(t, enHref, xDefault) }