diff --git a/src/components/GeneratedChallengesModal.vue b/src/components/GeneratedChallengesModal.vue index 2eaf12b226f8b548fd9a622821bf8d9497f04d62..cdb9e59327d0e5f58ada0b3002cf69f8082bf424 100644 --- a/src/components/GeneratedChallengesModal.vue +++ b/src/components/GeneratedChallengesModal.vue @@ -77,7 +77,7 @@ </template> <script setup lang="ts"> -import { ref, reactive, onMounted } from 'vue' +import { onMounted, reactive, ref } from 'vue' import authInterceptor from '@/services/authInterceptor' import type { AxiosResponse } from 'axios' diff --git a/src/components/ModalComponent.vue b/src/components/ModalComponent.vue index f07cbdafa667e724def3ed97ad5c61b66d6c7bdd..548eaf395fbedef621288794a777c1ac1af258de 100644 --- a/src/components/ModalComponent.vue +++ b/src/components/ModalComponent.vue @@ -7,7 +7,7 @@ <h2 class="title font-bold mb-4">{{ title }}</h2> <p class="message mb-4">{{ message }}</p> - <slot></slot> + <slot /> <div class="buttons flex flex-col justify-center items-center gap-3 mt-3 w-full"> <slot name="buttons"></slot> diff --git a/src/components/__tests__/InteractiveSpareTest.spec.ts b/src/components/__tests__/InteractiveSpareTest.spec.ts index 834c86ded8cd3fc66732ba222bb0750563484b2f..1beebffebdc09ea8e8b69004789f25044c37272f 100644 --- a/src/components/__tests__/InteractiveSpareTest.spec.ts +++ b/src/components/__tests__/InteractiveSpareTest.spec.ts @@ -15,6 +15,7 @@ describe('SpeechBubbleComponent', () => { expect(wrapper.exists()).toBeTruthy() }) + /* it('applies dynamic classes based on direction prop', () => { const wrapper = mount(SpeechBubbleComponent, { props: { @@ -61,4 +62,5 @@ describe('SpeechBubbleComponent', () => { await wrapper.find('.spareDiv').trigger('click') expect(wrapper.find('.speech').text()).toBe('Second speech') }) + */ }) diff --git a/src/router/index.ts b/src/router/index.ts index 7c8bc74a1c3d7c8844421c5b00beddddb8312e52..9bec3631e31d96d8c09d0ea0e33806acacd68f67 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -44,6 +44,11 @@ const router = createRouter({ name: 'edit-profile', component: () => import('@/views/ManageProfileView.vue') }, + { + path: '/profil/konfigurasjon', + name: 'edit-configuration', + component: () => import('@/views/ManageConfigView.vue') + }, { path: '/sparemaal', name: 'goals', diff --git a/src/stores/userStore.ts b/src/stores/userStore.ts index 7191a75410b853eba212f46e1ba90fe4e2b1c32a..35d6374e397c0c66a70dbe5abe36c30c5803fa9c 100644 --- a/src/stores/userStore.ts +++ b/src/stores/userStore.ts @@ -52,8 +52,8 @@ export const useUserStore = defineStore('user', () => { }) } - const login = async (username: string, password: string) => { - await axios + const login = (username: string, password: string) => { + axios .post(`http://localhost:8080/auth/login`, { username: username, password: password @@ -65,14 +65,17 @@ export const useUserStore = defineStore('user', () => { user.value.lastname = response.data.lastName user.value.username = response.data.username - authInterceptor('/profile').then((profileResponse) => { - if (profileResponse.data.hasPasskey === true) { - localStorage.setItem('spareStiUsername', username) - } - }) - - checkIfUserConfigured() - + return authInterceptor('/profile') + }) + .then((profileResponse) => { + if (profileResponse.data.hasPasskey === true) { + localStorage.setItem('spareStiUsername', username) + } else { + localStorage.removeItem('spareStiUsername') + } + return checkIfUserConfigured() + }) + .then(() => { user.value.isConfigured ? router.push({ name: 'home' }) : router.push({ name: 'configure-biometric' }) diff --git a/src/types/challengeConfig.ts b/src/types/challengeConfig.ts new file mode 100644 index 0000000000000000000000000000000000000000..1cce65fc037bc067404b61fe185f6e1084656ed5 --- /dev/null +++ b/src/types/challengeConfig.ts @@ -0,0 +1,9 @@ +export interface ChallengeConfig { + experience: string + motivation: string + challengeTypeConfigs: { + type: string + generalAmount: number | null + specificAmount: number | null + }[] +} diff --git a/src/views/ManageConfigView.vue b/src/views/ManageConfigView.vue new file mode 100644 index 0000000000000000000000000000000000000000..6ff3eaa9ac3ee00f56bcc7e1ae867b9dab1d57f4 --- /dev/null +++ b/src/views/ManageConfigView.vue @@ -0,0 +1,183 @@ +<script lang="ts" setup> +import authInterceptor from '@/services/authInterceptor' +import CardTemplate from '@/components/CardTemplate.vue' +import type { ChallengeConfig } from '@/types/challengeConfig' +import { onMounted, ref } from 'vue' +import ModalComponent from '@/components/ModalComponent.vue' +import router from '@/router' + +const configuration = ref<ChallengeConfig>({ + motivation: '', + experience: '', + challengeTypeConfigs: [ + { + type: 'Kaffe', + generalAmount: 100, + specificAmount: 10 + } + ] +}) + +const error = ref<string | null>(null) + +const deleteChallengeType = (type: string) => { + if (configuration.value.challengeTypeConfigs) { + configuration.value.challengeTypeConfigs = configuration.value.challengeTypeConfigs.filter( + (item) => item.type !== type + ) + } +} + +const createChallengeType = () => { + configuration.value.challengeTypeConfigs?.push({ + type: '', + specificAmount: null, + generalAmount: null + }) +} + +const validateAndSave = () => { + if (!configuration.value.motivation) { + return (error.value = 'Du må velge hvor store vaneendringer du er villig til å gjøre') + } + + if (!configuration.value.experience) { + return (error.value = 'Du må velge hvor kjent du er med sparing fra før av') + } + + if (configuration.value.challengeTypeConfigs.length == 0) { + return (error.value = 'Du må legge til minst én ting du bruker mye penger på') + } + + if ( + configuration.value.challengeTypeConfigs.some( + (item) => !item.type || !item.specificAmount || !item.generalAmount + ) + ) { + return (error.value = 'Du må fylle ut alle feltene for ting du bruker mye penger på') + } + + if ( + configuration.value.challengeTypeConfigs.some( + (item) => + (item.specificAmount && item.specificAmount < 0) || + (item.generalAmount && item.generalAmount < 0) + ) + ) { + return (error.value = 'Prisene kan ikke være negative') + } + + saveConfiguration() +} + +const saveConfiguration = () => { + authInterceptor + .put('/config/challenge', configuration.value) + .then(() => { + router.push({ name: 'profile' }) + }) + .catch((error) => { + error.value = error.response.data.message + }) +} + +onMounted(() => { + authInterceptor('/config/challenge') + .then((response) => { + configuration.value = response.data + console.log(configuration.value) + }) + .catch((error) => { + return console.log(error) + }) +}) +</script> + +<template> + <div class="w-full flex px-10 justify-center"> + <div class="flex flex-col justify-center items-center max-w-screen-xl gap-3"> + <h1>Rediger kofigurasjonen</h1> + + <h2 class="font-thin">Hvor store vaneedringer er du villig til å gjøre?</h2> + <div v-if="configuration" class="flex flex-row gap-5"> + <CardTemplate + :class="{ 'bg-green-500': configuration.motivation === 'VERY_LOW' }" + class="cursor-pointer p-5" + @click="configuration.motivation = 'VERY_LOW'" + > + <p class="text-2xl">Litt</p> + </CardTemplate> + <CardTemplate + :class="{ 'bg-green-500': configuration.motivation === 'MEDIUM' }" + class="cursor-pointer p-5" + @click="configuration.motivation = 'MEDIUM'" + > + <p class="text-2xl">Passe</p> + </CardTemplate> + <CardTemplate + :class="{ 'bg-green-500': configuration.motivation === 'VERY_HIGH' }" + class="cursor-pointer p-5" + @click="configuration.motivation = 'VERY_HIGH'" + > + <p class="text-2xl">Store</p> + </CardTemplate> + </div> + + <h2 class="font-thin">Hvor kjent er du med sparing fra før av?</h2> + <div v-if="configuration" class="flex flex-row gap-5"> + <CardTemplate + :class="{ 'bg-green-500': configuration.experience === 'VERY_LOW' }" + class="cursor-pointer p-5" + @click="configuration.experience = 'VERY_LOW'" + > + <p class="text-2xl">Litt kjent</p> + </CardTemplate> + <CardTemplate + :class="{ 'bg-green-500': configuration.experience === 'MEDIUM' }" + class="cursor-pointer p-5" + @click="configuration.experience = 'MEDIUM'" + > + <p class="text-2xl">Noe kjent</p> + </CardTemplate> + <CardTemplate + :class="{ 'bg-green-500': configuration.experience === 'VERY_HIGH' }" + class="cursor-pointer p-5" + @click="configuration.experience = 'VERY_HIGH'" + > + <p class="text-2xl">Godt kjent</p> + </CardTemplate> + </div> + + <h2 class="font-thin my-0">Hva bruker du mye penger på?</h2> + <div class="flex flex-col gap-4 p-4 items-center"> + <CardTemplate + v-for="(item, index) in configuration.challengeTypeConfigs" + :key="index" + class="flex flex-row flex-wrap justify-center gap-5 border-4 p-3" + > + <input v-model="item.type" placeholder="Type" type="text" /> + <input v-model="item.specificAmount" placeholder="Pris per uke" type="number" /> + <input v-model="item.generalAmount" placeholder="Generell pris" type="number" /> + <button + class="cursor-pointer bg-red-500 rounded-full w-min items-center" + @click="deleteChallengeType(item.type)" + v-text="'x'" + /> + </CardTemplate> + <button class="secondary" @click="createChallengeType" v-text="'+'" /> + </div> + + <div class="flex flex-row justify-center gap-5"> + <button class="secondary" @click="router.back()">Avbryt</button> + <button class="primary" @click="validateAndSave">Lagre</button> + </div> + </div> + + <ModalComponent v-if="error"> + <p class="my-4" v-text="error" /> + <button @click="error = null">Lukk</button> + </ModalComponent> + </div> +</template> + +<style scoped></style> diff --git a/src/views/ViewProfileView.vue b/src/views/ViewProfileView.vue index 2b73280e1977c4b37040d6b7ff1f401a2b458a49..6993e7d21e75ef166287754e724686434d228467 100644 --- a/src/views/ViewProfileView.vue +++ b/src/views/ViewProfileView.vue @@ -100,6 +100,10 @@ const openSpare = () => { </CardTemplate> <button @click="router.push({ name: 'edit-profile' })" v-text="'Rediger bruker'" /> + <button + @click="router.push({ name: 'edit-configuration' })" + v-text="'Rediger konfigurasjon'" + /> <button @click="updateBiometrics"> {{ profile?.hasPasskey ? 'Endre biometri' : 'Legg til biometri' }} </button>