diff --git a/src/App.vue b/src/App.vue index 9b1c3538f8a4a106c8ba5ed9d65cb2a4c0d9cd8b..80a5f3004ad74ae84f4d82d5d76f5ec2488980c4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,3 +5,20 @@ import NavBarComponent from '@/components/NavBarComponent.vue' <template> <NavBarComponent /> </template> + +<style> +nav { + display: flex; + justify-content: center; + gap: 1rem; + margin: 1rem 0; +} + +nav a.router-link-exact-active { + color: var(--color-text); +} + +nav a.router-link-exact-active:hover { + background-color: transparent; +} +</style> diff --git a/src/components/NavBarComponent.vue b/src/components/NavBarComponent.vue index bef306e26ec0414256225e1eecc6b91d0673ea23..e6c79d2b1ccb284a35994147cd288069c560eb4d 100644 --- a/src/components/NavBarComponent.vue +++ b/src/components/NavBarComponent.vue @@ -25,9 +25,6 @@ <router-link to="/profil" class="nav-link" active-class="border-b-2" >ðŸ¤Profil</router-link > - <router-link to="/konfigurasjonSteg1" class="nav-link" active-class="border-b-2" - >🛠ï¸Konfigurasjon</router-link - > <button @click="logout" class="hidden sm:flex absolute right-10 py-2 px-6 rounded-full focus:outline-none focus:ring focus:ring-black-300" @@ -50,9 +47,6 @@ >💰Spareutfordringer</router-link > <router-link to="/profil" @click="menuOpen = false">ðŸ¤Profil</router-link> - <router-link to="/konfigurasjonSteg1" @click="menuOpen = false" - >🛠ï¸Konfigurasjon</router-link - > <button @click="logout" class="py-2 px-6 mx-auto rounded-full focus:outline-none focus:ring focus:ring-black-300 bg-transparent" diff --git a/src/router/index.ts b/src/router/index.ts index 93d137b48bbadd87371d503ce071839867dc827e..d30507b3069a5736a759bdb4a7db9d0e33fd1a6b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -83,6 +83,11 @@ const router = createRouter({ name: 'configurations4', component: () => import('@/views/ConfigSpendingItemsAmountView.vue') }, + { + path: '/konfigurasjonSteg5', + name: 'configurations5', + component: () => import('@/views/ConfigSpendingItemsTotalAmountView.vue') + }, { path: '/forsteSparemaal', name: 'firstSavingGoal', diff --git a/src/stores/userConfigStore.ts b/src/stores/userConfigStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1f6b0f1f548e3b21bb1a5d5c45ac80da9d8eab4 --- /dev/null +++ b/src/stores/userConfigStore.ts @@ -0,0 +1,45 @@ +import { defineStore } from 'pinia' +import authInterceptor from '@/services/authInterceptor' +import axios from 'axios' + +export const useUserConfigStore = defineStore('userConfig', { + state: () => ({ + role: 'USER', + experience: 'VERY_HIGH', + motivation: 'VERY_HIGH', + challengeTypeConfigs: [] as { + type: string + specificAmount: number + generalAmount: number + }[] + }), + actions: { + setExperience(value: string) { + this.experience = value + }, + setMotivation(value: string) { + this.motivation = value + }, + addChallengeTypeConfig(type: string, specificAmount: number, generalAmount: number) { + this.challengeTypeConfigs.push({ type, specificAmount, generalAmount }) + }, + async postUserConfig() { + const payload = { + experience: this.experience, + motivation: this.motivation, + challengeTypeConfigs: Array.from(this.challengeTypeConfigs) + } + + try { + const response = await authInterceptor.post('/users/me/config/challenge', payload) + console.log('Success:', response.data) + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + console.error('Axios error:', error.response?.data || error.message) + } else { + console.error('An unexpected error occurred:', error) + } + } + } + } +}) diff --git a/src/views/ConfigFamiliarWithSavingsView.vue b/src/views/ConfigFamiliarWithSavingsView.vue index c7664c76fde01c1989200259eaf9a4801767a913..3b3c8a4d1c6b6143ce1100e1e850a97a82e27988 100644 --- a/src/views/ConfigFamiliarWithSavingsView.vue +++ b/src/views/ConfigFamiliarWithSavingsView.vue @@ -48,14 +48,37 @@ import { ref } from 'vue' import ContinueButtonComponent from '@/components/ContinueButtonComponent.vue' import router from '@/router' +import { useUserConfigStore } from '@/stores/userConfigStore' const selectedOption = ref<string | null>(null) +const userConfigStore = useUserConfigStore() const selectOption = (option: string) => { selectedOption.value = option + let experienceValue = '' + + switch (option) { + case 'litt': + experienceValue = 'VERY_LOW' + break + case 'noe': + experienceValue = 'MEDIUM' + break + case 'godt': + experienceValue = 'VERY_HIGH' + break + default: + experienceValue = 'VERY_LOW' + } + + userConfigStore.setExperience(experienceValue) } const onButtonClick = () => { - router.push('/konfigurasjonSteg3') + if (selectedOption.value) { + router.push('/konfigurasjonSteg3') + } else { + console.error('No option selected') + } } </script> diff --git a/src/views/ConfigHabitChangeView.vue b/src/views/ConfigHabitChangeView.vue index 8f6497e4822f8e79439bc4049ca7918bc939fcfd..c686e26112e56a2d298e5e80d5cb59f0b8736f83 100644 --- a/src/views/ConfigHabitChangeView.vue +++ b/src/views/ConfigHabitChangeView.vue @@ -50,14 +50,37 @@ import { ref } from 'vue' import ContinueButtonComponent from '@/components/ContinueButtonComponent.vue' import router from '@/router' +import { useUserConfigStore } from '@/stores/userConfigStore' const selectedOption = ref<string | null>(null) +const userConfigStore = useUserConfigStore() const selectOption = (option: string) => { selectedOption.value = option + let motivationValue = '' + + switch (option) { + case 'litt': + motivationValue = 'VERY_LOW' + break + case 'passe': + motivationValue = 'MEDIUM' + break + case 'store': + motivationValue = 'VERY_HIGH' + break + default: + motivationValue = 'VERY_LOW' + } + + userConfigStore.setMotivation(motivationValue) } const onButtonClick = () => { - router.push('/konfigurasjonSteg2') + if (selectedOption.value) { + router.push('/konfigurasjonSteg2') + } else { + console.error('No option selected') + } } </script> diff --git a/src/views/ConfigSpendingItemsAmountView.vue b/src/views/ConfigSpendingItemsAmountView.vue index 46d8a5b02c6ef5f7fb6858d47257badf050e68e2..2024c22128c6f56abbc6ff0aacf66fc19bae5337 100644 --- a/src/views/ConfigSpendingItemsAmountView.vue +++ b/src/views/ConfigSpendingItemsAmountView.vue @@ -1,24 +1,23 @@ <template> - <div class="flex flex-col items-center justify-center min-h-screen px-4 text-center"> + <div class="flex flex-col items-center justify-center min-h-screen px-4 text-center relative"> <h1 class="mb-8 lg:mb-12 text-4xl font-bold">Hvor mye bruker du per kjøp pÃ¥ ...</h1> <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-6"> - <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-100"> + <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-full"> <div - v-for="(option, index) in options.slice(0, 3)" - :key="index" + v-for="(option, index) in options.slice(0, 6)" + :key="`option-${index}`" class="w-full my-4" > <div class="flex justify-between items-center"> - <p class="text-xl font-bold mr-4">{{ option.name }}</p> + <p class="text-xl font-bold mr-4">{{ option.type }}</p> <div class="flex items-center w-2/3"> <input - type="text" - v-model="option.amount" - @input="($event) => filterAmount(index, $event)" + v-model="amounts[index]" + @input="filterAmount(index, $event)" class="h-11 px-3 rounded-md text-lg focus:outline-none border-2 w-full" :class="{ - 'border-gray-300': option.amount === '', - 'border-[var(--green)]': option.amount !== '' + 'border-gray-300': !amounts[index], + 'border-[var(--green)]': amounts[index] }" /> <p class="text-xl font-bold ml-2">kr</p> @@ -26,23 +25,22 @@ </div> </div> </div> - <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-100"> + <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-full"> <div - v-for="(option, index) in options.slice(3, 6)" - :key="index" + v-for="(option, index) in options.slice(6, 12)" + :key="`option-${index}`" class="w-full my-4" > <div class="flex justify-between items-center"> - <p class="text-xl font-bold mr-4">{{ option.name }}</p> + <p class="text-xl font-bold mr-4">{{ option.type }}</p> <div class="flex items-center w-2/3"> <input - type="text" - v-model="option.amount" - @input="($event) => filterAmount(index + 3, $event)" + v-model="amounts[index + 6]" + @input="filterAmount(index + 6, $event)" class="h-11 px-3 rounded-md text-lg focus:outline-none border-2 w-full" :class="{ - 'border-gray-300': option.amount === '', - 'border-[var(--green)]': option.amount !== '' + 'border-gray-300': !amounts[index + 6], + 'border-[var(--green)]': amounts[index + 6] }" /> <p class="text-xl font-bold ml-2">kr</p> @@ -51,10 +49,10 @@ </div> </div> </div> - <div class="w-full text-right mb-3 mt-16"> + <div class="absolute bottom-36 right-4"> <ContinueButtonComponent @click="onButtonClick" - :disabled="options.some((option) => option.amount === '')" + :disabled="!isAllAmountsFilled" class="px-10 py-3 text-2xl font-bold mb-4" ></ContinueButtonComponent> </div> @@ -62,38 +60,30 @@ </template> <script setup lang="ts"> -import { ref } from 'vue' +import { ref, computed } from 'vue' import ContinueButtonComponent from '@/components/ContinueButtonComponent.vue' import router from '@/router' +import { useUserConfigStore } from '@/stores/userConfigStore' -interface Option { - name: string - amount: string -} +const userConfigStore = useUserConfigStore() + +const options = ref(userConfigStore.challengeTypeConfigs) +const amounts = ref(options.value.map(() => '')) -const options = ref<Option[]>([ - { name: 'Snus', amount: '' }, - { name: 'Kaffe', amount: '' }, - { name: 'Kantina', amount: '' }, - { name: 'Annet', amount: '' }, - { name: 'Annet', amount: '' }, - { name: 'Annet', amount: '' } -]) +const isAllAmountsFilled = computed(() => amounts.value.every((amount) => amount.trim() !== '')) const onButtonClick = () => { + options.value.forEach((option, index) => { + userConfigStore.challengeTypeConfigs[index].specificAmount = + parseFloat(amounts.value[index]) || 0 + }) router.push('/konfigurasjonSteg5') } const filterAmount = (index: number, event: Event) => { const input = event.target as HTMLInputElement let filteredValue = input.value.replace(/[^\d,]/g, '') - if (filteredValue.includes(',')) { - filteredValue = filteredValue.replace(/,+/g, ',') - const firstCommaIndex = filteredValue.indexOf(',') - filteredValue = - filteredValue.slice(0, firstCommaIndex + 1) + - filteredValue.slice(firstCommaIndex + 1).replace(/,/g, '') - } - options.value[index].amount = filteredValue + filteredValue = filteredValue.replace(/(,.*?),/g, '$1').replace(/,+/g, ',') + amounts.value[index] = filteredValue } </script> diff --git a/src/views/ConfigSpendingItemsTotalAmountView.vue b/src/views/ConfigSpendingItemsTotalAmountView.vue index e872697fbe7dc8b9739bd3c2d81ac4778668d42e..d79963fe003051c6558fe5a16c9b2174582d8cc7 100644 --- a/src/views/ConfigSpendingItemsTotalAmountView.vue +++ b/src/views/ConfigSpendingItemsTotalAmountView.vue @@ -2,23 +2,23 @@ <div class="flex flex-col items-center justify-center min-h-screen px-4 text-center"> <h1 class="mb-8 lg:mb-12 text-4xl font-bold">Hvor mye bruker du totalt per uke pÃ¥ ...</h1> <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-6"> - <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-100"> + <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-full"> <div - v-for="(option, index) in options.slice(0, 3)" - :key="index" + v-for="(option, index) in options.slice(0, 6)" + :key="`option-${index}`" class="w-full my-4" > <div class="flex justify-between items-center"> - <p class="text-xl font-bold mr-4">{{ option.name }}</p> + <p class="text-xl font-bold mr-4">{{ option.type }}</p> <div class="flex items-center w-2/3"> <input type="text" - v-model="option.amount" + v-model="amounts[index]" @input="($event) => filterAmount(index, $event)" class="h-11 px-3 rounded-md text-lg focus:outline-none border-2 w-full" :class="{ - 'border-gray-300': option.amount === '', - 'border-[var(--green)]': option.amount !== '' + 'border-gray-300': !amounts[index], + 'border-[var(--green)]': amounts[index] }" /> <p class="text-xl font-bold ml-2">kr</p> @@ -26,23 +26,23 @@ </div> </div> </div> - <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-100"> + <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg w-full"> <div - v-for="(option, index) in options.slice(3, 6)" - :key="index" + v-for="(option, index) in options.slice(6, 12)" + :key="`option-${index}`" class="w-full my-4" > <div class="flex justify-between items-center"> - <p class="text-xl font-bold mr-4">{{ option.name }}</p> + <p class="text-xl font-bold mr-4">{{ option.type }}</p> <div class="flex items-center w-2/3"> <input type="text" - v-model="option.amount" - @input="($event) => filterAmount(index + 3, $event)" + v-model="amounts[index + 6]" + @input="($event) => filterAmount(index + 6, $event)" class="h-11 px-3 rounded-md text-lg focus:outline-none border-2 w-full" :class="{ - 'border-gray-300': option.amount === '', - 'border-[var(--green)]': option.amount !== '' + 'border-gray-300': !amounts[index + 6], + 'border-[var(--green)]': amounts[index + 6] }" /> <p class="text-xl font-bold ml-2">kr</p> @@ -51,10 +51,10 @@ </div> </div> </div> - <div class="w-full text-right mb-3 mt-16"> + <div class="absolute bottom-24 right-4"> <ContinueButtonComponent @click="onButtonClick" - :disabled="options.some((option) => option.amount === '')" + :disabled="!isAllAmountsFilled" class="px-10 py-3 text-2xl font-bold mb-4" ></ContinueButtonComponent> </div> @@ -62,38 +62,32 @@ </template> <script setup lang="ts"> -import { ref } from 'vue' +import { ref, computed } from 'vue' import ContinueButtonComponent from '@/components/ContinueButtonComponent.vue' import router from '@/router' +import { useUserConfigStore } from '@/stores/userConfigStore' -interface Option { - name: string - amount: string -} +const userConfigStore = useUserConfigStore() + +const options = ref(userConfigStore.challengeTypeConfigs) +const amounts = ref(options.value.map(() => '')) + +const isAllAmountsFilled = computed(() => amounts.value.every((amount) => amount.trim() !== '')) -const options = ref<Option[]>([ - { name: 'Snus', amount: '' }, - { name: 'Kaffe', amount: '' }, - { name: 'Kantina', amount: '' }, - { name: 'Annet', amount: '' }, - { name: 'Annet', amount: '' }, - { name: 'Annet', amount: '' } -]) +const onButtonClick = async () => { + options.value.forEach((option, index) => { + userConfigStore.challengeTypeConfigs[index].generalAmount = + parseFloat(amounts.value[index]) || 0 + }) -const onButtonClick = () => { - router.push('/') + await userConfigStore.postUserConfig() + router.push('/hjem') } const filterAmount = (index: number, event: Event) => { const input = event.target as HTMLInputElement let filteredValue = input.value.replace(/[^\d,]/g, '') - if (filteredValue.includes(',')) { - filteredValue = filteredValue.replace(/,+/g, ',') - const firstCommaIndex = filteredValue.indexOf(',') - filteredValue = - filteredValue.slice(0, firstCommaIndex + 1) + - filteredValue.slice(firstCommaIndex + 1).replace(/,/g, '') - } - options.value[index].amount = filteredValue + filteredValue = filteredValue.replace(/(,.*?),/g, '$1').replace(/,+/g, ',') + amounts.value[index] = filteredValue } </script> diff --git a/src/views/ConfigSpendingItemsView.vue b/src/views/ConfigSpendingItemsView.vue index 2dea8dffdfe9c300127795cd5981cb95b305d3d2..39b47f754a524035be2aae2db23c05152fd1354a 100644 --- a/src/views/ConfigSpendingItemsView.vue +++ b/src/views/ConfigSpendingItemsView.vue @@ -1,16 +1,25 @@ <template> - <div class="flex flex-col items-center justify-center min-h-screen px-4 sm:px-2 text-center"> + <div class="flex flex-col items-center justify-center min-h-screen text-center"> <h1 class="mb-8 lg:mb-12 text-4xl font-bold">Hva bruker du mye penger pÃ¥?</h1> - <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8"> - <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg md:w-96"> + <div class="flex flex-wrap justify-center gap-8 mb-8"> + <div + class="flex flex-col items-center justify-center bg-white rounded-lg p-8 shadow-lg w-full md:w-[45%]" + > <div - v-for="buttonText in ['Kaffe', 'Snus', 'Kantina']" + v-for="buttonText in [ + 'Kaffe', + 'Snus', + 'Kantina', + 'Sigaretter', + 'Transport', + 'Klær' + ]" :key="buttonText" class="w-full my-4" > <button :class="[ - 'w-full h-11 rounded-md text-xl font-bold', + 'w-full md:w-64 h-11 rounded-md text-xl font-bold', selectedOptions.includes(buttonText) ? 'border-2 border-[var(--green)]' : 'border-2 border-gray-300' @@ -22,24 +31,24 @@ </button> </div> </div> - - <div class="flex flex-col items-center bg-white rounded-lg p-8 shadow-lg md:w-96"> - <div v-for="(option, index) in options" :key="`input-${index}`" class="w-full my-4"> + <div + class="flex flex-col items-center justify-center bg-white rounded-lg p-8 shadow-lg w-full md:w-[45%]" + > + <div + v-for="(option, index) in customOptions" + :key="`custom-${index}`" + class="w-full my-4" + > <input - v-model="option.name" - :class="[ - 'w-full h-11 px-3 rounded-md text-xl focus:outline-none transition-colors', - option.name - ? 'border-2 border-[var(--green)]' - : 'border-2 border-gray-300' - ]" + v-model="customOptions[index]" + class="w-full md:w-64 h-11 px-3 rounded-md text-xl focus:outline-none transition-colors border-2 border-gray-300" type="text" - placeholder="Annet ..." + :placeholder="'Annet ' + ' ...'" /> </div> </div> </div> - <div class="w-full text-right mb-3 mt-14"> + <div class="w-full text-right mb-0 mt-0" style="position: relative; top: -92px; right: 8px"> <ContinueButtonComponent @click="onButtonClick" :disabled="!isFormValid" @@ -50,36 +59,49 @@ </template> <script setup lang="ts"> -import { computed, ref } from 'vue' +import { ref, computed } from 'vue' import ContinueButtonComponent from '@/components/ContinueButtonComponent.vue' import router from '@/router' +import { useUserConfigStore } from '@/stores/userConfigStore' -interface Option { - name: string -} - -const options = ref<Option[]>([{ name: '' }, { name: '' }, { name: '' }]) +const userConfigStore = useUserConfigStore() const selectedOptions = ref<string[]>([]) +const customOptions = ref(['', '', '', '', '', '']) -const toggleOption = (option: string) => { - const index = selectedOptions.value.indexOf(option) - if (index === -1) { - selectedOptions.value.push(option) - } else { - selectedOptions.value.splice(index, 1) +const toggleOption = (option: string, isCustom: boolean = false) => { + if (!isCustom) { + const index = selectedOptions.value.indexOf(option) + if (index === -1) { + selectedOptions.value.push(option) + } else { + selectedOptions.value.splice(index, 1) + } } } const isFormValid = computed(() => { - return ( - selectedOptions.value.length > 0 || - options.value.some((option) => option.name.trim() !== '') - ) + const predefinedSelected = selectedOptions.value.length > 0 + const customFilled = customOptions.value.some((option) => option.trim() !== '') + return predefinedSelected || (customFilled && predefinedSelected) }) const onButtonClick = () => { - if (isFormValid.value) { - router.push('/konfigurasjonSteg4') - } + const predefinedChallengeTypes = selectedOptions.value.map((option) => ({ + type: option, + specificAmount: 0, + generalAmount: 0 + })) + + const customChallengeTypes = customOptions.value + .filter((option) => option.trim() !== '') + .map((option) => ({ + type: option, + specificAmount: 0, + generalAmount: 0 + })) + + userConfigStore.challengeTypeConfigs = [...predefinedChallengeTypes, ...customChallengeTypes] + console.log('Selected Challenge Types:', userConfigStore.challengeTypeConfigs) + router.push('/konfigurasjonSteg4') } </script>