diff options
author | Carl Schwan <carl@carlschwan.eu> | 2022-09-06 17:54:33 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-06 17:54:33 +0300 |
commit | 36b2d3dc2a7978a3f19b9888bb03ecd2f566f59e (patch) | |
tree | bc8a81dcbf67b5a2c72f6d4bc988b24a8c4c6ccb /core | |
parent | 5104ee9dd494ff1916593139937d02e3affc8918 (diff) | |
parent | 43d1aa38df776a6767723db262af9e099a88fdfc (diff) |
Merge pull request #33781 from nextcloud/port/login-form/vue-password-components
Use new vue components in login form
Diffstat (limited to 'core')
-rw-r--r-- | core/css/guest.css | 81 | ||||
-rw-r--r-- | core/css/lostpassword/resetpassword.css | 7 | ||||
-rw-r--r-- | core/src/components/login/LoginButton.vue | 6 | ||||
-rw-r--r-- | core/src/components/login/LoginForm.vue | 177 | ||||
-rw-r--r-- | core/src/components/login/ResetPassword.vue | 80 | ||||
-rw-r--r-- | core/src/views/Login.vue | 67 |
6 files changed, 176 insertions, 242 deletions
diff --git a/core/css/guest.css b/core/css/guest.css index 813e04db213..7408d5485cc 100644 --- a/core/css/guest.css +++ b/core/css/guest.css @@ -108,14 +108,6 @@ form { margin: auto; padding: 0; } -form fieldset { - width: 260px; - margin-top: 8px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} form #sqliteInformation { margin-top: 0px; margin-bottom: 20px; @@ -146,8 +138,7 @@ form #datadirField legend { } /* Buttons and input */ -#submit-wrapper, -#reset-password-wrapper { +#submit-wrapper { display: flex; align-items: center; justify-content: center; @@ -160,14 +151,10 @@ form #datadirField legend { .wrapper { margin-top: 0; } - .alternative-logins { - margin: auto; - } } -#submit-wrapper .submit-icon, -#reset-password-wrapper .submit-icon { +#submit-wrapper .submit-icon { position: absolute; right: 24px; transition: right 100ms ease-in-out; @@ -187,12 +174,6 @@ form #datadirField legend { right: 20px; } -#reset-password-submit { - padding: 10px; - overflow: hidden; - text-overflow: ellipsis; -} - #submit-wrapper .icon-loading-small { position: absolute; top: 22px; @@ -262,9 +243,6 @@ input[type='email'] { color: var(--color-text-lighter); cursor: text; font-family: inherit; - -webkit-appearance: textfield; - -moz-appearance: textfield; - box-sizing: content-box; font-weight: normal; margin-left: 0; margin-right: 0; @@ -471,53 +449,6 @@ form .warning input[type='checkbox']+label { #remember_login { margin: 18px 5px 0 16px !important; } -.lost-password-container { - display: inline-block; - margin: 10px 0; - text-align: center; - width: 100%; -} -#lost-password, -#lost-password-back, -#reset-password-wrapper + a { - display: inline-block; - font-weight: normal !important; - padding: 12px 12px 0 12px; - cursor: pointer; -} -#forgot-password { - padding: 11px; - float: right; - color: var(--color-primary-text); -} - -/* Alternative Logins */ -.alternative-logins legend { - margin-bottom: 10px; -} -.alternative-logins li { - height: 40px; - white-space: nowrap; - padding: 05px; -} -.alternative-logins a.button, -.alternative-logins li a { - width: 100%; - display: inline-block; - text-align: center; - box-sizing: border-box; - border: 2px solid var(--color-primary-text); - background-color: var(--color-primary); - color: var(--color-primary-text); - border-radius: 100px; /* --border-radius-pill */ -} - -.alternative-logins a.button:focus, -.alternative-logins li a:focus { - border: 2px solid var(--color-primary-hover); - background-image: linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-light) 100%); - background-position: initial; -} /* fixes for update page TODO should be fixed some time in a proper way */ /* this is just for an error while updating the ownCloud instance */ @@ -792,24 +723,22 @@ a.legal { } .notecard.success { - --note-background: rgba(var(--color-success-rgb), 0.2); + --note-background: rgba(var(--color-success-rgb), 0.1); --note-theme: var(--color-success); } .notecard.error { - --note-background: rgba(var(--color-error-rgb), 0.2); + --note-background: rgba(var(--color-error-rgb), 0.1); --note-theme: var(--color-error); } .notecard.warning { - --note-background: rgba(var(--color-warning-rgb), 0.2); + --note-background: rgba(var(--color-warning-rgb), 0.1); --note-theme: var(--color-warning); } .notecard { color: var(--color-text-light); background-color: var(--note-background); - border: 1px solid var(--color-border); border-left: 4px solid var(--note-theme); border-radius: var(--border-radius); - box-shadow: rgba(43, 42, 51, 0.05) 0px 1px 2px 0px; margin: 1rem 0; margin-top: 1rem; padding: 1rem; diff --git a/core/css/lostpassword/resetpassword.css b/core/css/lostpassword/resetpassword.css deleted file mode 100644 index 0b3c251e8d7..00000000000 --- a/core/css/lostpassword/resetpassword.css +++ /dev/null @@ -1,7 +0,0 @@ -#reset-password p { - position: relative; -} - -.text-center { - text-align: center; -} diff --git a/core/src/components/login/LoginButton.vue b/core/src/components/login/LoginButton.vue index c9aaa2522c6..3d3ac25de6d 100644 --- a/core/src/components/login/LoginButton.vue +++ b/core/src/components/login/LoginButton.vue @@ -62,3 +62,9 @@ export default { }, } </script> + +<style lang="scss" scoped> +.button-vue { + margin-top: .5rem; +} +</style> diff --git a/core/src/components/login/LoginForm.vue b/core/src/components/login/LoginForm.vue index 88a465d1aa5..b794a2c02ce 100644 --- a/core/src/components/login/LoginForm.vue +++ b/core/src/components/login/LoginForm.vue @@ -21,28 +21,28 @@ <template> <form ref="loginForm" + class="login-form" method="post" name="login" :action="loginActionUrl" @submit="submit"> - <fieldset> - <div v-if="apacheAuthFailed" - class="warning"> - {{ t('core', 'Server side authentication failed!') }}<br> - <small>{{ t('core', 'Please contact your administrator.') }} - </small> - </div> - <div v-for="(message, index) in messages" - :key="index" - class="warning"> - {{ message }}<br> - </div> - <div v-if="internalException" - class="warning"> - {{ t('core', 'An internal error occurred.') }}<br> - <small>{{ t('core', 'Please try again or contact your administrator.') }} - </small> - </div> + <fieldset class="login-form__fieldset"> + <NcNoteCard v-if="apacheAuthFailed" + :title="t('core', 'Server side authentication failed!')" + type="warning"> + {{ t('core', 'Please contact your administrator.') }} + </NcNoteCard> + <NcNoteCard v-if="messages.length > 0"> + <div v-for="(message, index) in messages" + :key="index"> + {{ message }}<br> + </div> + </NcNoteCard> + <NcNoteCard v-if="internalException" + :class="t('core', 'An internal error occurred.')" + type="warning"> + {{ t('core', 'Please try again or contact your administrator.') }} + </NcNoteCard> <div id="message" class="hidden"> <img class="float-spinner" @@ -52,65 +52,35 @@ <!-- the following div ensures that the spinner is always inside the #message div --> <div style="clear: both;" /> </div> - <p class="grouptop" - :class="{shake: invalidPassword}"> - <input id="user" - ref="user" - v-model="user" - type="text" - name="user" - autocapitalize="none" - autocorrect="off" - :autocomplete="autoCompleteAllowed ? 'on' : 'off'" - :placeholder="t('core', 'Username or email')" - :aria-label="t('core', 'Username or email')" - required - @change="updateUsername"> - <label for="user" class="infield">{{ t('core', 'Username or email') }}</label> - </p> - - <p class="groupbottom" - :class="{shake: invalidPassword}"> - <input id="password" - ref="password" - :type="passwordInputType" - class="password-with-toggle" - name="password" - autocorrect="off" - autocapitalize="none" - :autocomplete="autoCompleteAllowed ? 'current-password' : 'off'" - :placeholder="t('core', 'Password')" - :aria-label="t('core', 'Password')" - required> - <label for="password" - class="infield">{{ t('core', 'Password') }}</label> - <NcButton class="toggle-password" - type="tertiary-no-background" - :aria-label="isPasswordHidden ? t('core', 'Show password') : t('core', 'Hide password')" - @click.stop.prevent="togglePassword"> - <template #icon> - <Eye v-if="isPasswordHidden" :size="20" /> - <EyeOff v-else :size="20" /> - </template> - </NcButton> - </p> + <NcTextField id="user" + ref="user" + :label="t('core', 'Account name or email')" + :label-visible="true" + name="user" + :value.sync="user" + :class="{shake: invalidPassword}" + autocapitalize="none" + :spellchecking="false" + :autocomplete="autoCompleteAllowed ? 'username' : 'off'" + required + @change="updateUsername" /> + + <NcPasswordField id="password" + ref="password" + name="password" + :label-visible="true" + :class="{shake: invalidPassword}" + :value="password" + :spellchecking="false" + autocapitalize="none" + :autocomplete="autoCompleteAllowed ? 'current-password' : 'off'" + :label="t('core', 'Password')" + :helper-text="errorLabel" + :error="isError" + required /> <LoginButton :loading="loading" /> - <p v-if="invalidPassword" - class="warning wrongPasswordMsg"> - {{ t('core', 'Wrong username or password.') }} - </p> - <p v-else-if="userDisabled" - class="warning userDisabledMsg"> - {{ t('core', 'User disabled') }} - </p> - - <p v-if="throttleDelay && throttleDelay > 5000" - class="warning throttledMsg"> - {{ t('core', 'We have detected multiple invalid login attempts from your IP. Therefore your next login is throttled up to 30 seconds.') }} - </p> - <input v-if="redirectUrl" type="hidden" name="redirect_url" @@ -136,20 +106,20 @@ import jstz from 'jstimezonedetect' import { generateUrl, imagePath } from '@nextcloud/router' -import NcButton from '@nextcloud/vue/dist/Components/NcButton' -import Eye from 'vue-material-design-icons/Eye' -import EyeOff from 'vue-material-design-icons/EyeOff' +import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' +import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' -import LoginButton from './LoginButton' +import LoginButton from './LoginButton.vue' export default { name: 'LoginForm', components: { - NcButton, - Eye, - EyeOff, LoginButton, + NcPasswordField, + NcTextField, + NcNoteCard, }, props: { @@ -188,13 +158,28 @@ export default { loading: false, timezone: jstz.determine().name(), timezoneOffset: (-new Date().getTimezoneOffset() / 60), - user: this.username, + user: '', password: '', - passwordInputType: 'password', } }, computed: { + isError() { + return this.invalidPassword || this.userDisabled + || (this.throttleDelay && this.throttleDelay > 5000) + }, + errorLabel() { + if (this.invalidPassword) { + return t('core', 'Wrong username or password.') + } + if (this.userDisabled) { + return t('core', 'User disabled') + } + if (this.throttleDelay && this.throttleDelay > 5000) { + return t('core', 'We have detected multiple invalid login attempts from your IP. Therefore your next login is throttled up to 30 seconds.') + } + return undefined + }, apacheAuthFailed() { return this.errors.indexOf('apacheAuthFailed') !== -1 }, @@ -213,27 +198,18 @@ export default { loginActionUrl() { return generateUrl('login') }, - isPasswordHidden() { - return this.passwordInputType === 'password' - }, }, mounted() { if (this.username === '') { this.$refs.user.focus() } else { + this.user = this.username this.$refs.password.focus() } }, methods: { - togglePassword() { - if (this.passwordInputType === 'password') { - this.passwordInputType = 'text' - } else { - this.passwordInputType = 'password' - } - }, updateUsername() { this.$emit('update:username', this.user) }, @@ -246,10 +222,15 @@ export default { </script> <style lang="scss" scoped> -.toggle-password { - position: absolute; - top: 2px; - right: 10px; - color: var(--color-text-lighter); +.login-form { + text-align: left; + font-size: 1rem; + + &__fieldset { + width: 100%; + display: flex; + flex-direction: column; + gap: .5rem; + } } </style> diff --git a/core/src/components/login/ResetPassword.vue b/core/src/components/login/ResetPassword.vue index aadf7863dfe..72ad7ebd812 100644 --- a/core/src/components/login/ResetPassword.vue +++ b/core/src/components/login/ResetPassword.vue @@ -20,41 +20,37 @@ --> <template> - <form @submit.prevent="submit"> - <fieldset> - <p> - <input id="user" - v-model="user" - type="text" - name="user" - autocapitalize="off" - :placeholder="t('core', 'Username or email')" - :aria-label="t('core', 'Username or email')" - required - @change="updateUsername"> - <!--<?php p($_['user_autofocus'] ? 'autofocus' : ''); ?> + <form class="login-form" @submit.prevent="submit"> + <fieldset class="login-form__fieldset"> + <NcTextField id="user" + :value.sync="user" + name="user" + autocapitalize="off" + :label="t('core', 'Account name or email')" + :label-visible="true" + required + @change="updateUsername" /> + <!--<?php p($_['user_autofocus'] ? 'autofocus' : ''); ?> autocomplete="<?php p($_['login_form_autocomplete']); ?>" autocapitalize="none" autocorrect="off"--> - <label for="user" class="infield">{{ t('core', 'Username or email') }}</label> - </p> - <div id="reset-password-wrapper"> - <LoginButton :value="t('core', 'Reset password')" /> - </div> - <p v-if="message === 'send-success'" - class="notecard success"> + <LoginButton :value="t('core', 'Reset password')" /> + + <NcNoteCard v-if="message === 'send-success'" + type="success"> {{ t('core', 'A password reset message has been sent to the email address of this account. If you do not receive it, check your spam/junk folders or ask your local administrator for help.') }} <br> {{ t('core', 'If it is not there ask your local administrator.') }} - </p> - <p v-else-if="message === 'send-error'" - class="notecard error"> + </NcNoteCard> + <NcNoteCard v-else-if="message === 'send-error'" + type="error"> {{ t('core', 'Couldn\'t send reset email. Please contact your administrator.') }} - </p> - <p v-else-if="message === 'reset-error'" - class="notecard error"> + </NcNoteCard> + <NcNoteCard v-else-if="message === 'reset-error'" + type="error"> {{ t('core', 'Password cannot be changed. Please contact your administrator.') }} - </p> + </NcNoteCard> - <a href="#" + <a class="login-form__link" + href="#" @click.prevent="$emit('abort')"> {{ t('core', 'Back to login') }} </a> @@ -66,11 +62,15 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' import LoginButton from './LoginButton.vue' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' +import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' export default { name: 'ResetPassword', components: { LoginButton, + NcNoteCard, + NcTextField, }, props: { username: { @@ -130,8 +130,26 @@ export default { } </script> -<style scoped> - .update { - width: auto; +<style lang="scss" scoped> +.login-form { + text-align: left; + font-size: 1rem; + + &__fieldset { + width: 100%; + display: flex; + flex-direction: column; + gap: .5rem; } + + &__link { + display: block; + font-weight: normal !important; + padding-bottom: 1rem; + cursor: pointer; + font-size: var(--default-font-size); + text-align: center; + padding: .5rem 1rem 1rem 1rem; + } +} </style> diff --git a/core/src/views/Login.vue b/core/src/views/Login.vue index 0cc8dc39cca..588369e5408 100644 --- a/core/src/views/Login.vue +++ b/core/src/views/Login.vue @@ -20,7 +20,7 @@ --> <template> - <div id="login" class="guest-box"> + <div class="guest-box login-box"> <div v-if="!hideLoginForm || directLogin"> <transition name="fade" mode="out-in"> <div v-if="!passwordlessLogin && !resetPassword && resetPasswordTarget === ''"> @@ -34,16 +34,17 @@ @submit="loading = true" /> <a v-if="canResetPassword && resetPasswordLink !== ''" id="lost-password" + class="login-box__link" :href="resetPasswordLink"> {{ t('core', 'Forgot password?') }} </a> <a v-else-if="canResetPassword && !resetPassword" id="lost-password" + class="login-box__link" :href="resetPasswordLink" @click.prevent="resetPassword = true"> {{ t('core', 'Forgot password?') }} </a> - <br> <template v-if="hasPasswordless"> <div v-if="countAlternativeLogins" class="alternative-logins"> @@ -72,7 +73,7 @@ :is-localhost="isLocalhost" :has-public-key-credential="hasPublicKeyCredential" @submit="loading = true" /> - <a href="#" @click.prevent="passwordlessLogin = false"> + <a href="#" class="login-box__link" @click.prevent="passwordlessLogin = false"> {{ t('core', 'Back') }} </a> </div> @@ -95,19 +96,16 @@ </div> <div v-else> <transition name="fade" mode="out-in"> - <div class="warning"> - {{ t('core', 'Login form is disabled.') }}<br> - <small> - {{ t('core', 'Please contact your administrator.') }} - </small> - </div> + <NcNoteCard type="warning" :title="t('core', 'Login form is disabled.')"> + {{ t('core', 'Please contact your administrator.') }} + </NcNoteCard> </transition> </div> <div id="alternative-logins" class="alternative-logins"> <NcButton v-for="(alternativeLogin, index) in alternativeLogins" :key="index" - type="primary" + type="secondary" :wide="true" :class="[alternativeLogin.class]" role="link" @@ -127,7 +125,8 @@ import LoginForm from '../components/login/LoginForm.vue' import PasswordLessLoginForm from '../components/login/PasswordLessLoginForm.vue' import ResetPassword from '../components/login/ResetPassword.vue' import UpdatePassword from '../components/login/UpdatePassword.vue' -import NcButton from '@nextcloud/vue/dist/Components/NcButton' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' const query = queryString.parse(location.search) if (query.clear === '1') { @@ -149,6 +148,7 @@ export default { ResetPassword, UpdatePassword, NcButton, + NcNoteCard, }, data() { @@ -192,28 +192,35 @@ export default { </script> <style lang="scss"> - .fade-enter-active, .fade-leave-active { - transition: opacity .3s; - } - .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { - opacity: 0; - } +body { + font-size: var(--default-font-size); +} + +.login-box { + width: 300px; - #lost-password { - padding: 4px; - margin: 8px; - border-radius: var(--border-radius); + &__link { + display: block; + padding: 1rem; + font-size: var(--default-font-size); + text-align: center; + font-weight: normal !important; } +} +.fade-enter-active, .fade-leave-active { + transition: opacity .3s; +} +.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { + opacity: 0; +} - .alternative-logins button { - margin-top: 12px; - margin-bottom: 12px; - &:first-child { - margin-top: 0; - } +.alternative-logins { + display: flex; + flex-direction: column; + gap: 0.75rem; - &:last-child { - margin-bottom: 0; - } + .button-vue { + box-sizing: border-box; } +} </style> |