aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2025-03-17 12:40:41 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2025-03-18 16:40:15 +0100
commitfb52d0a02b432012ece47fac51c637fe16d669e8 (patch)
treea1f68d58511ccf985cc4501e95535a4debf95f84
parent8035c8d6b80e81facea6fba362664014acbcf525 (diff)
downloadnextcloud-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.vue1
-rw-r--r--core/src/components/login/PasswordLessLoginForm.vue82
-rw-r--r--core/src/components/login/ResetPassword.vue145
-rw-r--r--core/src/views/Login.vue84
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>