feature/use-setup-api-for-user-and-about-pages (#929)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/929
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
This commit is contained in:
dpschen 2021-11-14 20:57:36 +00:00 committed by konrad
parent 507a73e74c
commit d0d4096f8b
9 changed files with 167 additions and 202 deletions

View file

@ -24,8 +24,8 @@ context('Registration', () => {
cy.visit('/register')
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password1').type(fixture.password)
cy.get('#password2').type(fixture.password)
cy.get('#password').type(fixture.password)
cy.get('#passwordValidation').type(fixture.password)
cy.get('#register-submit').click()
cy.url().should('include', '/')
cy.clock(1625656161057) // 13:00
@ -42,8 +42,8 @@ context('Registration', () => {
cy.visit('/register')
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password1').type(fixture.password)
cy.get('#password2').type(fixture.password)
cy.get('#password').type(fixture.password)
cy.get('#passwordValidation').type(fixture.password)
cy.get('#register-submit').click()
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
})

View file

@ -0,0 +1,12 @@
import { computed, watchEffect } from 'vue'
import { setTitle } from '@/helpers/setTitle'
import { ComputedGetter, ComputedRef } from '@vue/reactivity'
export function useTitle<T>(titleGetter: ComputedGetter<T>) : ComputedRef<T> {
const titleRef = computed(titleGetter)
watchEffect(() => setTitle(titleRef.value))
return titleRef
}

View file

@ -1,8 +1,5 @@
export const setTitle = title => {
if (typeof title === 'undefined' || title === '') {
document.title = 'Vikunja'
return
}
document.title = `${title} | Vikunja`
export function setTitle(title) {
document.title = (typeof title === 'undefined' || title === '')
? 'Vikunja'
: `${title} | Vikunja`
}

View file

@ -13,10 +13,10 @@
>
<div class="p-4">
<p>
{{ $t('about.frontendVersion', {version: this.frontendVersion}) }}
{{ $t('about.frontendVersion', {version: frontendVersion}) }}
</p>
<p>
{{ $t('about.apiVersion', {version: this.apiVersion}) }}
{{ $t('about.apiVersion', {version: apiVersion}) }}
</p>
</div>
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
@ -32,18 +32,11 @@
</template>
<script>
import {VERSION} from '../version.json'
<script setup>
import {computed} from 'vue'
export default {
name: 'About',
computed: {
frontendVersion() {
return VERSION
},
apiVersion() {
return this.$store.state.config.version
},
},
}
import { store } from '@/store'
import {VERSION as frontendVersion} from '@/version.json'
const apiVersion = computed(() => store.state.config.version)
</script>

View file

@ -35,36 +35,25 @@
</div>
</template>
<script>
import DataExportService from '../../services/dataExport'
<script setup>
import {ref, computed, reactive} from 'vue'
import DataExportService from '@/services/dataExport'
import {store} from '@/store'
export default {
name: 'data-export-download',
data() {
return {
dataExportService: DataExportService,
password: '',
errPasswordRequired: false,
}
},
created() {
this.dataExportService = new DataExportService()
},
computed: {
isLocalUser() {
return this.$store.state.auth.info?.isLocalUser
},
},
methods: {
download() {
if (this.password === '' && this.isLocalUser) {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
const dataExportService = reactive(new DataExportService())
const password = ref('')
const errPasswordRequired = ref(false)
const passwordInput = ref(null)
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
function download() {
if (password.value === '' && isLocalUser.value) {
errPasswordRequired.value = true
passwordInput.value.focus()
return
}
this.dataExportService.download(this.password)
},
},
dataExportService.download(password.value)
}
</script>

View file

@ -60,55 +60,49 @@
{{ $t('user.auth.login') }}
</x-button>
</div>
<legal/>
<Legal />
</div>
</div>
</template>
<script>
import PasswordResetModel from '../../models/passwordReset'
import PasswordResetService from '../../services/passwordReset'
import Legal from '../../components/misc/legal'
<script setup>
import {ref, reactive} from 'vue'
import { useI18n } from 'vue-i18n'
export default {
components: {
Legal,
},
data() {
return {
passwordResetService: new PasswordResetService(),
credentials: {
import Legal from '@/components/misc/legal'
import PasswordResetModel from '@/models/passwordReset'
import PasswordResetService from '@/services/passwordReset'
import { useTitle } from '@/composables/useTitle'
const { t } = useI18n()
useTitle(() => t('user.auth.resetPassword'))
const credentials = reactive({
password: '',
password2: '',
},
errorMsg: '',
successMessage: '',
}
},
})
mounted() {
this.setTitle(this.$t('user.auth.resetPassword'))
},
const passwordResetService = reactive(new PasswordResetService())
const errorMsg = ref('')
const successMessage = ref('')
methods: {
async submit() {
this.errorMsg = ''
async function submit() {
errorMsg.value = ''
if (this.credentials.password2 !== this.credentials.password) {
this.errorMsg = this.$t('user.auth.passwordsDontMatch')
if (credentials.password2 !== credentials.password) {
errorMsg.value = t('user.auth.passwordsDontMatch')
return
}
let passwordReset = new PasswordResetModel({newPassword: this.credentials.password})
const passwordReset = new PasswordResetModel({newPassword: credentials.password})
try {
const { message } = this.passwordResetService.resetPassword(passwordReset)
this.successMessage = message
const { message } = passwordResetService.resetPassword(passwordReset)
successMessage.value = message
localStorage.removeItem('passwordResetToken')
} catch(e) {
this.errorMsg = e.response.data.message
errorMsg.value = e.response.data.message
}
},
},
}
</script>

View file

@ -36,12 +36,12 @@
</div>
</div>
<div class="field">
<label class="label" for="password1">{{ $t('user.auth.password') }}</label>
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password1"
name="password1"
id="password"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
@ -52,17 +52,17 @@
</div>
</div>
<div class="field">
<label class="label" for="password2">{{ $t('user.auth.passwordRepeat') }}</label>
<label class="label" for="passwordValidation">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control">
<input
class="input"
id="password2"
name="password2"
id="passwordValidation"
name="passwordValidation"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
v-model="credentials.password2"
v-model="passwordValidation"
@keyup.enter="submit"
/>
</div>
@ -95,61 +95,50 @@
</div>
</template>
<script>
import router from '../../router'
import {mapState} from 'vuex'
import {LOADING} from '@/store/mutation-types'
import Legal from '../../components/misc/legal'
<script setup>
import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
import { useI18n } from 'vue-i18n'
export default {
components: {
Legal,
},
data() {
return {
credentials: {
import router from '@/router'
import { store } from '@/store'
import { useTitle } from '@/composables/useTitle'
import Legal from '@/components/misc/legal'
// FIXME: use the `beforeEnter` hook of vue-router
// Check if the user is already logged in, if so, redirect them to the homepage
onBeforeMount(() => {
if (store.state.auth.authenticated) {
router.push({name: 'home'})
}
})
const { t } = useI18n()
useTitle(() => t('user.auth.register'))
const credentials = reactive({
username: '',
email: '',
password: '',
password2: '',
},
errorMessage: '',
}
},
beforeMount() {
// Check if the user is already logged in, if so, redirect them to the homepage
if (this.authenticated) {
router.push({name: 'home'})
}
},
mounted() {
this.setTitle(this.$t('user.auth.register'))
},
computed: mapState({
authenticated: state => state.auth.authenticated,
loading: LOADING,
}),
methods: {
async submit() {
this.errorMessage = ''
})
const passwordValidation = ref('')
if (this.credentials.password2 !== this.credentials.password) {
this.errorMessage = this.$t('user.auth.passwordsDontMatch')
const loading = computed(() => store.state.loading)
const errorMessage = ref('')
async function submit() {
errorMessage.value = ''
if (credentials.password !== passwordValidation.value) {
errorMessage.value = t('user.auth.passwordsDontMatch')
return
}
const credentials = {
username: this.credentials.username,
email: this.credentials.email,
password: this.credentials.password,
}
try {
await this.$store.dispatch('auth/register', credentials)
await store.dispatch('auth/register', toRaw(credentials))
} catch(e) {
this.errorMessage = e.message
errorMessage.value = e.message
}
},
},
}
</script>

View file

@ -43,42 +43,38 @@
{{ $t('user.auth.login') }}
</x-button>
</div>
<legal/>
<Legal />
</div>
</div>
</template>
<script>
import PasswordResetModel from '../../models/passwordReset'
import PasswordResetService from '../../services/passwordReset'
import Legal from '../../components/misc/legal'
<script setup>
import {ref, reactive} from 'vue'
import { useI18n } from 'vue-i18n'
export default {
components: {
Legal,
},
data() {
return {
passwordResetService: new PasswordResetService(),
passwordReset: new PasswordResetModel(),
errorMsg: '',
isSuccess: false,
}
},
mounted() {
this.setTitle(this.$t('user.auth.resetPassword'))
},
methods: {
async submit() {
this.errorMsg = ''
import Legal from '@/components/misc/legal'
import PasswordResetModel from '@/models/passwordReset'
import PasswordResetService from '@/services/passwordReset'
import { useTitle } from '@/composables/useTitle'
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())
const errorMsg = ref('')
const isSuccess = ref(false)
async function submit() {
errorMsg.value = ''
try {
await this.passwordResetService.requestResetPassword(this.passwordReset)
this.isSuccess = true
await passwordResetService.requestResetPassword(passwordReset.value)
isSuccess.value = true
} catch(e) {
this.errorMsg = e.response.data.message
errorMsg.value = e.response.data.message
}
},
},
}
</script>

View file

@ -57,24 +57,19 @@
</div>
</template>
<script>
import {mapState} from 'vuex'
<script setup>
import {computed} from 'vue'
import { store } from '@/store'
import { useI18n } from 'vue-i18n'
import { useTitle } from '@/composables/useTitle'
export default {
name: 'Settings',
mounted() {
this.setTitle(this.$t('user.settings.title'))
},
computed: {
...mapState('config', ['totpEnabled', 'caldavEnabled']),
migratorsEnabled() {
return this.$store.getters['config/migratorsEnabled']
},
isLocalUser() {
return this.$store.state.auth.info?.isLocalUser
},
},
}
const { t } = useI18n()
useTitle(() => t('user.settings.title'))
const totpEnabled = computed(() => store.state.config.totpEnabled)
const caldavEnabled = computed(() => store.state.config.caldavEnabled)
const migratorsEnabled = computed(() => store.getters['config/migratorsEnabled'])
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
</script>
<style lang="scss" scoped>