feat(web): improve login error layout and add Terms of Service link

- Improve backend connection error display with bordered container,
  inline icon, and better visual hierarchy
- Extract actual error message from axios response object
- Add Terms of Service link (https://langbot.app/terms) to login footer
- Add termsOfService i18n key for all 7 locales
This commit is contained in:
Junyan Qin
2026-04-17 17:28:37 +08:00
committed by WangCham
parent b2ae4a6a82
commit 3340e984ed
8 changed files with 42 additions and 12 deletions

View File

@@ -75,9 +75,18 @@ export default function Login() {
// Also check if already logged in
checkIfAlreadyLoggedIn();
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : t('common.loginLoadError');
setLoadError(errorMessage);
let detail = '';
if (err instanceof Error) {
detail = err.message;
} else if (
err &&
typeof err === 'object' &&
'msg' in err &&
typeof (err as Record<string, unknown>).msg === 'string'
) {
detail = (err as Record<string, unknown>).msg as string;
}
setLoadError(detail || t('common.loginLoadError'));
setLoading(false);
}
}
@@ -146,8 +155,8 @@ export default function Login() {
if (loadError) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-neutral-900">
<Card className="w-[375px] shadow-lg dark:shadow-white/10">
<CardHeader>
<Card className="w-[400px] shadow-lg dark:shadow-white/10">
<CardHeader className="pb-2">
<div className="flex justify-between items-center mb-6">
<ThemeToggle />
<LanguageSelector />
@@ -161,20 +170,25 @@ export default function Login() {
{t('common.welcome')}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-col items-center gap-3 py-4">
<AlertCircle className="h-10 w-10 text-destructive" />
<p className="text-sm text-center text-muted-foreground">
<CardContent>
<div className="flex flex-col items-center gap-4 rounded-lg border border-destructive/20 bg-destructive/5 p-5">
<div className="flex items-center gap-2 text-destructive">
<AlertCircle className="h-5 w-5 shrink-0" />
<span className="text-sm font-medium">
{t('common.loginLoadError')}
</span>
</div>
<p className="text-sm text-center text-muted-foreground leading-relaxed">
{t('common.loginLoadErrorDesc')}
</p>
<code className="text-xs bg-muted px-3 py-2 rounded max-w-full overflow-x-auto block text-center text-muted-foreground">
<code className="text-xs bg-muted/80 px-3 py-2 rounded-md max-w-full overflow-x-auto block text-center text-muted-foreground/80 break-all">
{loadError}
</code>
<Button
onClick={handleRetry}
disabled={retrying}
variant="outline"
className="mt-2 cursor-pointer"
className="w-full cursor-pointer"
>
{retrying ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
@@ -348,6 +362,15 @@ export default function Login() {
<p className="text-xs text-center text-muted-foreground">
{t('common.agreementNotice')}{' '}
<a
href="https://langbot.app/terms"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground transition-colors"
>
{t('common.termsOfService')}
</a>
{'、'}
<a
href="https://langbot.app/privacy"
target="_blank"

View File

@@ -63,6 +63,7 @@ const enUS = {
test: 'Test',
forgotPassword: 'Forgot Password?',
agreementNotice: 'By continuing, you agree to our',
termsOfService: 'Terms of Service',
privacyPolicy: 'Privacy Policy',
and: 'and',
dataCollectionPolicy: 'Data Collection Policy',

View File

@@ -64,7 +64,8 @@ const esES = {
copyFailed: 'Error al copiar',
test: 'Probar',
forgotPassword: '¿Olvidaste tu contraseña?',
agreementNotice: 'Al continuar, aceptas nuestra',
agreementNotice: 'Al continuar, aceptas nuestros',
termsOfService: 'Términos de servicio',
privacyPolicy: 'Política de privacidad',
and: 'y',
dataCollectionPolicy: 'Política de recopilación de datos',

View File

@@ -64,6 +64,7 @@ const jaJP = {
test: 'テスト',
forgotPassword: 'パスワードを忘れた?',
agreementNotice: '続行することで、以下に同意したものとみなされます:',
termsOfService: '利用規約',
privacyPolicy: 'プライバシーポリシー',
and: 'および',
dataCollectionPolicy: 'データ収集ポリシー',

View File

@@ -63,6 +63,7 @@ const thTH = {
test: 'ทดสอบ',
forgotPassword: 'ลืมรหัสผ่าน?',
agreementNotice: 'การดำเนินการต่อแสดงว่าคุณยอมรับ',
termsOfService: 'ข้อกำหนดการให้บริการ',
privacyPolicy: 'นโยบายความเป็นส่วนตัว',
and: 'และ',
dataCollectionPolicy: 'นโยบายการเก็บรวบรวมข้อมูล',

View File

@@ -63,6 +63,7 @@ const viVN = {
test: 'Kiểm tra',
forgotPassword: 'Quên mật khẩu?',
agreementNotice: 'Bằng việc tiếp tục, bạn đồng ý với',
termsOfService: 'Điều khoản dịch vụ',
privacyPolicy: 'Chính sách bảo mật',
and: 'và',
dataCollectionPolicy: 'Chính sách thu thập dữ liệu',

View File

@@ -62,6 +62,7 @@ const zhHans = {
test: '测试',
forgotPassword: '忘记密码?',
agreementNotice: '继续即表示您同意我们的',
termsOfService: '服务条款',
privacyPolicy: '隐私政策',
and: '和',
dataCollectionPolicy: '数据收集政策',

View File

@@ -62,6 +62,7 @@ const zhHant = {
test: '測試',
forgotPassword: '忘記密碼?',
agreementNotice: '繼續即表示您同意我們的',
termsOfService: '服務條款',
privacyPolicy: '隱私政策',
and: '和',
dataCollectionPolicy: '數據收集政策',