feat: restyle unauthenticated screens #1103

Merged
dpschen merged 29 commits from feature/login-pages into main 2021-12-12 16:40:14 +00:00
10 changed files with 394 additions and 323 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

View File

@ -23,7 +23,7 @@
</div>
</div>
<div class="api-url-info" v-else>
<i18n-t keypath="apiConfig.signInOn">
<i18n-t keypath="apiConfig.use">
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
</i18n-t>
<br/>
@ -101,7 +101,7 @@ export default {
// Set it + save it to local storage to save us the hoops
this.errorMsg = ''
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain})
this.$message.success({message: this.$t('apiConfig.success', {domain: this.apiDomain})})
this.configureApi = false
this.apiUrl = url
this.$emit('foundApi', this.apiUrl)

View File

@ -1,6 +1,8 @@
<template>
<div class="message" :class="variant">
<slot/>
<div class="message-wrapper">
<div class="message" :class="variant">
<slot/>
</div>
</div>
</template>
@ -14,6 +16,11 @@ defineProps({
</script>
<style lang="scss" scoped>
.message-wrapper {
border-radius: $radius;
background: var(--white);
}
konrad marked this conversation as resolved
Review
  • I don't get the need for the new wrapper.

  • What's the overflow for?

- I don't get the need for the new wrapper. - What's the `overflow` for?
Review

That's because the message background is transparent. When I put the message on something with a background, it would shine through. The overlay puts a white background behind it so the message background color is always the same.

I just realized I don't need the overflow so I just removed it.

That's because the message background is transparent. When I put the message on something with a background, it would shine through. The overlay puts a white background behind it so the message background color is always the same. I just realized I don't need the overflow so I just removed it.
.message {
padding: .75rem 1rem;
border-radius: $radius;

View File

@ -1,40 +1,134 @@
<template>
<div class="no-auth-wrapper">
<Logo class="logo" width="200" height="58"/>
<div class="noauth-container">
<Logo class="logo" width="400" height="117" />
<Message v-if="motd !== ''" class="my-2">
{{ motd }}
</Message>
<slot/>
<section class="image" :class="{'has-message': motd !== ''}">
<Message v-if="motd !== ''">
{{ motd }}
</Message>
<h2 class="image-title">
{{ $t('misc.welcomeBack') }}
</h2>
</section>
<section class="content">
<div>
<h2 class="title" v-if="title">{{ title }}</h2>
<api-config @foundApi="hasApiUrl = true"/>
<slot/>
</div>
<legal/>
</section>
</div>
</div>
</template>
<script lang="ts" setup>
import Logo from '@/components/home/Logo.vue'
import Message from '@/components/misc/message.vue'
import Logo from '@/components/home/Logo'
konrad marked this conversation as resolved Outdated

Use @

Use `@`

Done.

Done.
import Message from '@/components/misc/message'
import Legal from '@/components/misc/legal'
import ApiConfig from '@/components/misc/api-config.vue'
import {useStore} from 'vuex'
import {computed} from 'vue'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@/composables/useTitle'
const route = useRoute()
const store = useStore()
const {t} = useI18n()
const motd = computed(() => store.state.config.motd)
konrad marked this conversation as resolved Outdated

Super picky: use place-items: center (I think this is replaced with the same after parsed :D)

Super picky: use `place-items: center` (I think this is replaced with the same after parsed :D)

Oh, that sounds like a nice property, never heard of it before.

Oh, that sounds like a nice property, never heard of it before.
// @ts-ignore
const title = computed(() => t(route.meta.title ?? ''))
useTitle(() => title.value)
</script>
<style lang="scss" scoped>
.no-auth-wrapper {
background: url('@/assets/llama.svg?url') no-repeat bottom left fixed var(--site-background);
background: var(--site-background) url('@/assets/llama.svg?url') no-repeat fixed bottom left;
min-height: 100vh;
konrad marked this conversation as resolved Outdated

Picky:
Use background-color => more specific, doesn't change other values if they are set (they are not in this case). Makes it easier to overwrite if necessary.

Picky: Use `background-color` => more specific, doesn't change other values if they are set (they are not in this case). Makes it easier to overwrite if necessary.
display: flex;
flex-direction: column;
place-items: center;
konrad marked this conversation as resolved Outdated

Why overflow: hidden?

Why overflow: hidden?

To prevent the background from overflowing in the (rounded) corners.

To prevent the background from overflowing in the (rounded) corners.
@media screen and (max-width: $fullhd) {
konrad marked this conversation as resolved Outdated

Use mobile first:

	@media screen and (min-width: $desktop) {
    		border-radius: $radius;
    }

=> no need to reset the border-radius for mobile

Use mobile first: ```scss @media screen and (min-width: $desktop) { border-radius: $radius; } ``` => no need to reset the border-radius for mobile
padding-bottom: 15rem;
konrad marked this conversation as resolved Outdated

Just checked this: The llama is so nice, it's too bad it's not visivle on mobile. How about adding a padding-bottom for mobile?

Just checked this: The llama is so nice, it's too bad it's not visivle on mobile. How about adding a padding-bottom for mobile?

done!

done!
}
}
.noauth-container {
max-width: 450px;
max-width: $desktop;
width: 100%;
konrad marked this conversation as resolved Outdated

Use mobile first:

.image {
    @media screen and (max-width: $tablet) {
        display: none;
    }
    @media screen and (min-width: $tablet) {
        width: 40%;
        background: url('@/assets/no-auth-image.jpg') no-repeat bottom/cover;
        position: relative;
    }
    @media screen and (min-width: $desktop) {
        width: 60%;
    }
}

Compress image and load with something like https://github.com/JonasKruckenberg/imagetools

Use mobile first: ```scss .image { @media screen and (max-width: $tablet) { display: none; } @media screen and (min-width: $tablet) { width: 40%; background: url('@/assets/no-auth-image.jpg') no-repeat bottom/cover; position: relative; } @media screen and (min-width: $desktop) { width: 60%; } } ``` Compress image and load with something like https://github.com/JonasKruckenberg/imagetools

Compress image and load with something like https://github.com/JonasKruckenberg/imagetools

With the vite wrapper and use it for other images as well?

> Compress image and load with something like https://github.com/JonasKruckenberg/imagetools With the vite wrapper and use it for other images as well?

Yes. But maybe let's move this to a new issue.

Yes. But maybe let's move this to a new issue.

yeah I think we should.

yeah I think we should.
margin: 0 auto;
min-height: 60vh;
display: flex;
background-color: var(--white);
box-shadow: var(--shadow-md);
overflow: hidden;
@media screen and (min-width: $desktop) {
border-radius: $radius;
}
}
.image {
width: 50%;
konrad marked this conversation as resolved
Review

Move these rules to the @media screen and (min-width: $tablet) media query
=> makes it clearer that it just appies there.

	width: 50%;
	padding: 1rem;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
    
	&::after {
		content: '';
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background-color: rgba(0, 0, 0, .2);
		z-index: 10;
	}

	> * {
		z-index: 20;
	}

Move media queries before &::after and > *
=> those are styling other elements
=> move styles together that style the same stuff

If you change &::after in &::before you can remove the z-index if you replace
z-index: 20; with position: relative (this creates a new stacking context.
=> less complexity with managing z-index.

Move these rules to the `@media screen and (min-width: $tablet)` media query => makes it clearer that it just appies there. ```scss width: 50%; padding: 1rem; display: flex; flex-direction: column; justify-content: flex-end; &::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, .2); z-index: 10; } > * { z-index: 20; } ``` Move media queries before `&::after` and `> *` => those are styling other elements => move styles together that style the same stuff If you change `&::after` in `&::before` you can remove the z-index if you replace `z-index: 20;` with `position: relative` (this creates a new [stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context). => less complexity with managing z-index.
Review

Done!

Done!
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
@media screen and (max-width: $tablet) {
display: none;
}
@media screen and (min-width: $tablet) {
konrad marked this conversation as resolved Outdated

Since the image is defined via CSS:
Not sure why the overlay is needed at all:
If the image uses flex itself you could position the title and message directly as a child.

Title could have: margin-top: auto or justify-self: flex-end. I think both should work.

EDIT:
I just saw that the overlay is used for the background-overlay. But the same could be archieved without DOM impact / new element by styling a ::after.

I got a warning for end so I guess it's still better to use flex-end.
=> Maybe support is better, and it seems that postcss-preset-env / autoprefixer doesn't change this automatically.

Picky: same for background as above.

Since the image is defined via CSS: Not sure why the overlay is needed at all: If the image uses flex itself you could position the title and message directly as a child. Title could have: `margin-top: auto` or `justify-self: flex-end`. I think both should work. **EDIT:** I just saw that the overlay is used for the background-overlay. But the same could be archieved without DOM impact / new element by styling a `::after`. I got a warning for `end` so I guess it's still better to use `flex-end`. => Maybe support is better, and it seems that postcss-preset-env / autoprefixer doesn't change this automatically. Picky: same for background as above.

Moved this to an after pseudo class.

Moved this to an after pseudo class.
background: url('@/assets/no-auth-image.jpg') no-repeat bottom/cover;
position: relative;
&.has-message {
justify-content: space-between;
}
&::before {
dpschen marked this conversation as resolved Outdated

If .image is either 40% or 60% why is this 50% wide? Also use mobile first for the width value.

If `.image` is either 40% or 60% why is this 50% wide? Also use mobile first for the width value.

Change it so everything is always 50%. That seems to work great.

Also use mobile first for the width value.

I'm not quite sure why but it does not work when I only use min-width: $desktop.

Change it so everything is always 50%. That seems to work great. > Also use mobile first for the width value. I'm not quite sure why but it does not work when I only use `min-width: $desktop`.

The default width of a flex child is auto as far as i know.

The default width of a flex child is auto as far as i know.
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .2);
}
> * {
position: relative;
dpschen marked this conversation as resolved Outdated

On desktop:
Maybe the logo should be not centered but left aligned with the content below.
=> Might make the Title easier to read because there is more room between those two headlines

On desktop: Maybe the logo should be not centered but left aligned with the content below. => Might make the Title easier to read because there is more room between those two headlines

You mean aligned in the middle like this?

You mean aligned in the middle like this?

Okay wow I can't put images in a comment here, here's the screenshot I wanted to post:

Okay wow I can't put images in a comment here, here's the screenshot I wanted to post: ![](https://kolaente.dev/attachments/1847041a-d987-46e1-9d70-99c5745e8306)

I'm also not really happy with this. Let's keep it for now how it is.

I'm also not really happy with this. Let's keep it for now how it is.
}
konrad marked this conversation as resolved Outdated

Color seems to be applied twice (from inside the component aswell).

Color seems to be applied twice (from inside the component aswell).
}
}
konrad marked this conversation as resolved Outdated

Picky: new css love: use margin-block: 1rem; => transformed by postcss-preset-env. Reason: you don't want to define the inline (left, right) value, but do it here as a sideeffect.

Picky: new css love: use `margin-block: 1rem;` => transformed by postcss-preset-env. Reason: you don't want to define the inline (left, right) value, but do it here as a sideeffect.

hmm it looks like this isn't transformed. But it definitely is a nice propery.

hmm it looks like this isn't transformed. But it definitely is a nice propery.

I checked this again.

I think that the browsers that we want to support according to our browserslist all support it natively so there is no need for postcss-preset-env to transform it =)

Because it does do that via postcss-logical

I checked this again. I think that the browsers that we want to support according to our browserslist all support it natively so there is no need for postcss-preset-env to transform it =) Because it does do that via [postcss-logical](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-logical)
.content {
display: flex;
konrad marked this conversation as resolved Outdated

Add class to Message component and use that.
=> By using a class inside the component we would create an implicit dependency.

Add class to Message component and use that. => By using a class inside the component we would create an implicit dependency.

It looks like the class here wasn't used anywhere. I've removed it.

It looks like the class here wasn't used anywhere. I've removed it.

Use mobile first:

.content {
    // start with common stuff
    display: flex;
    justify-content: space-between;
    flex-direction: column;
	padding: 2rem 2rem 1.5rem;
    
    // define stuff that applies mobile only
	@media screen and (max-width: $desktop) {
        width: 100%;
		max-width: 450px;
		margin-inline: auto;
	}
    
    // define desktop only stuff
    @media screen and (min-width: $desktop) {
		width: 50%;
	}
}
Use mobile first: ```scss .content { // start with common stuff display: flex; justify-content: space-between; flex-direction: column; padding: 2rem 2rem 1.5rem; // define stuff that applies mobile only @media screen and (max-width: $desktop) { width: 100%; max-width: 450px; margin-inline: auto; } // define desktop only stuff @media screen and (min-width: $desktop) { width: 50%; } } ```

Done!

Done!
justify-content: space-between;
flex-direction: column;
padding: 2rem 2rem 1.5rem;
@media screen and (max-width: $desktop) {
width: 100%;
max-width: 450px;
margin-inline: auto;
}
@media screen and (min-width: $desktop) {
width: 50%;
}
}
.logo {
color: var(--logo-text-color);
max-width: 100%;
margin: 1rem 0;
}
.image-title {
color: var(--white);
font-size: 2.5rem;
}
</style>

View File

@ -36,6 +36,7 @@
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
"resetPasswordAction": "Send me a password reset link",
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
@ -473,7 +474,8 @@
"download": "Download",
"showMenu": "Show the menu",
"hideMenu": "Hide the menu",
"forExample": "For example:"
"forExample": "For example:",
"welcomeBack": "Welcome Back!"
},
"input": {
"resetColor": "Reset Color",
@ -811,7 +813,7 @@
"url": "Vikunja URL",
"urlPlaceholder": "eg. https://localhost:3456",
"change": "change",
"signInOn": "Sign in to your Vikunja account on {0}",
"use": "Using Vikunja installation at {0}",
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
"success": "Using Vikunja installation at \"{domain}\".",
"urlRequired": "A url is required."

View File

@ -105,21 +105,33 @@ const router = createRouter({
path: '/login',
name: 'user.login',
component: LoginComponent,
meta: {
title: 'user.auth.login',
},
},
{
path: '/get-password-reset',
name: 'user.password-reset.request',
component: GetPasswordResetComponent,
meta: {
title: 'user.auth.resetPassword',
},
},
{
path: '/password-reset',
name: 'user.password-reset.reset',
component: PasswordResetComponent,
meta: {
title: 'user.auth.resetPassword',
},
},
{
path: '/register',
name: 'user.register',
component: RegisterComponent,
meta: {
title: 'user.auth.register',
},
},
{
path: '/user/settings',

View File

@ -1,103 +1,97 @@
<template>
<div>
<h2 class="title has-text-centered">Login</h2>
<div class="box">
<message variant="success" class="has-text-centered" v-if="confirmedEmailSuccess">
{{ $t('user.auth.confirmEmailSuccess') }}
</message>
<api-config @foundApi="hasApiUrl = true"/>
<form @submit.prevent="submit" id="loginform" v-if="hasApiUrl && localAuthEnabled">
<div class="field">
<label class="label" for="username">{{ $t('user.auth.usernameEmail') }}</label>
<div class="control">
<input
class="input" id="username"
name="username"
:placeholder="$t('user.auth.usernamePlaceholder')"
ref="username"
required
type="text"
autocomplete="username"
v-focus
@keyup.enter="submit"
/>
</div>
<message variant="success" class="has-text-centered" v-if="confirmedEmailSuccess">
{{ $t('user.auth.confirmEmailSuccess') }}
</message>
<message variant="danger" v-if="errorMessage">
{{ errorMessage }}
konrad marked this conversation as resolved Outdated

There is no <api-config> in the Register and Reset-password route. Should it be there aswell?

There is no `<api-config>` in the Register and Reset-password route. Should it be there aswell?

I think it should. I've moved the <api-config/> component to the wrapper and the logo as well in the process. Should be better now.

I think it should. I've moved the `<api-config/>` component to the wrapper and the logo as well in the process. Should be better now.
</message>
<form @submit.prevent="submit" id="loginform" v-if="localAuthEnabled">
<div class="field">
<label class="label" for="username">{{ $t('user.auth.usernameEmail') }}</label>
<div class="control">
<input
class="input" id="username"
name="username"
:placeholder="$t('user.auth.usernamePlaceholder')"
ref="username"
required
type="text"
autocomplete="username"
v-focus
@keyup.enter="submit"
/>
</div>
<div class="field">
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
ref="password"
required
type="password"
autocomplete="current-password"
@keyup.enter="submit"
/>
</div>
</div>
<div class="field">
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
ref="password"
required
type="password"
autocomplete="current-password"
@keyup.enter="submit"
/>
</div>
<div class="field" v-if="needsTotpPasscode">
<label class="label" for="totpPasscode">{{ $t('user.auth.totpTitle') }}</label>
<div class="control">
<input
autocomplete="one-time-code"
class="input"
id="totpPasscode"
:placeholder="$t('user.auth.totpPlaceholder')"
ref="totpPasscode"
required
type="text"
v-focus
@keyup.enter="submit"
/>
</div>
</div>
<div class="field" v-if="needsTotpPasscode">
<label class="label" for="totpPasscode">{{ $t('user.auth.totpTitle') }}</label>
<div class="control">
<input
autocomplete="one-time-code"
class="input"
id="totpPasscode"
:placeholder="$t('user.auth.totpPlaceholder')"
ref="totpPasscode"
required
type="text"
v-focus
@keyup.enter="submit"
/>
</div>
<div class="field is-grouped login-buttons">
<div class="control is-expanded">
<x-button
@click="submit"
:loading="loading"
>
{{ $t('user.auth.login') }}
</x-button>
<x-button
:to="{ name: 'user.register' }"
v-if="registrationEnabled"
type="secondary"
>
{{ $t('user.auth.register') }}
</x-button>
</div>
<div class="control">
<router-link :to="{ name: 'user.password-reset.request' }" class="reset-password-link">
{{ $t('user.auth.resetPassword') }}
</router-link>
</div>
</div>
<message variant="danger" v-if="errorMessage">
{{ errorMessage }}
</message>
</form>
<div
v-if="hasOpenIdProviders"
class="mt-4">
<x-button
@click="redirectToProvider(p)"
v-for="(p, k) in openidConnect.providers"
:key="k"
type="secondary"
class="is-fullwidth mt-2"
>
{{ $t('user.auth.loginWith', {provider: p.name}) }}
</x-button>
</div>
<legal/>
<div class="field is-grouped login-buttons">
<div class="control is-expanded">
konrad marked this conversation as resolved Outdated

Make login button larger
=> "hard" to tap on mobile if you keep in mind that this is the primary action of the page.

Make login button larger => "hard" to tap on mobile if you keep in mind that this is the primary action of the page.

Would like to do that in #1104 while changing the register link wording you mentioned earlier.

Would like to do that in #1104 while changing the register link wording you mentioned earlier.
<x-button
@click="submit"
:loading="loading"
>
{{ $t('user.auth.login') }}
</x-button>
<x-button
:to="{ name: 'user.register' }"
v-if="registrationEnabled"
type="secondary"
>
{{ $t('user.auth.register') }}
</x-button>
</div>
<div class="control">
<router-link :to="{ name: 'user.password-reset.request' }" class="reset-password-link">
{{ $t('user.auth.forgotPassword') }}
</router-link>
</div>
</div>
konrad marked this conversation as resolved Outdated

The error message should be on top => can't see on small screens without scrolling

The error message should be on top => can't see on small screens without scrolling

Done.

Done.
</form>
<div
v-if="hasOpenIdProviders"
class="mt-4">
<x-button
@click="redirectToProvider(p)"
v-for="(p, k) in openidConnect.providers"
:key="k"
type="secondary"
class="is-fullwidth mt-2"
>
{{ $t('user.auth.loginWith', {provider: p.name}) }}
</x-button>
</div>
</div>
</template>
@ -107,8 +101,6 @@ import {mapState} from 'vuex'
import {HTTPFactory} from '@/http-common'
import {LOADING} from '@/store/mutation-types'
import legal from '../../components/misc/legal'
import ApiConfig from '@/components/misc/api-config.vue'
import {getErrorText} from '@/message'
import Message from '@/components/misc/message'
import {redirectToProvider} from '../../helpers/redirectToProvider'
@ -117,13 +109,10 @@ import {getLastVisited, clearLastVisited} from '../../helpers/saveLastVisited'
export default {
components: {
Message,
ApiConfig,
legal,
},
data() {
return {
confirmedEmailSuccess: false,
hasApiUrl: false,
errorMessage: '',
}
},
@ -161,13 +150,11 @@ export default {
}
},
created() {
this.hasApiUrl = window.API_URL !== ''
this.setTitle(this.$t('user.auth.login'))
},
computed: {
hasOpenIdProviders() {
return this.hasApiUrl &&
this.openidConnect.enabled &&
return this.openidConnect.enabled &&
this.openidConnect.providers &&
this.openidConnect.providers.length > 0
},

View File

@ -1,67 +1,60 @@
<template>
<div>
<h2 class="title has-text-centered">{{ $t('user.auth.resetPassword') }}</h2>
<div class="box">
<form @submit.prevent="submit" id="form" v-if="!successMessage">
<div class="field">
<label class="label" for="password1">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password1"
name="password1"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-focus
v-model="credentials.password"/>
</div>
</div>
<div class="field">
<label class="label" for="password2">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control">
<input
class="input"
id="password2"
name="password2"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-model="credentials.password2"
@keyup.enter="submit"
/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<x-button
:loading="this.passwordResetService.loading"
@click="submit"
>
{{ $t('user.auth.resetPassword') }}
</x-button>
</div>
</div>
<message v-if="this.passwordResetService.loading">
{{ $t('misc.loading') }}
</message>
<message v-if="errorMsg">
{{ errorMsg }}
</message>
</form>
<div class="has-text-centered" v-if="successMessage">
<message variant="success">
{{ successMessage }}
</message>
<x-button :to="{ name: 'user.login' }">
{{ $t('user.auth.login') }}
</x-button>
</div>
<Legal/>
<message v-if="errorMsg">
{{ errorMsg }}
</message>
<div class="has-text-centered" v-if="successMessage">
<message variant="success">
{{ successMessage }}
</message>
<x-button :to="{ name: 'user.login' }">
{{ $t('user.auth.login') }}
</x-button>
</div>
<form @submit.prevent="submit" id="form" v-if="!successMessage">
<div class="field">
<label class="label" for="password1">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password1"
name="password1"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-focus
v-model="credentials.password"/>
</div>
</div>
<div class="field">
<label class="label" for="password2">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control">
<input
class="input"
id="password2"
name="password2"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-model="credentials.password2"
@keyup.enter="submit"
/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<x-button
:loading="this.passwordResetService.loading"
@click="submit"
>
{{ $t('user.auth.resetPassword') }}
</x-button>
</div>
konrad marked this conversation as resolved Outdated

Move error and succes to top here aswell.

Move error and succes to top here aswell.
</div>
</form>
</div>
</template>
@ -69,14 +62,11 @@
import {ref, reactive} from 'vue'
import {useI18n} from 'vue-i18n'
import Legal from '@/components/misc/legal'
import PasswordResetModel from '@/models/passwordReset'
import PasswordResetService from '@/services/passwordReset'
import {useTitle} from '@/composables/useTitle'
import Message from '@/components/misc/message'
const {t} = useI18n()
useTitle(() => t('user.auth.resetPassword'))
const credentials = reactive({
password: '',

View File

@ -1,97 +1,90 @@
<template>
<div>
<h2 class="title has-text-centered">{{ $t('user.auth.register') }}</h2>
<div class="box">
<form @submit.prevent="submit" id="registerform">
<div class="field">
<label class="label" for="username">{{ $t('user.auth.username') }}</label>
<div class="control">
<input
class="input"
id="username"
name="username"
:placeholder="$t('user.auth.usernamePlaceholder')"
required
type="text"
autocomplete="username"
v-focus
v-model="credentials.username"
@keyup.enter="submit"
/>
</div>
<message variant="danger" v-if="errorMessage !== ''">
{{ errorMessage }}
</message>
<form @submit.prevent="submit" id="registerform">
<div class="field">
<label class="label" for="username">{{ $t('user.auth.username') }}</label>
<div class="control">
<input
class="input"
id="username"
name="username"
:placeholder="$t('user.auth.usernamePlaceholder')"
required
type="text"
autocomplete="username"
v-focus
v-model="credentials.username"
@keyup.enter="submit"
/>
</div>
<div class="field">
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
<div class="control">
<input
class="input"
id="email"
name="email"
:placeholder="$t('user.auth.emailPlaceholder')"
required
type="email"
v-model="credentials.email"
@keyup.enter="submit"
/>
</div>
</div>
<div class="field">
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
<div class="control">
<input
class="input"
id="email"
name="email"
:placeholder="$t('user.auth.emailPlaceholder')"
required
type="email"
v-model="credentials.email"
@keyup.enter="submit"
/>
</div>
<div class="field">
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-model="credentials.password"
@keyup.enter="submit"
/>
</div>
</div>
<div class="field">
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-model="credentials.password"
@keyup.enter="submit"
/>
</div>
<div class="field">
<label class="label" for="passwordValidation">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control">
<input
class="input"
id="passwordValidation"
name="passwordValidation"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-model="passwordValidation"
@keyup.enter="submit"
/>
</div>
</div>
<div class="field">
<label class="label" for="passwordValidation">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control">
<input
class="input"
id="passwordValidation"
name="passwordValidation"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-model="passwordValidation"
@keyup.enter="submit"
/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<x-button
:loading="loading"
id="register-submit"
@click="submit"
class="mr-2"
>
{{ $t('user.auth.register') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
{{ $t('user.auth.login') }}
</x-button>
</div>
<div class="field is-grouped">
<div class="control">
<x-button
:loading="loading"
id="register-submit"
@click="submit"
class="mr-2"
>
{{ $t('user.auth.register') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
{{ $t('user.auth.login') }}
</x-button>
</div>
<message v-if="loading">
{{ $t('misc.loading') }}
</message>
<message variant="danger" v-if="errorMessage !== ''">
{{ errorMessage }}
</message>
</form>
<legal/>
</div>
</div>
</form>
</div>
</template>
@ -101,8 +94,6 @@ import {useI18n} from 'vue-i18n'
import router from '@/router'
import {store} from '@/store'
import {useTitle} from '@/composables/useTitle'
import Legal from '@/components/misc/legal'
import Message from '@/components/misc/message'
// FIXME: use the `beforeEnter` hook of vue-router
@ -114,7 +105,6 @@ onBeforeMount(() => {
})
const {t} = useI18n()
useTitle(() => t('user.auth.register'))
const credentials = reactive({
username: '',

View File

@ -1,67 +1,56 @@
<template>
<div>
<h2 class="title has-text-centered">{{ $t('user.auth.resetPassword') }}</h2>
<div class="box">
<form @submit.prevent="submit" v-if="!isSuccess">
<div class="field">
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
<div class="control">
<input
class="input"
id="email"
name="email"
:placeholder="$t('user.auth.emailPlaceholder')"
required
type="email"
v-focus
v-model="passwordReset.email"/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<x-button
@click="submit"
:loading="passwordResetService.loading"
>
{{ $t('user.auth.resetPasswordAction') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
{{ $t('user.auth.login') }}
</x-button>
</div>
</div>
<message variant="danger" v-if="errorMsg">
{{ errorMsg }}
</message>
</form>
<div class="has-text-centered" v-if="isSuccess">
<message variant="success">
{{ $t('user.auth.resetPasswordSuccess') }}
</message>
<x-button :to="{ name: 'user.login' }">
{{ $t('user.auth.login') }}
</x-button>
</div>
<Legal />
<message variant="danger" v-if="errorMsg">
{{ errorMsg }}
</message>
<div class="has-text-centered" v-if="isSuccess">
<message variant="success">
{{ $t('user.auth.resetPasswordSuccess') }}
</message>
<x-button :to="{ name: 'user.login' }">
{{ $t('user.auth.login') }}
</x-button>
</div>
<form @submit.prevent="submit" v-if="!isSuccess">
<div class="field">
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
<div class="control">
<input
class="input"
id="email"
name="email"
:placeholder="$t('user.auth.emailPlaceholder')"
required
type="email"
v-focus
v-model="passwordReset.email"/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<x-button
@click="submit"
konrad marked this conversation as resolved
Review

Move error messages here to top aswell.

Move error messages here to top aswell.
:loading="passwordResetService.loading"
>
{{ $t('user.auth.resetPasswordAction') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
{{ $t('user.auth.login') }}
</x-button>
</div>
</div>
</form>
</div>
</template>
<script setup>
import {ref, reactive} from 'vue'
import { useI18n } from 'vue-i18n'
import Legal from '@/components/misc/legal'
import PasswordResetModel from '@/models/passwordReset'
import PasswordResetService from '@/services/passwordReset'
import { useTitle } from '@/composables/useTitle'
import Message from '@/components/misc/message'
const { t } = useI18n()
useTitle(() => t('user.auth.resetPassword'))
// Not sure if this instance needs a shalloRef at all
const passwordResetService = reactive(new PasswordResetService())
const passwordReset = ref(new PasswordResetModel())
@ -73,7 +62,7 @@ async function submit() {
try {
await passwordResetService.requestResetPassword(passwordReset.value)
isSuccess.value = true
} catch(e) {
} catch (e) {
errorMsg.value = e.response.data.message
}
}