Skip to content
Snippets Groups Projects
Commit c05decec authored by Valdemar Åstorp Beere's avatar Valdemar Åstorp Beere
Browse files

feat(homepage):

Implemented components in HomeView.vue
parent dfc5c18d
No related branches found
No related tags found
3 merge requests!66Final merge,!12feat(homepage):,!4Pipeline fix
<template>
<div class="flex flex-row max-h-[80vh]">
<div class="flex flex-col basis-1/2">
<InteractiveSpare :speech="speech"
:direction="'right'"
:pngSize="60"
class="w-80 h-80"
></InteractiveSpare>
<div class="m-8 p-8">
<ButtonAddGoalOrChallenge
:buttonText="'Legg til sparemål'"
class="mb-4"
/>
<ButtonAddGoalOrChallenge
:buttonText="'Legg til spareutfordring'"
/>
</div>
</div>
<div class="flex flex-col basis-1/2">
<div class="flex justify-center align-center">
<span class="
w-full
max-w-60
max-h-12
bg-green-500
text-white font-bold py-2 rounded
mt-8
text-center
space-x-2
">
Din Sparesti
</span>
</div>
<div ref="containerRef" class="container mx-auto p-6 no-scrollbar max-h-[60vh] overflow-y-auto">
<div v-for="(challenge, index) in challenges" :key="challenge.title" class="flex flex-col items-center mx-8">
<!-- Challenge Row -->
<div :class="{ 'justify-end ml-30': index % 2 === 1, 'justify-start': index % 2 === 0 }" class="flex flex-row w-2/3 ml-8">
<!-- Challenge Icon and Details -->
<div class="flex">
<!-- Challenge Icon -->
<div class="flex flex-col">
<img
:src="getChallengeIcon(challenge)"
class="max-w-20 max-h-20"
:alt="challenge.title"
>
<!-- Progress Bar, if the challenge is not complete -->
<div v-if="challenge.completion<100" class="flex-grow w-full mt-2">
<div class="flex flex-row">
<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" :style="{ width: (challenge.saved / challenge.target * 100) + '%' }"></div>
</div>
<div class="text-center">
{{ challenge.saved }}kr / {{ challenge.target }}kr
</div>
</div>
<button
@click="incrementSaved(challenge)"
type="button"
class="inline-block mb-2 ml-2 max-h-8 max-w-8 rounded-full bg-green-500 p-1 uppercase leading-normal text-white bg-color-green shadow-green-500 transition duration-150 ease-in-out hover:bg-green-700 hover:shadow-green-200 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">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
</button>
</div>
</div>
<span v-else class="text-center">Ferdig: {{challenge.saved}}</span>
</div>
<!-- Check Icon -->
<div v-if="challenge.completion >= 100" class="max-w-16 max-h-16">
<img src="@/assets/completed.png" alt="">
</div>
<div v-else class="max-w-16 max-h-16">
<img src="@/assets/pending.png" alt="">
</div>
</div>
</div>
<!-- Piggy Steps, centered -->
<div v-if="index !== challenges.length - 1" class="flex justify-center w-full">
<img :src="getPigStepsIcon()" :class="{ 'transform scale-x-[-1]': (index) % 2 === 0 }" class="w-20 h-20" alt="Pig Steps">
</div>
</div>
</div>
<!-- Finish line -->
<img src="@/assets/finishLine.png" class="w-full max-h-4 mx-auto" alt="Finish Line">
<!-- Goal -->
<div v-if="currentGoal" class="flex items-center justify-between m-t-2 pt-6">
<div class="flex flex-col items-start">
<img :src="getGoalIcon(currentGoal)" class="w-12 h-12 mx-auto" :alt="currentGoal.title">
<div class="text-lg font-bold">{{ currentGoal.title }}</div>
</div>
<div class="flex flex-col items-end">
<div @click="goToEditGoal" class="cursor-pointer">
<h3 class="text-blue-500 text-base">Endre mål</h3>
</div>
<div ref="targetRef" class="bg-yellow-400 px-4 py-1 rounded-full text-black">
{{ currentGoal.saved }}kr / {{ currentGoal.target }}kr
</div>
</div>
</div>
</div>
<!-- Animation icon -->
<img
src="@/assets/coins.png"
alt="Coins"
ref="iconRef"
class="max-w-20 max-h-20 absolute opacity-0">
</div>
<div>
</div>
</template>
<script setup lang="ts">
import {computed, nextTick, onMounted, ref, watch} from 'vue';
import anime from 'animejs';
import InteractiveSpare from "@/components/InteractiveSpare.vue";
import ButtonAddGoalOrChallenge from "@/components/ButtonAddGoalOrChallange.vue";
import router from "@/router";
// Define your speech array
const speechArray = [
"Hei! Jeg er Sparemannen.",
"Jeg hjelper deg med å spare penger.",
"Klikk på meg for å høre mer."
];
// Correctly initialize the ref
const speech = ref(speechArray);
// Reactive references for DOM elements
const iconRef = ref<HTMLElement | null>(null);
const containerRef = ref<HTMLElement | null>(null);
const targetRef = ref<HTMLElement | null>(null);
const goals = ref([
{ title: 'Gaming', saved: 280, target: 100, description: 'Gaming console', priority: 1, completion: 0 },
// Other goals...
])
const challenges = ref([
{ title: 'Kaffe', challengeType: 'COFFEE', saved: 100, target: 100, description: 'Morning boost', completion: 100 },
{ title: 'Mat og Drikke', challengeType: 'SNACKS', saved: 80, target: 100, description: 'Morning boost', completion: 80 },
{ title: 'Gaming', challengeType: 'GAMING', saved: 20, target: 100, description: 'Morning boost', completion: 20 },
{ title: 'Kaffe', challengeType: 'COFFEE', saved: 90, target: 100, description: 'Morning boost', completion: 90 },
{ title: 'Mat og Drikke', challengeType: 'SNACKS', saved: 80, target: 100, description: 'Morning boost', completion: 80 },
{ title: 'Gaming', challengeType: 'GAMING', saved: 20, target: 100, description: 'Morning boost', completion: 20 },
// Other challenges...
])
// Additional state to track if the animation has been played
// Computed current goal
const currentGoal = computed(() => {
// Logic to determine the current goal
return goals.value.find(goal => goal.completion < 100)
})
// Increment saved amount
function incrementSaved(challenge) {
challenge.saved += 10;
if (challenge.saved >= challenge.target) {
challenge.completion = 100;
}
}
function recalculateAndAnimate() {
nextTick(() => {
if (iconRef.value && containerRef.value && targetRef.value) {
animateIcon();
} else {
console.error('Element references are not ready.');
}
});
}
const animatedChallenges = ref(new Set());
const loadAnimatedStates = () => {
const animated = localStorage.getItem('animatedChallenges');
animatedChallenges.value = animated ? new Set(JSON.parse(animated)) : new Set();
};
const saveAnimatedState = (title) => {
animatedChallenges.value.add(title);
localStorage.setItem('animatedChallenges', JSON.stringify([...animatedChallenges.value]));
};
const animateChallenge = (challenge) => {
if (challenge.completion >= 100 && !animatedChallenges.value.has(challenge.title)) {
console.log("Animating for:", challenge.title);
recalculateAndAnimate(); // Assumes this function triggers the actual animation
saveAnimatedState(challenge.title);
}
};
watch(challenges, (newChallenges) => {
newChallenges.forEach(challenge => {
if (challenge.completion === 100 && !animatedChallenges.value.has(challenge.title)) {
animateChallenge(challenge);
}
});
}, { deep: true });
onMounted(() => {
// Filter challenges that are already completed
const completedChallenges = challenges.value.filter(challenge => challenge.completion === 100).map(challenge => challenge.title);
// For testing purposes, clear localStorage
localStorage.clear();
// Update localStorage with the titles of completed challenges
localStorage.setItem('animatedChallenges', JSON.stringify(completedChallenges));
// Load the initial state of animated challenges from localStorage
loadAnimatedStates();
});
function animateIcon() {
const icon = iconRef.value;
const container = containerRef.value;
const target = targetRef.value;
if (!icon || !container || !target) {
console.error('Required animation elements are not available.');
return;
}
const containerRect = container.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();
const iconRect = icon.getBoundingClientRect();
const translateX1 = containerRect.left + (containerRect.width / 2) - (iconRect.width / 2) - iconRect.left;
const translateY1 = containerRect.top + (containerRect.height / 2) - (iconRect.height / 2) - iconRect.top;
const translateX2 = targetRect.left + (targetRect.width / 2) - (iconRect.width / 2) - iconRect.left;
const 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
opacity: 0, // Fade out once it reaches the target
scale: 1,
duration: 500,
});
}
// Helper methods to get icons
function getChallengeIcon(challenge) {
return `src/assets/${challenge.challengeType.toLowerCase()}.png`
}
function getGoalIcon(goal) {
return `src/assets/${goal.title.toLowerCase()}.png`
}
function getPigStepsIcon() {
return 'src/assets/pigSteps.png';
}
function goToEditGoal() {
router.push({ name: 'EditGoal' });
}
</script>
<template>
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<p>Paragraph</p>
<button>Button</button>
<br>
<a href="#">Link</a>
<div>Div</div>
<section>Section</section>
<article>Article</article>
</template>
<style scoped>
/* Tailwind CSS - Custom CSS for hiding scrollbars */
.no-scrollbar::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}
.no-scrollbar {
-ms-overflow-style: none; /* for Internet Explorer and Edge */
}
</style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment