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>
pull/51540/head
Ferdinand Thiessen 1 month ago
parent 8035c8d6b8
commit fb52d0a02b
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
  1. 1
      core/src/components/login/LoginForm.vue
  2. 82
      core/src/components/login/PasswordLessLoginForm.vue
  3. 145
      core/src/components/login/ResetPassword.vue
  4. 84
      core/src/views/Login.vue

@ -292,6 +292,7 @@ export default {
.login-form {
text-align: start;
font-size: 1rem;
margin: 0;
&__fieldset {
width: 100%;

@ -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>

@ -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>

@ -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>

Loading…
Cancel
Save