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:
parent
507a73e74c
commit
d0d4096f8b
9 changed files with 167 additions and 202 deletions
|
@ -24,8 +24,8 @@ context('Registration', () => {
|
||||||
cy.visit('/register')
|
cy.visit('/register')
|
||||||
cy.get('#username').type(fixture.username)
|
cy.get('#username').type(fixture.username)
|
||||||
cy.get('#email').type(fixture.email)
|
cy.get('#email').type(fixture.email)
|
||||||
cy.get('#password1').type(fixture.password)
|
cy.get('#password').type(fixture.password)
|
||||||
cy.get('#password2').type(fixture.password)
|
cy.get('#passwordValidation').type(fixture.password)
|
||||||
cy.get('#register-submit').click()
|
cy.get('#register-submit').click()
|
||||||
cy.url().should('include', '/')
|
cy.url().should('include', '/')
|
||||||
cy.clock(1625656161057) // 13:00
|
cy.clock(1625656161057) // 13:00
|
||||||
|
@ -42,8 +42,8 @@ context('Registration', () => {
|
||||||
cy.visit('/register')
|
cy.visit('/register')
|
||||||
cy.get('#username').type(fixture.username)
|
cy.get('#username').type(fixture.username)
|
||||||
cy.get('#email').type(fixture.email)
|
cy.get('#email').type(fixture.email)
|
||||||
cy.get('#password1').type(fixture.password)
|
cy.get('#password').type(fixture.password)
|
||||||
cy.get('#password2').type(fixture.password)
|
cy.get('#passwordValidation').type(fixture.password)
|
||||||
cy.get('#register-submit').click()
|
cy.get('#register-submit').click()
|
||||||
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
|
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
|
||||||
})
|
})
|
||||||
|
|
12
src/composables/useTitle.ts
Normal file
12
src/composables/useTitle.ts
Normal 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
|
||||||
|
}
|
|
@ -1,8 +1,5 @@
|
||||||
export const setTitle = title => {
|
export function setTitle(title) {
|
||||||
if (typeof title === 'undefined' || title === '') {
|
document.title = (typeof title === 'undefined' || title === '')
|
||||||
document.title = 'Vikunja'
|
? 'Vikunja'
|
||||||
return
|
: `${title} | Vikunja`
|
||||||
}
|
|
||||||
|
|
||||||
document.title = `${title} | Vikunja`
|
|
||||||
}
|
}
|
|
@ -13,10 +13,10 @@
|
||||||
>
|
>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<p>
|
<p>
|
||||||
{{ $t('about.frontendVersion', {version: this.frontendVersion}) }}
|
{{ $t('about.frontendVersion', {version: frontendVersion}) }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ $t('about.apiVersion', {version: this.apiVersion}) }}
|
{{ $t('about.apiVersion', {version: apiVersion}) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
|
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
|
||||||
|
@ -32,18 +32,11 @@
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import {VERSION} from '../version.json'
|
import {computed} from 'vue'
|
||||||
|
|
||||||
export default {
|
import { store } from '@/store'
|
||||||
name: 'About',
|
import {VERSION as frontendVersion} from '@/version.json'
|
||||||
computed: {
|
|
||||||
frontendVersion() {
|
const apiVersion = computed(() => store.state.config.version)
|
||||||
return VERSION
|
|
||||||
},
|
|
||||||
apiVersion() {
|
|
||||||
return this.$store.state.config.version
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -35,36 +35,25 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import DataExportService from '../../services/dataExport'
|
import {ref, computed, reactive} from 'vue'
|
||||||
|
import DataExportService from '@/services/dataExport'
|
||||||
|
import {store} from '@/store'
|
||||||
|
|
||||||
export default {
|
const dataExportService = reactive(new DataExportService())
|
||||||
name: 'data-export-download',
|
const password = ref('')
|
||||||
data() {
|
const errPasswordRequired = ref(false)
|
||||||
return {
|
const passwordInput = ref(null)
|
||||||
dataExportService: DataExportService,
|
|
||||||
password: '',
|
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
|
||||||
errPasswordRequired: false,
|
|
||||||
}
|
function download() {
|
||||||
},
|
if (password.value === '' && isLocalUser.value) {
|
||||||
created() {
|
errPasswordRequired.value = true
|
||||||
this.dataExportService = new DataExportService()
|
passwordInput.value.focus()
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isLocalUser() {
|
|
||||||
return this.$store.state.auth.info?.isLocalUser
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
download() {
|
|
||||||
if (this.password === '' && this.isLocalUser) {
|
|
||||||
this.errPasswordRequired = true
|
|
||||||
this.$refs.passwordInput.focus()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataExportService.download(this.password)
|
dataExportService.download(password.value)
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -60,55 +60,49 @@
|
||||||
{{ $t('user.auth.login') }}
|
{{ $t('user.auth.login') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
</div>
|
</div>
|
||||||
<legal/>
|
<Legal />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import PasswordResetModel from '../../models/passwordReset'
|
import {ref, reactive} from 'vue'
|
||||||
import PasswordResetService from '../../services/passwordReset'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Legal from '../../components/misc/legal'
|
|
||||||
|
|
||||||
export default {
|
import Legal from '@/components/misc/legal'
|
||||||
components: {
|
|
||||||
Legal,
|
import PasswordResetModel from '@/models/passwordReset'
|
||||||
},
|
import PasswordResetService from '@/services/passwordReset'
|
||||||
data() {
|
import { useTitle } from '@/composables/useTitle'
|
||||||
return {
|
|
||||||
passwordResetService: new PasswordResetService(),
|
const { t } = useI18n()
|
||||||
credentials: {
|
useTitle(() => t('user.auth.resetPassword'))
|
||||||
|
|
||||||
|
const credentials = reactive({
|
||||||
password: '',
|
password: '',
|
||||||
password2: '',
|
password2: '',
|
||||||
},
|
})
|
||||||
errorMsg: '',
|
|
||||||
successMessage: '',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
const passwordResetService = reactive(new PasswordResetService())
|
||||||
this.setTitle(this.$t('user.auth.resetPassword'))
|
const errorMsg = ref('')
|
||||||
},
|
const successMessage = ref('')
|
||||||
|
|
||||||
methods: {
|
async function submit() {
|
||||||
async submit() {
|
errorMsg.value = ''
|
||||||
this.errorMsg = ''
|
|
||||||
|
|
||||||
if (this.credentials.password2 !== this.credentials.password) {
|
if (credentials.password2 !== credentials.password) {
|
||||||
this.errorMsg = this.$t('user.auth.passwordsDontMatch')
|
errorMsg.value = t('user.auth.passwordsDontMatch')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let passwordReset = new PasswordResetModel({newPassword: this.credentials.password})
|
const passwordReset = new PasswordResetModel({newPassword: credentials.password})
|
||||||
try {
|
try {
|
||||||
const { message } = this.passwordResetService.resetPassword(passwordReset)
|
const { message } = passwordResetService.resetPassword(passwordReset)
|
||||||
this.successMessage = message
|
successMessage.value = message
|
||||||
localStorage.removeItem('passwordResetToken')
|
localStorage.removeItem('passwordResetToken')
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.errorMsg = e.response.data.message
|
errorMsg.value = e.response.data.message
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -36,12 +36,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<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">
|
<div class="control">
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
id="password1"
|
id="password"
|
||||||
name="password1"
|
name="password"
|
||||||
:placeholder="$t('user.auth.passwordPlaceholder')"
|
:placeholder="$t('user.auth.passwordPlaceholder')"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
|
@ -52,17 +52,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<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">
|
<div class="control">
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
id="password2"
|
id="passwordValidation"
|
||||||
name="password2"
|
name="passwordValidation"
|
||||||
:placeholder="$t('user.auth.passwordPlaceholder')"
|
:placeholder="$t('user.auth.passwordPlaceholder')"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
v-model="credentials.password2"
|
v-model="passwordValidation"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,61 +95,50 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import router from '../../router'
|
import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
|
||||||
import {mapState} from 'vuex'
|
import { useI18n } from 'vue-i18n'
|
||||||
import {LOADING} from '@/store/mutation-types'
|
|
||||||
import Legal from '../../components/misc/legal'
|
|
||||||
|
|
||||||
export default {
|
import router from '@/router'
|
||||||
components: {
|
import { store } from '@/store'
|
||||||
Legal,
|
import { useTitle } from '@/composables/useTitle'
|
||||||
},
|
|
||||||
data() {
|
import Legal from '@/components/misc/legal'
|
||||||
return {
|
|
||||||
credentials: {
|
// 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: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
password2: '',
|
})
|
||||||
},
|
const passwordValidation = ref('')
|
||||||
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 = ''
|
|
||||||
|
|
||||||
if (this.credentials.password2 !== this.credentials.password) {
|
const loading = computed(() => store.state.loading)
|
||||||
this.errorMessage = this.$t('user.auth.passwordsDontMatch')
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
errorMessage.value = ''
|
||||||
|
|
||||||
|
if (credentials.password !== passwordValidation.value) {
|
||||||
|
errorMessage.value = t('user.auth.passwordsDontMatch')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = {
|
|
||||||
username: this.credentials.username,
|
|
||||||
email: this.credentials.email,
|
|
||||||
password: this.credentials.password,
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('auth/register', credentials)
|
await store.dispatch('auth/register', toRaw(credentials))
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.errorMessage = e.message
|
errorMessage.value = e.message
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -43,42 +43,38 @@
|
||||||
{{ $t('user.auth.login') }}
|
{{ $t('user.auth.login') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
</div>
|
</div>
|
||||||
<legal/>
|
<Legal />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import PasswordResetModel from '../../models/passwordReset'
|
import {ref, reactive} from 'vue'
|
||||||
import PasswordResetService from '../../services/passwordReset'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Legal from '../../components/misc/legal'
|
|
||||||
|
|
||||||
export default {
|
import Legal from '@/components/misc/legal'
|
||||||
components: {
|
|
||||||
Legal,
|
import PasswordResetModel from '@/models/passwordReset'
|
||||||
},
|
import PasswordResetService from '@/services/passwordReset'
|
||||||
data() {
|
import { useTitle } from '@/composables/useTitle'
|
||||||
return {
|
|
||||||
passwordResetService: new PasswordResetService(),
|
const { t } = useI18n()
|
||||||
passwordReset: new PasswordResetModel(),
|
useTitle(() => t('user.auth.resetPassword'))
|
||||||
errorMsg: '',
|
|
||||||
isSuccess: false,
|
// Not sure if this instance needs a shalloRef at all
|
||||||
}
|
const passwordResetService = reactive(new PasswordResetService())
|
||||||
},
|
const passwordReset = ref(new PasswordResetModel())
|
||||||
mounted() {
|
const errorMsg = ref('')
|
||||||
this.setTitle(this.$t('user.auth.resetPassword'))
|
const isSuccess = ref(false)
|
||||||
},
|
|
||||||
methods: {
|
async function submit() {
|
||||||
async submit() {
|
errorMsg.value = ''
|
||||||
this.errorMsg = ''
|
|
||||||
try {
|
try {
|
||||||
await this.passwordResetService.requestResetPassword(this.passwordReset)
|
await passwordResetService.requestResetPassword(passwordReset.value)
|
||||||
this.isSuccess = true
|
isSuccess.value = true
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.errorMsg = e.response.data.message
|
errorMsg.value = e.response.data.message
|
||||||
}
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -57,24 +57,19 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import {mapState} from 'vuex'
|
import {computed} from 'vue'
|
||||||
|
import { store } from '@/store'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useTitle } from '@/composables/useTitle'
|
||||||
|
|
||||||
export default {
|
const { t } = useI18n()
|
||||||
name: 'Settings',
|
useTitle(() => t('user.settings.title'))
|
||||||
mounted() {
|
|
||||||
this.setTitle(this.$t('user.settings.title'))
|
const totpEnabled = computed(() => store.state.config.totpEnabled)
|
||||||
},
|
const caldavEnabled = computed(() => store.state.config.caldavEnabled)
|
||||||
computed: {
|
const migratorsEnabled = computed(() => store.getters['config/migratorsEnabled'])
|
||||||
...mapState('config', ['totpEnabled', 'caldavEnabled']),
|
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
|
||||||
migratorsEnabled() {
|
|
||||||
return this.$store.getters['config/migratorsEnabled']
|
|
||||||
},
|
|
||||||
isLocalUser() {
|
|
||||||
return this.$store.state.auth.info?.isLocalUser
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
Loading…
Reference in a new issue