diff --git a/web/src/app/RootLayout.tsx b/web/src/app/RootLayout.tsx
index 3a5dffb12..444de4b4a 100644
--- a/web/src/app/RootLayout.tsx
+++ b/web/src/app/RootLayout.tsx
@@ -1,15 +1,9 @@
import { Outlet } from 'react-router-dom';
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
-import { BetaBanner } from '@/components/BetaBanner';
// Top-level route layout: drives the dynamic document title from the active
// route and renders the matched child route via .
export default function RootLayout() {
useDocumentTitle();
- return (
- <>
-
-
- >
- );
+ return ;
}
diff --git a/web/src/app/home/components/workspace-settings/cloud-plans.ts b/web/src/app/home/components/workspace-settings/cloud-plans.ts
index 18dd07c62..7f5cc2cf6 100644
--- a/web/src/app/home/components/workspace-settings/cloud-plans.ts
+++ b/web/src/app/home/components/workspace-settings/cloud-plans.ts
@@ -104,7 +104,7 @@ export function cloudPlanCheckoutURL(
): string {
const url = spaceURL(base, '/cloud');
url.search = new URLSearchParams({
- environment: 'beta',
+ environment: 'new',
step: 'plan',
workspace,
plan,
@@ -121,7 +121,7 @@ export function cloudSubscriptionManagementURL(
): string {
const url = spaceURL(base, '/cloud');
url.search = new URLSearchParams({
- environment: 'beta',
+ environment: 'new',
step: 'subscription',
workspace,
}).toString();
diff --git a/web/src/app/wizard/page.tsx b/web/src/app/wizard/page.tsx
index 0409568f0..d9bb49d10 100644
--- a/web/src/app/wizard/page.tsx
+++ b/web/src/app/wizard/page.tsx
@@ -1059,10 +1059,7 @@ export default function WizardPage() {
if (isLoading) {
return (
-
+
);
@@ -1076,10 +1073,7 @@ export default function WizardPage() {
];
return (
-
+
{/* Top bar: Skip button */}
diff --git a/web/src/components/BetaBanner.tsx b/web/src/components/BetaBanner.tsx
deleted file mode 100644
index 87e09a2fa..000000000
--- a/web/src/components/BetaBanner.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import { AlertCircle, X } from 'lucide-react';
-import { useState, useEffect, useRef } from 'react';
-import { Alert, AlertDescription } from '@/components/ui/alert';
-import { backendClient } from '@/app/infra/http';
-
-const RETRY_DELAYS_MS = [1000, 2000, 4000, 8000];
-const CLOUD_BETA_RETRY_LIMIT = RETRY_DELAYS_MS.length;
-
-export function BetaBanner() {
- const { t } = useTranslation();
- const bannerRef = useRef
(null);
- const [show, setShow] = useState(false);
- const [dismissed, setDismissed] = useState(false);
- const visible = show && !dismissed;
-
- // Publish the banner height so the fixed app shell (sidebar + inset) starts
- // below the notice instead of painting over it.
- useEffect(() => {
- const root = document.documentElement;
- if (!visible) {
- root.style.removeProperty('--beta-banner-height');
- return;
- }
- const element = bannerRef.current;
- if (!element) {
- return;
- }
- const sync = () => {
- root.style.setProperty(
- '--beta-banner-height',
- `${element.getBoundingClientRect().height}px`,
- );
- };
- sync();
- const observer = new ResizeObserver(sync);
- observer.observe(element);
- return () => {
- observer.disconnect();
- root.style.removeProperty('--beta-banner-height');
- };
- }, [visible]);
-
- useEffect(() => {
- let cancelled = false;
- let retryTimer: number | undefined;
-
- const checkBetaStatus = async (attempt = 0) => {
- try {
- const info = await backendClient.getSystemInfo();
- if (cancelled) {
- return;
- }
- setShow(info.deployment_mode === 'cloud' && info.beta === true);
- } catch {
- if (cancelled) {
- return;
- }
- // A single failed probe (cold start, restarting backend, flaky
- // network) must not hide the banner for the rest of the session, so
- // keep the last known state and retry with backoff.
- if (attempt < CLOUD_BETA_RETRY_LIMIT) {
- retryTimer = window.setTimeout(
- () => void checkBetaStatus(attempt + 1),
- RETRY_DELAYS_MS[attempt],
- );
- }
- }
- };
-
- void checkBetaStatus();
-
- const recheck = () => {
- if (!cancelled) {
- void checkBetaStatus();
- }
- };
- window.addEventListener('focus', recheck);
-
- return () => {
- cancelled = true;
- clearTimeout(retryTimer);
- window.removeEventListener('focus', recheck);
- };
- }, []);
-
- if (!visible) {
- return null;
- }
-
- // The cloud product page opens the dedicated (paid v1) environment tab;
- // space.langbot.app resolves the locale itself, so no path prefix applies.
- const cloudUrl = 'https://space.langbot.app/cloud?environment=dedicated';
- const ossUrl = 'https://github.com/langbot-app/LangBot';
-
- return (
-
-
-
-
-
- {t('beta_banner.message')}{' '}
-
- {t('beta_banner.cloud_link')}
- {' '}
- {t('beta_banner.or')}{' '}
-
- {t('beta_banner.oss_link')}
-
- {t('beta_banner.period')}
-
-
-
-
-
- );
-}
diff --git a/web/src/components/ui/sidebar.tsx b/web/src/components/ui/sidebar.tsx
index 3ef69ff30..d142aaf03 100644
--- a/web/src/components/ui/sidebar.tsx
+++ b/web/src/components/ui/sidebar.tsx
@@ -203,9 +203,7 @@ function SidebarProvider({
{
'--sidebar-width': `${sidebarWidth}px`,
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
- // Global notices (e.g. the beta banner) publish their height so
- // the shell starts below them instead of underneath.
- height: 'calc(100svh - var(--beta-banner-height, 0px))',
+ height: '100svh',
...style,
} as React.CSSProperties
}
@@ -302,8 +300,8 @@ function Sidebar({
{
])
assert.equal(m.exports.cloudPlanCurrencyForLanguage(locale), 'USD');
});
+
+test('subscription management links use the production environment', () => {
+ const url = new URL(
+ cloudSubscriptionManagementURL('https://space.example', 'w'),
+ );
+ assert.deepEqual(Object.fromEntries(url.searchParams), {
+ environment: 'new',
+ step: 'subscription',
+ workspace: 'w',
+ });
+});