import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined"; import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined"; import Alert from "@mui/material/Alert"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Card from "@mui/material/Card"; import CardContent from "@mui/material/CardContent"; import CircularProgress from "@mui/material/CircularProgress"; import IconButton from "@mui/material/IconButton"; import InputAdornment from "@mui/material/InputAdornment"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; import Typography from "@mui/material/Typography"; import { useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; import { useExternalAuth } from "../auth/ExternalAuthProvider.jsx"; import { LanguageSwitcher } from "../i18n/LanguageSwitcher.jsx"; import { useExternalI18n } from "../i18n/ExternalI18nProvider.jsx"; import { translateExternalError } from "../i18n/errors.js"; export default function LoginPage() { const { initializing, login, session } = useExternalAuth(); const { t } = useExternalI18n(); const [form, setForm] = useState({ account: "", password: "" }); const [error, setError] = useState(""); const [submitting, setSubmitting] = useState(false); const [showPassword, setShowPassword] = useState(false); const navigate = useNavigate(); const location = useLocation(); useEffect(() => { // 登录成功前没有可信 App 上下文;使用通用标题并清掉上一个会话的图标,避免泄露或误导用户所属 App。 globalThis.document.title = t("app.title"); globalThis.document.head.querySelector('link[data-external-app-favicon="true"]')?.remove(); }, [t]); useEffect(() => { if (!initializing && session) { const destination = session.passwordChangeRequired ? "/change-password" : location.state?.from?.pathname || "/overview"; navigate(destination, { replace: true }); } }, [initializing, location.state, navigate, session]); const handleSubmit = async (event) => { event.preventDefault(); if (!form.account.trim() || !form.password) { setError(t("auth.fillAccountPassword")); return; } setSubmitting(true); setError(""); try { const nextSession = await login(form); navigate(nextSession.passwordChangeRequired ? "/change-password" : "/overview", { replace: true }); } catch (requestError) { // Every login 401 uses one generic credential message so unknown, disabled and locked accounts cannot be distinguished. setError(requestError?.status === 401 ? t("auth.invalidCredentials") : translateExternalError(requestError, t, "auth.loginFailed")); } finally { setSubmitting(false); } }; return ( {t("app.title")} {error ? {error} : null} setForm({ ...form, account: event.target.value })} /> setForm({ ...form, password: event.target.value })} slotProps={{ input: { endAdornment: setShowPassword((current) => !current)}>{showPassword ? : } } }} /> ); }