chore: login improvements

- added client-side form validation
- now with slow internet otp input does not appear later than all input
This commit is contained in:
Danil S. 2025-09-01 04:34:15 +00:00
parent 4a0914cb1e
commit 1eef8e16ca

View file

@ -466,71 +466,83 @@
</g> </g>
</svg> </svg>
</div> </div>
<a-row type="flex" justify="center" align="middle" :style="{ height: '100%', overflow: 'auto', overflowX: 'hidden' }"> <a-row type="flex" justify="center" align="middle"
:style="{ height: '100%', overflow: 'auto', overflowX: 'hidden' }">
<a-col :xs="22" :sm="12" :md="10" :lg="8" :xl="6" :xxl="5" id="login" :style="{ margin: '3rem 0' }"> <a-col :xs="22" :sm="12" :md="10" :lg="8" :xl="6" :xxl="5" id="login" :style="{ margin: '3rem 0' }">
<div class="setting-section"> <template v-if="!loadingStates.fetched">
<a-popover :overlay-class-name="themeSwitcher.currentTheme" title='{{ i18n "menu.settings" }}' placement="bottomRight" trigger="click"> <div :style="{ textAlign: 'center' }">
<template slot="content"> <a-spin size="large" />
<a-space direction="vertical" :size="10"> </div>
<a-theme-switch-login></a-theme-switch-login> </template>
<span>{{ i18n "pages.settings.language" }}</span> <template v-else>
<a-select ref="selectLang" :style="{ width: '100%' }" v-model="lang" @change="LanguageManager.setLanguage(lang)" :dropdown-class-name="themeSwitcher.currentTheme"> <div class="setting-section">
<a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages"> <a-popover :overlay-class-name="themeSwitcher.currentTheme" title='{{ i18n "menu.settings" }}'
<span role="img" aria-label="l.name" v-text="l.icon"></span> placement="bottomRight" trigger="click">
&nbsp;&nbsp;<span v-text="l.name"></span> <template slot="content">
</a-select-option> <a-space direction="vertical" :size="10">
</a-select> <a-theme-switch-login></a-theme-switch-login>
</a-space> <span>{{ i18n "pages.settings.language" }}</span>
</template> <a-select ref="selectLang" :style="{ width: '100%' }" v-model="lang"
<a-button shape="circle" icon="setting"></a-button> @change="LanguageManager.setLanguage(lang)" :dropdown-class-name="themeSwitcher.currentTheme">
</a-popover> <a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages">
</div> <span role="img" aria-label="l.name" v-text="l.icon"></span>
<a-row type="flex" justify="center"> &nbsp;&nbsp;<span v-text="l.name"></span>
<a-col :style="{ width: '100%' }"> </a-select-option>
<h2 class="title headline zoom"> </a-select>
<span class="words-wrapper"> </a-space>
<b class="is-visible">{{ i18n "pages.login.hello" }}</b> </template>
<b>{{ i18n "pages.login.title" }}</b> <a-button shape="circle" icon="setting"></a-button>
</span> </a-popover>
</h2> </div>
</a-col> <a-row type="flex" justify="center">
</a-row> <a-col :style="{ width: '100%' }">
<a-row type="flex" justify="center"> <h2 class="title headline zoom">
<a-col span="24"> <span class="words-wrapper">
<a-form> <b class="is-visible">{{ i18n "pages.login.hello" }}</b>
<a-space direction="vertical" size="middle"> <b>{{ i18n "pages.login.title" }}</b>
<a-form-item> </span>
<a-input autocomplete="username" name="username" v-model.trim="user.username" </h2>
placeholder='{{ i18n "username" }}' @keydown.enter.native="login" autofocus> </a-col>
<a-icon slot="prefix" type="user" :style="{ fontSize: '1rem' }"></a-icon> </a-row>
</a-input> <a-row type="flex" justify="center">
</a-form-item> <a-col span="24">
<a-form-item> <a-form @submit.prevent="login">
<a-input-password autocomplete="password" name="password" v-model.trim="user.password" <a-space direction="vertical" size="middle">
placeholder='{{ i18n "password" }}' @keydown.enter.native="login"> <a-form-item>
<a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon> <a-input autocomplete="username" name="username" v-model.trim="user.username"
</a-input-password> placeholder='{{ i18n "username" }}' autofocus required>
</a-form-item> <a-icon slot="prefix" type="user" :style="{ fontSize: '1rem' }"></a-icon>
<a-form-item v-if="twoFactorEnable"> </a-input>
<a-input autocomplete="one-time-code" name="twoFactorCode" v-model.trim="user.twoFactorCode" </a-form-item>
placeholder='{{ i18n "twoFactorCode" }}' @keydown.enter.native="login"> <a-form-item>
<a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon> <a-input-password autocomplete="password" name="password" v-model.trim="user.password"
</a-input> placeholder='{{ i18n "password" }}' required>
</a-form-item> <a-icon slot="prefix" type="lock" :style="{ fontSize: '1rem' }"></a-icon>
<a-form-item> </a-input-password>
<a-row justify="center" class="centered"> </a-form-item>
<div :style="{ height: '50px', marginTop: '1rem', ...loading ? { width: '52px' } : { display: 'inline-block' } }" class="wave-btn-bg wave-btn-bg-cl"> <a-form-item v-if="twoFactorEnable">
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" <a-input autocomplete="one-time-code" name="twoFactorCode" v-model.trim="user.twoFactorCode"
:icon="loading ? 'poweroff' : undefined"> placeholder='{{ i18n "twoFactorCode" }}' required>
[[ loading ? '' : '{{ i18n "login" }}' ]] <a-icon slot="prefix" type="key" :style="{ fontSize: '1rem' }"></a-icon>
</a-button> </a-input>
</div> </a-form-item>
</a-row> <a-form-item>
</a-form-item> <a-row justify="center" class="centered">
</a-space> <div
</a-form> :style="{ height: '50px', marginTop: '1rem', ...loadingStates.spinning ? { width: '52px' } : { display: 'inline-block' } }"
</a-col> class="wave-btn-bg wave-btn-bg-cl">
</a-row> <a-button class="ant-btn-primary-login" type="primary" :loading="loadingStates.spinning"
:icon="loadingStates.spinning ? 'poweroff' : undefined" html-type="submit">
[[ loadingStates.spinning ? '' : '{{ i18n "login" }}' ]]
</a-button>
</div>
</a-row>
</a-form-item>
</a-space>
</a-form>
</a-col>
</a-row>
</template>
</a-col> </a-col>
</a-row> </a-row>
</a-layout-content> </a-layout-content>
@ -544,7 +556,10 @@
el: '#app', el: '#app',
data: { data: {
themeSwitcher, themeSwitcher,
loading: false, loadingStates: {
fetched: false,
spinning: false
},
user: { user: {
username: "", username: "",
password: "", password: "",
@ -559,19 +574,23 @@
}, },
methods: { methods: {
async login() { async login() {
this.loading = true; this.loadingStates.spinning = true;
const msg = await HttpUtil.post('/login', this.user); const msg = await HttpUtil.post('/login', this.user);
this.loading = false;
if (msg.success) { if (msg.success) {
location.href = basePath + 'panel/'; location.href = basePath + 'panel/';
} }
this.loadingStates.spinning = false;
}, },
async getTwoFactorEnable() { async getTwoFactorEnable() {
this.loading = true;
const msg = await HttpUtil.post('/getTwoFactorEnable'); const msg = await HttpUtil.post('/getTwoFactorEnable');
this.loading = false;
if (msg.success) { if (msg.success) {
this.twoFactorEnable = msg.obj; this.twoFactorEnable = msg.obj;
this.loadingStates.fetched = true;
return msg.obj; return msg.obj;
} }
}, },
@ -615,4 +634,4 @@
} }
}); });
</script> </script>
{{ template "page/body_end" .}} {{ template "page/body_end" .}}