diff --git a/src/views/ManageChallengeView.vue b/src/views/ManageChallengeView.vue index 7cf90c7a0afbce65fd3c47a48a693370bbc2af48..20fcdfd86ac8663a43ac07040729108f8e3c2921 100644 --- a/src/views/ManageChallengeView.vue +++ b/src/views/ManageChallengeView.vue @@ -4,113 +4,90 @@ import { computed, onMounted, ref, watch } from 'vue' import ProgressBar from '@/components/ProgressBar.vue' import authInterceptor from '@/services/authInterceptor' import type { Challenge } from '@/types/challenge' +import ModalComponent from '@/components/ModalComponent.vue' const router = useRouter() +const modalTitle = ref(''); +const modalMessage = ref(''); +const confirmModalOpen = ref(false); +const errorModalOpen = ref(false); + const oneWeekFromNow = new Date() oneWeekFromNow.setDate(oneWeekFromNow.getDate() + 7) -const minDate = oneWeekFromNow.toISOString().slice(0, 16) +const minDate = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().slice(0, 10) +const selectedDate = ref<string>(minDate) + const thirtyDaysFromNow = new Date() thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30) -const maxDate = thirtyDaysFromNow.toISOString().slice(0, 16) +const maxDate = thirtyDaysFromNow.toISOString().slice(0, 10) + const challengeInstance = ref<Challenge>({ title: '', - perPurchase: 20, + perPurchase: 0, saved: 0, - target: 100, + target: 0, description: '', - due: minDate + ':00.000Z', - type: '' + due: '' }) -const isAmountSaved = ref(false) -const timesSaved = ref(challengeInstance.value.saved / challengeInstance.value.perPurchase) - -watch( - () => timesSaved.value, - (newVal) => { - challengeInstance.value.saved = newVal * challengeInstance.value.perPurchase - challengeInstance.value.saved = parseFloat(challengeInstance.value.saved.toFixed(2)) - } -) - -watch( - () => challengeInstance.value.saved, - (newVal) => { - challengeInstance.value.saved = Math.max( - 0, - Math.min(challengeInstance.value.target, newVal) - ) - challengeInstance.value.saved = parseFloat(challengeInstance.value.saved.toFixed(2)) - timesSaved.value = challengeInstance.value.saved / challengeInstance.value.perPurchase - timesSaved.value = parseFloat(timesSaved.value.toFixed(2)) - } -) - -watch( - () => challengeInstance.value.perPurchase, - (newVal) => { - challengeInstance.value.perPurchase = Math.max( - 1, - Math.min(challengeInstance.value.target, newVal) - ) - challengeInstance.value.perPurchase = parseFloat( - challengeInstance.value.perPurchase.toFixed(2) - ) - timesSaved.value = challengeInstance.value.saved / challengeInstance.value.perPurchase - timesSaved.value = parseFloat(timesSaved.value.toFixed(2)) - } -) - -watch( - () => challengeInstance.value.target, - (newVal) => { - challengeInstance.value.target = Math.max( - Math.max(challengeInstance.value.saved, 1), - newVal - ) - } -) -const selectedDate = ref(minDate) -watch( - () => selectedDate.value, - (newVal) => { - if (newVal) { - selectedDate.value = newVal < minDate ? minDate : newVal - challengeInstance.value.due = selectedDate.value + ':00.000Z' - } - } -) +watch(selectedDate, (newDate) => { + challengeInstance.value.due = newDate; +}); const isEdit = computed(() => router.currentRoute.value.name === 'edit-challenge') -const pageTitle = computed(() => (isEdit.value ? 'Rediger utfordring' : 'Ny utfordring')) +const pageTitle = computed(() => (isEdit.value ? 'Rediger utfordring🎨' : 'Ny utfordring🎨')) const submitButton = computed(() => (isEdit.value ? 'Oppdater' : 'Opprett')) const completion = computed( () => (challengeInstance.value.saved / challengeInstance.value.target) * 100 ) -const isInputValid = computed(() => { - return ( - challengeInstance.value.title.length > 0 && - challengeInstance.value.title.length <= 20 && - challengeInstance.value.description.length <= 280 && - challengeInstance.value.target > 0 && - challengeInstance.value.due !== '' - ) -}) +function validateInputs() { + const errors = []; + + challengeInstance.value.due = selectedDate.value + 'T23:59:59.999Z'; -const submitAction = () => { - if (!isInputValid.value) { - return () => alert('Fyll ut alle feltene') + if (!challengeInstance.value.title || challengeInstance.value.title.length > 20) { + errors.push("Tittelen mÃ¥ være mellom 1 og 20 tegn."); + } + if (challengeInstance.value.description.length > 280) { + errors.push("Beskrivelsen mÃ¥ være under 280 tegn."); + } + if (challengeInstance.value.target <= 0) { + errors.push("MÃ¥lbeløpet mÃ¥ være større enn 0."); + } + if (new Date(challengeInstance.value.due) < new Date(minDate)) { + errors.push("Forfallsdatoen mÃ¥ være minst en uke frem i tid."); + } + if (challengeInstance.value.perPurchase <= 0) { + errors.push("Pris per sparing mÃ¥ være større enn 0."); } + return errors; +} - if (isEdit.value) { - updateChallenge() - } else { - createChallenge() +const submitAction = async() => { + 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; + errorModalOpen.value = true; + return; + } + try { + if (isEdit.value) { + updateChallenge(); + } else { + createChallenge(); + } + } catch (error) { + console.error(error); + modalTitle.value = 'Systemfeil'; + modalMessage.value = 'En feil oppstod under lagring av utfordringen.'; + errorModalOpen.value = true; } } @@ -156,6 +133,27 @@ const updateChallenge = () => { console.error(error) }) } + +const cancelCreation = () => { + if (challengeInstance.value.title !== '' || + challengeInstance.value.description !== '' || + challengeInstance.value.perPurchase !== 0 || + challengeInstance.value.saved !== 0 || + challengeInstance.value.target !== 0) + { + modalTitle.value = 'Du er i ferd med Ã¥ avbryte redigeringen🚨'; + modalMessage.value = 'Er du sikker pÃ¥ at du vil avbryte?'; + confirmModalOpen.value = true; + } else { + router.push({ name: 'challenges' }) + } +} + +const confirmCancel = () => { + router.push({ name: 'challenges' }) + confirmModalOpen.value = false; +} + </script> <template> @@ -172,12 +170,7 @@ const updateChallenge = () => { </div> <div class="flex flex-col"> - <p class="mx-4">Type</p> - <input v-model="challengeInstance.type" placeholder="Skriv en type" type="text" /> - </div> - - <div class="flex flex-col"> - <p class="mx-4">Beskrivelse</p> + <p class="mx-4">Beskrivelse (valgfri)</p> <textarea v-model="challengeInstance.description" class="w-80 h-20 no-rezise" @@ -187,7 +180,7 @@ const updateChallenge = () => { <div class="flex flex-col sm:flex-row gap-3"> <div class="flex flex-col"> - <p class="mx-4">Pris per sparing</p> + <p class="mx-4">Spare per gang</p> <input v-model="challengeInstance.perPurchase" class="w-40 text-right" @@ -198,30 +191,18 @@ const updateChallenge = () => { <div class="flex flex-col"> <div class="flex flex-row justify-between mx-4"> - <p>{{ isAmountSaved ? 'Kroner spart' : 'Antall sparinger' }}</p> - <button class="p-0 bg-transparent" @click="isAmountSaved = !isAmountSaved"> - ðŸ”„ï¸ - </button> + <p>Kroner spart💸</p> </div> <input - v-if="isAmountSaved" v-model="challengeInstance.saved" class="w-40 text-right" - min="0" placeholder="Sparebeløp" type="number" /> - <input - v-else - v-model="timesSaved" - class="w-40 text-right" - placeholder="Kr spart per sparing" - type="number" - /> </div> <div class="flex flex-col"> - <p class="mx-4">Av mÃ¥lbeløp...*</p> + <p class="mx-4">Av mÃ¥lbeløp💯*</p> <input v-model="challengeInstance.target" class="w-40 text-right" @@ -232,26 +213,81 @@ const updateChallenge = () => { </div> <ProgressBar :completion="completion" /> - <div class="flex flex-col"> - <p class="mx-4">Forfallsdato*</p> - <input - v-model="selectedDate" - :max="maxDate" - :min="minDate" - placeholder="Forfallsdato" - type="datetime-local" - /> + <div class="flex flex-row gap-4"> + <div class="flex flex-col"> + <p class="mx-4">Forfallsdato*</p> + <input + :min="minDate" + :max="maxDate" + v-model="selectedDate" + placeholder="Forfallsdato" + type="date" + /> + </div> + + <div class="flex flex-col"> + <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">💾</button> + </div> </div> - <div class="flex flex-row justify-between w-full"> - <button :disabled="!isInputValid" @click="submitAction" v-text="submitButton" /> <button - class="bg-button-other" - @click="router.push({ name: 'challenges' })" + class="primary danger" + @click="cancelCreation" v-text="'Avbryt'" /> + + <button + class="primary" + @click="submitAction" + v-text="submitButton" /> </div> + <ModalComponent + :title="modalTitle" + :message="modalMessage" + :isModalOpen="errorModalOpen" + @close="errorModalOpen = false" + > + <template v-slot:input> + <div class="flex justify-center items-center"> + <div class="flex flex-col gap-5"> + <button + class="primary" + @click="errorModalOpen = false" + > + Lukk + </button> + </div> + </div> + </template> + </ModalComponent> + + <ModalComponent + :title="modalTitle" + :message="modalMessage" + :isModalOpen="confirmModalOpen" + @close="confirmModalOpen = false" + > + <template v-slot:input> + <div class="flex justify-center items-center"> + <div class="flex flex-col gap-5"> + <button + class="primary" + @click="confirmCancel" + > + Bekreft + </button> + <button + class="primary danger" + @click="confirmModalOpen = false" + > + Avbryt + </button> + </div> + </div> + </template> + </ModalComponent> </div> </div> </template> diff --git a/src/views/UserChallengesView.vue b/src/views/UserChallengesView.vue index fa5f8047a1a797065cc638881842ac1d6fecae23..dbc1e82c8c2fcf54f6aa1cd7d80e951fd9eb7a68 100644 --- a/src/views/UserChallengesView.vue +++ b/src/views/UserChallengesView.vue @@ -50,18 +50,23 @@ onMounted(async () => { <h1 class="font-bold text-center">Dine utfordringer</h1> <div class="flex flex-col gap-5 items-center"> <div class="flex flex-row gap-5"> - <button @click="router.push({ name: 'new-challenge' })"> + <button + class="primary" + @click="router.push({ name: 'new-challenge' })"> Opprett en ny utfordring </button> </div> - <h2 class="font-bold">Aktive utfordringer</h2> + <h2 class="font-bold">Aktive utfordringer🚀</h2> <div class="flex flex-row justify-center gap-10 flex-wrap"> <CardChallenge v-for="challenge in activeChallenges" :key="challenge.id" :challenge-instance="challenge" /> + <p v-if="!activeChallenges"> + Du har ingen aktive spareutfordringer😢 + </p> </div> <PageControl :currentPage="currentPageActive" @@ -69,9 +74,10 @@ onMounted(async () => { :totalPages="totalPagesActive" /> - <h2 class="font-bold">Fullførte utfordringer</h2> + <h2 class="font-bold">Fullførte utfordringer💯</h2> <div class="flex flex-row justify-center gap-10 flex-wrap"> <CardChallenge + class="border-2 border-slate-200 hover:bg-slate-50" v-for="challenge in completedChallenges" :key="challenge.id" :challenge-instance="challenge" @@ -85,4 +91,5 @@ onMounted(async () => { </div> </template> -<style scoped></style> +<style scoped> +</style> \ No newline at end of file diff --git a/src/views/ViewChallengeView.vue b/src/views/ViewChallengeView.vue index f7bf05e69a55a93bbb37f10f56a5b9a6f9f7370a..d1b1e0d9603efe20bb3f2600a72b7d26151672a8 100644 --- a/src/views/ViewChallengeView.vue +++ b/src/views/ViewChallengeView.vue @@ -81,14 +81,14 @@ const completeChallenge = () => { <div class="flex flex-row flex-wrap items-center justify-center gap-10"> <div class="flex flex-col gap-5 max-w-96"> <button - class="w-min" + 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 } })" > - Oversikt + 👈Oversikt </button> <div - class="flex flex-col justify-center border-4 border-black rounded-3xl align-middle p-5 card-shadow overflow-hidden w-full" + class="flex flex-col justify-center border-2 rounded-3xl align-middle p-5 card-shadow overflow-hidden w-full" > <h2 class="my-0">Spareutfordring:</h2> <h2 class="font-light"> @@ -106,10 +106,25 @@ const completeChallenge = () => { Du sparer {{ challengeInstance.perPurchase }}kr hver gang du dropper Ã¥ bruke penger pÃ¥ {{ challengeInstance.type }} </p> + <div class="justify-center pl-20"> + <button + class="primary danger mt-2 rounded-2xl p-2 w-40" + @click=" + authInterceptor + .delete(`/challenges/${challengeInstance.id}`) + .then(() => router.push({ name: 'challenges' })) + .catch((error) => console.error(error)) + " + > + Slett + </button> + + </div> </div> <div class="flex flex-row justify-between w-full"> <button + class="primary secondary" v-if="!isCompleted" @click=" router.push({ @@ -122,22 +137,11 @@ const completeChallenge = () => { </button> <button + class="primary" v-if="!isCompleted" @click="completeChallenge" v-text="'Sett utfordring til ferdig'" /> - - <button - class="bg-button-danger hover:bg-button-danger" - @click=" - authInterceptor - .delete(`/challenges/${challengeInstance.id}`) - .then(() => router.push({ name: 'challenges' })) - .catch((error) => console.error(error)) - " - > - Slett - </button> </div> </div> <InteractiveSpare :png-size="10" :speech="motivation" direction="left" />