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.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.')
|
||||
})
|
||||
|
|
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 => {
|
||||
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`
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
return
|
||||
}
|
||||
const dataExportService = reactive(new DataExportService())
|
||||
const password = ref('')
|
||||
const errPasswordRequired = ref(false)
|
||||
const passwordInput = ref(null)
|
||||
|
||||
this.dataExportService.download(this.password)
|
||||
},
|
||||
},
|
||||
const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
|
||||
|
||||
function download() {
|
||||
if (password.value === '' && isLocalUser.value) {
|
||||
errPasswordRequired.value = true
|
||||
passwordInput.value.focus()
|
||||
return
|
||||
}
|
||||
|
||||
dataExportService.download(password.value)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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: {
|
||||
password: '',
|
||||
password2: '',
|
||||
},
|
||||
errorMsg: '',
|
||||
successMessage: '',
|
||||
}
|
||||
},
|
||||
import Legal from '@/components/misc/legal'
|
||||
|
||||
mounted() {
|
||||
this.setTitle(this.$t('user.auth.resetPassword'))
|
||||
},
|
||||
import PasswordResetModel from '@/models/passwordReset'
|
||||
import PasswordResetService from '@/services/passwordReset'
|
||||
import { useTitle } from '@/composables/useTitle'
|
||||
|
||||
methods: {
|
||||
async submit() {
|
||||
this.errorMsg = ''
|
||||
const { t } = useI18n()
|
||||
useTitle(() => t('user.auth.resetPassword'))
|
||||
|
||||
if (this.credentials.password2 !== this.credentials.password) {
|
||||
this.errorMsg = this.$t('user.auth.passwordsDontMatch')
|
||||
return
|
||||
}
|
||||
const credentials = reactive({
|
||||
password: '',
|
||||
password2: '',
|
||||
})
|
||||
|
||||
let passwordReset = new PasswordResetModel({newPassword: this.credentials.password})
|
||||
try {
|
||||
const { message } = this.passwordResetService.resetPassword(passwordReset)
|
||||
this.successMessage = message
|
||||
localStorage.removeItem('passwordResetToken')
|
||||
} catch(e) {
|
||||
this.errorMsg = e.response.data.message
|
||||
}
|
||||
},
|
||||
},
|
||||
const passwordResetService = reactive(new PasswordResetService())
|
||||
const errorMsg = ref('')
|
||||
const successMessage = ref('')
|
||||
|
||||
async function submit() {
|
||||
errorMsg.value = ''
|
||||
|
||||
if (credentials.password2 !== credentials.password) {
|
||||
errorMsg.value = t('user.auth.passwordsDontMatch')
|
||||
return
|
||||
}
|
||||
|
||||
const passwordReset = new PasswordResetModel({newPassword: credentials.password})
|
||||
try {
|
||||
const { message } = passwordResetService.resetPassword(passwordReset)
|
||||
successMessage.value = message
|
||||
localStorage.removeItem('passwordResetToken')
|
||||
} catch(e) {
|
||||
errorMsg.value = e.response.data.message
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -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: {
|
||||
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 = ''
|
||||
import router from '@/router'
|
||||
import { store } from '@/store'
|
||||
import { useTitle } from '@/composables/useTitle'
|
||||
|
||||
if (this.credentials.password2 !== this.credentials.password) {
|
||||
this.errorMessage = this.$t('user.auth.passwordsDontMatch')
|
||||
return
|
||||
}
|
||||
import Legal from '@/components/misc/legal'
|
||||
|
||||
const credentials = {
|
||||
username: this.credentials.username,
|
||||
email: this.credentials.email,
|
||||
password: this.credentials.password,
|
||||
}
|
||||
// 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'})
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('auth/register', credentials)
|
||||
} catch(e) {
|
||||
this.errorMessage = e.message
|
||||
}
|
||||
},
|
||||
},
|
||||
const { t } = useI18n()
|
||||
useTitle(() => t('user.auth.register'))
|
||||
|
||||
const credentials = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
})
|
||||
const passwordValidation = ref('')
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await store.dispatch('auth/register', toRaw(credentials))
|
||||
} catch(e) {
|
||||
errorMessage.value = e.message
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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 = ''
|
||||
try {
|
||||
await this.passwordResetService.requestResetPassword(this.passwordReset)
|
||||
this.isSuccess = true
|
||||
} catch(e) {
|
||||
this.errorMsg = e.response.data.message
|
||||
}
|
||||
},
|
||||
},
|
||||
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 passwordResetService.requestResetPassword(passwordReset.value)
|
||||
isSuccess.value = true
|
||||
} catch(e) {
|
||||
errorMsg.value = e.response.data.message
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue