diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-03-17 12:40:41 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-03-18 16:40:15 +0100 |
commit | fb52d0a02b432012ece47fac51c637fe16d669e8 (patch) | |
tree | a1f68d58511ccf985cc4501e95535a4debf95f84 | |
parent | 8035c8d6b80e81facea6fba362664014acbcf525 (diff) | |
download | nextcloud-server-fb52d0a02b432012ece47fac51c637fe16d669e8.tar.gz nextcloud-server-fb52d0a02b432012ece47fac51c637fe16d669e8.zip |
fix(login): simplify code and use consistent layout
- Simplify vue code
- Use nc buttons for consistent design
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | core/src/components/login/LoginForm.vue | 1 | ||||
-rw-r--r-- | core/src/components/login/PasswordLessLoginForm.vue | 82 | ||||
-rw-r--r-- | core/src/components/login/ResetPassword.vue | 145 | ||||
-rw-r--r-- | core/src/views/Login.vue | 84 |
4 files changed, 137 insertions, 175 deletions
diff --git a/core/src/components/login/LoginForm.vue b/core/src/components/login/LoginForm.vue index cde05ab5200..9dc27bf51c2 100644 --- a/core/src/components/login/LoginForm.vue +++ b/core/src/components/login/LoginForm.vue @@ -292,6 +292,7 @@ export default { .login-form { text-align: start; font-size: 1rem; + margin: 0; &__fieldset { width: 100%; diff --git a/core/src/components/login/PasswordLessLoginForm.vue b/core/src/components/login/PasswordLessLoginForm.vue index aabfcb047de..d87f0ce8cb9 100644 --- a/core/src/components/login/PasswordLessLoginForm.vue +++ b/core/src/components/login/PasswordLessLoginForm.vue @@ -5,39 +5,40 @@ <template> <form v-if="(isHttps || isLocalhost) && supportsWebauthn" ref="loginForm" + class="password-less-login-form" method="post" name="login" @submit.prevent="submit"> <h2>{{ t('core', 'Log in with a device') }}</h2> - <fieldset> - <NcTextField required - :value="user" - :autocomplete="autoCompleteAllowed ? 'on' : 'off'" - :error="!validCredentials" - :label="t('core', 'Login or email')" - :placeholder="t('core', 'Login or email')" - :helper-text="!validCredentials ? t('core', 'Your account is not setup for passwordless login.') : ''" - @update:value="changeUsername" /> + <NcTextField required + :value="user" + :autocomplete="autoCompleteAllowed ? 'on' : 'off'" + :error="!validCredentials" + :label="t('core', 'Login or email')" + :placeholder="t('core', 'Login or email')" + :helper-text="!validCredentials ? t('core', 'Your account is not setup for passwordless login.') : ''" + @update:value="changeUsername" /> - <LoginButton v-if="validCredentials" - :loading="loading" - @click="authenticate" /> - </fieldset> + <LoginButton v-if="validCredentials" + :loading="loading" + @click="authenticate" /> </form> - <div v-else-if="!supportsWebauthn" class="update"> - <InformationIcon size="70" /> - <h2>{{ t('core', 'Browser not supported') }}</h2> - <p class="infogroup"> - {{ t('core', 'Passwordless authentication is not supported in your browser.') }} - </p> - </div> - <div v-else-if="!isHttps && !isLocalhost" class="update"> - <LockOpenIcon size="70" /> - <h2>{{ t('core', 'Your connection is not secure') }}</h2> - <p class="infogroup"> - {{ t('core', 'Passwordless authentication is only available over a secure connection.') }} - </p> - </div> + + <NcEmptyContent v-else-if="!isHttps && !isLocalhost" + :name="t('core', 'Your connection is not secure')" + :description="t('core', 'Passwordless authentication is only available over a secure connection.')"> + <template #icon> + <LockOpenIcon /> + </template> + </NcEmptyContent> + + <NcEmptyContent v-else + :name="t('core', 'Browser not supported')" + :description="t('core', 'Passwordless authentication is not supported in your browser.')"> + <template #icon> + <InformationIcon /> + </template> + </NcEmptyContent> </template> <script> @@ -46,10 +47,13 @@ import { startAuthentication, finishAuthentication, } from '../../services/WebAuthnAuthenticationService.ts' -import LoginButton from './LoginButton.vue' + +import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' +import NcTextField from '@nextcloud/vue/components/NcTextField' + import InformationIcon from 'vue-material-design-icons/Information.vue' +import LoginButton from './LoginButton.vue' import LockOpenIcon from 'vue-material-design-icons/LockOpen.vue' -import NcTextField from '@nextcloud/vue/components/NcTextField' import logger from '../../logger' export default { @@ -58,6 +62,7 @@ export default { LoginButton, InformationIcon, LockOpenIcon, + NcEmptyContent, NcTextField, }, props: { @@ -142,17 +147,10 @@ export default { </script> <style lang="scss" scoped> - fieldset { - display: flex; - flex-direction: column; - gap: 0.5rem; - - :deep(label) { - text-align: initial; - } - } - - .update { - margin: 0 auto; - } +.password-less-login-form { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin: 0; +} </style> diff --git a/core/src/components/login/ResetPassword.vue b/core/src/components/login/ResetPassword.vue index 8fa25d192d4..fee1deacc36 100644 --- a/core/src/components/login/ResetPassword.vue +++ b/core/src/components/login/ResetPassword.vue @@ -4,59 +4,65 @@ --> <template> - <form class="login-form" @submit.prevent="submit"> - <fieldset class="login-form__fieldset"> - <NcTextField id="user" - :value.sync="user" - name="user" - :maxlength="255" - autocapitalize="off" - :label="t('core', 'Login or email')" - :error="userNameInputLengthIs255" - :helper-text="userInputHelperText" - required - @change="updateUsername" /> - <LoginButton :value="t('core', 'Reset password')" /> - - <NcNoteCard v-if="message === 'send-success'" - type="success"> - {{ t('core', 'If this account exists, a password reset message has been sent to its email address. If you do not receive it, verify your email address and/or Login, check your spam/junk folders or ask your local administration for help.') }} - </NcNoteCard> - <NcNoteCard v-else-if="message === 'send-error'" - type="error"> - {{ t('core', 'Couldn\'t send reset email. Please contact your administrator.') }} - </NcNoteCard> - <NcNoteCard v-else-if="message === 'reset-error'" - type="error"> - {{ t('core', 'Password cannot be changed. Please contact your administrator.') }} - </NcNoteCard> - - <a class="login-form__link" - href="#" - @click.prevent="$emit('abort')"> - {{ t('core', 'Back to login') }} - </a> - </fieldset> + <form class="reset-password-form" @submit.prevent="submit"> + <h2>{{ t('core', 'Reset password') }}</h2> + + <NcTextField id="user" + :value.sync="user" + name="user" + :maxlength="255" + autocapitalize="off" + :label="t('core', 'Login or email')" + :error="userNameInputLengthIs255" + :helper-text="userInputHelperText" + required + @change="updateUsername" /> + + <LoginButton :loading="loading" :value="t('core', 'Reset password')" /> + + <NcButton type="tertiary" wide @click="$emit('abort')"> + {{ t('core', 'Back to login') }} + </NcButton> + + <NcNoteCard v-if="message === 'send-success'" + type="success"> + {{ t('core', 'If this account exists, a password reset message has been sent to its email address. If you do not receive it, verify your email address and/or Login, check your spam/junk folders or ask your local administration for help.') }} + </NcNoteCard> + <NcNoteCard v-else-if="message === 'send-error'" + type="error"> + {{ t('core', 'Couldn\'t send reset email. Please contact your administrator.') }} + </NcNoteCard> + <NcNoteCard v-else-if="message === 'reset-error'" + type="error"> + {{ t('core', 'Password cannot be changed. Please contact your administrator.') }} + </NcNoteCard> </form> </template> -<script> -import axios from '@nextcloud/axios' +<script lang="ts"> import { generateUrl } from '@nextcloud/router' -import LoginButton from './LoginButton.vue' +import { defineComponent } from 'vue' + +import axios from '@nextcloud/axios' +import NcButton from '@nextcloud/vue/components/NcButton' import NcTextField from '@nextcloud/vue/components/NcTextField' import NcNoteCard from '@nextcloud/vue/components/NcNoteCard' import AuthMixin from '../../mixins/auth.js' +import LoginButton from './LoginButton.vue' +import logger from '../../logger.js' -export default { +export default defineComponent({ name: 'ResetPassword', components: { LoginButton, + NcButton, NcNoteCard, NcTextField, }, + mixins: [AuthMixin], + props: { username: { type: String, @@ -67,11 +73,12 @@ export default { required: true, }, }, + data() { return { error: false, loading: false, - message: undefined, + message: '', user: this.username, } }, @@ -84,56 +91,38 @@ export default { updateUsername() { this.$emit('update:username', this.user) }, - submit() { + + async submit() { this.loading = true this.error = false this.message = '' const url = generateUrl('/lostpassword/email') - const data = { - user: this.user, - } + try { + const { data } = await axios.post(url, { user: this.user }) + if (data.status !== 'success') { + throw new Error(`got status ${data.status}`) + } + + this.message = 'send-success' + } catch (error) { + logger.error('could not send reset email request', { error }) - return axios.post(url, data) - .then(resp => resp.data) - .then(data => { - if (data.status !== 'success') { - throw new Error(`got status ${data.status}`) - } - - this.message = 'send-success' - }) - .catch(e => { - console.error('could not send reset email request', e) - - this.error = true - this.message = 'send-error' - }) - .then(() => { this.loading = false }) + this.error = true + this.message = 'send-error' + } finally { + this.loading = false + } }, }, -} +}) </script> <style lang="scss" scoped> -.login-form { - text-align: start; - font-size: 1rem; - - &__fieldset { - width: 100%; - display: flex; - flex-direction: column; - gap: .5rem; - } - - &__link { - display: block; - font-weight: normal !important; - cursor: pointer; - font-size: var(--default-font-size); - text-align: center; - padding: .5rem 1rem 1rem 1rem; - } +.reset-password-form { + display: flex; + flex-direction: column; + gap: .5rem; + width: 100%; } </style> diff --git a/core/src/views/Login.vue b/core/src/views/Login.vue index 7a35331b090..9236d1a9d09 100644 --- a/core/src/views/Login.vue +++ b/core/src/views/Login.vue @@ -7,7 +7,7 @@ <div class="guest-box login-box"> <template v-if="!hideLoginForm || directLogin"> <transition name="fade" mode="out-in"> - <div v-if="!passwordlessLogin && !resetPassword && resetPasswordTarget === ''"> + <div v-if="!passwordlessLogin && !resetPassword && resetPasswordTarget === ''" class="login-box__wrapper"> <LoginForm :username.sync="user" :redirect-url="redirectUrl" :direct-login="directLogin" @@ -17,40 +17,30 @@ :auto-complete-allowed="autoCompleteAllowed" :email-states="emailStates" @submit="loading = true" /> - <a v-if="canResetPassword && resetPasswordLink !== ''" + <NcButton v-if="hasPasswordless" + type="tertiary" + wide + @click.prevent="passwordlessLogin = true"> + {{ t('core', 'Log in with a device') }} + </NcButton> + <NcButton v-if="canResetPassword && resetPasswordLink !== ''" id="lost-password" - class="login-box__link" - :href="resetPasswordLink"> + :href="resetPasswordLink" + type="tertiary-no-background" + wide> {{ t('core', 'Forgot password?') }} - </a> - <a v-else-if="canResetPassword && !resetPassword" + </NcButton> + <NcButton v-else-if="canResetPassword && !resetPassword" id="lost-password" - class="login-box__link" - :href="resetPasswordLink" + type="tertiary" + wide @click.prevent="resetPassword = true"> {{ t('core', 'Forgot password?') }} - </a> - <template v-if="hasPasswordless"> - <div v-if="countAlternativeLogins" - class="alternative-logins"> - <a v-if="hasPasswordless" - class="button" - :class="{ 'single-alt-login-option': countAlternativeLogins }" - href="#" - @click.prevent="passwordlessLogin = true"> - {{ t('core', 'Log in with a device') }} - </a> - </div> - <a v-else - href="#" - @click.prevent="passwordlessLogin = true"> - {{ t('core', 'Log in with a device') }} - </a> - </template> + </NcButton> </div> <div v-else-if="!loading && passwordlessLogin" key="reset-pw-less" - class="login-additional login-passwordless"> + class="login-additional login-box__wrapper"> <PasswordLessLoginForm :username.sync="user" :redirect-url="redirectUrl" :auto-complete-allowed="autoCompleteAllowed" @@ -89,7 +79,7 @@ </transition> </template> - <div id="alternative-logins" class="alternative-logins"> + <div id="alternative-logins" class="login-box__alternative-logins"> <NcButton v-for="(alternativeLogin, index) in alternativeLogins" :key="index" type="secondary" @@ -169,22 +159,22 @@ export default { } </script> -<style lang="scss"> -body { - font-size: var(--default-font-size); -} - +<style scoped lang="scss"> .login-box { // Same size as dashboard panels width: 320px; box-sizing: border-box; - &__link { - display: block; - padding: 1rem; - font-size: var(--default-font-size); - text-align: center; - font-weight: normal !important; + &__wrapper { + display: flex; + flex-direction: column; + gap: calc(2 * var(--default-grid-baseline)); + } + + &__alternative-logins { + display: flex; + flex-direction: column; + gap: 0.75rem; } } @@ -195,20 +185,4 @@ body { .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { opacity: 0; } - -.alternative-logins { - display: flex; - flex-direction: column; - gap: 0.75rem; - - .button-vue { - box-sizing: border-box; - } -} - -.login-passwordless { - .button-vue { - margin-top: 0.5rem; - } -} </style> |