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/assets/401-error.png b/src/assets/401-error.png new file mode 100644 index 0000000000000000000000000000000000000000..a8b56b52cd443362bdf7c0b6243de89c5969e9b8 Binary files /dev/null and b/src/assets/401-error.png differ diff --git a/src/assets/friends.png b/src/assets/friends.png new file mode 100644 index 0000000000000000000000000000000000000000..a39d6faafecff57381efb1613246869ebdea78d1 Binary files /dev/null and b/src/assets/friends.png differ diff --git a/src/assets/globe.png b/src/assets/globe.png new file mode 100644 index 0000000000000000000000000000000000000000..952f5047eca01457ef32c4b4d300637e7ad7a560 Binary files /dev/null and b/src/assets/globe.png differ diff --git a/src/assets/items/adfree.png b/src/assets/items/adfree.png new file mode 100644 index 0000000000000000000000000000000000000000..af62209b5d29e2d659f9df580ae9670be83b9007 Binary files /dev/null and b/src/assets/items/adfree.png differ diff --git a/src/assets/items/coffee.jpg b/src/assets/items/coffee.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25562ca50b142118497b8b62afd5bcf98eb84fd9 Binary files /dev/null and b/src/assets/items/coffee.jpg differ diff --git a/src/assets/items/piggybank.webp b/src/assets/items/piggybank.webp new file mode 100644 index 0000000000000000000000000000000000000000..068336a163210042ce00db6aeb4e3836a90b1e88 Binary files /dev/null and b/src/assets/items/piggybank.webp differ diff --git a/src/assets/items/pirbad.png b/src/assets/items/pirbad.png new file mode 100644 index 0000000000000000000000000000000000000000..b68ddd17633f9a2e1ed83f7226ab97b9f05b65ed Binary files /dev/null and b/src/assets/items/pirbad.png differ diff --git a/src/assets/items/pirbadet.png b/src/assets/items/pirbadet.png new file mode 100644 index 0000000000000000000000000000000000000000..1789b7dc015c99dc8361949448e99719f34b79e8 Binary files /dev/null and b/src/assets/items/pirbadet.png differ diff --git a/src/assets/items/viaplay.jpg b/src/assets/items/viaplay.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aab9fd756faee9a151d3a3f1643e53876424c6b7 Binary files /dev/null and b/src/assets/items/viaplay.jpg differ diff --git a/src/components/Configuration/ChallangeCheckBox.vue b/src/components/Configuration/ChallangeCheckBox.vue index 529a72e5a37ea7e16e17a39dbc3625155c6cc08a..2157aa34568ad97ab1634ba44fec076463914a74 100644 --- a/src/components/Configuration/ChallangeCheckBox.vue +++ b/src/components/Configuration/ChallangeCheckBox.vue @@ -16,6 +16,12 @@ const props = defineProps({ } }) +/** + * This method is run whenever a change has occurred in the checkbox. It retrieves + * the current value of the checkbox (true/false) and emits a data object containing + * the challenge's description and the checked value. + * @param event The event object containing information about the input's checked value. + */ const onChallengeChanged = (event: any) => { const value = event.target.checked const data = [props.text, value] diff --git a/src/components/Configuration/Configuration.vue b/src/components/Configuration/Configuration.vue index 372b3d38384fbb5e9b5951dd73b7d843d11a5227..a02e631534b19c2cebcbc3cb86beaac7ee1aa804 100644 --- a/src/components/Configuration/Configuration.vue +++ b/src/components/Configuration/Configuration.vue @@ -1,51 +1,27 @@ <script setup lang="ts"> -import ProgressBar from '@/components/Configuration/ProgressBar.vue' +import ProgressBar from '@/components/Configuration/ConfigurationProgressBar.vue' 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 configurationSteps = {'/commitment': 1, '/experience': 2, '/suitable-challenges': 3} const length = Object.keys(configurationSteps).length let percentage = ref(1/length); -// Initially pushes to '/bank-id' RouterView and sets current path to this path. +// Initially pushes to the commitment-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) => { +const onNewRouteEvent = (path: string) => { 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> @@ -54,12 +30,7 @@ const onChallengesSelectedEvent = (value) => { <ProgressBar id="progressbar" :percentage="percentage"/> </div> <div class="configuration-container"> - <RouterView @changeRouterEvent="onNewRouteEvent" - @bankIdSelectedEvent="onBankIdSelectedEvent" - @commitmentSelectedEvent="onCommitmentSelectedEvent" - @experienceSelectedEvent="onExperienceSelectedEvent" - @challengesSelectedEvent="onChallengesSelectedEvent" - /> + <RouterView @changeRouterEvent="onNewRouteEvent"/> </div> </div> </template> diff --git a/src/components/Configuration/ProgressBar.vue b/src/components/Configuration/ConfigurationProgressBar.vue similarity index 100% rename from src/components/Configuration/ProgressBar.vue rename to src/components/Configuration/ConfigurationProgressBar.vue diff --git a/src/components/Configuration/ConfigurationSteps/BankId.vue b/src/components/Configuration/ConfigurationSteps/BankId.vue index 12a9e65031ef42821712c61996bbbce0e167aa91..bc40db3e7cd5aa6501e2daef33dc14842df2637c 100644 --- a/src/components/Configuration/ConfigurationSteps/BankId.vue +++ b/src/components/Configuration/ConfigurationSteps/BankId.vue @@ -1,4 +1,5 @@ <script setup lang="ts"> +/* import Button1 from '@/components/Buttons/Button1.vue' import { useRouter } from 'vue-router' import { ref } from 'vue' @@ -10,7 +11,7 @@ const vippsRef = ref(false) const router = useRouter(); -const emit = defineEmits(['changeRouterEvent', 'bankIdSelectedEvent']); +const emit = defineEmits(['changeRouterEvent']); emit('changeRouterEvent', '/bank-id'); const onClick = () => { @@ -30,10 +31,11 @@ const onClick = () => { emit('bankIdSelectedEvent', choice) router.push('/commitment') } - +*/ </script> <template> + <!-- <div class="container"> <div> <h3 class="d-flex align-items-center justify-content-center"> @@ -62,9 +64,11 @@ const onClick = () => { </div> </div> + --> </template> <style scoped> +/* #confirmButton { margin-bottom: 2rem; width: 300px; @@ -74,4 +78,5 @@ const onClick = () => { display: flex; justify-content: center; } + */ </style> \ No newline at end of file diff --git a/src/components/Configuration/ConfigurationSteps/Commitment.vue b/src/components/Configuration/ConfigurationSteps/Commitment.vue index af7c1b29bd9ca24afb3ce8505cca14700e76b06a..05f071e4966bdd1148a58295340c56185847080e 100644 --- a/src/components/Configuration/ConfigurationSteps/Commitment.vue +++ b/src/components/Configuration/ConfigurationSteps/Commitment.vue @@ -1,59 +1,63 @@ <script setup lang="ts"> - import Button1 from '@/components/Buttons/Button1.vue' import { useRouter } from 'vue-router' import { ref } from 'vue' +import { useConfigurationStore } from '@/stores/ConfigurationStore' + +const router = useRouter(); -const emit = defineEmits(['changeRouterEvent', 'commitmentSelectedEvent']) +// Updates progress bar in the parent Configuration component. +const emit = defineEmits(['changeRouterEvent']) emit('changeRouterEvent', '/commitment') +// Reactive variables for form and radio buttons. const formRef = ref() const lowRef = ref('') const mediumRef = ref('') const highRef = ref('') -const router = useRouter(); - +let errorMsg = ref(''); + +/** + * Validates the commitment form radio buttons and updates the commitment choice in the store. + * If form validation is successful, updates the commitment choice in the store + * and navigates to the '/experience' route. If form validation fails, displays + * an error message prompting the user to select an option before continuing. + */ const onClick = () => { - const radios = formRef.value.querySelectorAll('input[type="radio"]'); - const checkedRadios = Array.from(radios).filter(radio => radio.checked); - - if (checkedRadios.length === 0) { - alert('Please select an option.'); - return; + const form = formRef.value; + if (form.checkValidity()) { + let choice = ''; + if (lowRef.value.checked) choice = 'LITTLE' + else if (mediumRef.value.checked) choice = 'SOME' + else if (highRef.value.checked) choice = 'MUCH' + useConfigurationStore().setCommitment(choice) + router.push('/experience') + } + else { + errorMsg.value = 'Please select an option before continuing' } - - 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') } </script> <template> <div class="container"> - <div> - <h3 class="d-flex align-items-center justify-content-center"> - In which degree are you willing to make changes? - </h3> - </div> - + <h3 id="commitmentText" class="align-items-center justify-content-center"> + In which degree are you willing to make changes? + </h3> <form class="btn-group-vertical" ref="formRef" @submit.prevent="onClick"> - <input ref="lowRef" 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" required> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check-outlined">Low</label> - <input ref="mediumRef" 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" required> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check2-outlined">Medium</label> - <input ref="highRef" 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" required> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check3-outlined">High</label> </form> - + <p class="text-danger">{{ errorMsg }}</p> <div class="confirm-button-container"> <button1 id="confirmButton" @click="onClick" button-text="Continue"></button1> </div> @@ -61,6 +65,13 @@ const onClick = () => { </template> <style scoped> +div.container { + display: flex; + flex-direction: column; + justify-self: center; + max-width: 500px; +} + #confirmButton { margin-bottom: 2rem; width: 300px; diff --git a/src/components/Configuration/ConfigurationSteps/Experience.vue b/src/components/Configuration/ConfigurationSteps/Experience.vue index 4e890f5d230d4c7a222c85ae1017b0dcbf3b1f90..ae4e5b2ef104e3963229dcb8ffb1e58764b0d109 100644 --- a/src/components/Configuration/ConfigurationSteps/Experience.vue +++ b/src/components/Configuration/ConfigurationSteps/Experience.vue @@ -2,32 +2,40 @@ import Button1 from '@/components/Buttons/Button1.vue' import { useRouter } from 'vue-router' import { ref } from 'vue' +import { useConfigurationStore } from '@/stores/ConfigurationStore' const router = useRouter(); -const emit = defineEmits(['changeRouterEvent', 'experienceSelectedEvent']) + +// Updates progress bar in the parent Configuration component. +const emit = defineEmits(['changeRouterEvent']) emit('changeRouterEvent', '/experience') +// Declaration of reactive variables for the form and radio buttons const formRef = ref() const beginnerRef = ref('') const someExperienceRef = ref('') const expertRef = ref('') +let errorMsg = ref(''); +/** + * Validates the experience form radio buttons and updates the commitment choice in the store. + * If form validation is successful, updates the commitment choice in the store + * and navigates to the '/suitable challenges' route. If form validation fails, displays + * an error message prompting the user to select an option before continuing. + */ const onClick = () => { - const radios = formRef.value.querySelectorAll('input[type="radio"]'); - const checkedRadios = Array.from(radios).filter(radio => radio.checked); - - if (checkedRadios.length === 0) { - alert('Please select an option.'); - return; + const form = formRef.value; + if (form.checkValidity()) { + let choice = '' + if (beginnerRef.value.checked) choice = 'NONE' + else if (someExperienceRef.value.checked) choice = 'SOME' + else if (expertRef.value.checked) choice = 'EXPERT' + useConfigurationStore().setExperience(choice) + router.push('/suitable-challenges') + } + else { + errorMsg.value = 'Please select an option before continuing' } - - 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') } </script> @@ -42,16 +50,16 @@ const onClick = () => { <form class="btn-group-vertical" ref="formRef" @submit.prevent="onClick"> - <input ref="beginnerRef" 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" required> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check-outlined">Beginner</label> - <input ref="someExperienceRef" 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" required> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check2-outlined">Some experience</label> - <input ref="expertRef" 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" required> <label class="btn btn-outline-primary d-flex align-items-center justify-content-center" for="btn-check3-outlined">Expert</label> </form> - + <p class="text-danger">{{ errorMsg }}</p> <div class="confirm-button-container"> <button1 id="confirmButton" @click="onClick" button-text="Continue"></button1> </div> @@ -59,6 +67,13 @@ const onClick = () => { </template> <style scoped> +div.container { + display: flex; + flex-direction: column; + justify-self: center; + max-width: 500px; +} + #confirmButton { margin-bottom: 2rem; width: 300px; diff --git a/src/components/Configuration/ConfigurationSteps/FirstSavingGoal.vue b/src/components/Configuration/ConfigurationSteps/FirstSavingGoal.vue index 50e010cb29ea36b8d581d89ab7af59b21c0bb22b..da397304a8cdc40ea8e21212fc440a64d30c2a1c 100644 --- a/src/components/Configuration/ConfigurationSteps/FirstSavingGoal.vue +++ b/src/components/Configuration/ConfigurationSteps/FirstSavingGoal.vue @@ -1,4 +1,5 @@ <script setup lang="ts"> +/* import BaseInput from '@/components/InputFields/BaseInput.vue' import { ref } from 'vue' import Button1 from '@/components/Buttons/Button1.vue' @@ -33,10 +34,11 @@ const getTodayDate = () => { return `${year}-${month}-${day}`; }; - + */ </script> <template> + <!-- <div class="container"> <div> <h3 class="d-flex align-items-center justify-content-center"> @@ -72,9 +74,11 @@ const getTodayDate = () => { <button1 id="confirmButton" @click="onClick" button-text="Continue"></button1> </div> </div> + --> </template> <style scoped> +/* #confirmButton { margin-bottom: 2rem; width: 300px; @@ -84,4 +88,5 @@ const getTodayDate = () => { display: flex; justify-content: center; } +*/ </style> \ No newline at end of file diff --git a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue index dc849d5cdde9afd61f9e53e53b4eaefba203bf64..2401b49d3f1452d28f4d3aa91732be573c9279b7 100644 --- a/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue +++ b/src/components/Configuration/ConfigurationSteps/SuitableChallenges.vue @@ -3,15 +3,31 @@ import { useRouter } from 'vue-router' import ChallangeCheckBox from '@/components/Configuration/ChallangeCheckBox.vue' import Button1 from '@/components/Buttons/Button1.vue' import { ref } from 'vue' +import { useConfigurationStore } from '@/stores/ConfigurationStore' +import { useUserInfoStore } from '@/stores/UserStore' +import { AuthenticationService, OpenAPI, SignUpRequest } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' -const emit = defineEmits(['changeRouterEvent', 'challengesSelectedEvent']) -emit('changeRouterEvent', '/suitable-challenges') const router = useRouter(); +// Updates progress bar in the parent Configuration component. +const emit = defineEmits(['changeRouterEvent']) +emit('changeRouterEvent', '/suitable-challenges') + +// Reactive variables for chosen challenges and error message. let chosenChallenges = ref([]) +let errorMsg = ref('') + +// Represents a list of available challenges. const challenges = ['Make packed lunch', 'Stop shopping', 'Drop coffee', 'Quit subscription', 'Drop car', 'Short showers', 'Exercise outside', 'Make budget'] +/** + * Handles the event when a challenge is selected or deselected. + * @param {Array} value - An array containing the challenge value and its checked status. + * The first element is the challenge value, and the second element + * indicates whether the challenge is checked (true) or unchecked (false). + */ const onChangedChallengeEvent = (value) => { // if challenge is checked then add it to the chosenChallenges variable if (value[1]) { @@ -19,15 +35,59 @@ const onChangedChallengeEvent = (value) => { } // if challenge is unchecked then remove it from the chosenChallenges variable else { - console.log('Reached') chosenChallenges.value = chosenChallenges.value.filter(item => item !== value[0]); } - console.log(chosenChallenges.value) } -const onClick = () => { - emit('challengesSelectedEvent', chosenChallenges.value) - router.push('/first-saving-goal') +/** + * Retrieves user configuration and signup information, sends a signup request to the backend. + * + * @throws {Error} Throws an error if signup fails. + */ +const onClick = async () => { + try { + // Saves the chosen challenges to the configuration store + useConfigurationStore().setChallenges(chosenChallenges.value) + + /* + TODO: 'changeWilling' are updated to 'commitment' in backend, must update it in frontend + const signUpPayLoad: SignUpRequest = { + changeWilling: useConfigurationStore().getCommitment, + experience: useConfigurationStore().getExperience, + challenges: useConfigurationStore().getChallenges, + firstName: useUserInfoStore().getFirstName, + lastName: useUserInfoStore().getLastname, + email: useUserInfoStore().getEmail, + password: useUserInfoStore().getPassword, + }; + */ + + const signUpPayLoad = { + "commitment": useConfigurationStore().getCommitment, + "experience": useConfigurationStore().getExperience, + "challenges": useConfigurationStore().getChallenges, + "firstName": useUserInfoStore().getFirstName, + "lastName": useUserInfoStore().getLastname, + "email": useUserInfoStore().getEmail, + "password": useUserInfoStore().getPassword, + }; + + let response = await AuthenticationService.signup({ requestBody: signUpPayLoad }); + if (response.token == null) { + errorMsg.value = 'A valid token could not be created'; + return; + } + OpenAPI.TOKEN = response.token; + useUserInfoStore().setUserInfo({ + accessToken: response.token, + role: response.role, + }); + useUserInfoStore().resetPassword() + await router.push({ name: 'home' }); + } + catch (error) { + errorMsg.value = handleUnknownError(error); + } } </script> @@ -46,8 +106,10 @@ const onClick = () => { /> </div> + <p class="text-danger">{{ errorMsg }}</p> + <div class="confirm-button-container"> - <button1 id="confirmButton" @click="onClick" button-text="Continue"></button1> + <button1 id="confirmButton" @click="onClick" button-text="Finish configuration"></button1> </div> </div> </template> diff --git a/src/components/InputFields/BaseInput.vue b/src/components/InputFields/BaseInput.vue index c773e4c203294cabd1dee9d555dd94b013961871..8e20e43d95e8ac8cf46e6253d1ef39c4ea52d351 100644 --- a/src/components/InputFields/BaseInput.vue +++ b/src/components/InputFields/BaseInput.vue @@ -37,6 +37,10 @@ const props = defineProps({ invalidMessage: { type: String, default: '' + }, + required: { + type: Boolean, + default: true } }); @@ -53,9 +57,11 @@ const onInputEvent = (event: any) => { :type="type" class="form-control" :placeholder="placeholder" - :id="inputId" required + :id="inputId" :min="min" - :pattern="pattern"/> + :pattern="pattern" + :required="required" + /> <div class="valid-feedback">{{ validMessage }}</div> <div class="invalid-feedback">{{ invalidMessage }}</div> </div> diff --git a/src/components/LeaderboardComponents/Leaderboard.vue b/src/components/LeaderboardComponents/Leaderboard.vue index 73cc5da9cf9df92b12522a9577cf0e9a2c0faff9..0a1b9a5b472971d79cac4aa62380e891caa72ec7 100644 --- a/src/components/LeaderboardComponents/Leaderboard.vue +++ b/src/components/LeaderboardComponents/Leaderboard.vue @@ -4,7 +4,7 @@ <table> <tr v-for="(entry, index) in leaderboard" :key="entry.user.id"> <td class="number">{{ index + 1 }}</td> - <td class="name" @click="navigateToUserProfile(entry.user.id)">{{ entry.user.username }}</td> + <td class="name" @click="navigateToUserProfile(entry.user.id)">{{ entry.user.firstName }}</td> <td class="points" v-if="index === 0"> {{ entry.score }} <div class = "medal"> @@ -55,7 +55,7 @@ display: flex; align-items: center; justify-content: space-between; - flex-wrap: wrap; + height: 4rem; } tr:not(:first-child):hover { @@ -126,7 +126,7 @@ } .ribbon { - width: 100%; + width: 106%; height: 4.5rem; top: -0.5rem; background-color: #0A58CA; diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index 21d4de86686d68850b9ee49b09cb9246be596ced..6091ab82d28b5c081b081370cd01996d78d327d2 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -7,6 +7,7 @@ import { AuthenticationService, OpenAPI, LoginRequest } from '@/api'; import { useRouter, useRoute } from 'vue-router'; import handleUnknownError from '@/components/Exceptions/unkownErrorHandler'; import { useErrorStore } from '@/stores/ErrorStore'; +import SignUpLink from '@/components/SignUp/SignUpLink.vue' const emailRef = ref('') const passwordRef = ref('') @@ -54,7 +55,7 @@ const handleSubmit = async () => { email: emailRef.value, role: response.role, }); - router.push({ name: 'home' }); + await router.push({ name: 'home' }); } catch (error: any) { errorMsg.value = handleUnknownError(error); } @@ -95,6 +96,7 @@ const handleSubmit = async () => { <p class="text-danger">{{ errorMsg }}</p> <button1 id="confirmButton" type="submit" @click="handleSubmit" button-text="Login"></button1> + <SignUpLink/> </form> </div> </template> diff --git a/src/components/Login/LoginLink.vue b/src/components/Login/LoginLink.vue new file mode 100644 index 0000000000000000000000000000000000000000..0f23a2edbeed887995e5db725c0aac37092effa6 --- /dev/null +++ b/src/components/Login/LoginLink.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> + +</script> + +<template> + <p>Already have an account? <RouterLink to="/login">Login</RouterLink></p> +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/src/components/SignUp/SignUpForm.vue b/src/components/SignUp/SignUpForm.vue index 9a8dab783a1c291575375d0f8ea69fb1d3a6b525..dad760e62b52dab00c22e31e5c63dba26ad0c0bb 100644 --- a/src/components/SignUp/SignUpForm.vue +++ b/src/components/SignUp/SignUpForm.vue @@ -3,8 +3,13 @@ import BaseInput from '@/components/InputFields/BaseInput.vue' import Button1 from '@/components/Buttons/Button1.vue' import { ref } from 'vue' import { useRouter } from 'vue-router' +import { AuthenticationService } from '@/api' +import handleUnknownError from '@/components/Exceptions/unkownErrorHandler' +import { useUserInfoStore } from '@/stores/UserStore' +import LoginLink from '@/components/Login/LoginLink.vue' const router = useRouter(); +const userStore = useUserInfoStore(); const firstNameRef = ref('') const surnameRef = ref('') @@ -13,10 +18,10 @@ const passwordRef = ref('') const confirmPasswordRef = ref('') const formRef = ref() let samePasswords = ref(true) +let errorMsg = ref(''); const handleFirstNameInputEvent = (newValue: any) => { firstNameRef.value = newValue - console.log(firstNameRef.value) } const handleSurnameInputEvent = (newValue: any) => { @@ -29,41 +34,43 @@ const handleEmailInputEvent = (newValue: any) => { const handlePasswordInputEvent = (newValue: any) => { passwordRef.value = newValue - console.log(passwordRef.value) } const handleConfirmPasswordInputEvent = (newValue: any) => { confirmPasswordRef.value = newValue - console.log(confirmPasswordRef.value) } const handleSubmit = async () => { - console.log(firstNameRef.value) samePasswords.value = (passwordRef.value === confirmPasswordRef.value) - console.log(samePasswords.value) - const form = formRef.value; + formRef.value.classList.add("was-validated") - // Check if the form is valid + const form = formRef.value; if (form.checkValidity()) { - // Form is valid, submit the form or perform other actions - console.log('Form is valid'); - } else { - console.log('Form is not valid'); + if (samePasswords.value) { + try { + let response = await AuthenticationService.validateEmail({email: emailRef.value}); + userStore.setUserInfo({ + firstname: firstNameRef.value, + lastname: surnameRef.value, + email: emailRef.value, + }); + userStore.setPassword(passwordRef.value) + await router.push('/configuration') + } catch (error) { + errorMsg.value = handleUnknownError(error); + } + } } - - - formRef.value.classList.add("was-validated") } </script> <template> <div class="container"> - <form ref="formRef" id="signUpForm" @submit.prevent="handleSubmit"> - <BaseInput :model-value="firstNameRef" + <form ref="formRef" id="signUpForm" @submit.prevent="handleSubmit" novalidate> + <BaseInput :model-value=firstNameRef @input-change-event="handleFirstNameInputEvent" - ref="firstNameRef" id="firstNameInput" input-id="first-name" type="text" @@ -72,7 +79,6 @@ const handleSubmit = async () => { invalid-message="Please enter your first name"/> <BaseInput :model-value="surnameRef" @input-change-event="handleSurnameInputEvent" - ref="surnameRef" id="surnameInput" input-id="surname" type="text" @@ -81,7 +87,6 @@ const handleSubmit = async () => { invalid-message="Please enter your surname"/> <BaseInput :model-value="emailRef" @input-change-event="handleEmailInputEvent" - ref="emailRef" id="emailInput" input-id="email" type="email" @@ -90,7 +95,6 @@ const handleSubmit = async () => { invalid-message="Invalid email"/> <BaseInput :model-value="passwordRef" @input-change-event="handlePasswordInputEvent" - ref="passwordRef" id="passwordInput" input-id="password" type="password" @@ -100,7 +104,6 @@ const handleSubmit = async () => { invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number"/> <BaseInput :modelValue="confirmPasswordRef" @input-change-event="handleConfirmPasswordInputEvent" - ref="confirmPasswordRef" id="confirmPasswordInput" input-id="confirmPassword" type="password" @@ -108,8 +111,10 @@ const handleSubmit = async () => { label="Confirm Password" placeholder="Confirm password" invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number"/> - <p v-if="samePasswords" class="text-danger">The passwords are not identical</p> + <p class="text-danger">{{ errorMsg }}</p> + <p v-if="!samePasswords" class="text-danger">The passwords are not identical</p> <button1 id="confirmButton" @click="handleSubmit" button-text="Sign up"></button1> + <LoginLink/> </form> </div> diff --git a/src/components/SignUp/SignUpLink.vue b/src/components/SignUp/SignUpLink.vue new file mode 100644 index 0000000000000000000000000000000000000000..f7c354b6b9d64f7cc776d64f5336d739a95fa116 --- /dev/null +++ b/src/components/SignUp/SignUpLink.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> + +</script> + +<template> + <p>Don't have an account? <RouterLink to="/sign-up">Sign up</RouterLink></p> +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/src/stores/ConfigurationStore.ts b/src/stores/ConfigurationStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb9e0dd0d13fecdf719f8bc68970fe793b465027 --- /dev/null +++ b/src/stores/ConfigurationStore.ts @@ -0,0 +1,38 @@ +import { defineStore } from 'pinia' +export const useConfigurationStore = defineStore('ConfigurationStore', { + state: () => ({ + commitment: '', + experience: '', + challenges: [] + }), + actions: { + setCommitment(commitment: string) { + this.commitment = commitment + }, + setExperience(experience: string) { + this.experience = experience + }, + setChallenges(challenges: Array<string>) { + this.challenges = challenges + }, + resetConfiguration() { + this.commitment = '' + this.experience = '' + this.challenges = [] + } + }, + getters: { + getCommitment(): string { + return this.commitment + }, + getExperience(): string { + return this.experience + }, + getChallenges(): string { + return this.challenges + } + }, + persist: { + enabled: true, + } +}); diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index 74e952ab685696d19016d1e57fc88912d10d37be..5622d43f2392f7194e8d386cdce96ab4ba5930cf 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -75,6 +75,15 @@ export const useUserInfoStore = defineStore('UserInfoStore', { getPassword(): string { return this.password }, + getFirstName(): string { + return this.firstname + }, + getLastname(): string { + return this.lastname + }, + getEmail(): string { + return this.email + }, isLoggedIn(): boolean { return this.accessToken !== ''; }, diff --git a/src/views/Authentication/SignUpView.vue b/src/views/Authentication/SignUpView.vue index ce0bd3ada3a372fbae632554d2e28bf02f92e162..b6b2f2e951c3c776ae746a3779e73301bb6c6f48 100644 --- a/src/views/Authentication/SignUpView.vue +++ b/src/views/Authentication/SignUpView.vue @@ -1,13 +1,9 @@ <script setup lang="ts"> -import Footer from '@/components/BaseComponents/Footer.vue' -import Menu from '@/components/BaseComponents/Menu.vue' import SignUp from '@/components/SignUp/SignUp.vue' </script> <template> - <Menu/> <SignUp/> - <Footer/> </template> <style scoped> diff --git a/src/views/ConfigurationView.vue b/src/views/ConfigurationView.vue index 6732fee8b3dc4710002df7d991334fdaa15e3ea5..58a63f43e9d6cb7ab5f205a8fb09489b9bd2701c 100644 --- a/src/views/ConfigurationView.vue +++ b/src/views/ConfigurationView.vue @@ -5,7 +5,5 @@ import Footer from '@/components/BaseComponents/Footer.vue' </script> <template> - <Menu/> <Configuration/> - <Footer/> </template> \ No newline at end of file diff --git a/src/views/LeaderboardView.vue b/src/views/LeaderboardView.vue index 681a963a43723485f5eac19e1a7b8b3d32753140..5a7adf09279d07a24d470098e85a1dd6c491ada2 100644 --- a/src/views/LeaderboardView.vue +++ b/src/views/LeaderboardView.vue @@ -1,43 +1,96 @@ <template> + <br> <div id="dropdownContainer"> - <Button class="btn btn-primary text-white leaderBoardButton">Global</Button> - <Button class="btn btn-primary text-white leaderBoardButton">Friends</Button> + <h1 class="box">Leaderboard</h1> + </div> + <div id = "content"> + <div id="dropdownContainer"> + <div class="box"> + <div class="btn-group-vertical" id="radioContainer" role="group" + aria-label="Vertical radio toggle button group"> + <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio1" autocomplete="off" checked> + <label class="btn btn-outline-primary" for="vbtn-radio1" @click="global"><img src="@/assets/globe.png" style="width: 60px"> Global</label> + <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio2" autocomplete="off"> + <label class="btn btn-outline-primary" for="vbtn-radio2" @click="friends"><img src="@/assets/friends.png" style="width: 60px"> Friends</label> + </div> + </div> </div> <main> <div id="leaderboard"> <h1><img src="@/assets/items/v-buck.png" style="width: 2rem"> Total points</h1> - <Leaderboard :leaderboard="leaderboardData" @navigateToUserProfile="navigateToUserProfile" /> + <Leaderboard :leaderboard="pointsLeaderboardData" @navigateToUserProfile="navigateToUserProfile" /> </div> <div id="leaderboard"> <h1><img src="@/assets/icons/fire.png" style="width: 2rem"> Current streak</h1> - <Leaderboard :leaderboard="leaderboardData" @navigateToUserProfile="navigateToUserProfile" /> + <Leaderboard :leaderboard="currentLeaderboardData" @navigateToUserProfile="navigateToUserProfile" /> </div> <div id="leaderboard"> <h1><img src="@/assets/icons/fire.png" style="width: 2rem"> Highest streak</h1> - <Leaderboard :leaderboard="leaderboardData" @navigateToUserProfile="navigateToUserProfile" /> + <Leaderboard :leaderboard="streakLeaderboardData" @navigateToUserProfile="navigateToUserProfile" /> </div> </main> - <div id = "communityContainer"> + </div> + <div id="communityContainer"> <h1>Total points earned as a community</h1> <h2>1000000 <img src="@/assets/items/v-buck.png" style="width: 2rem"></h2> </div> </template> <script setup lang="ts"> -import { ref } from 'vue'; +import { onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import Leaderboard from '@/components/LeaderboardComponents/Leaderboard.vue'; +import { on } from 'events'; +import { LeaderboardService } from '@/api'; -let leaderboardData = ref([]); +let streakLeaderboardData = ref([]); +let currentLeaderboardData = ref([]); +let pointsLeaderboardData = ref([]); const router = useRouter(); async function fetchQuizData() { - /*leaderboard(quizId).then((response) => { - leaderboardData.value = response.data.slice(0, 10); - }).catch((error) => { - console.error("Failed to fetch leaderboard data:", error); - });*/ + global(); +} + +onMounted(() => { + fetchQuizData(); +}); + +async function global() { + let globalPoints = await LeaderboardService.getLeaderboard({ + type: "TOTAL_POINTS", + filter: "GLOBAL", + }); + let globalStreak = await LeaderboardService.getLeaderboard({ + type: "TOP_STREAK", + filter: "GLOBAL", + }); + let globalCurrentStreak = await LeaderboardService.getLeaderboard({ + type: "CURRENT_STREAK", + filter: "GLOBAL", + }); + pointsLeaderboardData.value = globalPoints.entries; + currentLeaderboardData.value = globalCurrentStreak.entries; + streakLeaderboardData.value = globalStreak.entries; +} + +async function friends() { + let friendsPoints = await LeaderboardService.getLeaderboard({ + type: "TOTAL_POINTS", + filter: "FRIENDS", + }); + let friendsStreak = await LeaderboardService.getLeaderboard({ + type: "TOP_STREAK", + filter: "FRIENDS", + }); + let friendsCurrentStreak = await LeaderboardService.getLeaderboard({ + type: "CURRENT_STREAK", + filter: "FRIENDS", + }); + pointsLeaderboardData.value = friendsPoints.entries; + currentLeaderboardData.value = friendsCurrentStreak.entries; + streakLeaderboardData.value = friendsStreak.entries; } const navigateToUserProfile = (userId: number) => { @@ -47,9 +100,8 @@ const navigateToUserProfile = (userId: number) => { <style scoped> main { - margin-top: 2rem; margin-bottom: 4rem; - width: 100%; + width: 80%; display: flex; justify-content: space-around; align-items: center; @@ -66,6 +118,18 @@ main { margin-bottom: 3rem; } +#content { + display: flex; + flex-direction: row; + + justify-content: center; + flex-wrap: wrap; +} + +.box { + width: 90%; +} + h1 { font-weight: 500; margin-bottom: 1rem; @@ -75,7 +139,15 @@ h1 { display: flex; justify-content: center; margin-bottom: 2rem; - margin-top: 3rem; + +} + +#radioContainer { + display: flex; + justify-content: center; + margin-bottom: 2rem; + width: 100%; + margin-top: 3.6rem; } #communityContainer { @@ -87,9 +159,9 @@ h1 { } .leaderBoardButton { - padding: 1rem 4rem; - font-weight: 700; - border-radius: 2rem; - margin: 1rem; + padding: 1rem 4rem; + font-weight: 700; + border-radius: 2rem; + margin: 1rem; } </style> \ No newline at end of file diff --git a/src/views/NotFoundView.vue b/src/views/NotFoundView.vue index 650e2b20ce9dca2abb7fa351c48948a304fbb28e..7637014bf836c4e94a3526e503080990f93bab13 100644 --- a/src/views/NotFoundView.vue +++ b/src/views/NotFoundView.vue @@ -1,22 +1,22 @@ <template> <div class="container-fluid"> <!-- Changed from 'container' to 'container-fluid' --> - <div class="row"> - <div class="col-md-12"> - <div class="error-template text-center"> <!-- 'text-center' for centering text content --> - <h1> - Oops!</h1> - <h2> - 404 Not Found</h2> - <div class="error-details"> - Sorry, an error has occurred, Requested page not found! - </div> - <div class="error-actions"> - <Button1 button-text="Take Me Home" @click="home" /> + <div class="row"> + <div class="col-md-12"> + <div class="error-template text-center"> <!-- 'text-center' for centering text content --> + <h1> + Oops!</h1> + <h2> + 404 Not Found</h2> + <div class="error-details"> + Sorry, an error has occurred, Requested page not found! + </div> + <div class="error-actions"> + <Button1 button-text="Take Me Home" @click="home" /> + </div> </div> </div> </div> </div> -</div> </template> <script setup lang="ts"> @@ -32,13 +32,15 @@ const home = () => { <style scoped> - .error-template { - text-align: center; /* Ensures all text and inline elements within are centered */ +.error-template { + text-align: center; + /* Ensures all text and inline elements within are centered */ display: flex; flex-direction: column; - align-items: center; /* Aligns child elements (which are block-level) centrally */ - justify-content: center; /* Optional: if you want vertical centering */ + align-items: center; + /* Aligns child elements (which are block-level) centrally */ + justify-content: center; + /* Optional: if you want vertical centering */ margin: 2rem; } - </style> \ No newline at end of file diff --git a/src/views/ShopView.vue b/src/views/ShopView.vue index 55ef335f11efa0b03a899182231f1208d0f17888..2bf2eafe93dff64150ce1365535defa33e28fa01 100644 --- a/src/views/ShopView.vue +++ b/src/views/ShopView.vue @@ -1,8 +1,12 @@ <template> + <br> + <div id="dropdownContainer"> + <h1 class="box">Shop</h1> + </div> <div class="container"> <div class="row"> <div class="col-md-12"> - <h1>League</h1> + <h1>Fantacy</h1> <div class="category row justify-content-between mb-5 m-2"> <!--<div class="col-md-4" v-for="product in products" :key="product.id">--> <div class="card text-center" style="width: 16rem; border: none"> @@ -33,48 +37,55 @@ <ShopButton button-text="100"></ShopButton> </div> </div> - - <!--</div>--> </div> </div> <div class="col-md-12"> - <h1>Fantacy</h1> + <h1>Stash</h1> <div class="category row justify-content-between mb-5 m-2"> - <!--<div class="col-md-4" v-for="product in products" :key="product.id">--> <div class="card text-center" style="width: 16rem; border: none"> - <img src="@/assets/items/galaxy.jpg" class="card-img-top" alt="..."> + <img src="@/assets/items/coffee.jpg" class="card-img-top" alt="..."> <div class="card-body"> - <h5 class="card-title">The panda</h5> - <ShopButton button-text="100"></ShopButton> + <h5 class="card-title">Free Coffee</h5> + <ShopButton button-text="500"></ShopButton> </div> - </div> + </div> <div class="card text-center" style="width: 16rem; border: none"> - <img src="@/assets/items/galaxy.jpg" class="card-img-top" alt="..."> + <img src="@/assets/items/viaplay.jpg" class="card-img-top" alt="..."> <div class="card-body"> - <h5 class="card-title">The panda</h5> - <ShopButton button-text="100"></ShopButton> + <h5 class="card-title">1 Month Viaplay</h5> + <ShopButton button-text="10000"></ShopButton> </div> - </div> + </div> <div class="card text-center" style="width: 16rem; border: none"> - <img src="@/assets/items/galaxy.jpg" class="card-img-top" alt="..."> + <img src="@/assets/items/pirbad.png" class="card-img-top" alt="..."> <div class="card-body"> - <h5 class="card-title">The panda</h5> - <ShopButton button-text="100"></ShopButton> + <h5 class="card-title">-10% rabatt</h5> + <ShopButton button-text="1000"></ShopButton> </div> - </div> + </div> + </div> + </div> + <div class="col-md-12"> + <h1>Stash</h1> + <div class="category row justify-content-between mb-5 m-2"> <div class="card text-center" style="width: 16rem; border: none"> - <img src="@/assets/items/galaxy.jpg" class="card-img-top" alt="..."> + <img src="@/assets/items/adfree.png" class="card-img-top" alt="..."> <div class="card-body"> - <h5 class="card-title">The panda</h5> - <ShopButton button-text="100"></ShopButton> + <h5 class="card-title">Adfree</h5> + <ShopButton button-text="35kr"></ShopButton> </div> - </div> - - - <!--</div>--> + </div> + <div class="card text-center" style="width: 16rem; border: none"> + <img src="@/assets/items/piggybank.webp" class="card-img-top" alt="..."> + <div class="card-body"> + <h5 class="card-title">Premium</h5> + <ShopButton button-text="50kr"></ShopButton> + </div> + </div> </div> </div> + </div> </div> </template> @@ -93,6 +104,10 @@ import ShopButton from '@/components/Buttons/ShopButton.vue'; /* Rounded corners */ } +.box { + width:90%; +} + .card:hover { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } @@ -105,4 +120,10 @@ import ShopButton from '@/components/Buttons/ShopButton.vue'; .col-md-12 { border-bottom: 2px solid #000000; } + +#dropdownContainer { + display: flex; + justify-content: center; + margin-bottom: 2rem; +} </style> \ No newline at end of file diff --git a/src/views/UnauthorizedView.vue b/src/views/UnauthorizedView.vue index 8a3f78ed6bdc87283db0ea40dbcdd5b2306c371d..2e79554d2ddf9024e05cde67064b0d14533d611c 100644 --- a/src/views/UnauthorizedView.vue +++ b/src/views/UnauthorizedView.vue @@ -26,3 +26,10 @@ const home = () => { router.push('/'); }; </script> + + +<style scoped> + body { + background-image: url('@/assets/401-error.jpg'); + } +</style> \ No newline at end of file