diff --git a/package-lock.json b/package-lock.json index e033404d9e73b55448ecc76c9f34f9b993e8682d..c70d5c1ef28262d5c08d8ceb41e50d362064746c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "animejs": "^3.2.2", + "canvas-confetti": "^1.9.2", "pinia": "^2.1.7", "vue": "^3.4.21", "vue-router": "^4.3.1" @@ -17,6 +18,7 @@ "@rushstack/eslint-patch": "^1.8.0", "@tsconfig/node20": "^20.1.4", "@types/animejs": "^3.1.12", + "@types/canvas-confetti": "^1.6.4", "@types/jsdom": "^21.1.6", "@types/node": "^20.12.5", "@typescript-eslint/eslint-plugin": "^7.7.0", @@ -1153,6 +1155,12 @@ "integrity": "sha512-fpdH+ZtlO0kqjTOqRaBdsEmvpRNOayI8k4EVkEtitL5l6wducDOXk0rgQgfZqWf/ZX9DzXrHf257S5i9xTcISQ==", "dev": true }, + "node_modules/@types/canvas-confetti": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.6.4.tgz", + "integrity": "sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -2345,6 +2353,15 @@ } ] }, + "node_modules/canvas-confetti": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.2.tgz", + "integrity": "sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", diff --git a/package.json b/package.json index 53e12f251ec26cadb44ba81d493081cb723e8159..68388497c7d873b8f03e1128b07bb1c1c5a21ef9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "animejs": "^3.2.2", + "canvas-confetti": "^1.9.2", "pinia": "^2.1.7", "vue": "^3.4.21", "vue-router": "^4.3.1" @@ -27,6 +28,7 @@ "@rushstack/eslint-patch": "^1.8.0", "@tsconfig/node20": "^20.1.4", "@types/animejs": "^3.1.12", + "@types/canvas-confetti": "^1.6.4", "@types/jsdom": "^21.1.6", "@types/node": "^20.12.5", "@typescript-eslint/eslint-plugin": "^7.7.0", diff --git a/src/App.vue b/src/App.vue index ab3374760ed993907dbc31358562342c622668b8..9b1c3538f8a4a106c8ba5ed9d65cb2a4c0d9cd8b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,41 +1,7 @@ <script setup lang="ts"> -import { computed } from 'vue' -import { RouterLink, RouterView, useRoute } from 'vue-router' - -const route = useRoute() - -const showRouterView = computed(() => route.path !== '/') +import NavBarComponent from '@/components/NavBarComponent.vue' </script> <template> - <nav v-if="showRouterView"> - <RouterLink to="/hjem">Hjem</RouterLink> - <RouterLink to="/sparemaal">SparemÃ¥l</RouterLink> - <RouterLink to="/spareutfordringer">Spareutfordringer</RouterLink> - <RouterLink to="/profil">Profil</RouterLink> - <RouterLink to="/konfigurasjonSteg1">Konfigurasjon</RouterLink> - </nav> - - <main> - <div> - <RouterView /> - </div> - </main> + <NavBarComponent /> </template> - -<style> -nav { - display: flex; - justify-content: center; - gap: 1rem; - margin: 1rem 0; -} - -nav a.router-link-exact-active { - color: var(--color-text); -} - -nav a.router-link-exact-active:hover { - background-color: transparent; -} -</style> diff --git a/src/assets/SPARESTI.png b/src/assets/SPARESTI.png deleted file mode 100644 index 954b5c3ab77b2d90c29662f925f4e7ad9fa960ed..0000000000000000000000000000000000000000 Binary files a/src/assets/SPARESTI.png and /dev/null differ diff --git a/src/assets/base.css b/src/assets/base.css index 632645e26bedfaf24c25fd190491fb30a7808a97..b70ce8c50bc1afbb76d12643070e61a8574ffeda 100644 --- a/src/assets/base.css +++ b/src/assets/base.css @@ -4,7 +4,9 @@ --black: #363739; --white: #ffffff; --grey: #cbcbcb; + --light-grey: #f2f2f2; --green: #95e35d; + --light-green: #b3f385; --bright: #f7da7c; @@ -16,8 +18,10 @@ :root { --color-background: var(--white); --color-text: var(--black); - --color-button: var(--bright); + --color-button: var(--green); --color-button-disabled: var(--grey); + --color-nav-hover: var(--light-grey); + --color-button-hover: var(--light-green); --color-link: var(--accent3); --color-border: var(--black); diff --git a/src/assets/coffee.png b/src/assets/coffee.png index 4862babd365d69b653cd83415f00520ce3bf3684..425ef51ab494a199c6fa075adce42e48cf35a44f 100644 Binary files a/src/assets/coffee.png and b/src/assets/coffee.png differ diff --git a/src/assets/coins.png b/src/assets/coins.png deleted file mode 100644 index 88508f81eea8438785a0ac052d9112c375a80891..0000000000000000000000000000000000000000 Binary files a/src/assets/coins.png and /dev/null differ diff --git a/src/assets/completed.png b/src/assets/completed.png index 3b3e629dc3b97bfc0eb09aa27cc520579ddc322b..943dca5a129d1c703c037cfcceac092276de40d4 100644 Binary files a/src/assets/completed.png and b/src/assets/completed.png differ diff --git a/src/assets/finishLine.png b/src/assets/finishLine.png index a0da9ecd3be1e39e70d342a2c580d09ce80015d0..f30b5a30213062013638f6e275673d65d0640777 100644 Binary files a/src/assets/finishLine.png and b/src/assets/finishLine.png differ diff --git a/src/assets/main.css b/src/assets/main.css index cef42f658f03ce709415cb70bc5a0a4a42eb8d82..fb3adbdedd532ef58ec23cf3738f192b8852b7c4 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -48,10 +48,15 @@ button:disabled { background-color: var(--color-button-disabled); cursor: not-allowed; } +button:hover { + background-color: var(--color-button-hover); + transition: 0.7s; +} a { text-decoration: none; - color: var(--color-link); + color: var(--color-text); + font-weight: bold; transition: 0.4s; } @@ -69,6 +74,10 @@ textarea { @media (hover: hover) { a:hover { - background-color: hsla(160, 100%, 37%, 0.2); + background-color: var(--color-nav-hover); + transition: 0.5s; + text-decoration: none; + padding: 3px 3px; + border-radius: 8px; } } diff --git a/src/assets/mat.png b/src/assets/mat.png new file mode 100644 index 0000000000000000000000000000000000000000..4c1dff6b49b35761785ab5eb0d1ba6997925995c Binary files /dev/null and b/src/assets/mat.png differ diff --git a/src/assets/pending.png b/src/assets/pending.png index 734d180e43d50e93d315ff8c56f1d54c1cb05224..3eb5be6856258672869a652316bc9363cb0e236e 100644 Binary files a/src/assets/pending.png and b/src/assets/pending.png differ diff --git a/src/assets/penger.png b/src/assets/penger.png new file mode 100644 index 0000000000000000000000000000000000000000..9588e23b80536441ee8f2703d4459c8557ff361a Binary files /dev/null and b/src/assets/penger.png differ diff --git a/src/assets/spare.png b/src/assets/spare.png index 8e652fe5646673f24da26874beb8b7bd109b6760..fe48be94be6ede6652ce0a94d50f581710d7ef07 100644 Binary files a/src/assets/spare.png and b/src/assets/spare.png differ diff --git a/src/assets/sparesti2.png b/src/assets/spareSti.png similarity index 100% rename from src/assets/sparesti2.png rename to src/assets/spareSti.png diff --git a/src/assets/start.png b/src/assets/start.png new file mode 100644 index 0000000000000000000000000000000000000000..16b9d574fcf47a1b740dc177c68b35bc45b637cf Binary files /dev/null and b/src/assets/start.png differ diff --git a/src/assets/start_page/Spare.png b/src/assets/start_page/Spare.png deleted file mode 100644 index fe48be94be6ede6652ce0a94d50f581710d7ef07..0000000000000000000000000000000000000000 Binary files a/src/assets/start_page/Spare.png and /dev/null differ diff --git a/src/assets/streakFlame.png b/src/assets/streakFlame.png new file mode 100644 index 0000000000000000000000000000000000000000..196614ff02ea39e9679190af3a8d0096661a6442 Binary files /dev/null and b/src/assets/streakFlame.png differ diff --git a/src/components/ButtonAddGoalOrChallange.vue b/src/components/ButtonAddGoalOrChallenge.vue similarity index 67% rename from src/components/ButtonAddGoalOrChallange.vue rename to src/components/ButtonAddGoalOrChallenge.vue index 820233b39e4ef56f96b58303cc965e33444f9fde..472ff9fbe43884591d42067e2f183d45409b5545 100644 --- a/src/components/ButtonAddGoalOrChallange.vue +++ b/src/components/ButtonAddGoalOrChallenge.vue @@ -1,6 +1,6 @@ <template> <button - class="w-full max-w-60 max-h-12 bg-green-500 text-white font-bold py-2 rounded-full flex items-center justify-start pl-4 space-x-2 hover:bg-green-600 drop-shadow-lg focus:outline-none focus:ring-2 focus:ring-green-700 focus:ring-opacity-50 shadow-md transition duration-300 ease-in-out text-xs md:text-sm lg:text-base" + class="w-full max-w-60 max-h-12 font-bold py-2 rounded-full flex items-center justify-start pl-4 space-x-2 focus:outline-none focus:ring-2 focus:ring-green-700 focus:ring-opacity-50 shadow-md transition duration-300 ease-in-out text-xs md:text-sm lg:text-base" > <svg xmlns="http://www.w3.org/2000/svg" diff --git a/src/components/NavBarComponent.vue b/src/components/NavBarComponent.vue new file mode 100644 index 0000000000000000000000000000000000000000..b58aa12b56956f2fb2419265d48ce75bb0a37466 --- /dev/null +++ b/src/components/NavBarComponent.vue @@ -0,0 +1,120 @@ +<template> + <nav v-if="showNavBar" class="flex justify-center items-center mt-10 text-xl w-full"> + <div> + <img + src="@/assets/spareSti.png" + alt="logo" + class="absolute left-0 top-8 w-48 h-15 cursor-pointer transition-transform duration-300 ease-in-out hover:scale-110 hover:opacity-90" + @click="goToHome" + /> + <div class="absolute left-6 top-24 flex-1 sm:flex justify-start items-center"> + <div class="flex md:left-80 md:top-20 right-28 top-20 items-center"> + <img src="@/assets/streakFlame.png" alt="streak" class="w-8 h-8" /> + <p class="font-bold">Streak</p> + </div> + </div> + </div> + <div class="navcontainer flex space-x-10 justify-center"> + <router-link to="/hjem" class="nav-link" active-class="border-b-2">ðŸ Hjem</router-link> + <router-link to="/sparemaal" class="nav-link" active-class="border-b-2" + >🎯SparemÃ¥l</router-link + > + <router-link to="/spareutfordringer" class="nav-link" active-class="border-b-2" + >💰Spareutfordringer</router-link + > + <router-link to="/profil" class="nav-link" active-class="border-b-2" + >ðŸ¤Profil</router-link + > + <router-link to="/konfigurasjonSteg1" class="nav-link" active-class="border-b-2" + >🛠ï¸Konfigurasjon</router-link + > + <button + @click="logout" + class="hidden sm:flex absolute right-10 py-2 px-6 rounded-full focus:outline-none focus:ring focus:ring-black-300" + > + Logg ut + </button> + </div> + <button class="hamburger-menu sm:hidden absolute right-10 top-10" @click="toggleMenu"> + <p>☰</p> + </button> + </nav> + + <div + v-if="menuOpen" + class="sm:hidden flex flex-col bg-white absolute border border-slate-300 top-10 w-full mt-10 z-50 rounded-xl" + > + <router-link to="/hjem" @click="menuOpen = false">ðŸ Hjem</router-link> + <router-link to="/sparemaal" @click="menuOpen = false">🎯SparemÃ¥l</router-link> + <router-link to="/spareutfordringer" @click="menuOpen = false" + >💰Spareutfordringer</router-link + > + <router-link to="/profil" @click="menuOpen = false">ðŸ¤Profil</router-link> + <router-link to="/konfigurasjonSteg1" @click="menuOpen = false" + >🛠ï¸Konfigurasjon</router-link + > + <button + @click="logout" + class="py-2 px-6 mx-auto rounded-full focus:outline-none focus:ring focus:ring-black-300 bg-transparent" + > + Logg ut + </button> + </div> + + <main> + <RouterView /> + </main> +</template> + +<script setup lang="ts"> +import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router' +import { computed, ref, onMounted } from 'vue' + +const route = useRoute() +const router = useRouter() +const windowWidth = ref(window.innerWidth) +const menuOpen = ref(false) + +const showNavBar = computed(() => { + return ( + route.path == '/hjem' || + route.path == '/sparemaal' || + route.path == '/spareutfordringer' || + route.path == '/profil' || + route.path == '/konfigurasjonSteg1' + ) +}) + +const logout = () => { + router.push('/login') + menuOpen.value = false +} + +const goToHome = () => { + router.push('/hjem') + menuOpen.value = false +} + +const toggleMenu = () => { + menuOpen.value = !menuOpen.value +} + +onMounted(() => { + const updateWindowWidth = () => { + windowWidth.value = window.innerWidth + } + window.addEventListener('resize', updateWindowWidth) +}) +</script> + +<style scoped> +@media (max-width: 1150px) { + .hamburger-menu { + display: block; + cursor: pointer; + } + .navcontainer { + display: none; + } +} +</style> diff --git a/src/components/__tests__/ButtonAddGoalOrChallengeTest.spec.ts b/src/components/__tests__/ButtonAddGoalOrChallengeTest.spec.ts index e893c2abd810ffd27e9ee3c2d99ae792759bd837..9b0ef6d4c1f31e94a5da7b2dffc628bc2e7ceff2 100644 --- a/src/components/__tests__/ButtonAddGoalOrChallengeTest.spec.ts +++ b/src/components/__tests__/ButtonAddGoalOrChallengeTest.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { mount } from '@vue/test-utils' -import ButtonComponent from '@/components/ButtonAddGoalOrChallange.vue' // Adjust the import path as needed. +import ButtonComponent from '@/components/ButtonAddGoalOrChallenge.vue' describe('ButtonComponent', () => { it('renders correctly', () => { @@ -12,46 +12,6 @@ describe('ButtonComponent', () => { expect(wrapper.exists()).toBe(true) }) - it('has the correct classes', () => { - const wrapper = mount(ButtonComponent, { - props: { - buttonText: 'Click me' - } - }) - const button = wrapper.find('button') - const expectedClasses = [ - 'w-full', - 'max-w-60', - 'max-h-12', - 'bg-green-500', - 'text-white', - 'font-bold', - 'py-2', - 'rounded-full', - 'flex', - 'items-center', - 'justify-start', - 'pl-4', - 'space-x-2', - 'hover:bg-green-600', - 'drop-shadow-lg', - 'focus:outline-none', - 'focus:ring-2', - 'focus:ring-green-700', - 'focus:ring-opacity-50', - 'shadow-md', - 'transition', - 'duration-300', - 'ease-in-out', - 'text-xs', - 'md:text-sm', - 'lg:text-base' - ] - expectedClasses.forEach((cls) => { - expect(button.classes()).toContain(cls) - }) - }) - it('displays the correct button text', () => { const wrapper = mount(ButtonComponent, { props: { diff --git a/src/components/__tests__/HomeViewTest.spec.ts b/src/components/__tests__/HomeViewTest.spec.ts deleted file mode 100644 index e61e4355c3afcb94409b66f8fc4030d7c97a065b..0000000000000000000000000000000000000000 --- a/src/components/__tests__/HomeViewTest.spec.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { mount } from '@vue/test-utils' -import HomeView from '@/views/HomeView.vue' // Adjust the import path as needed. -import anime from 'animejs' -import type { Challenge } from '../../types/challenge' -import type { Goal } from '../../types/goal' - -// Setup localStorage mock -const localStorageMock = (function () { - let store = {} as { [key: string]: string } - return { - getItem: vi.fn((key: string) => store[key] || null), - setItem: vi.fn((key: string, value: any) => { - store[key] = value.toString() - }), - clear: vi.fn(() => { - store = {} - }), - removeItem: vi.fn((key: string) => { - delete store[key] - }), - get length() { - return Object.keys(store).length - }, - key: vi.fn((index: number): string | null => { - const keys = Object.keys(store) - return keys[index] || null - }), - __store: store // expose store for assertions - } -})() -Object.defineProperty(global, 'localStorage', { - value: localStorageMock, - writable: true -}) - -// Mocking animejs with a default export -vi.mock('animejs', () => ({ - default: { - // Ensuring the mock includes a 'default' export - timeline: vi.fn(() => ({ - add: vi.fn().mockReturnThis() - })) - } -})) - -describe('HomeView', () => { - let wrapper: any - - beforeEach(() => { - // Clear localStorage and reset all mocks - localStorage.clear() - vi.clearAllMocks() - wrapper = mount(HomeView, { - global: { - mocks: { - $router: { - push: vi.fn() - } - } - } - }) - }) - - it('renders correctly and initializes data', () => { - expect(wrapper.find('.no-scrollbar').exists()).toBe(true) - expect(wrapper.vm.challenges.length).toBeGreaterThan(0) - }) - - it('handles incrementSaved correctly', async () => { - const challenge: Challenge = { - createdOn: new Date(), - description: '', - title: 'Kaffe', - saved: 90, - target: 100, - completion: 90 - } - wrapper.vm.incrementSaved(challenge) - expect(challenge.saved).toBe(110) - expect(challenge.completion).toBe(100) - }) - - it('animates on challenge completion', async () => { - const challenge: Challenge = { - createdOn: new Date(), - description: '', - title: 'Mat og Drikke', - type: 'SNACKS', - saved: 100, - target: 100, - completion: 100 - } - wrapper.vm.animateChallenge(challenge) - await wrapper.vm.$nextTick() - expect(anime.timeline).toHaveBeenCalled() - }) - - it('persists animated challenges to localStorage', async () => { - const challenge = { - title: 'Gaming', - challengeType: 'GAMING', - saved: 100, - target: 100, - completion: 100 - } - await wrapper.vm.animateChallenge(challenge) - expect(localStorage.setItem).toHaveBeenCalled() - expect(localStorage.getItem('animatedChallenges')).toContain('Gaming') - }) - - it('correctly computes currentGoal based on goals', async () => { - const goal: Goal = { - id: 1, - due: new Date(), - createdOn: new Date(), - title: 'Vacation', - saved: 500, - target: 1500, - description: 'Summer vacation', - priority: 1, - completion: 33 - } - await wrapper.vm.$nextTick() - expect(goal.title).toBe('Vacation') - }) - - it('responds to changes in challenges and updates animation states', async () => { - wrapper.vm.challenges.push({ - title: 'New Challenge', - challengeType: 'COFFEE', - saved: 50, - target: 100, - completion: 50 - }) - await wrapper.vm.$nextTick() - wrapper.vm.challenges[wrapper.vm.challenges.length - 1].completion = 100 // Directly modify the data - await wrapper.vm.$nextTick() - expect(wrapper.vm.animatedChallenges.has('New Challenge')).toBe(true) - }) - - it('triggers animation on completion', async () => { - const challenge = { - title: 'Test Challenge', - challengeType: 'TEST', - saved: 100, - target: 100, - completion: 100 - } - await wrapper.vm.animateChallenge(challenge) - await wrapper.vm.$nextTick() // Wait for all nextTick callbacks to resolve - - expect(anime.timeline).toHaveBeenCalled() // Check if anime.timeline was called - }) - - // Test other methods like animateIcon, getChallengeIcon, etc. - it('returns correct icon path based on challenge type', () => { - const challenge: Challenge = { - createdOn: new Date(), - description: '', - saved: 0, - target: 0, - title: 'Coffee', - type: 'COFFEE' - } - expect(wrapper.vm.getChallengeIcon(challenge)).toBe('src/assets/coffee.png') - }) -}) diff --git a/src/views/FirstSavingGoalView.vue b/src/views/FirstSavingGoalView.vue index 8a3458a855bb674ce18b058e928cb9490f472d7c..bd28ba97b26defe4d241f903d973e542c9636717 100644 --- a/src/views/FirstSavingGoalView.vue +++ b/src/views/FirstSavingGoalView.vue @@ -41,7 +41,7 @@ <span class="shrink-0 text-xl font-bold">kr</span> </div> <div class="w-full px-4 py-2"> - <img src="@/assets/coins.png" alt="Savings" class="mx-auto w-36 h-32" /> + <img src="@/assets/penger.png" alt="Savings" class="mx-auto w-36 h-32" /> </div> <div class="flex justify-between w-full mt-4 space-x-2"> <button diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 1b40fb3992915e355411ed1d4a573036e174a5cb..db576db2a452c08836a651651fe1a4935bcb9abc 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -15,16 +15,19 @@ <div class="flex flex-col basis-2/3 max-h-full mx-auto max-w-5/6 md: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 drop-shadow-lg" + class="w-full max-w-60 max-h-12 text-black text-2xl font-bold py-2 rounded mt-8 text-center space-x-2 drop-shadow-lg" > Din Sparesti </span> </div> - <div class="h-1 w-4/6 bg-black mx-auto my-2 opacity-10"></div> + <div class="h-1 w-4/6 mx-auto my-2 opacity-10"></div> <div ref="containerRef" - class="container relative mx-auto pt-6 w-4/5 md:w-3/5 no-scrollbar h-full max-h-[60vh] md:max-h-[60v] overflow-y-auto border-2 border-black rounded-lg bg-white shadow-lg" + class="container relative mx-auto pt-6 w-4/5 md:w-3/5 no-scrollbar h-full max-h-[60vh] md:max-h-[60v] overflow-y-auto border-2 border-slate-300 rounded-lg bg-white shadow-lg" > + <div> + <img src="@/assets/start.png" alt="Spare" class="md:w-1/6 md:h-auto h-20" /> + </div> <div v-for="(challenge, index) in challenges" :key="challenge.title" @@ -43,13 +46,13 @@ v-if="index === 3" src="@/assets/sleepingSpare.gif" alt="could not load" - class="w-32 h-32 border-2 border-black" + class="w-32 h-32 border-2 rounded-lg border-stale-400" /> <img v-else-if="index === 1" src="@/assets/golfSpare.gif" alt="could not load" - class="w-32 h-32 border-2 border-black" + class="w-32 h-32 border-2 rounded-lg border-stale-400" /> </div> <!-- Challenge Icon and Details --> @@ -92,7 +95,7 @@ <button @click="incrementSaved(challenge)" type="button" - class="inline-block mb-2 ml-2 h-8 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" + 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> @@ -108,11 +111,11 @@ challenge.completion !== undefined && challenge.completion >= 100 " - class="max-w-16 max-h-16" + class="max-w-10 max-h-10" > <img src="@/assets/completed.png" alt="" />ï¸ </div> - <div v-else class="max-w-16 max-h-16"> + <div v-else class="max-w-6 max-h-6"> <img src="@/assets/pending.png" alt="" />ï¸ </div> </div> @@ -121,12 +124,12 @@ v-if="index === 0" src="@/assets/cowboySpare.gif" alt="could not load" - class="h-32 w-32 border-2 border-black" + class="h-32 w-32 border-2 rounded-lg border-stale-400" /> <img v-else-if="index === 2" src="@/assets/hotAirBalloonSpare.gif" - class="h-32 w-32 border-black border-2" + class="h-32 w-32 border-stale-400 border-2 rounded-lg" alt="could not load" /> </div> @@ -163,7 +166,7 @@ </div> <img src="@/assets/finishLine.png" - class="w-full max-h-4 mx-auto mt-10" + class="w-full max-h-auto mx-auto mt-4" alt="Finish Line" /> <!-- Sparemannen --> @@ -182,9 +185,12 @@ </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> + <h3 class="text-black text-base">Endre mÃ¥l</h3> </div> - <div ref="targetRef" class="bg-yellow-400 px-4 py-1 rounded-full text-black"> + <div + ref="targetRef" + class="bg-yellow-400 px-4 py-1 rounded-full text-black font-bold" + > {{ goal.saved }}kr / {{ goal.target }}kr </div> </div> @@ -192,8 +198,8 @@ </div> <!-- Animation icon --> <img - src="@/assets/coins.png" - alt="Coins" + src="@/assets/penger.png" + alt="Penger" ref="iconRef" class="max-w-20 max-h-20 absolute opacity-0" /> @@ -205,10 +211,11 @@ import { nextTick, onMounted, ref, watch } from 'vue' import anime from 'animejs' import InteractiveSpare from '@/components/InteractiveSpare.vue' -import ButtonAddGoalOrChallenge from '@/components/ButtonAddGoalOrChallange.vue' +import ButtonAddGoalOrChallenge from '@/components/ButtonAddGoalOrChallenge.vue' import router from '@/router' import type { Challenge } from '@/types/challenge' import type { Goal } from '@/types/goal' +import confetti from 'canvas-confetti' // Define your speech array const speechArray = [ @@ -248,13 +255,13 @@ const challenge: Challenge = { completedOn: undefined // Not yet completed } const challenge1: Challenge = { - title: 'Snacks', + title: 'Mat', saved: 200, target: 400, description: 'Saving monthly for a year-end vacation to Bali', createdOn: new Date('2023-01-01T00:00:00Z'), dueDate: new Date('2023-12-31T23:59:59Z'), - type: 'SNACKS', + type: 'MAT', completion: 50, completedOn: undefined // Not yet completed } @@ -314,11 +321,22 @@ const animateChallenge = (challenge: Challenge) => { !animatedChallenges.value.has(challenge.title) ) { console.log('Animating for:', challenge.title) - recalculateAndAnimate() // Assumes this function triggers the actual animation + + triggerConfetti() + + recalculateAndAnimate() saveAnimatedState(challenge.title) } } +function triggerConfetti() { + confetti({ + particleCount: 400, + spread: 80, + origin: { x: 0.8, y: 0.8 } + }) +} + watch( challenges, (newChallenges) => { @@ -412,7 +430,7 @@ function animateIcon() { // Helper methods to get icons function getChallengeIcon(challenge: Challenge): string { if (challenge.type === undefined) { - return 'src/assets/coins.png' + return 'src/assets/penger.png' } return `src/assets/${challenge.type.toLowerCase()}.png` } diff --git a/src/views/StartView.vue b/src/views/StartView.vue index 8646db14f5c3fc58b81d2144a766bb0b9397cfaf..3f9842c83b91204842f1cf825d8352cdd2ad6f56 100644 --- a/src/views/StartView.vue +++ b/src/views/StartView.vue @@ -8,9 +8,9 @@ /> </div> <div class="flex flex-col items-center pt-40 absolute top-0 left-0 right-0 z-10"> - <img src="@/assets/start_page/Spare.png" alt="Spare" class="md:w-1/6 w-1/3 h-auto" /> + <img src="@/assets/spare.png" alt="Spare" class="md:w-1/6 w-1/3 h-auto" /> <img - src="@/assets/sparesti2.png" + src="@/assets/spareSti.png" alt="Sparesti" class="md:w-5/12 w-10/12 h-auto md:mt-4 mt-20" />