From 3ff47d7db5d55d1f6439958310732bab8c431824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdemar=20=C3=85storp=20Beere?= <valdemb@stud.ntnu.no> Date: Thu, 2 May 2024 12:58:43 +0200 Subject: [PATCH] fix(format): Ran format --- src/components/ButtonDisplayStreak.vue | 42 +- src/components/CardChallengeSavingsPath.vue | 172 +++-- src/components/ImgGifTemplate.vue | 17 +- src/components/SavingsPath.vue | 714 ++++++++++---------- src/stores/challengeStore.ts | 12 +- src/stores/goalStore.ts | 5 +- src/views/HomeView.vue | 38 +- 7 files changed, 491 insertions(+), 509 deletions(-) diff --git a/src/components/ButtonDisplayStreak.vue b/src/components/ButtonDisplayStreak.vue index 1f246f1..b9fbb10 100644 --- a/src/components/ButtonDisplayStreak.vue +++ b/src/components/ButtonDisplayStreak.vue @@ -63,20 +63,27 @@ class="flex flex-row items-center mx-auto h-20 w-4/5 md:w-full bg-black-400 gap-4" > <div class="flex flex-1 overflow-x-auto"> - <div v-for="index in 7" :key="index" class="min-w-max mx-auto"> - <div class="flex flex-col justify-around items-center"> - <!-- Display the current streak day number adjusted by index --> - <span class="text-black text-xs md:text-1xl font-bold"> - {{ currentStreak! - ((currentStreak! % 7) - index) }} - </span> - <!-- Display images based on completion --> - <img - src="@/assets/pengesekkStreak.png" - :alt="index <= currentStreak! % 7 ? 'challenge completed' : 'challenge not completed'" - :class="{'max-h-6 max-w-6 md:max-h-10 md:max-w-10': true, 'grayscale': index > currentStreak! % 7}" - /> + <div v-for="index in 7" :key="index" class="min-w-max mx-auto"> + <div class="flex flex-col justify-around items-center"> + <!-- Display the current streak day number adjusted by index --> + <span class="text-black text-xs md:text-1xl font-bold"> + {{ currentStreak! - ((currentStreak! % 7) - index) }} + </span> + <!-- Display images based on completion --> + <img + src="@/assets/pengesekkStreak.png" + :alt=" + index <= currentStreak! % 7 + ? 'challenge completed' + : 'challenge not completed' + " + :class="{ + 'max-h-6 max-w-6 md:max-h-10 md:max-w-10': true, + grayscale: index > currentStreak! % 7 + }" + /> + </div> </div> - </div> </div> </div> </div> @@ -98,7 +105,6 @@ onMounted(async () => { if (userStore.streak) { currentStreak.value = userStore.streak?.streak deadline.value = userStore.streak?.firstDue - } console.log('Streak:', currentStreak.value) if (typeof window !== 'undefined') { @@ -116,15 +122,13 @@ const handleWindowSizeChange = () => { screenSize.value = window.innerWidth } - - const displayStreakCard = ref(false) const display = () => { displayStreakCard.value = true - userStore.getUserStreak(); - currentStreak.value = userStore.streak?.streak; - deadline.value = userStore.streak?.firstDue; + userStore.getUserStreak() + currentStreak.value = userStore.streak?.streak + deadline.value = userStore.streak?.firstDue } const hide = () => { diff --git a/src/components/CardChallengeSavingsPath.vue b/src/components/CardChallengeSavingsPath.vue index f56167e..d48b508 100644 --- a/src/components/CardChallengeSavingsPath.vue +++ b/src/components/CardChallengeSavingsPath.vue @@ -1,120 +1,112 @@ <template> - <!-- Challenge Icon and Details --> - <div v-if="challenge" class="flex items-center justify-center shadow-black min-w-24 w-full h-auto md:max-h-full min-h-24 max-w-32 max-h-32 md:min-h-32 md:min-w-32 md:max-w-48 overflow-hidden"> - <!-- Challenge Icon --> - <div class="flex flex-col items-center mx-auto md:mx-2 my-auto"> - - <div class="flex flex-col flex-nowrap self-center"> - <!-- Check Icon --> - <div - v-if="challenge.completion !== undefined && challenge.completion >= 100" - class="min-w-6 min-h-6 max-w-6 max-h-6 md:min-h-8 md:max-h-8 md:min-w-8 md:max-w-8 ml-20 md:ml-32 p-1 basis-1/4 self-end" - > - <img src="@/assets/completed.png" alt="" />ï¸ - </div> - <div v-else class="min-w-6 min-h-6 max-w-6 max-h-6 md:min-h-8 md:max-h-8 md:min-w-8 md:max-w-8 ml-20 md:ml-32 p-1 basis-1/4 self-end"> - <img src="@/assets/pending.png" alt="" />ï¸ - </div> - <div class="basis-3/4"> - <p - class="text-center text-wrap text-xs lg:text-lg md:text-md" - data-cy="challenge-title" - > - {{ challenge.title }} - </p> - </div> - </div> - <img - @click="editChallenge(challenge)" - :data-cy="'challenge-icon-' + challenge.id" - :src="getChallengeIcon(challenge)" - class="max-w-12 max-h-12 md:max-h-16 md:max-w-16 lg:max-w-20 lg:max-h-20 cursor-pointer hover:scale-125" - :alt="challenge.title" - /> - <!-- Progress Bar, if the challenge is not complete --> - <div - v-if=" - challenge.completion != undefined && challenge.completion < 100 - " - class="flex-grow w-full mt-2" - > - <div class="flex flex-row ml-5 md:ml-10 justify-center"> - <div class="flex flex-col"> + <!-- Challenge Icon and Details --> + <div + v-if="challenge" + class="flex items-center justify-center shadow-black min-w-24 w-full h-auto md:max-h-full min-h-24 max-w-32 max-h-32 md:min-h-32 md:min-w-32 md:max-w-48 overflow-hidden" + > + <!-- Challenge Icon --> + <div class="flex flex-col items-center mx-auto md:mx-2 my-auto"> + <div class="flex flex-col flex-nowrap self-center"> + <!-- Check Icon --> + <div + v-if="challenge.completion !== undefined && challenge.completion >= 100" + class="min-w-6 min-h-6 max-w-6 max-h-6 md:min-h-8 md:max-h-8 md:min-w-8 md:max-w-8 ml-20 md:ml-32 p-1 basis-1/4 self-end" + > + <img src="@/assets/completed.png" alt="" />ï¸ + </div> + <div + v-else + class="min-w-6 min-h-6 max-w-6 max-h-6 md:min-h-8 md:max-h-8 md:min-w-8 md:max-w-8 ml-20 md:ml-32 p-1 basis-1/4 self-end" + > + <img src="@/assets/pending.png" alt="" />ï¸ + </div> + <div class="basis-3/4"> + <p + class="text-center text-wrap text-xs lg:text-lg md:text-md" + data-cy="challenge-title" + > + {{ challenge.title }} + </p> + </div> + </div> + <img + @click="editChallenge(challenge)" + :data-cy="'challenge-icon-' + challenge.id" + :src="getChallengeIcon(challenge)" + class="max-w-12 max-h-12 md:max-h-16 md:max-w-16 lg:max-w-20 lg:max-h-20 cursor-pointer hover:scale-125" + :alt="challenge.title" + /> + <!-- Progress Bar, if the challenge is not complete --> <div - class="bg-gray-200 rounded-full h-2.5 dark:bg-gray-700" + v-if="challenge.completion != undefined && challenge.completion < 100" + class="flex-grow w-full mt-2" > - <div - class="bg-green-600 h-2.5 rounded-full" - data-cy="challenge-progress" - :style="{ - width: - (challenge.saved / challenge.target) * 100 + - '%' - }" - ></div> - </div> - <div class=" text-center text-nowrap text-xs md:text-base"> - {{ challenge.saved }}kr / {{ challenge.target }}kr + <div class="flex flex-row ml-5 md:ml-10 justify-center"> + <div class="flex flex-col"> + <div class="bg-gray-200 rounded-full h-2.5 dark:bg-gray-700"> + <div + class="bg-green-600 h-2.5 rounded-full" + data-cy="challenge-progress" + :style="{ + width: (challenge.saved / challenge.target) * 100 + '%' + }" + ></div> + </div> + <div class="text-center text-nowrap text-xs md:text-base"> + {{ challenge.saved }}kr / {{ challenge.target }}kr + </div> + </div> + + <button + @click="incrementSaved(challenge)" + :data-cy="'increment-challenge' + challenge.id" + type="button" + class="inline-block mb-2 ml-2 h-7 w-8 rounded-full p-1 uppercase leading-normal transition duration-150 ease-in-out focus:bg-green-accent-300 focus:shadow-green-2 focus:outline-none focus:ring-0 active:bg-green-600 active:shadow-green-200 motion-reduce:transition-none dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong" + > + + + </button> + </div> </div> - </div> - - <button - @click="incrementSaved(challenge)" - :data-cy="'increment-challenge' + challenge.id" - type="button" - class="inline-block mb-2 ml-2 h-7 w-8 rounded-full p-1 uppercase leading-normal transition duration-150 ease-in-out focus:bg-green-accent-300 focus:shadow-green-2 focus:outline-none focus:ring-0 active:bg-green-600 active:shadow-green-200 motion-reduce:transition-none dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong" - > - + - </button> + <span v-else class="text-center text-xs md:text-base" + >Ferdig: {{ challenge.saved }}</span + > </div> - </div> - <span v-else class="text-center text-xs md:text-base" - >Ferdig: {{ challenge.saved }}</span - > </div> - </div> </template> <script setup lang="ts"> -import type {Challenge} from "@/types/challenge"; -import {useChallengeStore} from "@/stores/challengeStore"; -import router from "@/router"; +import type { Challenge } from '@/types/challenge' +import { useChallengeStore } from '@/stores/challengeStore' +import router from '@/router' -const challengeStore = useChallengeStore(); +const challengeStore = useChallengeStore() interface Props { - challenge: Challenge, + challenge: Challenge } defineProps<Props>() - -const emit = defineEmits(['update-challenge', 'complete-challenge']); - +const emit = defineEmits(['update-challenge', 'complete-challenge']) // Increment saved amount // In your incrementSaved function in the child component const incrementSaved = async (challenge: Challenge) => { + challenge.saved += challenge.perPurchase + // Trigger the update in the store + const updatedChallenge = (await challengeStore.editUserChallenge(challenge)) as Challenge - challenge.saved += challenge.perPurchase; - // Trigger the update in the store - - const updatedChallenge = await challengeStore.editUserChallenge(challenge) as Challenge; - - console.log('updated challenge in child: ', updatedChallenge); - - // Emit an event to inform the parent component of the update - emit('update-challenge', updatedChallenge); + console.log('updated challenge in child: ', updatedChallenge) + // Emit an event to inform the parent component of the update + emit('update-challenge', updatedChallenge) } - const editChallenge = (challenge: Challenge) => { - router.push(`/spareutfordringer/rediger/${challenge.id}`) + router.push(`/spareutfordringer/rediger/${challenge.id}`) } // Helper methods to get icons const getChallengeIcon = (challenge: Challenge): string => { - return `src/assets/${challenge.type.toLowerCase()}.png` + return `src/assets/${challenge.type.toLowerCase()}.png` } - </script> diff --git a/src/components/ImgGifTemplate.vue b/src/components/ImgGifTemplate.vue index a1fd033..f23f172 100644 --- a/src/components/ImgGifTemplate.vue +++ b/src/components/ImgGifTemplate.vue @@ -1,15 +1,14 @@ <template> - <div class="hover:scale-110 flex justify-center items-center"> - <img - v-if="index % 6 === modValue" - :src="url" - alt="could not load" - class="min-w-24 w-full h-auto min-h-24 max-w-32 max-h-32 md:min-h-32 md:max-h-44 md:min-w-32 md:max-w-44 border-2 rounded-lg border-stale-400 shadow-md shadow-black" - /> - </div> + <div class="hover:scale-110 flex justify-center items-center"> + <img + v-if="index % 6 === modValue" + :src="url" + alt="could not load" + class="min-w-24 w-full h-auto min-h-24 max-w-32 max-h-32 md:min-h-32 md:max-h-44 md:min-w-32 md:max-w-44 border-2 rounded-lg border-stale-400 shadow-md shadow-black" + /> + </div> </template> - <script setup lang="ts"> interface Props { url: string diff --git a/src/components/SavingsPath.vue b/src/components/SavingsPath.vue index 2778c48..81afbc3 100644 --- a/src/components/SavingsPath.vue +++ b/src/components/SavingsPath.vue @@ -1,5 +1,6 @@ <template> - <div v-if="isMounted" + <div + v-if="isMounted" class="flex flex-col basis-2/3 max-h-full mx-auto md:ml-20 md:mr-2 max-w-5/6 md:basis-3/4 md:max-pr-20 md:pr-10 md:max-mr-20" > <div class="flex justify-center align-center"> @@ -18,7 +19,8 @@ Ufullførte utfordringer<br />↓ </button> <div class="h-1 w-4/6 mx-auto my-2 opacity-10"></div> - <div v-if="challenges" + <div + v-if="challenges" ref="containerRef" class="container relative pt-6 w-4/5 bg-cover bg-[center] md:[background-position: center;] mx-auto md:w-4/5 no-scrollbar h-full max-h-[60vh] md:max-h-[60vh] md:min-w-2/5 overflow-y-auto border-2 border-transparent rounded-xl bg-white shadow-lg shadow-slate-400" style="background-image: url('src/assets/backgroundSavingsPath.png')" @@ -39,7 +41,7 @@ 'justify-center mx-auto md:justify-between': index % 2 === 1, 'justify-center md:justify-between mx-auto': index % 2 === 0 }" - class="flex flex-row w-full md:w-4/5 justify-start gap-4 md:gap-8 " + class="flex flex-row w-full md:w-4/5 justify-start gap-4 md:gap-8" > <div class="flex"> <img-gif-template @@ -58,10 +60,11 @@ url="src/assets/archerSpare.gif" ></img-gif-template> </div> - <card-challenge-savings-path :goal="goal!" - :challenge="challenge" - @update-challenge="handleChallengeUpdate" - ></card-challenge-savings-path> + <card-challenge-savings-path + :goal="goal!" + :challenge="challenge" + @update-challenge="handleChallengeUpdate" + ></card-challenge-savings-path> <div class="flex"> <img-gif-template :index="index" @@ -97,7 +100,10 @@ <button class="text-2xl ml-48" @click="addSpareUtfordring">+</button> <p class="">Legg til <br />Spareutfordring</p> </div> - <div v-else-if="index === challenges.length - 1 && index % 2 !== 0" class="mr-20 flex flex-row"> + <div + v-else-if="index === challenges.length - 1 && index % 2 !== 0" + class="mr-20 flex flex-row" + > <button class="text-2xl ml-10 rounded-full" @click="addSpareUtfordring"> + </button> @@ -115,8 +121,7 @@ <div v-if="goal" class="flex flex-row justify-around m-t-2 pt-6 w-full mx-auto"> <div class="grid grid-rows-2 grid-flow-col gap 4"> <div class="row-span-3 cursor-pointer" @click="editGoal(goal)"> - <img - :src="getGoalIcon(goal)" class="w-12 h-12 mx-auto" :alt="goal.title" /> + <img :src="getGoalIcon(goal)" class="w-12 h-12 mx-auto" :alt="goal.title" /> <div class="text-lg font-bold" data-cy="goal-title">{{ goal.title }}</div> </div> </div> @@ -124,7 +129,8 @@ <div @click="goToEditGoal" class="cursor-pointer"> <h3 class="text-blue-500 text-base">Endre mÃ¥l</h3> </div> - <div :key="componentKey" + <div + :key="componentKey" ref="targetRef" class="bg-yellow-400 px-4 py-1 rounded-full text-black font-bold" > @@ -140,23 +146,24 @@ ref="iconRef" class="max-w-20 max-h-20 absolute opacity-0" /> - <img v-if="goal" + <img + v-if="goal" :src="getGoalIcon(goal)" alt="could not load" ref="goalIconRef" class="shadow-sm shadow-amber-300 max-w-20 max-h-20 absolute opacity-0" - /> </template> <script setup lang="ts"> import { - type ComponentPublicInstance, nextTick, - onMounted, - onUnmounted, - reactive, - type Ref, - ref, + type ComponentPublicInstance, + nextTick, + onMounted, + onUnmounted, + reactive, + type Ref, + ref } from 'vue' import anime from 'animejs' import type { Challenge } from '@/types/challenge' @@ -165,7 +172,7 @@ import confetti from 'canvas-confetti' import { useRouter } from 'vue-router' import { useGoalStore } from '@/stores/goalStore' import ImgGifTemplate from '@/components/ImgGifTemplate.vue' -import CardChallengeSavingsPath from "@/components/CardChallengeSavingsPath.vue"; +import CardChallengeSavingsPath from '@/components/CardChallengeSavingsPath.vue' const router = useRouter() const goalStore = useGoalStore() @@ -178,9 +185,9 @@ const props = defineProps<Props>() const challenges = ref<Challenge[]>() let goal: Goal | null | undefined = reactive({ - title: '', // Default empty string to prevent undefined errors - saved: 0, - target: 0, + title: '', // Default empty string to prevent undefined errors + saved: 0, + target: 0 } as Goal) const isMounted = ref<boolean>(false) const componentKey = ref<number>(0) @@ -188,144 +195,133 @@ const componentKey = ref<number>(0) //Initialisation: onMounted(async () => { - window.addEventListener('resize', handleWindowSizeChange) - handleWindowSizeChange(); - challenges.value = props.challenges - goal = props.goal - sortChallenges(); - allChallengesCompleted(); - // Delay the execution of the following logic by 300ms - setTimeout(() => { - const container = containerRef.value; - if (container) { - container.addEventListener('scroll', () => { - if (!firstUncompletedRef.value) return; - const containerRect = container.getBoundingClientRect(); - const firstUncompletedRect = firstUncompletedRef.value.getBoundingClientRect(); - isAtFirstUncompleted.value = !( - firstUncompletedRect.top > containerRect.bottom || - firstUncompletedRect.bottom < containerRect.top - ); - }); - } - scrollToFirstUncompleted(); - }, 300); // Timeout set to 300 milliseconds - // Load existing animated states first - loadAnimatedStates() - - // Get completed challenge IDs, ensuring that only defined IDs are considered - const completedChallenges = challenges.value - .filter((challenge) => challenge.completion! >= 100 && challenge.id !== undefined) - .map((challenge) => challenge.id as number) // Use 'as number' to assert that ids are numbers after the check - - // Update only new completions that are not already in the animatedChallenges - const newAnimations = completedChallenges.filter((id) => !animatedChallenges.value.includes(id)) - animatedChallenges.value = [...animatedChallenges.value, ...newAnimations] - - // Save the updated list back to localStorage - localStorage.setItem('animatedChallenges', JSON.stringify(animatedChallenges.value)) - isMounted.value = true; + window.addEventListener('resize', handleWindowSizeChange) + handleWindowSizeChange() + challenges.value = props.challenges + goal = props.goal + sortChallenges() + allChallengesCompleted() + // Delay the execution of the following logic by 300ms + setTimeout(() => { + const container = containerRef.value + if (container) { + container.addEventListener('scroll', () => { + if (!firstUncompletedRef.value) return + const containerRect = container.getBoundingClientRect() + const firstUncompletedRect = firstUncompletedRef.value.getBoundingClientRect() + isAtFirstUncompleted.value = !( + firstUncompletedRect.top > containerRect.bottom || + firstUncompletedRect.bottom < containerRect.top + ) + }) + } + scrollToFirstUncompleted() + }, 300) // Timeout set to 300 milliseconds + // Load existing animated states first + loadAnimatedStates() + + // Get completed challenge IDs, ensuring that only defined IDs are considered + const completedChallenges = challenges.value + .filter((challenge) => challenge.completion! >= 100 && challenge.id !== undefined) + .map((challenge) => challenge.id as number) // Use 'as number' to assert that ids are numbers after the check + + // Update only new completions that are not already in the animatedChallenges + const newAnimations = completedChallenges.filter((id) => !animatedChallenges.value.includes(id)) + animatedChallenges.value = [...animatedChallenges.value, ...newAnimations] + + // Save the updated list back to localStorage + localStorage.setItem('animatedChallenges', JSON.stringify(animatedChallenges.value)) + isMounted.value = true }) - - onUnmounted(() => { - window.removeEventListener('resize', handleWindowSizeChange) - const container = containerRef.value - if (container) { - container.removeEventListener('scroll', () => { - // Clean up the scroll listener - }) - } + window.removeEventListener('resize', handleWindowSizeChange) + const container = containerRef.value + if (container) { + container.removeEventListener('scroll', () => { + // Clean up the scroll listener + }) + } }) const handleChallengeUpdate = (updatedChallenge: Challenge) => { - if (challenges.value) { - const index = challenges.value.findIndex(c => c.id === updatedChallenge.id); - if (index !== -1) { - challenges.value[index] = {...updatedChallenge}; - } + if (challenges.value) { + const index = challenges.value.findIndex((c) => c.id === updatedChallenge.id) + if (index !== -1) { + challenges.value[index] = { ...updatedChallenge } + } - if (updatedChallenge.completion! >= 100 && !animatedChallenges.value.includes(updatedChallenge.id as number)) { - animateChallenge(updatedChallenge); - saveAnimatedStateChallenge(updatedChallenge); - } + if ( + updatedChallenge.completion! >= 100 && + !animatedChallenges.value.includes(updatedChallenge.id as number) + ) { + animateChallenge(updatedChallenge) + saveAnimatedStateChallenge(updatedChallenge) + } - if (goal) { - incrementGoalSaved(updatedChallenge); - // Force component update right here might be more appropriate - componentKey.value++; + if (goal) { + incrementGoalSaved(updatedChallenge) + // Force component update right here might be more appropriate + componentKey.value++ + } } - } } const incrementGoalSaved = async (challenge: Challenge) => { - if (goal) { - // Correct the addition mistake and remove setTimeout - goal.saved = goal.saved + challenge.perPurchase; - await nextTick();// Only add the perPurchase amount - - const completion = (goal.saved / goal.target) * 100; - if (completion >= 100 && !animatedGoals.value.includes(goal.id as number)) { - animateGoal(goal); - setTimeout(() => { - goalStore.getUserGoals(); - goal = goalStore.priorityGoal; - }, 4000); // Keep this delay only for the store update and goal switch - } else { - await goalStore.getUserGoals(); - goal = goalStore.priorityGoal; + if (goal) { + // Correct the addition mistake and remove setTimeout + goal.saved = goal.saved + challenge.perPurchase + await nextTick() // Only add the perPurchase amount + + const completion = (goal.saved / goal.target) * 100 + if (completion >= 100 && !animatedGoals.value.includes(goal.id as number)) { + animateGoal(goal) + setTimeout(() => { + goalStore.getUserGoals() + goal = goalStore.priorityGoal + }, 4000) // Keep this delay only for the store update and goal switch + } else { + await goalStore.getUserGoals() + goal = goalStore.priorityGoal + } } - } } - - - /** * Navigates to the spareutfordringer page */ const addSpareUtfordring = () => { - router.push('/spareutfordringer').catch((error) => { - console.error('Routing error:', error) - }) + router.push('/spareutfordringer').catch((error) => { + console.error('Routing error:', error) + }) } /** * Checks if all challenges are completed */ const allChallengesCompleted = () => { - // Assuming challenges.value is an array of challenge objects - if (challenges.value) { - for (const challenge of challenges.value) { - if (challenge.completion !== 100) { - return false; // If any challenge is not completed, return false - } - } - return true; - }// If all challenges are completed, return true -}; + // Assuming challenges.value is an array of challenge objects + if (challenges.value) { + for (const challenge of challenges.value) { + if (challenge.completion !== 100) { + return false // If any challenge is not completed, return false + } + } + return true + } // If all challenges are completed, return true +} //-----------Animation for goal and challenge completion-----------------// // Reactive references for DOM elements const iconRef = ref<HTMLElement | null>(null) -const goalIconRef = ref<HTMLElement |null>(null); +const goalIconRef = ref<HTMLElement | null>(null) const containerRef = ref<HTMLElement | null>(null) const targetRef = ref<HTMLElement | null>(null) - - - - - - - - // Declare the ref with a type annotation for an array of strings const animatedChallenges: Ref<number[]> = ref([]) -const animatedGoals: Ref<number[]> =ref([]) - +const animatedGoals: Ref<number[]> = ref([]) /** * Loads the states for animated goals and challenges @@ -334,11 +330,9 @@ const loadAnimatedStates = () => { const animated = localStorage.getItem('animatedChallenges') const animatedG = localStorage.getItem('animatedGoals') animatedChallenges.value = animated ? JSON.parse(animated) : [] - animatedGoals.value =animatedG ? JSON.parse(animatedG):[] + animatedGoals.value = animatedG ? JSON.parse(animatedG) : [] } - - /** * Saves the animated state for challenge * triggers the confetti method @@ -359,7 +353,6 @@ const animateChallenge = (challenge: Challenge) => { } } - /** * Saves the animated state for goal * triggers the confetti method @@ -367,14 +360,14 @@ const animateChallenge = (challenge: Challenge) => { * @param goal */ const animateGoal = (goal: Goal) => { - console.log('im in animated goal') - - if (goal.id != null) { - animatedGoals.value.push(goal.id) - } // Ensure no duplication - saveAnimatedStateGoal(goal) // Refactor this to update localStorage correctly - triggerConfetti() - recalculateAndAnimate(true) + console.log('im in animated goal') + + if (goal.id != null) { + animatedGoals.value.push(goal.id) + } // Ensure no duplication + saveAnimatedStateGoal(goal) // Refactor this to update localStorage correctly + triggerConfetti() + recalculateAndAnimate(true) } /** @@ -382,20 +375,16 @@ const animateGoal = (goal: Goal) => { * @param isGoal */ const recalculateAndAnimate = (isGoal: boolean) => { - console.log('im in recalculate and animate') - + console.log('im in recalculate and animate') if (!isGoal && iconRef.value && containerRef.value && targetRef.value) { - animateIcon(isGoal) - } - else if (isGoal && containerRef.value && goalIconRef.value){ - animateIcon(isGoal) - } - else if (!isGoal && !targetRef.value) { - animateIcon(isGoal) - } - else { - console.error('Element references are not ready.') + animateIcon(isGoal) + } else if (isGoal && containerRef.value && goalIconRef.value) { + animateIcon(isGoal) + } else if (!isGoal && !targetRef.value) { + animateIcon(isGoal) + } else { + console.error('Element references are not ready.') } } @@ -404,10 +393,10 @@ const recalculateAndAnimate = (isGoal: boolean) => { * @param challenge */ const saveAnimatedStateChallenge = (challenge: Challenge) => { - if (challenge.id != null) { - animatedChallenges.value.push(challenge.id) - } - localStorage.setItem('animatedChallenges', JSON.stringify(animatedChallenges.value)) + if (challenge.id != null) { + animatedChallenges.value.push(challenge.id) + } + localStorage.setItem('animatedChallenges', JSON.stringify(animatedChallenges.value)) } /** @@ -415,178 +404,188 @@ const saveAnimatedStateChallenge = (challenge: Challenge) => { * @param goal */ const saveAnimatedStateGoal = (goal: Goal) => { - console.log('Saving animated state for:', goal.id) - if (goal.id != null) { - animatedGoals.value.push(goal.id) - } - localStorage.setItem('animatedGoals', JSON.stringify(animatedGoals.value)) + console.log('Saving animated state for:', goal.id) + if (goal.id != null) { + animatedGoals.value.push(goal.id) + } + localStorage.setItem('animatedGoals', JSON.stringify(animatedGoals.value)) } - - /** * animates the icon images * @param isGoal */ const animateIcon = (isGoal: boolean) => { - console.log('im in animate icon') - const icon = iconRef.value - const container = containerRef.value - const target = targetRef.value - - if (!icon || !container) { - console.error('Required animation elements are not available.') - return - } - // Obtain bounding rectangles safely - const containerRect = container.getBoundingClientRect(); - const targetRect = target?.getBoundingClientRect(); - const iconRect = icon.getBoundingClientRect(); - - // Initialize translation coordinates - let translateX1 = 0, translateY1 = 0, translateX2 = 0, translateY2 = 0; - - if (isGoal) { - const goal = goalIconRef.value; - if (goal) { - const goalRect = goal.getBoundingClientRect(); - if (goalRect) { - // Calculate the translation coordinates for the goal - translateX1 = containerRect.left + containerRect.width / 2 - goalRect.width / 2 - goalRect.left; - translateY1 = containerRect.top + containerRect.height / 2 - goalRect.height / 2 - goalRect.top; - - anime.timeline({ - easing: 'easeInOutQuad', - duration: 1500 - }) - .add({ - targets: goal, - translateX: translateX1, - translateY: translateY1, - opacity: [0, 1], // Fix: start from 0 opacity and animate to 1 - duration: 1000 - }) - .add({ - targets: goal, - opacity: [1, 0], // Fade out after moving - duration: 3000, - scale: 3, - begin: function (anim) { - if (icon) icon.classList.add('glow'); // Ensure icon exists before applying class - }, - complete: function (anim) { - if (icon) icon.classList.remove('glow'); // Clean up: remove class after animation - } - }); - } else { - console.error('Goal rectangle is not available.'); - } - } else { - console.error('Goal element is not available.'); - } + console.log('im in animate icon') + const icon = iconRef.value + const container = containerRef.value + const target = targetRef.value + + if (!icon || !container) { + console.error('Required animation elements are not available.') + return } - else if (!isGoal && target && targetRect) { - // Calculate the translation coordinates for the icon - translateX1 = containerRect.left + containerRect.width / 2 - iconRect.width / 2 - iconRect.left; - translateY1 = containerRect.top + containerRect.height / 2 - iconRect.height / 2 - iconRect.top; - translateX2 = targetRect.left + targetRect.width / 2 - iconRect.width / 2 - iconRect.left; - translateY2 = targetRect.top + targetRect.height / 2 - iconRect.height / 2 - iconRect.top; - - anime - .timeline({ - easing: 'easeInOutQuad', - duration: 1500 - }) - .add({ - targets: icon, - translateX: translateX1, - translateY: translateY1, - opacity: 0, // Start invisible - duration: 1000 - }) - .add({ - targets: icon, - opacity: 1, // Reveal the icon once it starts moving to the container - duration: 1000, // Make the opacity change almost instantaneously - scale: 3 - }) - .add({ - targets: icon, - translateX: translateX2, - translateY: translateY2, - scale: 0.5, - opacity: 1, // Keep the icon visible while moving to the target - duration: 1500 - }) - .add({ - targets: icon, - opacity: 0, // Fade out once it reaches the target - scale: 1, - duration: 500 - }) - .add({ - targets: icon, - translateX: 0, // Reset translation to original - translateY: 0, // Reset translation to original - duration: 500 - }) - } - else if(!isGoal && !target) { - // Calculate the translation coordinates for the icon - translateX1 = containerRect.left + containerRect.width / 2 - iconRect.width / 2 - iconRect.left; - translateY1 = containerRect.top + containerRect.height / 2 - iconRect.height / 2 - iconRect.top; - anime - .timeline({ - easing: 'easeInOutQuad', - duration: 1500 - }) - .add({ - targets: icon, - translateX: translateX1, - translateY: translateY1, - opacity: 0, // Start invisible - duration: 1000 - }) - .add({ - targets: icon, - opacity: 1, // Reveal the icon once it starts moving to the container - duration: 1000, // Make the opacity change almost instantaneously - scale: 3 - }) - .add({ - targets: icon, - opacity: 0, // Fade out once it reaches the target - scale: 1, - duration: 500 - }) - .add({ - targets: icon, - translateX: 0, // Reset translation to original - translateY: 0, // Reset translation to original - duration: 500 - }) + // Obtain bounding rectangles safely + const containerRect = container.getBoundingClientRect() + const targetRect = target?.getBoundingClientRect() + const iconRect = icon.getBoundingClientRect() + + // Initialize translation coordinates + let translateX1 = 0, + translateY1 = 0, + translateX2 = 0, + translateY2 = 0 + + if (isGoal) { + const goal = goalIconRef.value + if (goal) { + const goalRect = goal.getBoundingClientRect() + if (goalRect) { + // Calculate the translation coordinates for the goal + translateX1 = + containerRect.left + + containerRect.width / 2 - + goalRect.width / 2 - + goalRect.left + translateY1 = + containerRect.top + + containerRect.height / 2 - + goalRect.height / 2 - + goalRect.top + + anime + .timeline({ + easing: 'easeInOutQuad', + duration: 1500 + }) + .add({ + targets: goal, + translateX: translateX1, + translateY: translateY1, + opacity: [0, 1], // Fix: start from 0 opacity and animate to 1 + duration: 1000 + }) + .add({ + targets: goal, + opacity: [1, 0], // Fade out after moving + duration: 3000, + scale: 3, + begin: function (anim) { + if (icon) icon.classList.add('glow') // Ensure icon exists before applying class + }, + complete: function (anim) { + if (icon) icon.classList.remove('glow') // Clean up: remove class after animation + } + }) + } else { + console.error('Goal rectangle is not available.') + } + } else { + console.error('Goal element is not available.') + } + } else if (!isGoal && target && targetRect) { + // Calculate the translation coordinates for the icon + translateX1 = + containerRect.left + containerRect.width / 2 - iconRect.width / 2 - iconRect.left + translateY1 = + containerRect.top + containerRect.height / 2 - iconRect.height / 2 - iconRect.top + translateX2 = targetRect.left + targetRect.width / 2 - iconRect.width / 2 - iconRect.left + translateY2 = targetRect.top + targetRect.height / 2 - iconRect.height / 2 - iconRect.top + + anime + .timeline({ + easing: 'easeInOutQuad', + duration: 1500 + }) + .add({ + targets: icon, + translateX: translateX1, + translateY: translateY1, + opacity: 0, // Start invisible + duration: 1000 + }) + .add({ + targets: icon, + opacity: 1, // Reveal the icon once it starts moving to the container + duration: 1000, // Make the opacity change almost instantaneously + scale: 3 + }) + .add({ + targets: icon, + translateX: translateX2, + translateY: translateY2, + scale: 0.5, + opacity: 1, // Keep the icon visible while moving to the target + duration: 1500 + }) + .add({ + targets: icon, + opacity: 0, // Fade out once it reaches the target + scale: 1, + duration: 500 + }) + .add({ + targets: icon, + translateX: 0, // Reset translation to original + translateY: 0, // Reset translation to original + duration: 500 + }) + } else if (!isGoal && !target) { + // Calculate the translation coordinates for the icon + translateX1 = + containerRect.left + containerRect.width / 2 - iconRect.width / 2 - iconRect.left + translateY1 = + containerRect.top + containerRect.height / 2 - iconRect.height / 2 - iconRect.top + anime + .timeline({ + easing: 'easeInOutQuad', + duration: 1500 + }) + .add({ + targets: icon, + translateX: translateX1, + translateY: translateY1, + opacity: 0, // Start invisible + duration: 1000 + }) + .add({ + targets: icon, + opacity: 1, // Reveal the icon once it starts moving to the container + duration: 1000, // Make the opacity change almost instantaneously + scale: 3 + }) + .add({ + targets: icon, + opacity: 0, // Fade out once it reaches the target + scale: 1, + duration: 500 + }) + .add({ + targets: icon, + translateX: 0, // Reset translation to original + translateY: 0, // Reset translation to original + duration: 500 + }) } } /** * Triggers confeti animation */ const triggerConfetti = () => { - confetti({ - particleCount: 400, - spread: 80, - origin: { x: 0.8, y: 0.8 } - }) + confetti({ + particleCount: 400, + spread: 80, + origin: { x: 0.8, y: 0.8 } + }) } - //fetching images const getGoalIcon = (goal: Goal): string => { - if (goal){ - return `src/assets/${goal.title.toLowerCase()}.png` - } - else{ - return 'src/assets/pengesekkStreak.png' - } + if (goal) { + return `src/assets/${goal.title.toLowerCase()}.png` + } else { + return 'src/assets/pengesekkStreak.png' + } } const getPigStepsIcon = () => { return 'src/assets/pigSteps.png' @@ -597,36 +596,34 @@ const goToEditGoal = () => { } const editGoal = (goal: Goal) => { - router.push(`/sparemaal/rediger/${goal.id}`) + router.push(`/sparemaal/rediger/${goal.id}`) } - - /** * Sorts the challenges by completion status and due date */ const sortChallenges = () => { - if (challenges.value) { - challenges.value.sort((a, b) => { - // First, sort by completion status: non-completed (less than 100) before completed (100) - if (a.completion !== 100 && b.completion === 100) { - return 1 // 'a' is not completed and 'b' is completed, 'a' should come first - } else if (a.completion === 100 && b.completion !== 100) { - return -1 // 'a' is completed and 'b' is not, 'b' should come first - } else { - // Explicitly convert dates to numbers for subtraction - const dateA = new Date(a.due).getTime() - const dateB = new Date(b.due).getTime() - return dateA - dateB - } - }) - } + if (challenges.value) { + challenges.value.sort((a, b) => { + // First, sort by completion status: non-completed (less than 100) before completed (100) + if (a.completion !== 100 && b.completion === 100) { + return 1 // 'a' is not completed and 'b' is completed, 'a' should come first + } else if (a.completion === 100 && b.completion !== 100) { + return -1 // 'a' is completed and 'b' is not, 'b' should come first + } else { + // Explicitly convert dates to numbers for subtraction + const dateA = new Date(a.due).getTime() + const dateB = new Date(b.due).getTime() + return dateA - dateB + } + }) + } } // Interface for element references interface ElementRefs { - [key: string]: HTMLElement | undefined + [key: string]: HTMLElement | undefined } const elementRefs = reactive<ElementRefs>({}) @@ -638,35 +635,34 @@ const screenSize = ref<number>(window.innerWidth) * Handles the window size change event */ const handleWindowSizeChange = () => { - screenSize.value = window.innerWidth + screenSize.value = window.innerWidth } /** * Scrolls to the first uncompleted challenge */ -const scrollToFirstUncompleted= ()=> { - if (challenges.value) { - let found = false - for (let i = 0; i < challenges.value.length; i++) { - if (challenges.value[i].completion! < 100) { - const refKey = `uncompleted-${i}` - if (elementRefs[refKey]) { - elementRefs[refKey]!.scrollIntoView({behavior: 'smooth', block: 'start'}) - firstUncompletedRef.value = elementRefs[refKey] // Store the reference - found = true - isAtFirstUncompleted.value = true - break +const scrollToFirstUncompleted = () => { + if (challenges.value) { + let found = false + for (let i = 0; i < challenges.value.length; i++) { + if (challenges.value[i].completion! < 100) { + const refKey = `uncompleted-${i}` + if (elementRefs[refKey]) { + elementRefs[refKey]!.scrollIntoView({ behavior: 'smooth', block: 'start' }) + firstUncompletedRef.value = elementRefs[refKey] // Store the reference + found = true + isAtFirstUncompleted.value = true + break + } + } + } + if (!found) { + isAtFirstUncompleted.value = false } - } - } - if (!found) { - isAtFirstUncompleted.value = false } - } } - /** * Assigns the reference to the element * @param el @@ -678,21 +674,19 @@ const assignRef = ( challenge: Challenge, index: number ) => { - const refKey = `uncompleted-${index}` - if (el instanceof HTMLElement) { - // Ensure that el is an HTMLElement - if (challenge.completion! < 100) { - elementRefs[refKey] = el - } - } else { - // Cleanup if the element is unmounted or not an HTMLElement - if (elementRefs[refKey]) { - delete elementRefs[refKey] + const refKey = `uncompleted-${index}` + if (el instanceof HTMLElement) { + // Ensure that el is an HTMLElement + if (challenge.completion! < 100) { + elementRefs[refKey] = el + } + } else { + // Cleanup if the element is unmounted or not an HTMLElement + if (elementRefs[refKey]) { + delete elementRefs[refKey] + } } - } } - - </script> <style scoped> @@ -703,6 +697,4 @@ const assignRef = ( .no-scrollbar { -ms-overflow-style: none; /* for Internet Explorer and Edge */ } - - </style> diff --git a/src/stores/challengeStore.ts b/src/stores/challengeStore.ts index faa63f8..7eb27b7 100644 --- a/src/stores/challengeStore.ts +++ b/src/stores/challengeStore.ts @@ -32,15 +32,15 @@ export const useChallengeStore = defineStore('challenge', () => { if (index !== -1) { challenges.value[index] = { ...challenges.value[index], ...response.data } console.log('Updated Challenge:', response.data) - return challenges.value[index]; + return challenges.value[index] } } else { console.error('No challenge content found in response data') - return null; + return null } } catch (error) { console.error('Error updating challenge:', error) - return null; + return null } } const completeUserChallenge = async (challenge: Challenge) => { @@ -56,15 +56,15 @@ export const useChallengeStore = defineStore('challenge', () => { challenges.value[index] = { ...challenges.value[index], ...response.data } console.log('Updated Challenge:', response.data) console.log('Challenge Completed store:', challenges.value[index]) - return challenges.value[index]; + return challenges.value[index] } } else { console.error('No challenge content found in response data') - return null; + return null } } catch (error) { console.error('Error updating challenge:', error) - return null; + return null } } diff --git a/src/stores/goalStore.ts b/src/stores/goalStore.ts index 11a917c..ce03382 100644 --- a/src/stores/goalStore.ts +++ b/src/stores/goalStore.ts @@ -15,8 +15,7 @@ export const useGoalStore = defineStore('goal', () => { if (goal.priority === 1) { priorityGoal.value = goal break - } - else { + } else { priorityGoal.value = null } } @@ -56,6 +55,6 @@ export const useGoalStore = defineStore('goal', () => { goals, priorityGoal, getUserGoals, - editUserGoal, + editUserGoal } }) diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index ceb4f1b..3292731 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -1,15 +1,15 @@ <template> <div class="flex flex-col items-center max-h-[60vh] md:flex-row md:max-h-[80vh] mx-auto"> <div class="flex flex-col basis-1/3 order-last md:order-first md:basis-1/4 md:pl-1 mt-10"> - <SpareComponent - :speech="speech" - :show="showWelcome" - :png-size="12" - :direction="'right'" - :imageDirection="'right'" - class="mt-24" - ></SpareComponent> - <div class="flex flex-row gap-2 items-center mx-auto my-4 md:flex-col md:gap-4 md:m-8"> + <SpareComponent + :speech="speech" + :show="showWelcome" + :png-size="12" + :direction="'right'" + :imageDirection="'right'" + class="mt-24" + ></SpareComponent> + <div class="flex flex-row gap-2 items-center mx-auto my-4 md:flex-col md:gap-4 md:m-8"> <ButtonAddGoalOrChallenge :buttonText="'Legg til sparemÃ¥l'" :type="'goal'" /> <ButtonAddGoalOrChallenge :buttonText="'Legg til spareutfordring'" @@ -30,8 +30,7 @@ </template> <script setup lang="ts"> -import {onMounted, ref} from 'vue' -import InteractiveSpare from '@/components/InteractiveSpare.vue' +import { onMounted, ref } from 'vue' import ButtonAddGoalOrChallenge from '@/components/ButtonAddGoalOrChallenge.vue' import type { Challenge } from '@/types/challenge' import type { Goal } from '@/types/goal' @@ -60,15 +59,13 @@ onMounted(async () => { goal.value = goalStore.priorityGoal const lastModalShow = localStorage.getItem('lastModalShow') if (!lastModalShow || Date.now() - Number(lastModalShow) >= 24 * 60 * 60 * 1000) { - showModal.value = true + showModal.value = true } firstLoggedInSpeech() - SpareSpeech(); + SpareSpeech() isMounted.value = true }) - - const firstLoggedInSpeech = () => { const isFirstLogin = router.currentRoute.value.query.firstLogin === 'true' if (isFirstLogin) { @@ -83,13 +80,12 @@ const firstLoggedInSpeech = () => { } const SpareSpeech = () => { - speech.value = [ - 'Hei! Jeg er sparegrisen, Spare!', - 'Valkommen til SpareSti 👑', - 'Du kan trykke pÃ¥ meg for Ã¥ høre hva jeg har Ã¥ si ðŸ·' - ] + speech.value = [ + 'Hei! Jeg er sparegrisen, Spare!', + 'Valkommen til SpareSti 👑', + 'Du kan trykke pÃ¥ meg for Ã¥ høre hva jeg har Ã¥ si ðŸ·' + ] } - </script> <style> -- GitLab