diff --git a/src/components/LeaderboardComponents/Leaderboard.vue b/src/components/LeaderboardComponents/Leaderboard.vue index 0a1b9a5b472971d79cac4aa62380e891caa72ec7..9003a25a4c37050a30eafc00cb1551c4885b0dc7 100644 --- a/src/components/LeaderboardComponents/Leaderboard.vue +++ b/src/components/LeaderboardComponents/Leaderboard.vue @@ -1,161 +1,197 @@ <template> - <div id="leaderboard"> - <div class="ribbon"></div> - <table> - <tr v-for="(entry, index) in leaderboard" :key="entry.user.id"> - <td class="number">{{ index + 1 }}</td> + <div id="leaderboard"> + <div class="ribbon"></div> + <table> + <tbody> + <tr v-for="(entry, index) in leaderboard" :key="entry.user.id" :class="{ 'is-user-5': entry.user.firstName === 'User' }"> + <td class="number">{{ entry.rank }}</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"> - <img class="gold-medal" src="https://github.com/malunaridev/Challenges-iCodeThis/blob/master/4-leaderboard/assets/gold-medal.png?raw=true" alt="gold medal" /> + <div class="medal"> + <img class="gold-medal" + src="https://github.com/malunaridev/Challenges-iCodeThis/blob/master/4-leaderboard/assets/gold-medal.png?raw=true" + alt="gold medal" /> </div> - </td> - <td v-else class="points">{{ entry.score }}</td> + </td> + <td v-else class="points">{{ entry.score }}</td> </tr> - </table> - </div> - </template> - - <script setup lang="ts"> - import { ref } from 'vue'; - import { useRouter } from 'vue-router'; - - const router = useRouter(); - - const props = defineProps({ - leaderboard: { - type: Array, - required: true - } - }); - - const navigateToUserProfile = () => { - router.push({ name: 'news' }); - }; - </script> - - <style scoped> - #leaderboard { - width: 100%; - position: relative; - } - - table { - width: 100%; - border-collapse: collapse; - table-layout: fixed; - color: #141a39; - cursor: default; - } - - tr { - transition: all 0.2s ease-in-out; - border-radius: 0.2rem; - display: flex; - align-items: center; - justify-content: space-between; - height: 4rem; - } - - tr:not(:first-child):hover { - background-color: #fff; - transform: scale(1.1); - -webkit-box-shadow: 0px 5px 15px 8px #e4e7fb; - box-shadow: 0px 5px 15px 8px #e4e7fb; - } - - tr:nth-child(even) { - background-color: #f9f9f9; - } - - tr:nth-child(1) { - color: #fff; + </tbody> + <tbody id="line">`</tbody> + <tbody v-if="!userInLeaderboard"> + <tr></tr> + <tr v-for="(entry, index) in leaderboardExtra" :key="entry.user.id" :class="{ 'is-user-5': entry.user.firstName === 'User' }"> + <td class="number">{{ entry.rank }}</td> + <td class="name" @click="navigateToUserProfile(entry.user.id)">{{ entry.user.firstName }}</td> + <td class="points">{{ entry.score }}</td> + </tr> + </tbody> + <tbody v-else></tbody> + </table> + </div> +</template> + + +<script setup lang="ts"> +import { computed } from 'vue'; +import { useRouter } from 'vue-router'; +import { useUserInfoStore } from '@/stores/UserStore'; + +const router = useRouter(); +const userStore = useUserInfoStore(); + +const props = defineProps({ + leaderboard: { + type: Array, + required: true + }, + leaderboardExtra: { + type: Array, + required: true + } +}); + +console.log(props.leaderboardExtra); + +const userInLeaderboard = computed(() => props.leaderboard.some(entry => entry.user.email === userStore.email)); + +const navigateToUserProfile = () => { + router.push({ name: 'user-profile' }); +}; +</script> + +<style scoped> +#leaderboard { + width: 100%; + position: relative; +} + +table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; + color: #141a39; + cursor: default; +} + +tr { + transition: all 0.2s ease-in-out; + border-radius: 0.2rem; + display: flex; + align-items: center; + justify-content: space-between; + height: 4rem; +} + +tr:not(:first-child):hover { + background-color: #fff; + transform: scale(1.1); + -webkit-box-shadow: 0px 5px 15px 8px #e4e7fb; + box-shadow: 0px 5px 15px 8px #e4e7fb; +} + +tr:nth-child(even) { + background-color: #f9f9f9; +} + +tr:nth-child(1) { + color: #fff; +} + +td { + height: 2rem; + font-family: "Rubik", sans-serif; + font-size: 1.4rem; + padding: 1rem 2rem; + position: relative; +} + +.number { + width: 1rem; + font-size: 2.2rem; + font-weight: bold; + text-align: left; + display: flex; + align-items: center; +} + +.name { + font-size: 1.3rem; + cursor: pointer; + display: flex; + align-items: center; +} + +.points { + font-weight: bold; + font-size: 1.3rem; + display: flex; + justify-content: flex-end; + align-items: center; +} + +@media (max-width: 1000px) { + .number .name .points { + font-size: 0.5rem; } - + td { - height: 2rem; - font-family: "Rubik", sans-serif; - font-size: 1.4rem; - padding: 1rem 2rem; - position: relative; - } - - .number { - width: 1rem; - font-size: 2.2rem; - font-weight: bold; - text-align: left; - display: flex; - align-items: center; - } - - .name { - font-size: 1.3rem; - cursor: pointer; - display: flex; - align-items: center; - } - - .points { - font-weight: bold; - font-size: 1.3rem; - display: flex; - justify-content: flex-end; - align-items: center; + padding: 0.2rem 0.5rem; } +} - @media (max-width: 1000px) { - .number .name .points { - font-size: 0.5rem; - } - td { - padding: 0.2rem 0.5rem; - } - } +.points:first-child { + width: 10rem; +} - - .points:first-child { - width: 10rem; - } - - .gold-medal { - height: 3rem; - margin-left: 1.5rem; - } - - .ribbon { - width: 106%; - height: 4.5rem; - top: -0.5rem; - background-color: #0A58CA; - position: absolute; - /**left: -1rem;*/ - box-shadow: 0px 15px 11px -6px #7a7a7d; - } - - .ribbon::before { - content: ""; - height: 1.5rem; - width: 1.5rem; - bottom: -0.8rem; - left: 0.35rem; - transform: rotate(45deg); - background-color: #0A58CA; - position: absolute; - z-index: -1; - } - - .ribbon::after { - content: ""; - height: 1.5rem; - width: 1.5rem; - bottom: -0.8rem; - right: 0.35rem; - transform: rotate(45deg); - background-color: #0A58CA; - position: absolute; - z-index: -1; - } - </style> \ No newline at end of file +.gold-medal { + height: 3rem; + margin-left: 1.5rem; +} + +.ribbon { + width: 106%; + height: 4.5rem; + top: -0.5rem; + background-color: #0A58CA; + position: absolute; + /**left: -1rem;*/ + box-shadow: 0px 15px 11px -6px #7a7a7d; +} + +.ribbon::before { + content: ""; + height: 1.5rem; + width: 1.5rem; + bottom: -0.8rem; + left: 0.35rem; + transform: rotate(45deg); + background-color: #0A58CA; + position: absolute; + z-index: -1; +} + +.ribbon::after { + content: ""; + height: 1.5rem; + width: 1.5rem; + bottom: -0.8rem; + right: 0.35rem; + transform: rotate(45deg); + background-color: #0A58CA; + position: absolute; + z-index: -1; +} + +#line { + width: 100%; + height: 0.01rem; + border-top: 8px solid #0A58CA; +} + +tr.is-user-5 { + background-color: #419c5c !important; + color: #fff !important; +} +</style> \ No newline at end of file diff --git a/src/components/UpdateUserComponents/UpdateUserLayout.vue b/src/components/UpdateUserComponents/UpdateUserLayout.vue index 0f9dd3fd163e45d56aebd0a6beb58ebf406847d5..e90c5cd6f503c23c640bafa9a11b90d60e4ad582 100644 --- a/src/components/UpdateUserComponents/UpdateUserLayout.vue +++ b/src/components/UpdateUserComponents/UpdateUserLayout.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import BaseInput from "@/components/InputFields/BaseInput.vue"; import {onMounted, ref} from "vue"; -import {AuthenticationService, LeaderboardService, UserControllerService, type UserUpdateDTO} from "@/api"; +import {AuthenticationService, LeaderboardService, UserService, type UserUpdateDTO} from "@/api"; import {useUserInfoStore} from "@/stores/UserStore"; @@ -18,7 +18,7 @@ let samePasswords = ref(true) async function setupForm() { try { - let response = await UserControllerService.getUser(); + let response = await UserService.getUser(); console.log(response.firstName) firstNameRef.value = response.firstName; diff --git a/src/views/Authentication/ChangePasswordView.vue b/src/views/Authentication/ChangePasswordView.vue index ad52ab1b300380fa1c665e882c88b83f217424ac..f4f4dc6dea62fd54eccfb470c54717279f690c11 100644 --- a/src/views/Authentication/ChangePasswordView.vue +++ b/src/views/Authentication/ChangePasswordView.vue @@ -1,61 +1,73 @@ <template> <div class="container"> - <div class="row justify-content-center"> - <div class="col-lg-5"> - <div class="card shadow-lg border-0 rounded-lg mt-5"> - <div class="card-header"><h3 class="text-center font-weight-light my-4">Password Recovery</h3></div> - <div class="card-body"> - <div class="small mb-3 text-muted">Enter the new password for your account</div> - <form @submit.prevent="submitForm"> - <div class="form-floating mb-3"> - <input v-model="newPassword" class="form-control" id="newPassword" type="password" placeholder="New Password" required> - <label for="newPassword">Enter your new password</label> + <div class="row justify-content-center"> + <div class="col-lg-5"> + <div class="card shadow-lg border-0 rounded-lg mt-5"> + <div class="card-header"> + <h3 class="text-center font-weight-light my-4">Password Recovery</h3> + </div> + <div class="card-body"> + <div class="small mb-3 text-muted">Enter the new password for your account</div> + <form @submit.prevent="submitForm"> + <div class="form-floating mb-3"> + <input v-model="newPassword" class="form-control" id="newPassword" type="password" + placeholder="New Password" required> + <label for="newPassword">Enter your new password</label> + </div> + <div class="form-floating mb-3"> + <input v-model="confirmPassword" class="form-control" id="confirmPassword" + type="password" placeholder="Confirm Password" required> + <label for="confirmPassword">Confirm your new password</label> + </div> + <div class="errorMsg">{{ errormsg }}</div> + <div class="d-flex align-items-center justify-content-between mt-4 mb-0"> + <router-link to="/login" class="small">Return to login</router-link> + <button class="btn btn-primary" type="submit">Confirm Password</button> + </div> + </form> + </div> + <div class="card-footer text-center py-3"> + <div class="small"><router-link to="/sign-up">Need an account? Sign up!</router-link></div> + </div> </div> - <div class="form-floating mb-3"> - <input v-model="confirmPassword" class="form-control" id="confirmPassword" type="password" placeholder="Confirm Password" required> - <label for="confirmPassword">Confirm your new password</label> - </div> - <div class="d-flex align-items-center justify-content-between mt-4 mb-0"> - <router-link to="/login" class="small">Return to login</router-link> - <button class="btn btn-primary" type="submit">Confirm Password</button> - </div> - </form> - </div> - <div class="card-footer text-center py-3"> - <div class="small"><router-link to="/sign-up">Need an account? Sign up!</router-link></div> </div> - </div> </div> - </div> </div> - </template> - - <script setup lang="ts"> - import { ref } from 'vue'; - import { useRouter } from 'vue-router'; - import axios from 'axios'; +</template> + +<script setup lang="ts"> +import { ref } from 'vue'; +import { useRouter, useRoute } from 'vue-router'; +import axios from 'axios'; +import { UserService } from '@/api'; + +const router = useRouter(); +const route = useRoute(); - const router = useRouter(); - - const newPassword = ref(''); - const confirmPassword = ref(''); - - const submitForm = async () => { +const token = route.params.token; + +const newPassword = ref(''); +const confirmPassword = ref(''); + +let errormsg = ref(''); + +const submitForm = async () => { if (newPassword.value !== confirmPassword.value) { - alert('Passwords do not match!'); - return; + errormsg.value = 'The passwords do not match'; + return; } + errormsg.value = ''; try { - const response = await axios.post('/api/reset-password', { - password: newPassword.value, - confirmPassword: confirmPassword.value - }); - console.log('Success:', response.data); + const resetPassword = { + password: newPassword.value, + token: token + }; + const response = await UserService.confirmPasswordReset({ requestBody: resetPassword }); + console.log(response); router.push('/login'); } catch (error) { - console.error('Error:', error); + console.error('Error:', error); } - }; - - </script> - \ No newline at end of file +}; + +</script> \ No newline at end of file diff --git a/src/views/Authentication/ForgottenPasswordView.vue b/src/views/Authentication/ForgottenPasswordView.vue index 521c2aa54b52169ae720d94d9862f8464cb10ebf..e43e961e8a0ec3c391562cc2c3ced3def1cb1315 100644 --- a/src/views/Authentication/ForgottenPasswordView.vue +++ b/src/views/Authentication/ForgottenPasswordView.vue @@ -1,52 +1,59 @@ <template> <div class="container"> - <div class="row justify-content-center"> - <div class="col-lg-5"> - <div class="card shadow-lg border-0 rounded-lg mt-5"> - <div class="card-header"><h3 class="text-center font-weight-light my-4">Password Recovery</h3></div> - <div class="card-body"> - <div class="small mb-3 text-muted">Enter your email address and we will send you a link to reset your password.</div> - <form @submit.prevent="submitForm"> - <div class="form-floating mb-3"> - <input v-model="email" class="form-control" id="inputEmail" type="email" placeholder="name@example.com" required> - <label for="inputEmail">Enter email address</label> + <div class="row justify-content-center"> + <div class="col-lg-5"> + <div class="card shadow-lg border-0 rounded-lg mt-5"> + <div class="card-header"> + <h3 class="text-center font-weight-light my-4">Password Recovery</h3> + </div> + <div class="card-body"> + <div class="small mb-3 text-muted">Enter your email address and we will send you a link to reset + your password.</div> + <form @submit.prevent="submitForm"> + <div class="form-floating mb-3"> + <input v-model="email" class="form-control" id="inputEmail" type="email" + placeholder="name@example.com" required> + <label for="inputEmail">Enter email address</label> + </div> + <div class="d-flex align-items-center justify-content-between mt-4 mb-0"> + <router-link to="/login" class="small">Return to login</router-link> + <button class="btn btn-primary" type="submit">Reset Password</button> + </div> + <div class="text-success"> + {{ confirmationMessage }} + </div> + </form> + </div> + <div class="card-footer text-center py-3"> + <div class="small"><router-link to="/sign-up">Need an account? Sign up!</router-link></div> + </div> </div> - <div class="d-flex align-items-center justify-content-between mt-4 mb-0"> - <router-link to="/login" class="small">Return to login</router-link> - <button class="btn btn-primary" type="submit">Reset Password</button> - </div> - <div class="text-success"> - {{ confirmationMessage }} - </div> - </form> </div> - <div class="card-footer text-center py-3"> - <div class="small"><router-link to="/sign-up">Need an account? Sign up!</router-link></div> - </div> - </div> </div> - </div> </div> - </template> - - <script setup lang="ts"> - import { ref } from 'vue'; - import { useRouter } from 'vue-router'; - import axios from 'axios'; - - const router = useRouter(); - const email = ref(''); +</template> + +<script setup lang="ts"> +import { ref } from 'vue'; +import { useRouter, useRoute } from 'vue-router'; +import axios from 'axios'; +import { UserService } from '@/api'; - let confirmationMessage = ref(''); - - const submitForm = async () => { +const router = useRouter(); + +const email = ref(''); +let confirmationMessage = ref(''); + +const submitForm = async () => { try { - const response = await axios.post('/api/password-reset', { email: email.value }); - console.log('Success:', response.data); - confirmationMessage.value = 'An email has been sent to your email address with a link to reset your password.'; + const response = await UserService.resetPassword({ + requestBody: email.value + }); + console.log('Success:', response.data); + confirmationMessage.value = 'An email has been sent to your email address with a link to reset your password.'; } catch (error) { - console.error('Error:', error); + console.error('Error:', error); } - }; - </script> - \ No newline at end of file +}; + +</script> \ No newline at end of file diff --git a/src/views/LeaderboardView.vue b/src/views/LeaderboardView.vue index 769c67973bae2d8b15612e38a1141279d845ed3e..ef8c426d075af014d33c3db83e0ff01093ecd367 100644 --- a/src/views/LeaderboardView.vue +++ b/src/views/LeaderboardView.vue @@ -18,15 +18,15 @@ <main> <div id="leaderboard"> <h1><img src="@/assets/items/v-buck.png" style="width: 2rem"> Total points</h1> - <Leaderboard :leaderboard="pointsLeaderboardData" @navigateToUserProfile="navigateToUserProfile" /> + <Leaderboard :leaderboard="pointsLeaderboardData" :leaderboardExtra="pointsLeaderboardDataExtra" @navigateToUserProfile="navigateToUserProfile" /> </div> <div id="leaderboard"> <h1><img src="@/assets/icons/fire.png" style="width: 2rem"> Current streak</h1> - <Leaderboard :leaderboard="currentLeaderboardData" @navigateToUserProfile="navigateToUserProfile" /> + <Leaderboard :leaderboard="currentLeaderboardData" :leaderboardExtra="currentLeaderboardDataExtra" @navigateToUserProfile="navigateToUserProfile" /> </div> <div id="leaderboard"> <h1><img src="@/assets/icons/fire.png" style="width: 2rem"> Highest streak</h1> - <Leaderboard :leaderboard="streakLeaderboardData" @navigateToUserProfile="navigateToUserProfile" /> + <Leaderboard :leaderboard="streakLeaderboardData" :leaderboardExtra="streakLeaderboardDataExtra" @navigateToUserProfile="navigateToUserProfile" /> </div> </main> </div> @@ -41,12 +41,16 @@ 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'; +import { LeaderboardService, UserControllerService } from '@/api'; let streakLeaderboardData = ref([]); let currentLeaderboardData = ref([]); let pointsLeaderboardData = ref([]); +let streakLeaderboardDataExtra = ref([]); +let currentLeaderboardDataExtra = ref([]); +let pointsLeaderboardDataExtra = ref([]); + const router = useRouter(); async function fetchQuizData() { @@ -73,23 +77,28 @@ async function global() { let globalPointsYou = await LeaderboardService.getSurrounding({ type: "TOTAL_POINTS", filter: "GLOBAL", + entryCount: 1, }); let globalStreakYou = await LeaderboardService.getSurrounding({ type: "TOP_STREAK", filter: "GLOBAL", + entryCount: 1, }); let globalCurrentStreakYou = await LeaderboardService.getSurrounding({ type: "CURRENT_STREAK", filter: "GLOBAL", + entryCount: 1, }); - - console.log(globalPointsYou); - console.log(globalStreakYou); - console.log(globalCurrentStreakYou); - + pointsLeaderboardData.value = globalPoints.entries; currentLeaderboardData.value = globalCurrentStreak.entries; streakLeaderboardData.value = globalStreak.entries; + + pointsLeaderboardDataExtra.value = globalPointsYou.entries; + currentLeaderboardDataExtra.value = globalCurrentStreakYou.entries; + streakLeaderboardDataExtra.value = globalStreakYou.entries; + + console.log(pointsLeaderboardDataExtra.value); } async function friends() { @@ -105,9 +114,32 @@ async function friends() { type: "CURRENT_STREAK", filter: "FRIENDS", }); + let friendsPointsYou = await LeaderboardService.getSurrounding({ + type: "TOTAL_POINTS", + filter: "FRIENDS", + entryCount: 3, + }); + let friendsStreakYou = await LeaderboardService.getSurrounding({ + type: "TOP_STREAK", + filter: "FRIENDS", + entryCount: 3, + }); + let friendsCurrentStreakYou = await LeaderboardService.getSurrounding({ + type: "CURRENT_STREAK", + filter: "FRIENDS", + entryCount: 3, + }); + + pointsLeaderboardData.value = friendsPoints.entries; currentLeaderboardData.value = friendsCurrentStreak.entries; streakLeaderboardData.value = friendsStreak.entries; + + pointsLeaderboardDataExtra.value = friendsPointsYou.entries; + currentLeaderboardDataExtra.value = friendsStreakYou.entries; + streakLeaderboardDataExtra.value = friendsCurrentStreakYou.entries; + + } const navigateToUserProfile = (userId: number) => { @@ -121,7 +153,7 @@ main { width: 80%; display: flex; justify-content: space-around; - align-items: center; + align-items: start; flex-wrap: wrap; flex-direction: row; }