Files
mnote/rust/crates/mnote-web/src/ssr/pages/auth.rs
T

221 lines
8.6 KiB
Rust
Raw Normal View History

2026-04-29 12:24:44 +08:00
//! MNOTE 登录页面组件
2026-04-29 14:36:24 +08:00
use leptos::prelude::*;
2026-04-29 12:24:44 +08:00
2026-05-06 21:44:20 +08:00
const TEST_ACCOUNT_EMAIL: &str = "mnote.e2e@example.com";
const TEST_ACCOUNT_PASSWORD: &str = "MnoteE2E123!";
const TEST_ACCOUNT_NAME: &str = "mnote-e2e";
2026-04-29 12:24:44 +08:00
/// MNOTE 登录页面
#[component]
pub fn AuthPage() -> impl IntoView {
view! {
2026-05-06 21:44:20 +08:00
<main class="mnote-auth" data-testid="mnote-auth-page">
<a class="mnote-auth-brand" href="/auth" aria-label="MNOTE">
<span class="mnote-auth-brand-mark" aria-hidden="true">"M"</span>
<span>"MNOTE"</span>
</a>
<section class="mnote-auth-panel" aria-labelledby="mnote-auth-title">
<div class="mnote-auth-heading">
<h1 id="mnote-auth-title">"邮箱登录"</h1>
<p>"登录后进入你的工作区"</p>
</div>
<form class="mnote-auth-form" method="post" action="/api/auth" data-auth-mode="convex-password">
<input type="hidden" name="action" value="auth:signIn" />
<input type="hidden" name="provider" value="password" />
<input type="hidden" name="flow" value="signIn" data-auth-flow />
<label class="mnote-auth-field">
<span>"邮箱"</span>
<input
id="email"
name="email"
type="email"
autocomplete="email"
placeholder="请输入邮箱"
required
/>
</label>
<label class="mnote-auth-field mnote-auth-username" hidden>
<span>"用户名"</span>
<input
id="username"
name="username"
type="text"
autocomplete="username"
placeholder="用于显示的用户名"
/>
</label>
<label class="mnote-auth-field">
<span>"密码"</span>
<input
id="password"
name="password"
type="password"
autocomplete="current-password"
placeholder="请输入密码"
minlength="8"
required
/>
</label>
<p class="mnote-auth-message" role="status" aria-live="polite" data-auth-message></p>
<button class="mnote-auth-submit" type="submit" data-auth-submit>"登录"</button>
<button
class="mnote-auth-quick-login"
type="button"
data-auth-test-login
data-test-email=TEST_ACCOUNT_EMAIL
data-test-password=TEST_ACCOUNT_PASSWORD
data-test-name=TEST_ACCOUNT_NAME
>
"测试账号快速登录"
</button>
</form>
<button class="mnote-auth-switch" type="button" data-auth-switch data-flow="signIn">
"没有账号?注册"
</button>
2026-04-29 12:24:44 +08:00
</section>
2026-05-06 21:44:20 +08:00
<script>{AUTH_SCRIPT}</script>
</main>
2026-04-29 12:24:44 +08:00
}
}
2026-05-06 21:44:20 +08:00
const AUTH_SCRIPT: &str = r#"
(function () {
var root = document.querySelector('[data-testid="mnote-auth-page"]');
if (!root) return;
var form = root.querySelector('form[data-auth-mode="convex-password"]');
var flowInput = root.querySelector('[data-auth-flow]');
var usernameField = root.querySelector('.mnote-auth-username');
var usernameInput = root.querySelector('#username');
var title = root.querySelector('#mnote-auth-title');
var subtitle = root.querySelector('.mnote-auth-heading p');
var submit = root.querySelector('[data-auth-submit]');
var quickLogin = root.querySelector('[data-auth-test-login]');
var switcher = root.querySelector('[data-auth-switch]');
var message = root.querySelector('[data-auth-message]');
if (!form || !flowInput || !submit || !quickLogin || !switcher || !message) return;
function setMessage(text, type) {
message.textContent = text || '';
message.dataset.type = type || '';
}
function setBusy(isBusy) {
submit.disabled = !!isBusy;
quickLogin.disabled = !!isBusy;
switcher.disabled = !!isBusy;
}
function setFlow(nextFlow) {
var isSignUp = nextFlow === 'signUp';
flowInput.value = nextFlow;
switcher.dataset.flow = nextFlow;
if (title) title.textContent = isSignUp ? '创建账号' : '邮箱登录';
if (subtitle) subtitle.textContent = isSignUp ? '创建账号后进入你的工作区' : '登录后进入你的工作区';
submit.textContent = isSignUp ? '注册并登录' : '登录';
switcher.textContent = isSignUp ? '已有账号?登录' : '没有账号?注册';
if (usernameField) usernameField.hidden = !isSignUp;
if (usernameInput) usernameInput.required = isSignUp;
quickLogin.hidden = isSignUp;
var passwordInput = root.querySelector('#password');
if (passwordInput) passwordInput.autocomplete = isSignUp ? 'new-password' : 'current-password';
setMessage('', '');
}
function buildPayload(flow, email, password, username) {
var payload = {
action: 'auth:signIn',
args: {
provider: 'password',
params: {
email: email,
password: password,
flow: flow
}
}
};
if (flow === 'signUp' && username) {
payload.args.params.name = username;
}
return payload;
}
async function requestAuth(flow, email, password, username) {
var response = await fetch('/api/auth', {
method: 'POST',
headers: { 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify(buildPayload(flow, email, password, username))
});
var data = await response.json().catch(function () { return {}; });
if (!response.ok || data.error) {
throw new Error(data.error || '登录失败,请重试');
}
return data;
}
switcher.addEventListener('click', function () {
setFlow(flowInput.value === 'signIn' ? 'signUp' : 'signIn');
});
form.addEventListener('submit', async function (event) {
event.preventDefault();
var email = String((root.querySelector('#email') || {}).value || '').trim();
var password = String((root.querySelector('#password') || {}).value || '');
var username = String((usernameInput || {}).value || '').trim();
var flow = flowInput.value === 'signUp' ? 'signUp' : 'signIn';
if (!email || !password || (flow === 'signUp' && !username)) {
setMessage('请完整填写信息', 'error');
return;
}
setBusy(true);
setMessage(flow === 'signUp' ? '正在创建账号...' : '正在登录...', 'info');
try {
await requestAuth(flow, email, password, username);
setMessage('登录成功,正在进入工作区...', 'success');
window.location.assign('/');
} catch (error) {
setMessage(error && error.message ? error.message : '登录失败,请重试', 'error');
setBusy(false);
}
});
quickLogin.addEventListener('click', async function () {
var email = String(quickLogin.dataset.testEmail || '').trim();
var password = String(quickLogin.dataset.testPassword || '');
var username = String(quickLogin.dataset.testName || '').trim();
if (!email || !password || !username) {
setMessage('测试账号配置缺失,请联系开发者', 'error');
return;
}
var emailInput = root.querySelector('#email');
var passwordInput = root.querySelector('#password');
if (emailInput) emailInput.value = email;
if (passwordInput) passwordInput.value = password;
setBusy(true);
setMessage('正在登录测试账号...', 'info');
try {
await requestAuth('signIn', email, password, username);
setMessage('登录成功,正在进入工作区...', 'success');
window.location.assign('/');
return;
} catch (_signInError) {
setMessage('测试账号不存在,正在自动创建...', 'info');
}
try {
await requestAuth('signUp', email, password, username);
setMessage('测试账号已创建,正在进入工作区...', 'success');
window.location.assign('/');
} catch (error) {
setMessage(error && error.message ? error.message : '测试账号登录失败,请重试', 'error');
setBusy(false);
}
});
setFlow('signIn');
})();
"#;