Files
lislgosms/src/apps/LoginPage.tsx
T

115 lines
5.0 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { adminApi, clientApi, type CaptchaResponse } from '@/api/adminApi';
import { consumeSessionRecovery, markUserActivity, readSessionRecovery, writeSession, type Portal } from '@/api/session';
import { Button, Input, Modal } from '@/components/ui';
type LoginPageProps = {
portal: Portal;
};
function loginErrorMessage(err: unknown) {
const message = err instanceof Error ? err.message : '';
if (message.includes('Invalid login or password')) return '用户名或密码错误';
if (message.includes('login and password are required')) return '请输入用户名和密码';
if (message.includes('Captcha expired')) return '验证码已过期,请刷新后重试';
if (message.includes('Captcha is incorrect')) return '验证码错误,请重新输入';
if (message.includes('User is locked')) return '账号已锁定,请 24 小时后再试';
if (message.includes('User is disabled or deleted')) return '账号已停用或已删除';
if (message.includes('Only platform admins can login to admin portal')) return '该账号不是运营端管理员';
if (message.includes('Only enterprise admins linked to a tenant can login to client portal')) return '该账号不是已绑定企业的客户端管理员';
return message || '登录失败,请检查账号信息后重试';
}
export function LoginPage({ portal }: LoginPageProps) {
const navigate = useNavigate();
const [login, setLogin] = useState('');
const [password, setPassword] = useState('');
const [captchaText, setCaptchaText] = useState('');
const [captcha, setCaptcha] = useState<CaptchaResponse | null>(null);
const [error, setError] = useState('');
const [alertMessage, setAlertMessage] = useState('');
const [loading, setLoading] = useState(false);
const isAdmin = portal === 'admin';
const recovery = readSessionRecovery(portal);
async function refreshCaptcha(options: { clearError?: boolean } = {}) {
if (options.clearError ?? true) {
setError('');
}
setCaptchaText('');
setCaptcha(await (isAdmin ? adminApi.getCaptcha() : clientApi.getCaptcha()));
}
useEffect(() => {
void refreshCaptcha();
}, [portal]);
async function submit() {
if (!captcha) return;
setLoading(true);
setError('');
try {
const session = await (isAdmin ? adminApi.login : clientApi.login)({
login,
password,
captchaId: captcha.captchaId,
captchaText,
});
writeSession(session);
// A login can happen without a full page reload after the previous session
// expired. Reset the in-memory activity clock so the new session is not
// immediately locked using the previous session's stale idle duration.
markUserActivity();
const target = consumeSessionRecovery(portal)?.returnUrl;
navigate(target ?? (isAdmin ? '/admin' : '/client'), { replace: true });
} catch (err) {
const message = loginErrorMessage(err);
setError(message);
setAlertMessage(message);
await refreshCaptcha({ clearError: false });
} finally {
setLoading(false);
}
}
return (
<main className="login-page">
<section className="login-panel">
<div className="login-brand">
<img alt={isAdmin ? 'CMPP 运营端 logo' : 'CMPP 客户端 logo'} src="/logo/logo1.png" />
<div>
<h1>短信服务平台</h1>
<p>{isAdmin ? '运营端登录' : '客户端登录'}</p>
</div>
</div>
<div className="login-form">
{recovery ? (
<p className="login-session-notice" role="status">
{recovery.message ?? '登录会话已失效,请重新登录。'} 登录成功后将返回之前访问的页面。
</p>
) : null}
<Input label="用户名/登录账号" onChange={(event) => setLogin(event.target.value)} placeholder="请输入用户名、邮箱或手机号" value={login} />
<Input label="密码" onChange={(event) => setPassword(event.target.value)} placeholder="请输入密码" type="password" value={password} />
<div className="login-captcha-row">
<Input label="图形验证码" onChange={(event) => setCaptchaText(event.target.value)} placeholder="请输入计算结果" value={captchaText} />
<button className="login-captcha" onClick={() => void refreshCaptcha()} type="button">
{captcha?.challenge ?? '刷新'}
</button>
</div>
{error ? <p className="login-error">{error}</p> : null}
<Button disabled={loading} onClick={submit}>{loading ? '登录中...' : '登录'}</Button>
</div>
</section>
<Modal
footer={<Button onClick={() => setAlertMessage('')}>知道了</Button>}
onClose={() => setAlertMessage('')}
open={Boolean(alertMessage)}
title="登录失败"
>
<p className="login-alert-message">{alertMessage}</p>
</Modal>
</main>
);
}