252 lines
9.7 KiB
Rust
252 lines
9.7 KiB
Rust
//! MNOTE 登录页面组件
|
|
|
|
use leptos::prelude::*;
|
|
|
|
const TEST_ACCOUNT_EMAIL: &str = "mnote.e2e@example.com";
|
|
const TEST_ACCOUNT_PASSWORD: &str = "MnoteE2E123!";
|
|
const TEST_ACCOUNT_NAME: &str = "mnote-e2e";
|
|
|
|
/// MNOTE 登录页面
|
|
#[component]
|
|
pub fn AuthPage() -> impl IntoView {
|
|
view! {
|
|
<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 data-auth-account-label>"邮箱或用户名"</span>
|
|
<input
|
|
id="account"
|
|
name="account"
|
|
type="text"
|
|
autocomplete="username"
|
|
placeholder="请输入邮箱或用户名"
|
|
required
|
|
/>
|
|
</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>
|
|
</section>
|
|
<script>{AUTH_SCRIPT}</script>
|
|
</main>
|
|
}
|
|
}
|
|
|
|
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 accountInput = root.querySelector('#account');
|
|
var accountLabel = root.querySelector('[data-auth-account-label]');
|
|
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 (accountLabel) accountLabel.textContent = isSignUp ? '邮箱' : '邮箱或用户名';
|
|
if (accountInput) {
|
|
accountInput.placeholder = isSignUp ? '请输入邮箱' : '请输入邮箱或用户名';
|
|
accountInput.autocomplete = isSignUp ? 'email' : 'username';
|
|
}
|
|
quickLogin.hidden = isSignUp;
|
|
var passwordInput = root.querySelector('#password');
|
|
if (passwordInput) passwordInput.autocomplete = isSignUp ? 'new-password' : 'current-password';
|
|
setMessage('', '');
|
|
}
|
|
|
|
function accountMap() {
|
|
try {
|
|
return JSON.parse(window.localStorage.getItem('mnote.auth.accountEmailByName') || '{}') || {};
|
|
} catch (_) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function rememberAccount(email, name) {
|
|
var normalizedEmail = String(email || '').trim();
|
|
var normalizedName = String(name || '').trim();
|
|
if (!normalizedEmail || !normalizedName || normalizedName.indexOf('@') !== -1) return;
|
|
try {
|
|
var map = accountMap();
|
|
map[normalizedName.toLowerCase()] = normalizedEmail;
|
|
window.localStorage.setItem('mnote.auth.accountEmailByName', JSON.stringify(map));
|
|
} catch (_) {}
|
|
}
|
|
|
|
function resolveAccountEmail(account) {
|
|
var normalized = String(account || '').trim();
|
|
if (normalized.indexOf('@') !== -1) return normalized;
|
|
var mapped = accountMap()[normalized.toLowerCase()];
|
|
return String(mapped || normalized).trim();
|
|
}
|
|
|
|
function defaultNameFromEmail(email) {
|
|
return String(email || '').split('@')[0].trim();
|
|
}
|
|
|
|
function buildPayload(flow, account, password) {
|
|
var email = flow === 'signIn' ? resolveAccountEmail(account) : String(account || '').trim();
|
|
var name = defaultNameFromEmail(email);
|
|
var payload = {
|
|
action: 'auth:signIn',
|
|
args: {
|
|
provider: 'password',
|
|
params: {
|
|
email: email,
|
|
password: password,
|
|
flow: flow
|
|
}
|
|
}
|
|
};
|
|
if (name) {
|
|
payload.args.params.name = name;
|
|
}
|
|
return payload;
|
|
}
|
|
|
|
async function requestAuth(flow, account, password) {
|
|
var response = await fetch('/api/auth', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify(buildPayload(flow, account, password))
|
|
});
|
|
var data = await response.json().catch(function () { return {}; });
|
|
if (!response.ok || data.error) {
|
|
throw new Error(data.error || '登录失败,请重试');
|
|
}
|
|
var params = buildPayload(flow, account, password).args.params;
|
|
rememberAccount(params.email, params.name);
|
|
return data;
|
|
}
|
|
|
|
switcher.addEventListener('click', function () {
|
|
setFlow(flowInput.value === 'signIn' ? 'signUp' : 'signIn');
|
|
});
|
|
|
|
form.addEventListener('submit', async function (event) {
|
|
event.preventDefault();
|
|
var account = String((accountInput || {}).value || '').trim();
|
|
var password = String((root.querySelector('#password') || {}).value || '');
|
|
var flow = flowInput.value === 'signUp' ? 'signUp' : 'signIn';
|
|
if (!account || !password) {
|
|
setMessage('请完整填写信息', 'error');
|
|
return;
|
|
}
|
|
if (flow === 'signUp' && account.indexOf('@') === -1) {
|
|
setMessage('注册时请填写邮箱;登录时可输入邮箱或已记住的用户名', 'error');
|
|
return;
|
|
}
|
|
setBusy(true);
|
|
setMessage(flow === 'signUp' ? '正在创建账号...' : '正在登录...', 'info');
|
|
try {
|
|
await requestAuth(flow, account, password);
|
|
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 passwordInput = root.querySelector('#password');
|
|
if (accountInput) accountInput.value = email;
|
|
if (passwordInput) passwordInput.value = password;
|
|
|
|
setBusy(true);
|
|
setMessage('正在登录测试账号...', 'info');
|
|
try {
|
|
await requestAuth('signIn', email, password);
|
|
rememberAccount(email, username);
|
|
setMessage('登录成功,正在进入工作区...', 'success');
|
|
window.location.assign('/');
|
|
return;
|
|
} catch (_signInError) {
|
|
setMessage('测试账号不存在,正在自动创建...', 'info');
|
|
}
|
|
|
|
try {
|
|
await requestAuth('signUp', email, password);
|
|
rememberAccount(email, username);
|
|
setMessage('测试账号已创建,正在进入工作区...', 'success');
|
|
window.location.assign('/');
|
|
} catch (error) {
|
|
setMessage(error && error.message ? error.message : '测试账号登录失败,请重试', 'error');
|
|
setBusy(false);
|
|
}
|
|
});
|
|
|
|
setFlow('signIn');
|
|
})();
|
|
"#;
|