diff --git a/src/App.vue b/src/App.vue index dc693f471ad9c9828c6519d81cea07c8bd5ce5ee..12ad03a6b286a47ed6a392e7338562c82b213f64 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { RouterView } from 'vue-router' -// import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.vue'; +import ErrorBoundaryCatcher from '@/components/Exceptions/ErrorBoundaryCatcher.vue'; </script> <template> diff --git a/src/components/BaseComponents/Menu.vue b/src/components/BaseComponents/Menu.vue index 1edc325513c43ee7c2373874f229f42bfd07033c..01f51f6d27942905236879a50568050475cfe3e8 100644 --- a/src/components/BaseComponents/Menu.vue +++ b/src/components/BaseComponents/Menu.vue @@ -28,10 +28,10 @@ <a class="nav-link text-white" href="#" @click="toStore"><img src="@/assets/icons/storefront.svg">Store</a> </li> - <li class="nav-item dropdown"> + <li v-if="userStore.isLoggedIn" class="nav-item dropdown"> <a class="nav-link dropdown-toggle username-text text-white " href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> - <img src="@/assets/icons/person.svg">Username + <img src="@/assets/icons/person.svg">{{ userStore.firstname }} </a> <ul class="dropdown-menu dropdown-username-content"> <li><a class="dropdown-item text-white dropdown-username-link" href="#" @@ -41,9 +41,12 @@ <li><a class="dropdown-item text-white dropdown-username-link" href="#" @click="toFeedback"><img src="@/assets/icons/feedback.svg">Feedback</a></li> <li><a class="dropdown-item text-white dropdown-username-link" href="#" - @click="toFeedback"><img src="@/assets/icons/logout.svg">Log out</a></li> + @click="toLogout"><img src="@/assets/icons/logout.svg">Log out</a></li> </ul> </li> + <li v-else class="nav-item"> + <a class="nav-link text-white" href="#" @click="toLogout">Login</a> + </li> </ul> </div> </div> @@ -51,9 +54,12 @@ </template> <script setup lang="ts"> import { useRouter } from "vue-router"; +import { useUserInfoStore } from '@/stores/UserStore'; const router = useRouter(); +const userStore = useUserInfoStore(); + function toHome() { router.push('/') } @@ -83,8 +89,13 @@ function toFeedback() { } -function toUserProfile(){ - router.push('/profile') +function toUserProfile() { + router.push('/profile') +} + +function toLogout() { + userStore.clearUserInfo(); + router.push('/login') } @@ -127,6 +138,11 @@ function toUserProfile(){ .dropdown-menu { background-color: #0A58CA; + right: -0.5rem; +} + +.dropdown-menu[data-bs-popper] { + left: auto; } .dropdown-username-link { diff --git a/src/components/Configuration/ChallangeCheckBox.vue b/src/components/Configuration/ChallangeCheckBox.vue index f607eaf3e35b40aaf68003805b53a24a8ff2f711..529a72e5a37ea7e16e17a39dbc3625155c6cc08a 100644 --- a/src/components/Configuration/ChallangeCheckBox.vue +++ b/src/components/Configuration/ChallangeCheckBox.vue @@ -1,5 +1,6 @@ <script setup lang="ts"> +const emit = defineEmits(['challengeChangedEvent']) const props = defineProps({ id: { type: String, @@ -8,14 +9,24 @@ const props = defineProps({ text: { type: String, default: '' + }, + modelValue: { + type: Boolean, + default: false } }) +const onChallengeChanged = (event: any) => { + const value = event.target.checked + const data = [props.text, value] + emit('challengeChangedEvent', data) +} + </script> <template> <span> - <input type="checkbox" class="btn-check" :id="props.id" autocomplete="off"> + <input @change="onChallengeChanged" type="checkbox" class="btn-check" :id="props.id" autocomplete="off"> <label class="btn btn-outline-primary align-items-center justify-content-center" :for="props.id">{{ props.text }}</label> </span> </template> diff --git a/src/components/Configuration/Configuration.vue b/src/components/Configuration/Configuration.vue index 3b5cd1a77d9568df7fbd46dab645aa0fd6c755ce..372b3d38384fbb5e9b5951dd73b7d843d11a5227 100644 --- a/src/components/Configuration/Configuration.vue +++ b/src/components/Configuration/Configuration.vue @@ -4,24 +4,48 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' import { useRoute } from 'vue-router' +// Configuration variables +let bankId = ref('') +let commitment = ref('') +let experience = ref('') +let suitableChallenges = ref([]) +let savingGoalTitle = ref('') +let sumToSpare = ref(0) +let dueDate = ref(null) const router = useRouter() - // The configuration steps with path and order value. const configurationSteps = {'/bank-id': 1, '/commitment': 2, '/experience': 3, '/suitable-challenges': 4, '/first-saving-goal': 5} const length = Object.keys(configurationSteps).length let percentage = ref(1/length); -// Pushes to '/bank-id' RouterView and sets current path to this path. +// Initially pushes to '/bank-id' RouterView and sets current path to this path. router.push(Object.keys(configurationSteps)[0]) let currentRoute = useRoute() let currentPath = currentRoute.fullPath +// Sets the current path to a new path and updates progressbar const onNewRouteEvent = (path) => { currentPath = path percentage.value = (1/length) * configurationSteps[path] } +const onBankIdSelectedEvent = (value) => { + bankId.value = value +} + +const onCommitmentSelectedEvent = (value) => { + commitment.value = value +} + +const onExperienceSelectedEvent = (value) => { + experience.value = value +} + +const onChallengesSelectedEvent = (value) => { + suitableChallenges.value = value +} + </script> <template> @@ -30,7 +54,12 @@ const onNewRouteEvent = (path) => { <ProgressBar id="progressbar" :percentage="percentage"/> </div> <div class="configuration-container"> - <RouterView @changeRouterEvent="onNewRouteEvent"/> + <RouterView @changeRouterEvent="onNewRouteEvent" + @bankIdSelectedEvent="onBankIdSelectedEvent" + @commitmentSelectedEvent="onCommitmentSelectedEvent" + @experienceSelectedEvent="onExperienceSelectedEvent" + @challengesSelectedEvent="onChallengesSelectedEvent" + /> </div> </div> </template> diff --git a/src/components/Configuration/ConfigurationSteps/BankId.vue b/src/components/Configuration/ConfigurationSteps/BankId.vue index 5fe760dffe3568ec89453e0197c1eb974ea9c20e..12a9e65031ef42821712c61996bbbce0e167aa91 100644 --- a/src/components/Configuration/ConfigurationSteps/BankId.vue +++ b/src/components/Configuration/ConfigurationSteps/BankId.vue @@ -4,8 +4,13 @@ import { useRouter } from 'vue-router' import { ref } from 'vue' const formRef = ref() +const bankIDRef = ref(false) +const minIdRef = ref(false) +const vippsRef = ref(false) + + const router = useRouter(); -const emit = defineEmits(['changeRouterEvent']); +const emit = defineEmits(['changeRouterEvent', 'bankIdSelectedEvent']); emit('changeRouterEvent', '/bank-id'); const onClick = () => { @@ -17,6 +22,12 @@ const onClick = () => { return; } + let choice = '' + if (bankIDRef.value.checked) choice = 'BankId på mobil' + else if (minIdRef.value.checked) choice = 'MinId' + else if (vippsRef.value.checked) choice = 'Vipps' + + emit('bankIdSelectedEvent', choice) router.push('/commitment') } @@ -32,13 +43,13 @@ const onClick = () => { <form class="btn-group-vertical" ref="formRef" @submit.prevent="onClick"> - <input type="radio" class="btn-check" name="bank-id" id="btn-check-outlined" autocomplete="off"> + <input ref="bankIDRef" type="radio" class="btn-check" name="bank-id" id="btn-check-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check-outlined">BankID på mobil</label> - <input type="radio" class="btn-check" name="bank-id" id="btn-check2-outlined" autocomplete="off"> + <input ref="minIdRef" type="radio" class="btn-check" name="bank-id" id="btn-check2-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check2-outlined">MinID</label> - <input type="radio" class="btn-check" name="bank-id" id="btn-check3-outlined" autocomplete="off"> + <input ref="vippsRef" type="radio" class="btn-check" name="bank-id" id="btn-check3-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check3-outlined">Vipps</label> </form> diff --git a/src/components/Configuration/ConfigurationSteps/Commitment.vue b/src/components/Configuration/ConfigurationSteps/Commitment.vue index 7fb352e4572209dd041e7d862f00a6cbf5077a2c..af7c1b29bd9ca24afb3ce8505cca14700e76b06a 100644 --- a/src/components/Configuration/ConfigurationSteps/Commitment.vue +++ b/src/components/Configuration/ConfigurationSteps/Commitment.vue @@ -4,8 +4,15 @@ import Button1 from '@/components/Buttons/Button1.vue' import { useRouter } from 'vue-router' import { ref } from 'vue' -const router = useRouter(); +const emit = defineEmits(['changeRouterEvent', 'commitmentSelectedEvent']) +emit('changeRouterEvent', '/commitment') + const formRef = ref() +const lowRef = ref('') +const mediumRef = ref('') +const highRef = ref('') +const router = useRouter(); + const onClick = () => { const radios = formRef.value.querySelectorAll('input[type="radio"]'); const checkedRadios = Array.from(radios).filter(radio => radio.checked); @@ -14,12 +21,16 @@ const onClick = () => { alert('Please select an option.'); return; } + + let choice = ''; + if (lowRef.value.checked) choice = 'Low' + else if (mediumRef.value.checked) choice = 'Medium' + else if (highRef.value.checked) choice = 'High' + + emit('commitmentSelectedEvent', choice) router.push('/experience') } -const emit = defineEmits(['changeRouterEvent']) -emit('changeRouterEvent', '/commitment') - </script> <template> @@ -32,13 +43,13 @@ emit('changeRouterEvent', '/commitment') <form class="btn-group-vertical" ref="formRef" @submit.prevent="onClick"> - <input type="radio" class="btn-check" name="commitment" id="btn-check-outlined" autocomplete="off"> + <input ref="lowRef" type="radio" class="btn-check" name="commitment" id="btn-check-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check-outlined">Low</label> - <input type="radio" class="btn-check" name="commitment" id="btn-check2-outlined" autocomplete="off"> + <input ref="mediumRef" type="radio" class="btn-check" name="commitment" id="btn-check2-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check2-outlined">Medium</label> - <input type="radio" class="btn-check" name="commitment" id="btn-check3-outlined" autocomplete="off"> + <input ref="highRef" type="radio" class="btn-check" name="commitment" id="btn-check3-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check3-outlined">High</label> </form> diff --git a/src/components/Configuration/ConfigurationSteps/Experience.vue b/src/components/Configuration/ConfigurationSteps/Experience.vue index fe699a992c0201cda6d4e5c7cde06e4c0da1db20..4e890f5d230d4c7a222c85ae1017b0dcbf3b1f90 100644 --- a/src/components/Configuration/ConfigurationSteps/Experience.vue +++ b/src/components/Configuration/ConfigurationSteps/Experience.vue @@ -1,12 +1,17 @@ <script setup lang="ts"> - import Button1 from '@/components/Buttons/Button1.vue' - import { useRouter } from 'vue-router' import { ref } from 'vue' const router = useRouter(); +const emit = defineEmits(['changeRouterEvent', 'experienceSelectedEvent']) +emit('changeRouterEvent', '/experience') + const formRef = ref() +const beginnerRef = ref('') +const someExperienceRef = ref('') +const expertRef = ref('') + const onClick = () => { const radios = formRef.value.querySelectorAll('input[type="radio"]'); const checkedRadios = Array.from(radios).filter(radio => radio.checked); @@ -16,31 +21,34 @@ const onClick = () => { return; } + let choice = '' + if (beginnerRef.value.checked) choice = 'Beginner' + else if (someExperienceRef.value.checked) choice = 'Some experience' + else if (expertRef.value.checked) choice = 'Expert' + + emit('experienceSelectedEvent', choice) router.push('/suitable-challenges') } -const emit = defineEmits(['changeRouterEvent']) -emit('changeRouterEvent', '/experience') - </script> <template> <div class="container"> <div> <h3 class="d-flex align-items-center justify-content-center"> - How much experice do you have with saving money? + How much experience do you have with saving money? </h3> </div> <form class="btn-group-vertical" ref="formRef" @submit.prevent="onClick"> - <input type="radio" class="btn-check" name="experience" id="btn-check-outlined" autocomplete="off"> + <input ref="beginnerRef" type="radio" class="btn-check" name="experience" id="btn-check-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check-outlined">Beginner</label> - <input type="radio" class="btn-check" name="experience" id="btn-check2-outlined" autocomplete="off"> + <input ref="someExperienceRef" type="radio" class="btn-check" name="experience" id="btn-check2-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check2-outlined">Some experience</label> - <input type="radio" class="btn-check" name="experience" id="btn-check3-outlined" autocomplete="off"> + <input ref="expertRef" type="radio" class="btn-check" name="experience" id="btn-check3-outlined" autocomplete="off"> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check3-outlined">Expert</label> </form> diff --git a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue index 09e7fcd0dc0a2263c7740ad3a2e0e816181f185a..91c39045671a85816e70cab8bd81ed8131b4452a 100644 --- a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue +++ b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue @@ -1,21 +1,33 @@ <script setup lang="ts"> - import { useRouter } from 'vue-router' - -const router = useRouter(); -const onClick = () => { - router.push('/first-saving-goal') -} - import ChallangeCheckBox from '@/components/Configuration/ChallangeCheckBox.vue' import Button1 from '@/components/Buttons/Button1.vue' +import { ref } from 'vue' -const emit = defineEmits(['changeRouterEvent']) +const emit = defineEmits(['changeRouterEvent', 'challengesSelectedEvent']) emit('changeRouterEvent', '/suitable-challenges') +const router = useRouter(); +let chosenChallenges = ref([]) const challenges = ['Make packed lunch', 'Stop shopping', 'Drop coffee', - 'Quit subscription', 'Drop car', 'Short showers', 'Exercise outside', 'Make budget', 'Others' -] + 'Quit subscription', 'Drop car', 'Short showers', 'Exercise outside', 'Make budget'] + +const onChangedChallengeEvent = (value) => { + // if challenge is checked then add it to the chosenChallenges variable + if (value[1]) { + chosenChallenges.value.push(value[0]) + } + // if challenge is unchecked then remove it from the chosenChallenges variable + else { + console.log('Reached') + chosenChallenges.value = chosenChallenges.value.filter(item => item !== value[0]); + } +} + +const onClick = () => { + emit('challengesSelectedEvent', chosenChallenges.value) + router.push('/first-saving-goal') +} </script> @@ -28,7 +40,9 @@ const challenges = ['Make packed lunch', 'Stop shopping', 'Drop coffee', </div> <div class="challenge-container"> - <ChallangeCheckBox v-for="(item, index) in challenges" :id="index" :text="item"/> + <ChallangeCheckBox v-for="(item, index) in challenges" :id="index" :text="item" + @challengeChangedEvent="onChangedChallengeEvent" + /> </div> <div class="confirm-button-container"> diff --git a/src/components/Exceptions/ErrorBoundaryCatcher.vue b/src/components/Exceptions/ErrorBoundaryCatcher.vue index 3bc046fb271f7d3b3ce3acee559329d18dce53bb..fa80d8db5bcc4694fb10be7ad2ae1fad08af48aa 100644 --- a/src/components/Exceptions/ErrorBoundaryCatcher.vue +++ b/src/components/Exceptions/ErrorBoundaryCatcher.vue @@ -1,19 +1,19 @@ <template> - <error-box :error-message="errorStore.getFirstError" @update:errorMessage="errorStore.removeCurrentError" /> - <slot /> - </template> - - <script setup lang="ts"> - import { onErrorCaptured } from 'vue'; - import { useErrorStore } from '@/stores/ErrorStore'; - import ErrorBox from '@/components/Exceptions/ErrorBox.vue'; - import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; - - const errorStore = useErrorStore(); - - onErrorCaptured((err, _vm, _info): boolean => { - const message = handleUnknownError(err.message); - errorStore.addError(message); //If no openAPI axios error, use err.message - return false; - }); - </script> \ No newline at end of file + <error-box :error-message="errorStore.getFirstError" @update:errorMessage="errorStore.removeCurrentError" /> + <slot /> +</template> + +<script setup lang="ts"> +import { onErrorCaptured } from 'vue'; +import { useErrorStore } from '@/stores/ErrorStore'; +import ErrorBox from '@/components/Exceptions/ErrorBox.vue'; +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; + +const errorStore = useErrorStore(); + +onErrorCaptured((err, _vm, _info): boolean => { + const message = handleUnknownError(err); + errorStore.addError(message); + return false; +}); +</script> \ No newline at end of file diff --git a/src/components/Exceptions/ErrorBox.vue b/src/components/Exceptions/ErrorBox.vue index f6dbe0d4adfb07e7ae1b4720ecf322b08fa3de71..d73c8b5feea6dd4e35bf86bb84ea042cce6081d5 100644 --- a/src/components/Exceptions/ErrorBox.vue +++ b/src/components/Exceptions/ErrorBox.vue @@ -1,90 +1,90 @@ <template> - <div class="error-box" v-if="errorMessage" data-testid="error-box"> - <span> - <v-icon name="bi-exclamation-triangle" /> - <button @click="wrapText = !wrapText" class="error-message-button"> - <h4>{{ errorMessage }}</h4> - </button> - <button class="error-remove-button" @click="$emit('update:errorMessage', '')" data-testid="hide-button"> - <v-icon scale="2" name="bi-dash" /> - </button> - </span> - </div> - </template> - - <script lang="ts"> - import { defineComponent } from 'vue'; - import { OhVueIcon, addIcons } from 'oh-vue-icons'; - import { BiDash, BiExclamationTriangle } from 'oh-vue-icons/icons'; - - addIcons(BiDash, BiExclamationTriangle); - - export default defineComponent({ - components: { - 'v-icon': OhVueIcon, + <div class="error-box" v-if="errorMessage" data-testid="error-box"> + <span> + <v-icon name="bi-exclamation-triangle" /> + <button @click="wrapText = !wrapText" class="error-message-button"> + <h4>{{ errorMessage }}</h4> + </button> + <button class="error-remove-button" @click="$emit('update:errorMessage', '')" data-testid="hide-button"> + <v-icon scale="2" name="bi-dash" /> + </button> + </span> + </div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import { OhVueIcon, addIcons } from 'oh-vue-icons'; +import { BiDash, BiExclamationTriangle } from 'oh-vue-icons/icons'; + +addIcons(BiDash, BiExclamationTriangle); + +export default defineComponent({ + components: { + 'v-icon': OhVueIcon, + }, + props: { + errorMessage: { + type: String, + default: '', }, - props: { - errorMessage: { - type: String, - default: '', - }, - }, - data() { - return { - wrapText: false, - }; - }, - }); - </script> - - <style scoped> - .error-box { - position: fixed; - top: 2em; - left: 50%; - transform: translate(-50%, 0); - width: min(100%, 700px); - background-color: var(--red-color); - padding: 7px; - border-radius: 5px; - z-index: 1000; - } - - .error-box span { - display: flex; - justify-content: space-between; - align-items: center; - } - - .error-message-button { - white-space: v-bind('wrapText ? "normal" : "nowrap"'); - overflow: hidden; - border: none; - } - - .error-message-button h4 { - overflow: hidden; - text-overflow: ellipsis; - } - - .error-box * { - margin: 2px; - } - - .error-box button { - background-color: transparent; - box-shadow: none; - color: black; - cursor: pointer; - } - - .error-remove-button { - color: rgb(79, 77, 77); - border: solid black 0.5px; - padding: 0.3em; - } - - .error-box button:hover { - color: rgb(40, 38, 38); - } - </style> \ No newline at end of file + }, + data() { + return { + wrapText: false, + }; + }, +}); +</script> + +<style scoped> +.error-box { + position: fixed; + top: 2em; + left: 50%; + transform: translate(-50%, 0); + width: min(100%, 700px); + background-color: var(--red-color); + padding: 7px; + border-radius: 5px; + z-index: 1000; +} + +.error-box span { + display: flex; + justify-content: space-between; + align-items: center; +} + +.error-message-button { + white-space: v-bind('wrapText ? "normal" : "nowrap"'); + overflow: hidden; + border: none; +} + +.error-message-button h4 { + overflow: hidden; + text-overflow: ellipsis; +} + +.error-box * { + margin: 2px; +} + +.error-box button { + background-color: transparent; + box-shadow: none; + color: black; + cursor: pointer; +} + +.error-remove-button { + color: rgb(79, 77, 77); + border: solid black 0.5px; + padding: 0.3em; +} + +.error-box button:hover { + color: rgb(40, 38, 38); +} +</style> \ No newline at end of file diff --git a/src/components/Exceptions/unkownErrorHandler.ts b/src/components/Exceptions/unkownErrorHandler.ts index 6f7b6d914edc88b03029a68b907f7ef45f05ee7e..68ecb6e09a91141f60b4641f7f38854a5aa0ca42 100644 --- a/src/components/Exceptions/unkownErrorHandler.ts +++ b/src/components/Exceptions/unkownErrorHandler.ts @@ -11,9 +11,9 @@ const handleUnknownError = (error: any): string => { if (error instanceof AxiosError) { return error.code!!; } else if (error instanceof BackendApiError) { - return error.body.detail ?? error.body; + return error.body.message ?? error.body; } - return 'ContextErrorMessage'; + return error; }; export default handleUnknownError; \ No newline at end of file diff --git a/src/components/InputFields/BaseInput.vue b/src/components/InputFields/BaseInput.vue index 28f6f3e8b7be448314cbf1cf5e26a3846f261d71..22ed03466408636bb262bb56fe77aa63f914b6ac 100644 --- a/src/components/InputFields/BaseInput.vue +++ b/src/components/InputFields/BaseInput.vue @@ -29,6 +29,10 @@ const props = defineProps({ min: { type: String, required: false + }, + pattern: { + type: String, + default: null } }); @@ -47,6 +51,7 @@ const onInputEvent = (event: any) => { :placeholder="props.placeholder" :id="inputId" required :min="min" + :pattern="pattern" /> <div v-if="props.isValid" class="invalid-feedback">Invalid {{ label }}</div> <div v-else class="valid-feedback">Valid {{ label }}</div> diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index 22d6bfcc01d426a98a51dae4b110fe5216deb3e8..953ecb8e13403f5efcd766222a1f936b6280a630 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -11,6 +11,7 @@ import { useErrorStore } from '@/stores/ErrorStore'; const emailRef = ref() const passwordRef = ref() const formRef = ref() +let errorMsg = ref(''); const errorStore = useErrorStore(); const router = useRouter(); @@ -25,6 +26,7 @@ const handlePasswordInputEvent = (newValue: any) => { } const handleSubmit = async () => { + formRef.value.classList.add("was-validated") const loginUserPayload: LoginRequest = { email: emailRef.value, password: passwordRef.value @@ -34,7 +36,7 @@ const handleSubmit = async () => { let response = await AuthenticationService.login({ requestBody: loginUserPayload }); if (response.token == null || response.token == undefined) { - //errorBoxMsg.value = 'A valid token could not be created'; + errorMsg.value = 'A valid token could not be created'; return; } @@ -49,7 +51,7 @@ const handleSubmit = async () => { }); router.push({ name: 'home' }); } catch (error: any) { - console.log(error); + errorMsg.value = handleUnknownError(error); } } @@ -57,11 +59,17 @@ const handleSubmit = async () => { <template> <div class="container-fluid"> - <form ref="formRef" id="loginForm" @submit.prevent="handleSubmit"> + <div class="container-fluid d-flex justify-content-center align-items-center flex-column mt-5"> + <img src="@/assets/Sparesti-logo.png" style="width: 300px"> + <h1>Sparesti.no</h1> + </div> + <form ref="formRef" id="loginForm" @submit.prevent="handleSubmit" novalidate> <BaseInput :model-value="emailRef" @input-change-event="handleEmailInputEvent" id="emailInput" input-id="email" type="email" label="Email" placeholder="Enter your email" /> - <BaseInput :model-value="passwordRef" @input-change-event="handlePasswordInputEvent" id="passwordInput" - input-id="password" type="password" label="Password" placeholder="Enter password" /> + <BaseInput pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" :model-value="passwordRef" + @input-change-event="handlePasswordInputEvent" id="passwordInput" input-id="password" type="password" + label="Password" placeholder="Enter password" /> + <p class="text-danger">{{ errorMsg }}</p> <button1 id="confirmButton" type="submit" @click="handleSubmit" button-text="Login"></button1> </form> </div> @@ -83,4 +91,8 @@ const handleSubmit = async () => { #confirmButton { margin: 1rem 0; } + +h1 { + font-size: 4rem; +} </style> \ No newline at end of file diff --git a/src/components/SignUp/SignUpForm.vue b/src/components/SignUp/SignUpForm.vue index 9ef325bd77ddadb23c4691050d9c49c7ece548fe..b085f6ee59b1b3d38c777f8b041fa9d1b76013d6 100644 --- a/src/components/SignUp/SignUpForm.vue +++ b/src/components/SignUp/SignUpForm.vue @@ -14,7 +14,6 @@ const formRef = ref() const handleFirstNameInputEvent = (newValue: any) => { firstNameRef.value = newValue - console.log(firstNameRef.value) } const handleSurnameInputEvent = (newValue: any) => { @@ -34,9 +33,15 @@ const handleConfirmPasswordInputEvent = (newValue: any) => { } const handleSubmit = () => { + const form = formRef.value; + + // Check if the form is valid + if (form.checkValidity()) { + // Form is valid, submit the form or perform other actions + console.log('Form is valid'); + } + formRef.value.classList.add("was-validated") - alert("Expected to be transferred to initial configuration") // Todo remove this line - router.push("/configuration") } </script> diff --git a/src/router/index.ts b/src/router/index.ts index 89735e92847bc901ec0abc79287c063ed93d4f6c..153ab82c970772c63b946fa0a0936bb8a44c40a8 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -12,15 +12,15 @@ const routes = [ path: '/', name: 'base', component: () => import('@/views/BasePageView.vue'), + meta: { requiresAuth: true }, children: [ { path: '', name: 'home', - component: () => import('../views/HomeView.vue'), - meta: { requiresAuth: true }, + component: () => import('../views/SavingGoalView/RoadmapView.vue'), }, { - path: '/news', + path: 'news', name: 'news', component: () => import('@/views/NewsView.vue'), }, @@ -59,11 +59,16 @@ const routes = [ name: 'shop', component: () => import('@/views/ShopView.vue'), }, + { + path: '/profile', + name: 'profile', + component: UserProfileView + }, { path: 'admin', name: 'admin', component: () => import('@/views/TestView.vue'), - meta: { requiresAdmin: true } + meta: { requiresAdmin: true }, }, { path: 'unauthorized', @@ -136,9 +141,13 @@ const router = createRouter({ router.beforeEach((to, from, next) => { const requiresAuth = to.matched.some(record => record.meta.requiresAuth); const requiresAdmin = to.matched.some(record => record.meta.requiresAdmin); - const userRole = useUserInfoStore().role; + let user = useUserInfoStore(); + const userRole = user.role; + const isAuthenticated = user.isLoggedIn; - if (requiresAdmin && userRole !== 'admin') { + if (requiresAuth && !isAuthenticated) { + next({ name: 'login', query: { redirect: to.fullPath } }); + } else if (requiresAdmin && userRole !== 'admin') { next({ name: 'unauthorized' }); } else { next(); diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index 5fbdf1e65bb7759f4434051694661543033900e1..74e952ab685696d19016d1e57fc88912d10d37be 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -33,6 +33,7 @@ export type UserStoreInfo = { email?: string; firstname?: string; lastname?: string; + password?: string; accessToken?: string; role?: string; }; @@ -42,10 +43,17 @@ export const useUserInfoStore = defineStore('UserInfoStore', { email: '', firstname: '', lastname: '', + password: '', accessToken: '', role: '', }), actions: { + setPassword(password: string) { + this.password = password + }, + resetPassword() { + this.password = '' + }, setUserInfo(userinfo: UserStoreInfo) { userinfo.email && (this.$state.email = userinfo.email); userinfo.firstname && (this.$state.firstname = userinfo.firstname); @@ -64,6 +72,9 @@ export const useUserInfoStore = defineStore('UserInfoStore', { }, }, getters: { + getPassword(): string { + return this.password + }, isLoggedIn(): boolean { return this.accessToken !== ''; }, diff --git a/src/views/Authentication/LoginView.vue b/src/views/Authentication/LoginView.vue index a2aa0f410c32b81ef253338a1b657b19d732e442..92ff4ea8f2288a9e61950c797add14dfd0e20ac7 100644 --- a/src/views/Authentication/LoginView.vue +++ b/src/views/Authentication/LoginView.vue @@ -5,9 +5,7 @@ import Login from '@/components/Login/Login.vue' </script> <template> - <Menu/> <Login/> - <Footer/> </template> <style scoped>