diff --git a/src/components/ButtonAddGoalOrChallenge.vue b/src/components/ButtonAddGoalOrChallenge.vue index 3516e5c8e1f65d73e58c032fa5c0cd5209dc6404..315df32fde52a4eb46597be56b1a0c2e6b6ba4cd 100644 --- a/src/components/ButtonAddGoalOrChallenge.vue +++ b/src/components/ButtonAddGoalOrChallenge.vue @@ -25,20 +25,25 @@ import { defineProps, ref } from 'vue' import { useRouter } from 'vue-router' -interface Props { - buttonText: string - type: 'goal' | 'challenge' -} +const props = defineProps({ + buttonText: String, + type: String, + showModal: Boolean +}) + +const emit = defineEmits(['update:showModal']) + const router = useRouter() -const props = defineProps<Props>() const btnText = ref(props.buttonText) const routeToGoalOrChallenge = () => { if (props.type === 'goal') { router.push('/sparemaal') - } else { + } else if (props.type === 'challenge') { router.push('/spareutfordringer') + } else if (props.type === 'generatedChallenge') { + emit('update:showModal', true) } } </script> diff --git a/src/components/GeneratedChallengesModal.vue b/src/components/GeneratedChallengesModal.vue index ddfbcbede76d06fffb0c0f4f2eff905dac0bed51..2eaf12b226f8b548fd9a622821bf8d9497f04d62 100644 --- a/src/components/GeneratedChallengesModal.vue +++ b/src/components/GeneratedChallengesModal.vue @@ -1,13 +1,10 @@ <template> <div - v-if="generatedChallenges.length > 0" - class="fixed inset-0 bg-gray-300 bg-opacity-75 flex justify-center items-center" + v-if="showModal" + class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50" > - <div class="relative bg-white pt-10 p-6 rounded-lg shadow-xl" style="width: 40rem"> - <button - @click="closeModal" - class="absolute top-0 right-0 m-2 text-gray-600 hover:text-gray-800" - > + <div class="relative bg-white pt-10 p-4 rounded-lg shadow-xl" style="width: 40rem"> + <button @click="closeModal" class="absolute top-0 right-0 m-2 text-white"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" @@ -23,112 +20,133 @@ /> </svg> </button> - <div class="text-center font-bold text-3xl mb-4 mt-2"> - Personlig tilpassede spareutfordringer: - </div> - <div class="grid grid-cols-7 gap-4 p-3 border-b-2"> - <span class="font-bold col-span-2">Tittel</span> - <span class="font-bold col-span-1">Målsum</span> - <span class="font-bold col-span-2">Frist</span> - <span class="col-span-2"></span> - </div> - <div class="space-y-2"> - <div - v-for="(challenge, index) in generatedChallenges" - :key="challenge.id" - :class="{ 'bg-gray-100': index % 2 === 0 }" - class="grid grid-cols-7 gap-4 items-center border p-3 rounded" - > - <span class="break-words col-span-2 font-bold">{{ challenge.title }}</span> - <span class="col-span-1 font-bold">{{ challenge.target }}</span> - <span class="col-span-2 font-bold">{{ challenge.due }}</span> - <div class="flex items-center justify-end space-x-2 col-span-2"> - <button - @click="declineChallenge(challenge.id)" - class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-1 px-4" - > - Skip - </button> - <button - @click="acceptChallenge(challenge.id)" - class="text-white font-bold py-1 px-4" + <div v-if="generatedChallenges.length > 0"> + <div class="text-center font-bold text-3xl mb-4 mt-2"> + Personlig tilpassede spareutfordringer: + </div> + <div class="grid grid-cols-7 sm:grid-cols-11 gap-2 p-3 pb-1 border-b-2"> + <span class="font-bold col-span-2 md:col-span-3 sm:text-lg pt-1 mb-0" + >Tittel</span + > + <span class="font-bold col-span-2 md:col-span-2 sm:text-lg pt-1 mb-0" + >Målsum</span + > + <span + class="font-bold col-span-2 md:col-span-1 sm:text-lg pt-1 pr-1 md:pr-3 mb-0" + >Frist</span + > + <span class="col-span-2"></span> + </div> + <div class="space-y-2"> + <div + v-for="(challenge, index) in generatedChallenges" + :key="index" + :class="{ 'bg-gray-100': index % 2 === 0 }" + class="grid grid-cols-7 md:grid-cols-7 sm:grid-cols-2 lg:grid-cols-7 gap-4 items-center border p-3 rounded mt-[-8px]" + > + <span class="break-words col-span-2 md:col-span-1 lg:col-span-2 text-lg">{{ + challenge.title + }}</span> + <span class="col-span-2 md:col-span-2 lg:col-span-1 text-lg">{{ + challenge.target + }}</span> + <span class="col-span-2 md:col-span-1 lg:col-span-2 text-lg">{{ + challenge.due + }}</span> + <div + class="col-span-7 sm:col-start-3 sm:col-span-2 md:col-span-2 lg:col-span-2 flex items-center justify-end space-x-2" > - Godta - </button> + <span v-if="challenge.isAccepted" class="font-bold text-lg" + >Godtatt!</span + > + <button + @click="acceptChallenge(challenge)" + class="text-white font-bold py-1 px-4 mt-[-14px] sm:mt-0" + > + Godta + </button> + </div> </div> </div> </div> + <div v-else class="text-center text-2xl font-bold mt-1"> + Ingen nye spareutfordringer enda ... sjekk igjen senere! + </div> </div> </div> </template> -<script setup> -import { onMounted, ref } from 'vue' +<script setup lang="ts"> +import { ref, reactive, onMounted } from 'vue' import authInterceptor from '@/services/authInterceptor' -import { useChallengeStore } from '@/stores/challengeStore' +import type { AxiosResponse } from 'axios' + +interface Challenge { + title: string + target: number + due: string + dueFull: string + isAccepted: boolean + perPurchase?: number + description?: string + type?: string +} -const generatedChallenges = ref([]) +const showModal = ref(true) +const generatedChallenges = reactive<Challenge[]>([]) -const fetchGeneratedChallenges = async () => { +async function fetchGeneratedChallenges() { try { - const response = await authInterceptor.get('/challenges/active') - if (response.status === 200 && response.data.content) { - console.log('Active challenges:', response.data.content) - generatedChallenges.value = response.data.content.map((challenge) => ({ - id: challenge.id, - title: challenge.title, - target: challenge.target.toString(), - due: challenge.due.substring(0, 10) - })) + const response: AxiosResponse = await authInterceptor.get('/challenges/generate') + if (response.status === 200) { + generatedChallenges.splice( + 0, + generatedChallenges.length, + ...response.data.map((ch: any) => ({ + ...ch, + due: new Date(ch.due).toISOString().split('T')[0], + dueFull: ch.due, + isAccepted: false + })) + ) } else { - console.error('No challenges found for the user.') - generatedChallenges.value = [] + generatedChallenges.splice(0, generatedChallenges.length) } } catch (error) { console.error('Error fetching challenges:', error) - generatedChallenges.value = [] } } onMounted(() => { fetchGeneratedChallenges() + localStorage.setItem('lastModalShow', Date.now().toString()) }) -const removeChallenge = (id) => { - const index = generatedChallenges.value.findIndex((challenge) => challenge.id === id) - if (index !== -1) { - generatedChallenges.value.splice(index, 1) - generatedChallenges.value = [...generatedChallenges.value] - } - if (generatedChallenges.value.length === 0) { - closeModal() +function acceptChallenge(challenge: Challenge) { + if (!challenge) { + console.error('No challenge data provided to acceptChallenge function.') + return } -} - -function acceptChallenge(id) { - console.log('Accepted challenge:', id) - const acceptedChallenge = generatedChallenges.value.find((challenge) => challenge.id === id) - if (acceptedChallenge) { - useChallengeStore.editUserChallenge(acceptedChallenge) - removeChallenge(id) - } -} - -const declineChallenge = async (id) => { - try { - const response = authInterceptor.delete(`/challenges/${id}`) - if (response.status === 200) { - console.log('Challenge declined and removed:', id) - removeChallenge(id) - } else { - console.error('Failed to decline challenge:', response.data) - } - } catch (error) { - console.error('Error declining challenge:', error) + const postData = { + title: challenge.title, + saved: 0, + target: challenge.target, + perPurchase: challenge.perPurchase, + description: challenge.description, + due: challenge.dueFull, + type: challenge.type } + authInterceptor + .post('/challenges', postData) + .then((response: AxiosResponse) => { + challenge.isAccepted = true + }) + .catch((error) => { + console.error('Failed to save challenge:', error) + }) } const closeModal = () => { - generatedChallenges.value = [] + showModal.value = false } </script> diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 91d7176dd15c188a8abf0e9be59c2cc572cc6171..26eb48670e60508f68110c3d776d1fdcf6d255e8 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -22,6 +22,13 @@ :buttonText="'Legg til spareutfordring'" :type="'challenge'" /> + <ButtonAddGoalOrChallenge + :buttonText="'Generer spareutfordring'" + :type="'generatedChallenge'" + :showModal="showModal" + @click="showModal = true" + @update:showModal="showModal = $event" + /> </div> </div> <savings-path :challenges="challenges" :goal="goal"></savings-path> @@ -38,6 +45,7 @@ <img alt="Hjelp" class="w-1/12" src="@/assets/hjelp.png" /> </div> </div> + <GeneratedChallengesModal v-show="showModal" @update:showModal="showModal = $event" /> </template> <script setup lang="ts"> @@ -50,8 +58,9 @@ import { useGoalStore } from '@/stores/goalStore' import { useChallengeStore } from '@/stores/challengeStore' import SavingsPath from '@/components/SavingsPath.vue' import router from '@/router' +import GeneratedChallengesModal from '@/components/GeneratedChallengesModal.vue' -const showModal = ref(true) +const showModal = ref(false) const goalStore = useGoalStore() const challengeStore = useChallengeStore() @@ -70,10 +79,15 @@ onMounted(async () => { challenges.value = challengeStore.challenges goals.value = goalStore.goals goal.value = goals.value[0] + console.log('Goals:', goals.value) + + const lastModalShow = localStorage.getItem('lastModalShow') + if (!lastModalShow || Date.now() - Number(lastModalShow) >= 24 * 60 * 60 * 1000) { + showModal.value = true + } firstLoggedInSpeech() }) -// Check if the user is logging in for the first time, and display the first login speech const firstLoggedInSpeech = () => { const isFirstLogin = router.currentRoute.value.query.firstLogin === 'true' if (isFirstLogin) { @@ -87,6 +101,13 @@ const firstLoggedInSpeech = () => { } } +// Define your speech array +const speechArray = [ + 'Hei! Jeg er Sparemannen.', + 'Jeg hjelper deg med å spare penger.', + 'Klikk på meg for å høre mer.' +] + const openInteractiveSpare = () => { // Check if there's new speech available before opening the modal. if (newSpeechAvailable.value) {