diff --git a/src/components/CardChallengeSavingsPath.vue b/src/components/CardChallengeSavingsPath.vue index 77136449eca34ab9335be9c2dd85359eb330448b..3acd26ccedfea9407170c929ca5f8e7733adae52 100644 --- a/src/components/CardChallengeSavingsPath.vue +++ b/src/components/CardChallengeSavingsPath.vue @@ -33,7 +33,7 @@ @click="editChallenge(challenge)" :data-cy="'challenge-icon-' + challenge.id" :src="challengeImageUrl" - class="max-w-12 max-h-12 md:max-h-8 md:max-w-8 lg:max-w-10 lg:max-h-10 cursor-pointer hover:scale-125 rounded-sm" + class="max-w-8 max-h-12 md:max-h-8 md:max-w-8 lg:max-w-10 lg:max-h-10 cursor-pointer hover:scale-125 rounded-sm" :alt="challenge.title" /> <!-- Progress Bar, if the challenge is not complete --> @@ -55,16 +55,15 @@ <div class="text-center text-nowrap text-xs md:text-base"> {{ challenge.saved }}kr / {{ challenge.target }}kr </div> + <button + @click="incrementSaved(challenge)" + :data-cy="'increment-challenge' + challenge.id" + type="button" + class="primary text-xs ml-2 z-10 relative" + > + + {{ challenge.perPurchase }}kr på {{ challenge.title }} + </button> </div> - - <button - @click="incrementSaved(challenge)" - :data-cy="'increment-challenge' + challenge.id" - type="button" - class="primary text-xl inline-block mb-2 ml-2 h-7 w-8 rounded-full uppercase leading-normal transition duration-150 ease-in-out focus:bg-lime-400 focus:shadow-green-2 focus:outline-none focus:ring-0 active:bg-lime-400 active:shadow-green-200 motion-reduce:transition-none" - > - + - </button> </div> </div> <span v-else class="text-center text-xs md:text-base" @@ -112,7 +111,6 @@ const getChallengeIcon = async (challengeId: number) => { }) challengeImageUrl.value = URL.createObjectURL(imageResponse.data) } catch (error) { - console.error('Failed to load challenge icon:', error) challengeImageUrl.value = '/src/assets/star.png' // Fallback on error } } diff --git a/src/components/FormRegister.vue b/src/components/FormRegister.vue index 2e6a264405df28bc2989c29d04ad6251fd6ad562..9ec933ae7a7969273c9f06545ddc7de1ff5b4c83 100644 --- a/src/components/FormRegister.vue +++ b/src/components/FormRegister.vue @@ -12,6 +12,8 @@ const confirm = ref<string>('') const showPassword = ref<boolean>(false) const errorMessage = ref<string>('') +const passwordValidations = ref<string[]>([]); + const userStore = useUserStore() @@ -42,6 +44,39 @@ const toggleShowPassword = () => { showPassword.value = !showPassword.value } +const validatePassword = () => { + const messages = []; + const lengthValid = password.value.length >= 8 && password.value.length <= 30; + const numberValid = /[0-9]/.test(password.value); + const lowercaseValid = /[a-zæøå]/.test(password.value); + const uppercaseValid = /[ÆØÅA-Z]/.test(password.value); + const specialCharacterValid = /[@#$%^&+=!]/.test(password.value); + const noSpacesValid = !/\s/.test(password.value); + + if (!lengthValid) { + messages.push('Må være mellom 8 og 30 karakterer. '); + } + if (!numberValid) { + messages.push('Må inneholde minst ett tall. '); + } + if (!lowercaseValid) { + messages.push('Må inneholde minst én liten bokstav. '); + } + if (!uppercaseValid) { + messages.push('Må inneholde minst én stor bokstav. '); + } + if (!specialCharacterValid) { + messages.push('Må inneholde minst ett spesialtegn (@#$%^&+=!). '); + } + if (!noSpacesValid) { + messages.push('Må ikke inneholde mellomrom. '); + } + + passwordValidations.value = messages; +}; + +watch(password, validatePassword); + watch( () => userStore.errorMessage, (newValue: string) => { @@ -56,7 +91,7 @@ watch( <div class="flex flex-row justify-between mx-4"> <p>Fornavn*</p> <ToolTip - :message="'Must include only letters, spaces, commas, apostrophes, periods, and hyphens. 1-30 characters long'" + :message="'Må kun inneholde bokstaver, mellomrom, komma, apostrof, punktum, og bindestrek. 1-30 karakterer langt'" /> </div> <input @@ -71,7 +106,7 @@ watch( <div class="flex flex-row justify-between mx-4"> <p>Etternavn*</p> <ToolTip - :message="'Must include only letters, spaces, commas, apostrophes, periods, and hyphens. 1-30 characters long'" + :message="'Må kun inneholde bokstaver, mellomrom, komma, apostrof, punktum, og bindestrek. 1-30 karakterer langt'" /> </div> <input @@ -86,7 +121,7 @@ watch( <div class="flex flex-row justify-between mx-4"> <p>E-post*</p> <ToolTip - :message="'Valid email: Starts with Norwegian letters, numbers, or special characters. Includes \@\ followed by a domain. Ends with 2-7 letters.'" + :message="'Gyldig email: Må starte med norske bokstaver, tall, eller spesielle karakterer. Inkluderer \@\ fulgt av et domene. Ender med 2-7 bokstaver.'" /> </div> <input @@ -101,7 +136,7 @@ watch( <div class="flex flex-row justify-between mx-4"> <p>Brukernavn*</p> <ToolTip - :message="'Must start with a letter and can include numbers and underscores. 3-30 characters long.'" + :message="'Må starte med en bokstav og kan inneholde tall og understrek. 3-30 karakterer langt.'" /> </div> <input @@ -116,7 +151,7 @@ watch( <div class="flex flex-row justify-between mx-4"> <p>Passord*</p> <ToolTip - :message="'Must be at least 8 characters, including at least one number, one lowercase letter, one uppercase letter, one special character (@#$%^&+=!), and no spaces.'" + :message="'Må være minst 8 karakterer, inkludert et tall, en liten bokstav, en stor bokstav, et spesialtegn (@#$%^&+=!), og ingen mellomrom.'" /> </div> <div class="relative"> @@ -145,6 +180,11 @@ watch( placeholder="Bekreft passord" type="password" /> + <div class="ml-4"> + <p class="text-sm"> + <span v-for="message in passwordValidations" :key="message">{{ message }}</span> + </p> + </div> </div> <div class="flex flex-row gap-5"> <button @@ -155,7 +195,6 @@ watch( > Registrer deg </button> - <p>{{ errorMessage }}</p> </div> </div> </template> diff --git a/src/components/SavingsPath.vue b/src/components/SavingsPath.vue index 4b7c8b2463ace5c2f97402a0de3fbe0bdf593f76..a783b2496c775cde9274633fff638b39c3d069f5 100644 --- a/src/components/SavingsPath.vue +++ b/src/components/SavingsPath.vue @@ -41,7 +41,7 @@ 'justify-center mx-auto md:justify-between': index % 2 === 1, 'justify-center md:justify-between mx-auto': index % 2 === 0 }" - class="flex flex-row w-full md:w-4/5 justify-start gap-4 md:gap-8" + class="flex flex-row w-full md:w-4/5 justify-start gap-4 md:gap-8 h-auto" > <div class="flex"> <img-gif-template @@ -97,14 +97,16 @@ v-if="index === challengesLocal.length - 1 && index % 2 === 0" class="flex flex-row mt-2" > - <button class="text-2xl ml-48" @click="addSpareUtfordring">+</button> + <button class="text-2xl ml-48 mr-2 primary" @click="addSpareUtfordring"> + + + </button> <p class="">Legg til <br />Spareutfordring</p> </div> <div v-else-if="index === challengesLocal.length - 1 && index % 2 !== 0" class="mr-20 flex flex-row" > - <button class="text-2xl ml-10 rounded-full" @click="addSpareUtfordring"> + <button class="text-2xl ml-10 rounded-full primary" @click="addSpareUtfordring"> + </button> <p class="pl-2">Legg til <br />Spareutfordring</p> diff --git a/src/types/profile.ts b/src/types/profile.ts index aeac7b95e11b50242944b08d2a4c70c99f98b53d..7d0ee92ff43589dc64d6d19507a92428d349307d 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -16,7 +16,6 @@ export interface Profile { accountType?: string balance?: number } - savedAmount?: number badges?: object[] hasPasskey?: boolean } diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 63ac2fe0bb64cd0ab21bd883f6796acc0c297689..f2db22a8d0e0601e2d11bf4649a77cb13813ae58 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -9,7 +9,7 @@ :imageDirection="'right'" class="my-10 md:ml-5" ></SpareComponent> - <div class="flex flex-col gap-2 items-center mx-auto mt-4 mb-20 md:gap-4 md:m-0 w-full"> + <div class="flex flex-col gap-2 items-center mx-auto mt-4 mb-20 md:gap-4 md:m-0 md:ml-4 w-full"> <ButtonAddGoalOrChallenge :buttonText="'Legg til sparemål'" :type="'goal'" /> <ButtonAddGoalOrChallenge :buttonText="'Legg til spareutfordring'" diff --git a/src/views/ManageChallengeView.vue b/src/views/ManageChallengeView.vue index 0a2fa0d9c147d761fdfc97c16bf89e8f79fc4371..72b45935a459388a3717bb639c3920ddf255bffe 100644 --- a/src/views/ManageChallengeView.vue +++ b/src/views/ManageChallengeView.vue @@ -237,26 +237,23 @@ const removeUploadedFile = () => { <p class="mx-4">Forfallsdato*</p> <input :min="minDate" - :max="maxDate" v-model="selectedDate" placeholder="Forfallsdato" type="date" /> </div> - </div> - <div class="flex flex-row justify-between w-full"> <div class="flex flex-col items-center"> <p>Last opp ikon for utfordringen📸</p> <label for="fileUpload" - class="bg-white text-black text-lg p-1 mt-2 rounded cursor-pointer leading-none" + class="bg-white text-black text-lg cursor-pointer leading-none rounded-full border p-3 border-black" > - 💾 + Legg til 💾 </label> <input id="fileUpload" type="file" - accept=".jpg" + accept=".jpg, .png" hidden @change="handleFileChange" /> @@ -271,6 +268,7 @@ const removeUploadedFile = () => { </div> </div> </div> + <div class="flex flex-row justify-between w-full"> <button class="primary danger" @click="cancelCreation" v-text="'Avbryt'" /> diff --git a/src/views/ManageConfigView.vue b/src/views/ManageConfigView.vue index c928a18f66b8211acc57ffaef2d6011c8f4e85c0..2a5ad08eaf1f68ff0d5e299965567082fb8c635e 100644 --- a/src/views/ManageConfigView.vue +++ b/src/views/ManageConfigView.vue @@ -159,12 +159,16 @@ onMounted(() => { class="flex flex-row flex-wrap justify-center gap-5 border-2 p-4" > <input v-model="item.type" placeholder="Type" type="text" /> - <input v-model="item.specificAmount" placeholder="Generell pris" type="number" /> + <input + v-model="item.specificAmount" + placeholder="Generell pris" + type="number" + /> <input v-model="item.generalAmount" placeholder="Pris per uke" type="number" /> <button class="primary danger w-min items-center" @click="deleteChallengeType(item.type)" - v-text="'x'" + v-text="'Slett'" /> </CardTemplate> <button diff --git a/src/views/ManageGoalView.vue b/src/views/ManageGoalView.vue index e1edfd5357120bc536d90d14e47a0567649bda6c..78415a96f31adb569ee3734542125a326504d901 100644 --- a/src/views/ManageGoalView.vue +++ b/src/views/ManageGoalView.vue @@ -255,95 +255,97 @@ onMounted(async () => { </div> <ProgressBar :completion="completion" /> - <div class="flex flex-row gap-4"> - <div class="flex flex-col"> - <p class="mx-4">Forfallsdato*</p> - <input - :min="minDate" - v-model="selectedDate" - placeholder="Forfallsdato" - type="date" - /> - </div> - <div class="flex flex-col items-center"> - <p>Last opp ikon for utfordringen📸</p> - <label - for="fileUpload" - class="bg-white text-black text-lg p-1 mt-2 rounded cursor-pointer leading-none" - > - 💾 - </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" + <div class="flex flex-row gap-4"> + <div class="flex flex-col"> + <p class="mx-4">Forfallsdato*</p> + <input + :min="minDate" + v-model="selectedDate" + placeholder="Forfallsdato" + type="date" + /> + </div> + <div class="flex flex-col items-center"> + <p>Last opp ikon for utfordringen📸</p> + <label + for="fileUpload" + class="bg-white text-black text-lg cursor-pointer leading-none rounded-full border p-3 border-black" > - Fjern fil - </button> + Legg til 💾 + </label> + <input + id="fileUpload" + type="file" + accept=".jpg, .png" + 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> - <div class="flex flex-row justify-between w-full"> - <button - v-if="isEdit" - class="ml-2 primary danger" - @click="deleteGoal" - v-text="'Slett'" - /> - <button - v-else - class="ml-2 primary danger" - @click="cancelCreation" - v-text="'Avbryt'" - /> - <button class="primary" @click="submitAction" v-text="submitButton" /> + <div class="flex flex-row justify-between w-full"> + <button + v-if="isEdit" + class="ml-2 primary danger" + @click="deleteGoal" + v-text="'Slett'" + /> + <button + v-else + class="ml-2 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:buttons> + <button class="primary" @click="errorModalOpen = false">Lukk</button> + </template> + </ModalComponent> + + <ModalComponent + :title="modalTitle" + :message="modalMessage" + :isModalOpen="confirmModalOpen" + @close="confirmModalOpen = false" + > + <template v-slot:buttons> + <button class="primary" @click="confirmCancel">Bekreft</button> + <button class="primary danger" @click="confirmModalOpen = false"> + Avbryt + </button> + </template> + </ModalComponent> </div> - <ModalComponent - :title="modalTitle" - :message="modalMessage" - :isModalOpen="errorModalOpen" - @close="errorModalOpen = false" + <div + class="lg:absolute right-5 lg:top-1/4 max-lg:bottom-0 max-lg:mt-44 transform -translate-y-1/2 lg:w-1/4 lg:max-w-xs" > - <template v-slot:buttons> - <button class="primary" @click="errorModalOpen = false">Lukk</button> - </template> - </ModalComponent> - - <ModalComponent - :title="modalTitle" - :message="modalMessage" - :isModalOpen="confirmModalOpen" - @close="confirmModalOpen = false" - > - <template v-slot:buttons> - <button class="primary" @click="confirmCancel">Bekreft</button> - <button class="primary danger" @click="confirmModalOpen = false">Avbryt</button> - </template> - </ModalComponent> - </div> - <div - class="lg:absolute right-5 lg:top-1/4 max-lg:bottom-0 max-lg:mt-44 transform -translate-y-1/2 lg:w-1/4 lg:max-w-xs" - > - <InteractiveSpare - :png-size="10" - :speech="[ - 'Her kan du lage et sparemål! 💎', - `Trenger du hjelp? Trykk på ⓠnede i høyre hjørne!` - ]" - direction="left" - /> + <InteractiveSpare + :png-size="10" + :speech="[ + 'Her kan du lage et sparemål! 💎', + `Trenger du hjelp? Trykk på ⓠnede i høyre hjørne!` + ]" + direction="left" + /> + </div> </div> </div> - </div> </template> <style scoped> diff --git a/src/views/ViewChallengeView.vue b/src/views/ViewChallengeView.vue index 0e86e81ccd951309180cddfb80aa188918426678..8012da2aa78d434531f1a765d91b8b4c2f9996f3 100644 --- a/src/views/ViewChallengeView.vue +++ b/src/views/ViewChallengeView.vue @@ -112,14 +112,14 @@ const completeChallenge = () => { <h2 class="font-light"> {{ challengeInstance.title }} </h2> - <div class="flex flex-row gap-4 justify-center"> + <div class="flex flex-col gap-4 justify-center"> <p class="text-wrap break-words">{{ challengeInstance.description }}</p> - <div> + <div class="flex justify-center items-center"> <img v-if="isImageLoaded" :src="challengeImageUrl || '@/assets/star.png'" alt="Goal Image" - class="w-full h-40 object-cover rounded-lg" + class="w-44 h-44 object-cover rounded-lg" /> </div> </div> diff --git a/src/views/ViewGoalView.vue b/src/views/ViewGoalView.vue index 3158876f78e1195091593b1bd642bd868d3755dd..c62eabf0561f53754bf79d3e0b48be2af75dd3d5 100644 --- a/src/views/ViewGoalView.vue +++ b/src/views/ViewGoalView.vue @@ -101,14 +101,14 @@ const completeGoal = () => { <h2 class="font-light"> {{ goalInstance.title }} </h2> - <div class="flex flex-row gap-4 justify-center"> + <div class="flex flex-col gap-4 justify-center"> <p class="text-wrap break-words">{{ goalInstance.description }}</p> - <div> + <div class="flex justify-center items-center"> <img v-if="isImageLoaded" :src="goalImageUrl || '@/assets/star.png'" alt="Goal Image" - class="w-full h-40 object-cover rounded-lg" + class="w-44 h-44 object-cover rounded-lg" /> </div> </div> diff --git a/src/views/ViewProfileView.vue b/src/views/ViewProfileView.vue index 2b534afca975f3678feb5d32310f502ca9b947db..b4329b648b78541115adbbe67b3843563883349b 100644 --- a/src/views/ViewProfileView.vue +++ b/src/views/ViewProfileView.vue @@ -103,11 +103,11 @@ const openSpare = () => { <ModalEditAvatar @update-profile-picture="updateProfilePicture" /> </div> <div class="w-full flex flex-col justify-between"> - <h3 class="font-thin my-0">{{ profile?.username }}</h3> - <h3 class="font-thin my-0"> + <h3 class="font-thin my-0 md:text-xl text-lg">{{ profile?.username }}</h3> + <h3 class="font-thin my-0 md:text-xl text-lg"> {{ profile?.firstName + ' ' + profile?.lastName }} </h3> - <h3 class="font-thin my-0">{{ profile?.email }}</h3> + <h3 class="font-thin my-0 md:text-xl text-lg">{{ profile?.email }}</h3> </div> </div>