fix(cloud): retire beta banner and use production subscription links

This commit is contained in:
RockChinQ
2026-10-15 22:50:24 +08:00
parent 978db1701b
commit aabcaedf15
14 changed files with 21 additions and 228 deletions
-136
View File
@@ -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<HTMLDivElement>(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 (
<Alert
ref={bannerRef}
className="relative z-30 rounded-none border-x-0 border-t-0 bg-amber-50 dark:bg-amber-950/20 border-amber-200 dark:border-amber-900/50 py-2"
>
<AlertCircle className="text-amber-600 dark:text-amber-500" />
<AlertDescription className="text-sm text-amber-900 dark:text-amber-100">
<div className="flex w-full items-start gap-3">
<span className="flex-1">
{t('beta_banner.message')}{' '}
<a
href={cloudUrl}
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-amber-700 dark:hover:text-amber-300 font-medium"
>
{t('beta_banner.cloud_link')}
</a>{' '}
{t('beta_banner.or')}{' '}
<a
href={ossUrl}
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-amber-700 dark:hover:text-amber-300 font-medium"
>
{t('beta_banner.oss_link')}
</a>
{t('beta_banner.period')}
</span>
<button
onClick={() => setDismissed(true)}
className="shrink-0 text-amber-600 dark:text-amber-500 hover:text-amber-800 dark:hover:text-amber-300 p-1 -mt-1 -mr-2"
aria-label={t('beta_banner.dismiss')}
>
<X className="h-4 w-4" />
</button>
</div>
</AlertDescription>
</Alert>
);
}
+3 -5
View File
@@ -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({
<div
data-slot="sidebar-container"
style={{
top: 'var(--beta-banner-height, 0px)',
height: 'calc(100svh - var(--beta-banner-height, 0px))',
top: 0,
height: '100svh',
}}
className={cn(
'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',