mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-24 02:57:13 +00:00
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:
+34
-11
@@ -75,9 +75,18 @@ export default function Login() {
|
|||||||
// Also check if already logged in
|
// Also check if already logged in
|
||||||
checkIfAlreadyLoggedIn();
|
checkIfAlreadyLoggedIn();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage =
|
let detail = '';
|
||||||
err instanceof Error ? err.message : t('common.loginLoadError');
|
if (err instanceof Error) {
|
||||||
setLoadError(errorMessage);
|
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);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,8 +155,8 @@ export default function Login() {
|
|||||||
if (loadError) {
|
if (loadError) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-neutral-900">
|
<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">
|
<Card className="w-[400px] shadow-lg dark:shadow-white/10">
|
||||||
<CardHeader>
|
<CardHeader className="pb-2">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<LanguageSelector />
|
<LanguageSelector />
|
||||||
@@ -161,20 +170,25 @@ export default function Login() {
|
|||||||
{t('common.welcome')}
|
{t('common.welcome')}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent>
|
||||||
<div className="flex flex-col items-center gap-3 py-4">
|
<div className="flex flex-col items-center gap-4 rounded-lg border border-destructive/20 bg-destructive/5 p-5">
|
||||||
<AlertCircle className="h-10 w-10 text-destructive" />
|
<div className="flex items-center gap-2 text-destructive">
|
||||||
<p className="text-sm text-center text-muted-foreground">
|
<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')}
|
{t('common.loginLoadErrorDesc')}
|
||||||
</p>
|
</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}
|
{loadError}
|
||||||
</code>
|
</code>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleRetry}
|
onClick={handleRetry}
|
||||||
disabled={retrying}
|
disabled={retrying}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="mt-2 cursor-pointer"
|
className="w-full cursor-pointer"
|
||||||
>
|
>
|
||||||
{retrying ? (
|
{retrying ? (
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<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">
|
<p className="text-xs text-center text-muted-foreground">
|
||||||
{t('common.agreementNotice')}{' '}
|
{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
|
<a
|
||||||
href="https://langbot.app/privacy"
|
href="https://langbot.app/privacy"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const enUS = {
|
|||||||
test: 'Test',
|
test: 'Test',
|
||||||
forgotPassword: 'Forgot Password?',
|
forgotPassword: 'Forgot Password?',
|
||||||
agreementNotice: 'By continuing, you agree to our',
|
agreementNotice: 'By continuing, you agree to our',
|
||||||
|
termsOfService: 'Terms of Service',
|
||||||
privacyPolicy: 'Privacy Policy',
|
privacyPolicy: 'Privacy Policy',
|
||||||
and: 'and',
|
and: 'and',
|
||||||
dataCollectionPolicy: 'Data Collection Policy',
|
dataCollectionPolicy: 'Data Collection Policy',
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ const esES = {
|
|||||||
copyFailed: 'Error al copiar',
|
copyFailed: 'Error al copiar',
|
||||||
test: 'Probar',
|
test: 'Probar',
|
||||||
forgotPassword: '¿Olvidaste tu contraseña?',
|
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',
|
privacyPolicy: 'Política de privacidad',
|
||||||
and: 'y',
|
and: 'y',
|
||||||
dataCollectionPolicy: 'Política de recopilación de datos',
|
dataCollectionPolicy: 'Política de recopilación de datos',
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ const jaJP = {
|
|||||||
test: 'テスト',
|
test: 'テスト',
|
||||||
forgotPassword: 'パスワードを忘れた?',
|
forgotPassword: 'パスワードを忘れた?',
|
||||||
agreementNotice: '続行することで、以下に同意したものとみなされます:',
|
agreementNotice: '続行することで、以下に同意したものとみなされます:',
|
||||||
|
termsOfService: '利用規約',
|
||||||
privacyPolicy: 'プライバシーポリシー',
|
privacyPolicy: 'プライバシーポリシー',
|
||||||
and: 'および',
|
and: 'および',
|
||||||
dataCollectionPolicy: 'データ収集ポリシー',
|
dataCollectionPolicy: 'データ収集ポリシー',
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const thTH = {
|
|||||||
test: 'ทดสอบ',
|
test: 'ทดสอบ',
|
||||||
forgotPassword: 'ลืมรหัสผ่าน?',
|
forgotPassword: 'ลืมรหัสผ่าน?',
|
||||||
agreementNotice: 'การดำเนินการต่อแสดงว่าคุณยอมรับ',
|
agreementNotice: 'การดำเนินการต่อแสดงว่าคุณยอมรับ',
|
||||||
|
termsOfService: 'ข้อกำหนดการให้บริการ',
|
||||||
privacyPolicy: 'นโยบายความเป็นส่วนตัว',
|
privacyPolicy: 'นโยบายความเป็นส่วนตัว',
|
||||||
and: 'และ',
|
and: 'และ',
|
||||||
dataCollectionPolicy: 'นโยบายการเก็บรวบรวมข้อมูล',
|
dataCollectionPolicy: 'นโยบายการเก็บรวบรวมข้อมูล',
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const viVN = {
|
|||||||
test: 'Kiểm tra',
|
test: 'Kiểm tra',
|
||||||
forgotPassword: 'Quên mật khẩu?',
|
forgotPassword: 'Quên mật khẩu?',
|
||||||
agreementNotice: 'Bằng việc tiếp tục, bạn đồng ý với',
|
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',
|
privacyPolicy: 'Chính sách bảo mật',
|
||||||
and: 'và',
|
and: 'và',
|
||||||
dataCollectionPolicy: 'Chính sách thu thập dữ liệu',
|
dataCollectionPolicy: 'Chính sách thu thập dữ liệu',
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const zhHans = {
|
|||||||
test: '测试',
|
test: '测试',
|
||||||
forgotPassword: '忘记密码?',
|
forgotPassword: '忘记密码?',
|
||||||
agreementNotice: '继续即表示您同意我们的',
|
agreementNotice: '继续即表示您同意我们的',
|
||||||
|
termsOfService: '服务条款',
|
||||||
privacyPolicy: '隐私政策',
|
privacyPolicy: '隐私政策',
|
||||||
and: '和',
|
and: '和',
|
||||||
dataCollectionPolicy: '数据收集政策',
|
dataCollectionPolicy: '数据收集政策',
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const zhHant = {
|
|||||||
test: '測試',
|
test: '測試',
|
||||||
forgotPassword: '忘記密碼?',
|
forgotPassword: '忘記密碼?',
|
||||||
agreementNotice: '繼續即表示您同意我們的',
|
agreementNotice: '繼續即表示您同意我們的',
|
||||||
|
termsOfService: '服務條款',
|
||||||
privacyPolicy: '隱私政策',
|
privacyPolicy: '隱私政策',
|
||||||
and: '和',
|
and: '和',
|
||||||
dataCollectionPolicy: '數據收集政策',
|
dataCollectionPolicy: '數據收集政策',
|
||||||
|
|||||||
Reference in New Issue
Block a user