XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
import { useState, type FormEvent } from 'react';
import { KeyRound, Languages } from 'lucide-react';
import { useServer } from '../context/server-context';
import { useI18n } from '../lib/i18n';
import { Button, Field, Input } from '../components/ui';
import { MinecraftIcon } from '../components/minecraft-icon';

export function LoginPage() {
  const { t, locale, setLocale } = useI18n();
  const { login, setup, session } = useServer();
  const setupRequired = session?.bootstrapRequired === true;
  const [bootstrapToken, setBootstrapToken] = useState('');
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [passwordConfirmation, setPasswordConfirmation] = useState('');
  const [totp, setTotp] = useState('');
  const [useRecoveryCode, setUseRecoveryCode] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string>();
  const submit = async (event: FormEvent) => {
    event.preventDefault();
    setBusy(true);
    try {
      if (setupRequired) {
        if (password.length < 12) throw new Error(t('auth.passwordHint'));
        if (password !== passwordConfirmation) throw new Error(t('auth.passwordMismatch'));
        await setup(bootstrapToken.trim(), username.trim(), password);
      } else {
        await login(username.trim(), password, totp.trim() || undefined, useRecoveryCode);
      }
      setError(undefined);
    }
    catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)); }
    finally { setBusy(false); }
  };
  return <main className="login-page">
    <div className="login-page__landscape" aria-hidden="true">
      <div className="login-page__sun" />
      <div className="login-page__terrain login-page__terrain--far" />
      <div className="login-page__terrain login-page__terrain--near" />
    </div>
    <button className="language-button login-language" onClick={() => setLocale(locale === 'zh-CN' ? 'en-US' : 'zh-CN')}>
      <Languages size={16} />{locale === 'zh-CN' ? 'English' : '简体中文'}
    </button>
    <section className="login-card">
      <div className="login-brand">
        <div className="brand__mark brand__mark--minecraft" aria-hidden="true"><MinecraftIcon name="grass" size={32} /></div>
        <span>{t('app.name')}</span>
      </div>
      <div className="login-icon" aria-hidden="true"><MinecraftIcon name="shield" size={32} /></div>
      <h1>{setupRequired ? t('auth.setupTitle') : t('auth.title')}</h1>
      <p>{setupRequired ? t('auth.setupDescription') : t('auth.description')}</p>
      {setupRequired && <div className="bootstrap-note"><KeyRound />{t('auth.bootstrap')}</div>}
      <form className="form-stack" onSubmit={submit}>
        {setupRequired && <Field label={t('auth.bootstrapToken')}>
          <Input autoComplete="off" value={bootstrapToken} onChange={(event) => setBootstrapToken(event.target.value)} required />
        </Field>}
        <Field label={t('auth.username')}>
          <Input autoComplete="username" minLength={3} value={username} onChange={(event) => setUsername(event.target.value)} required />
        </Field>
        <Field label={t('auth.password')} hint={setupRequired ? t('auth.passwordHint') : undefined}>
          <Input type="password" autoComplete={setupRequired ? 'new-password' : 'current-password'}
            minLength={setupRequired ? 12 : undefined} value={password} onChange={(event) => setPassword(event.target.value)} required />
        </Field>
        {setupRequired && <Field label={t('auth.passwordConfirm')}>
          <Input type="password" autoComplete="new-password" minLength={12} value={passwordConfirmation}
            onChange={(event) => setPasswordConfirmation(event.target.value)} required />
        </Field>}
        {!setupRequired && <>
          <Field label={useRecoveryCode ? t('auth.recoveryCode') : t('auth.totp')}>
            <Input inputMode={useRecoveryCode ? 'text' : 'numeric'} autoComplete="one-time-code"
              pattern={useRecoveryCode ? undefined : '[0-9]{6}'} value={totp}
              onChange={(event) => setTotp(useRecoveryCode ? event.target.value : event.target.value.replace(/\D/g, '').slice(0, 6))} />
          </Field>
          <button type="button" className="auth-mode-toggle" onClick={() => { setUseRecoveryCode((current) => !current); setTotp(''); }}>
            {useRecoveryCode ? t('auth.useTotp') : t('auth.useRecovery')}
          </button>
        </>}
        {error && <div className="form-error" role="alert">{error}</div>}
        <Button type="submit" disabled={busy || !username || !password || (setupRequired && (!bootstrapToken.trim() || !passwordConfirmation))}>
          <MinecraftIcon name="shield" size={20} />{setupRequired ? t('auth.createOwner') : t('auth.signIn')}
        </Button>
      </form>
    </section>
  </main>;
}