diff --git a/src/views/ManageChallengeView.vue b/src/views/ManageChallengeView.vue index a60084093ad69cd9d586c5cc61fb759cc6610a50..8cb79dcc1df1ec94c8bf523cdf1efa8a84e876f2 100644 --- a/src/views/ManageChallengeView.vue +++ b/src/views/ManageChallengeView.vue @@ -7,6 +7,8 @@ import type { Challenge } from '@/types/challenge' import ModalComponent from '@/components/ModalComponent.vue' const router = useRouter() +const uploadedFile = ref<File | null>(null); + const modalTitle = ref('') const modalMessage = ref('') @@ -65,6 +67,15 @@ function validateInputs() { return errors } +const handleFileChange = (event: Event) => { + const target = event.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + uploadedFile.value = target.files[0]; + } else { + uploadedFile.value = null; + } +}; + const submitAction = async () => { const errors = validateInputs() if (errors.length > 0) { @@ -75,18 +86,33 @@ const submitAction = async () => { return } try { + let response; if (isEdit.value) { - updateChallenge() + response = await updateChallenge(); } else { - createChallenge() + response = await createChallenge(); + } + + const challengeId = isEdit.value ? challengeInstance.value.id : response.id; + + if (uploadedFile.value && challengeId) { + const formData = new FormData(); + formData.append('file', uploadedFile.value); + formData.append('id', challengeId.toString()); + + await authInterceptor.post('/challenges/picture', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); } + + await router.push({ name: 'challenges' }); } catch (error) { - console.error(error) - modalTitle.value = 'Systemfeil' - modalMessage.value = 'En feil oppstod under lagring av utfordringen.' - errorModalOpen.value = true + console.error('Error during challenge submission:', error); + modalTitle.value = 'Systemfeil'; + modalMessage.value = 'En feil oppstod under lagring av utfordringen.'; + errorModalOpen.value = true; } -} +}; onMounted(async () => { if (isEdit.value) { @@ -109,26 +135,14 @@ onMounted(async () => { } }) -const createChallenge = () => { - authInterceptor - .post('/challenges', challengeInstance.value, {}) - .then(() => { - return router.push({ name: 'challenges' }) - }) - .catch((error) => { - console.error(error) - }) +const createChallenge = async () => { + const response = await authInterceptor.post('/challenges', challengeInstance.value); + return response.data; } -const updateChallenge = () => { - authInterceptor - .put(`/challenges/${challengeInstance.value.id}`, challengeInstance.value) - .then(() => { - router.push({ name: 'challenges' }) - }) - .catch((error) => { - console.error(error) - }) +const updateChallenge = async () => { + const response = await authInterceptor.put(`/challenges/${challengeInstance.value.id}`, challengeInstance.value); + return response.data; } const cancelCreation = () => { @@ -151,6 +165,11 @@ const confirmCancel = () => { router.push({ name: 'challenges' }) confirmModalOpen.value = false } + +const removeUploadedFile = () => { + uploadedFile.value = null; +}; + </script> <template> @@ -222,13 +241,16 @@ const confirmCancel = () => { /> </div> - <div class="flex flex-col"> + <div class="flex flex-col items-center"> <p>Last opp ikon for utfordringen📸</p> - <button - class="mt-2 font-bold cursor-pointer transition-transform duration-300 ease-in-out hover:scale-110 hover:opacity-90" - > + <label for="fileUpload" class="bg-white text-black text-lg p-1 mt-2 rounded cursor-pointer leading-none"> 💾 - </button> + </label> + <input id="fileUpload" type="file" accept=".jpg" hidden @change="handleFileChange" /> + <div v-if="uploadedFile" class="flex justify-center items-center mt-2"> + <p class="text-sm">{{ uploadedFile.name }}</p> + <button @click="removeUploadedFile" class="ml-2 text-xs font-bold border-2 p-1 rounded text-red-500">Fjern fil</button> + </div> </div> </div> <div class="flex flex-row justify-between w-full"> diff --git a/src/views/ManageGoalView.vue b/src/views/ManageGoalView.vue index d2d8e75bc429e79a1f0aed0dc3f498f6960c541a..cc46ff49a64fbb61ca4be7c275d20cf7ecc90518 100644 --- a/src/views/ManageGoalView.vue +++ b/src/views/ManageGoalView.vue @@ -1,12 +1,14 @@ <script lang="ts" setup> import { useRouter } from 'vue-router' -import { computed, onMounted, ref, watch } from 'vue' +import { computed, onMounted, type Ref, ref, watch } from 'vue' import type { Goal } from '@/types/goal' import ProgressBar from '@/components/ProgressBar.vue' import authInterceptor from '@/services/authInterceptor' import ModalComponent from '@/components/ModalComponent.vue' const router = useRouter() +const uploadedFile: Ref<File | null> = ref(null); + const minDate = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().slice(0, 10) const selectedDate = ref<string>(minDate) @@ -62,28 +64,47 @@ function validateInputs() { return errors } + const submitAction = async () => { - const errors = validateInputs() + const errors = validateInputs(); if (errors.length > 0) { - const formatErrors = errors.join('\n') - modalTitle.value = 'Oops! Noe er feil med det du har fylt ut🚨' - modalMessage.value = formatErrors.replace(/\n/g, '<br>') - errorModalOpen.value = true - return + const formatErrors = errors.join('<br>'); + modalTitle.value = 'Oops! Noe er feil med det du har fylt ut🚨'; + modalMessage.value = formatErrors; + errorModalOpen.value = true; + return; } + try { + let response; + if (isEdit.value) { - updateGoal() + response = await updateGoal(); } else { - createGoal() + response = await createGoal(); + } + + const goalId = isEdit.value ? goalInstance.value.id : response.id; // Adjusted to handle the returned data + + if (uploadedFile.value && goalId) { + const formData = new FormData(); + formData.append('file', uploadedFile.value); + formData.append('id', goalId.toString()); + + await authInterceptor.post('/goals/picture', formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); } + + await router.push({ name: 'goals' }); } catch (error) { - console.error(error) - modalTitle.value = 'Systemfeil' - modalMessage.value = 'En feil oppstod under lagring av utfordringen.' - errorModalOpen.value = true + console.error('Error during goal submission:', error); + modalTitle.value = 'Systemfeil'; + modalMessage.value = 'En feil oppstod under lagring av utfordringen.'; + errorModalOpen.value = true; } -} +}; + watch(selectedDate, (newDate) => { console.log(newDate) @@ -108,27 +129,25 @@ onMounted(async () => { } }) -const createGoal = () => { - authInterceptor - .post('/goals', goalInstance.value, {}) - .then(() => { - return router.push({ name: 'goals' }) - }) - .catch((error) => { - console.error(error) - }) -} +const createGoal = async (): Promise<any> => { + try { + const response = await authInterceptor.post('/goals', goalInstance.value); + return response.data; // Ensure the response data is returned + } catch (error) { + console.error('Failed to create goal:', error); + throw error; // Rethrow the error to handle it in the submitAction method + } +}; -const updateGoal = () => { - authInterceptor - .put(`/goals/${goalInstance.value.id}`, goalInstance.value) - .then(() => { - router.back() - }) - .catch((error) => { - console.error(error) - }) -} +const updateGoal = async (): Promise<any> => { + try { + const response = await authInterceptor.put(`/goals/${goalInstance.value.id}`, goalInstance.value); + return response.data; // Ensure the response data is returned + } catch (error) { + console.error('Failed to update goal:', error); + throw error; // Rethrow the error to handle it in the submitAction method + } +}; const deleteGoal = () => { authInterceptor @@ -160,6 +179,36 @@ const confirmCancel = () => { router.push({ name: 'goals' }) confirmModalOpen.value = false } + +const handleFileChange = (event: Event) => { + const target = event.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + uploadedFile.value = target.files[0]; // Save the first selected file + } else { + uploadedFile.value = null; + } +}; + +const removeUploadedFile = () => { + uploadedFile.value = null; +}; + +onMounted(async () => { + if (isEdit.value) { + const goalId = router.currentRoute.value.params.id + if (!goalId) return router.push({ name: 'goals' }) + + await authInterceptor(`/goals/${goalId}`) + .then((response) => { + goalInstance.value = response.data + selectedDate.value = response.data.due.slice(0, 16) + }) + .catch((error) => { + console.error(error) + router.push({ name: 'goals' }) + }) + } +}) </script> <template> @@ -213,13 +262,16 @@ const confirmCancel = () => { type="date" /> </div> - <div class="flex flex-col"> + <div class="flex flex-col items-center"> <p>Last opp ikon for utfordringen📸</p> - <button - class="mt-2 font-bold cursor-pointer transition-transform duration-300 ease-in-out hover:scale-110 hover:opacity-90" - > + <label for="fileUpload" class="bg-white text-black text-lg p-1 mt-2 rounded cursor-pointer leading-none"> 💾 - </button> + </label> + <input id="fileUpload" type="file" accept=".jpg" hidden @change="handleFileChange" /> + <div v-if="uploadedFile" class="flex justify-center items-center mt-2"> + <p class="text-sm">{{ uploadedFile.name }}</p> + <button @click="removeUploadedFile" class="ml-2 text-xs font-bold border-2 p-1 rounded text-red-500">Fjern fil</button> + </div> </div> </div> diff --git a/src/views/ViewChallengeView.vue b/src/views/ViewChallengeView.vue index 924d5ae1ab49df8b8b912655fb49b449b835224f..9547641ece6bea2f8533a883956a9836fae31797 100644 --- a/src/views/ViewChallengeView.vue +++ b/src/views/ViewChallengeView.vue @@ -5,8 +5,11 @@ import ProgressBar from '@/components/ProgressBar.vue' import authInterceptor from '@/services/authInterceptor' import type { Challenge } from '@/types/challenge' import SpareComponent from '@/components/SpareComponent.vue' +import starImage from '@/assets/star.png'; const router = useRouter() +const challengeImageUrl = ref(starImage); +const isImageLoaded = ref(false); const challengeInstance = ref<Challenge>({ title: 'Tittel', @@ -53,17 +56,27 @@ const calculateSpeech = () => { } } -onMounted(() => { - const challengeId = router.currentRoute.value.params.id - if (!challengeId) return router.push({ name: 'challenges' }) +onMounted(async () => { + const challengeId = router.currentRoute.value.params.id; + if (!challengeId) return router.push({ name: 'challenges' }); - authInterceptor(`/challenges/${challengeId}`) - .then((response) => { - challengeInstance.value = response.data - calculateSpeech() - }) - .catch(() => router.push({ name: 'challenges' })) -}) + try { + const challengeResponse = await authInterceptor.get(`/challenges/${challengeId}`); + challengeInstance.value = challengeResponse.data; + calculateSpeech(); + + try { + const imageResponse = await authInterceptor.get(`/challenges/picture?id=${challengeId}`, { responseType: 'blob' }); + challengeImageUrl.value = URL.createObjectURL(imageResponse.data); + } catch (imageError) { + console.error("Failed to load image:", imageError); + } + isImageLoaded.value = true; + } catch (error) { + console.error("Failed to load challenge details:", error); + await router.push({ name: 'challenges' }); + } +}); const completeChallenge = () => { authInterceptor @@ -80,6 +93,9 @@ const completeChallenge = () => { <template> <div class="flex flex-row flex-wrap items-center justify-center gap-10"> <div class="flex flex-col gap-5 max-w-96"> + <div class="flex flex-col items-center"> + </div> + <button class="w-min bg-transparent rounded-lg font-bold left-10 cursor-pointer transition-transform duration-300 ease-in-out hover:scale-110 hover:opacity-100 justify-start" @click="router.push({ name: 'challenges', params: { id: challengeInstance.id } })" @@ -92,15 +108,22 @@ const completeChallenge = () => { > <h2 class="my-0">Spareutfordring:</h2> <h2 class="font-light"> - {{ challengeInstance.title + ' ' + challengeInstance.type }} + {{ challengeInstance.title }} </h2> + <div class="flex flex-row gap-4 justify-center"> + <p class="text-wrap break-words">{{ challengeInstance.description }}</p> + <div> + <img v-if="isImageLoaded" :src="challengeImageUrl || '@/assets/star.png'" alt="Goal Image" class="w-full h-40 object-cover rounded-lg"> + </div> + </div> + <br /> <p class="text-center"> Du har spart {{ timesSaved }} ganger som er {{ challengeInstance.saved }}kr av {{ challengeInstance.target }}kr </p> <ProgressBar :completion="completion" /> <br /> - <p class="text-wrap break-words">{{ challengeInstance.description }}</p> + <br /> <p> Du sparer {{ challengeInstance.perPurchase }}kr hver gang du dropper å bruke diff --git a/src/views/ViewGoalView.vue b/src/views/ViewGoalView.vue index 04f4b42d2cf34b1df7eba571c095f723e7740c90..523c8436e808880d79f2850e2a1c6202d0d30c06 100644 --- a/src/views/ViewGoalView.vue +++ b/src/views/ViewGoalView.vue @@ -5,8 +5,13 @@ import ProgressBar from '@/components/ProgressBar.vue' import authInterceptor from '@/services/authInterceptor' import type { Goal } from '@/types/goal' import SpareComponent from '@/components/SpareComponent.vue' +import starImage from '@/assets/star.png'; + const router = useRouter() +const goalImageUrl = ref(starImage); +const isImageLoaded = ref(false); + const goalInstance = ref<Goal>({ title: 'Test tittel', @@ -45,17 +50,28 @@ const calculateSpeech = () => { } } -onMounted(() => { - const goalId = router.currentRoute.value.params.id - if (!goalId) return router.push({ name: 'goals' }) +onMounted(async () => { + const goalId = router.currentRoute.value.params.id; + if (!goalId) return router.push({ name: 'goals' }); + + try { + const goalResponse = await authInterceptor.get(`/goals/${goalId}`); + goalInstance.value = goalResponse.data; + calculateSpeech(); + + try { + const imageResponse = await authInterceptor.get(`/goals/picture?id=${goalId}`, { responseType: 'blob' }); + goalImageUrl.value = URL.createObjectURL(imageResponse.data); + } catch (imageError) { + console.error("Failed to load image:", imageError); + } + isImageLoaded.value = true; + } catch (error) { + console.error("Failed to load goal details:", error); + await router.push({ name: 'goals' }); + } +}); - authInterceptor(`/goals/${goalId}`) - .then((response) => { - goalInstance.value = response.data - calculateSpeech() - }) - .catch(() => router.push({ name: 'goals' })) -}) const completeGoal = () => { authInterceptor @@ -89,11 +105,7 @@ const completeGoal = () => { <div class="flex flex-row gap-4 justify-center"> <p class="text-wrap break-words">{{ goalInstance.description }}</p> <div> - <img - class="w-20 h-20" - src="https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png" - alt="Profilbilde" - /> + <img v-if="isImageLoaded" :src="goalImageUrl || '@/assets/star.png'" alt="Goal Image" class="w-full h-40 object-cover rounded-lg"> </div> </div> <br />