Skip to content
Snippets Groups Projects
Commit f4d12611 authored by Ina Martini's avatar Ina Martini
Browse files

Merge branch 'enhancement/6/update-login-page-2' into 'dev'

Update login page and implement forgot password feature

See merge request !18
parents 2bdcc575 3bd7f712
No related branches found
No related tags found
3 merge requests!66Final merge,!18Update login page and implement forgot password feature,!4Pipeline fix
Pipeline #277872 passed
describe('Login', () => {
beforeEach(() => {
cy.visit('/login')
cy.visit('/logginn')
})
function fullInput() {
......
describe('Register', () => {
beforeEach(() => {
cy.visit('/login')
cy.visit('/registrer')
cy.contains('h3', 'Registrer deg').click()
})
......
src/assets/spare_og_sti.png

51.6 KiB

<script lang="ts" setup>
import { ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import { useUserStore } from '@/stores/userStore'
import ModalComponent from '@/components/ModalComponent.vue'
import { useRouter } from 'vue-router'
import axios from 'axios'
const username = ref<string>('')
const password = ref<string>('')
const showPassword = ref<boolean>(false)
const errorMessage = ref<string>('')
const isModalOpen = ref<boolean>(false)
const resetEmail = ref<string>('')
const emailRegex = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/
const userStore = useUserStore()
const router = useRouter()
const isEmailValid = computed(() => emailRegex.test(resetEmail.value))
const submitForm = () => {
userStore.login(username.value, password.value)
router.push('/hjem')
}
const toggleShowPassword = () => {
showPassword.value = !showPassword.value
}
const openForgotPasswordModal = (event: MouseEvent) => {
event.preventDefault()
isModalOpen.value = true
}
const submitReset = async () => {
await axios.post('http://localhost:8080/forgotPassword/changePasswordRequest', {
email: resetEmail.value
})
resetEmail.value = ''
isModalOpen.value = false
}
const closeModal = () => {
resetEmail.value = ''
isModalOpen.value = false
}
watch(
() => userStore.errorMessage,
(newValue: string) => {
......@@ -49,6 +78,11 @@ watch(
<button class="absolute right-0 top-1 bg-transparent" @click="toggleShowPassword">
{{ showPassword ? '🔓' : '🔒' }}
</button>
<a
@click="openForgotPasswordModal"
class="absolute right-3 top-10 hover:underline hover:bg-transparent cursor-pointer"
>Glemt passord?</a
>
</div>
</div>
<div class="flex flex-row gap-5">
......@@ -63,6 +97,34 @@ watch(
<p>{{ errorMessage }}</p>
</div>
</div>
<modal-component
:title="'Glemt passord'"
:message="'Vennligst skriv inn e-posten din for å endre passordet.'"
:is-modal-open="isModalOpen"
@close="isModalOpen = false"
>
<template v-slot:input>
<input
type="email"
v-model="resetEmail"
class="border border-gray-300 p-2 w-full mb-7"
placeholder="Skriv e-postadressen din her"
/>
</template>
<template v-slot:buttons>
<button
:disabled="!isEmailValid"
@click="submitReset"
class="active-button font-bold py-2 px-4 w-1/2 hover:bg-[#f7da7c] border-2 border-[#f7da7c] disabled:border-transparent"
>
Send mail
</button>
<button
@click="closeModal"
class="active-button font-bold py-2 px-4 w-1/2 hover:bg-[#f7da7c] border-2 border-[#f7da7c] disabled:border-transparent"
>
Lukk
</button>
</template>
</modal-component>
</template>
<style scoped></style>
......@@ -2,6 +2,7 @@
import { computed, ref, watch } from 'vue'
import { useUserStore } from '@/stores/userStore'
import ToolTip from '@/components/ToolTip.vue'
import { useRouter } from 'vue-router'
const firstname = ref<string>('')
const lastname = ref<string>('')
......@@ -14,6 +15,7 @@ const showPassword = ref<boolean>(false)
const errorMessage = ref<string>('')
const userStore = useUserStore()
const router = useRouter()
const nameRegex = /^[a-zA-Z ,.'-]+$/
const emailRegex = /^[a-zA-Z0-9_+&*-]+(?:\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,7}$/
......@@ -35,6 +37,7 @@ const isFormInvalid = computed(
const submitForm = () => {
userStore.register(firstname.value, lastname.value, email.value, username.value, password.value)
router.push('/konfigurasjonSteg1')
}
const toggleShowPassword = () => {
......@@ -148,5 +151,3 @@ watch(
</div>
</div>
</template>
<style scoped></style>
<template>
<div
v-if="isModalOpen"
class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center"
>
<div class="bg-white p-6 rounded-lg shadow-lg max-w-sm w-full text-center">
<h2 class="font-bold mb-4">{{ title }}</h2>
<p class="mb-4">{{ message }}</p>
<slot name="input"></slot>
<div class="flex flex-col justify-center items-center gap-3 mt-3 w-full">
<slot name="buttons"></slot>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineProps({
title: String,
message: String,
isModalOpen: Boolean
})
</script>
import { describe, it, expect, beforeEach } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import ModalComponent from '@/components/ModalComponent.vue'
describe('ModalComponent', () => {
let wrapper: any
beforeEach(() => {
wrapper = shallowMount(ModalComponent, {
props: {
title: 'Test Title',
message: 'Test Message',
button1: 'Test button',
isModalOpen: true,
showButton: true,
showInput: false,
typeValue: 'text',
inputPlaceholder: 'Placeholder',
isInputValid: true
}
})
})
it('opens modal when button is clicked', async () => {
expect(wrapper.props().isModalOpen).toBe(true)
})
})
......@@ -15,10 +15,20 @@ const router = createRouter({
component: HomeView
},
{
path: '/login',
path: '/logginn',
name: 'login',
component: () => import('@/views/RegisterLoginView.vue')
},
{
path: '/registrer',
name: 'register',
component: () => import('@/views/RegisterLoginView.vue')
},
{
path: '/forgotPassword',
name: 'resetPassword',
component: () => import('@/views/ResetPasswordView.vue')
},
{
path: '/profil',
name: 'profile',
......@@ -62,12 +72,12 @@ const router = createRouter({
{
path: '/forsteSparemaal',
name: 'firstSavingGoal',
component: () => import('../views/FirstSavingGoalView.vue')
component: () => import('@/views/FirstSavingGoalView.vue')
},
{
path: '/forsteSpareutfordring',
name: 'firstSavingChallengde',
component: () => import('../views/FirstSavingChallengeView.vue')
component: () => import('@/views/FirstSavingChallengeView.vue')
}
],
scrollBehavior(to, from, savedPosition) {
......
<script setup lang="ts">
import FormLogin from '@/components/FormLogin.vue'
import FormRegister from '@/components/FormRegister.vue'
import { ref } from 'vue'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const isLogin = ref<boolean>(true)
onMounted(() => {
isLogin.value = router.currentRoute.value.path === '/logginn'
})
</script>
<template>
<div class="flex flex-col items-center gap-5 justify-center sm:flex-row">
<div class="border-2 border-black flex items-center">
<h1>Dette er et bilde</h1>
<div class="flex flex-col items-center gap-5 justify-center md:flex-row h-screen">
<div class="flex items-center justify-center md:w-2/3">
<img
src="@/assets/spare_og_sti.png"
alt="Spare og sti logo"
class="w-5/6 ml-10 md:mb-64"
/>
</div>
<div class="flex flex-col">
<div class="flex flex-col md:mr-10 md:mt-20 md:w-1/3 h-screen justify-start">
<div class="flex flex-row gap-5 justify-center">
<h3
:class="{ selected: isLogin }"
......
<template>
<div>
<h1 class="flex flex-col items-center mt-8">Oppdater passord</h1>
<p class="flex flex-col items-center mb-16">Skriv inn ditt nye passord 🔐</p>
<div class="flex justify-center items-center w-full">
<div class="flex flex-col md:w-1/4 w-2/3">
<div class="flex flex-row justify-between mx-4">
<p>Nytt 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.'"
/>
</div>
<div class="relative">
<input
name="password"
v-model="newPassword"
:type="showPassword ? 'text' : 'password'"
placeholder="Skriv inn passord"
class="w-full"
:class="{ 'bg-green-200': isPasswordValid }"
/>
<button
class="absolute right-0 top-1 bg-transparent"
@click="toggleShowPassword"
>
{{ showPassword ? '🔓' : '🔒' }}
</button>
</div>
<input
v-model="confirm"
:class="{ 'bg-green-200': newPassword == confirm && '' !== confirm.valueOf() }"
class="mt-2 w-full"
name="confirm"
placeholder="Bekreft passord"
type="password"
/>
<div class="flex justify-center mt-10">
<button
:disabled="!canResetPassword"
@click="resetPassword"
class="p-2 w-2/3 md:w-5/6 disabled:opacity-50"
>
Oppdater passord
</button>
</div>
</div>
</div>
<ModalComponent
:title="'Passordet er oppdatert ✨'"
:message="'Passordet ditt er nå oppdatert. Du kan nå logge inn med ditt nye passord.'"
:is-modal-open="isModalOpen"
@close="isModalOpen = false"
>
<template v-slot:buttons>
<button
@click="goToLogin"
class="active-button font-bold py-2 px-4 w-1/2 hover:bg-[#f7da7c] border-2 border-[#f7da7c] disabled:border-transparent"
>
Logg inn
</button>
</template>
</ModalComponent>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
import ToolTip from '@/components/ToolTip.vue'
import ModalComponent from '@/components/ModalComponent.vue'
const route = useRoute()
const router = useRouter()
const resetID = ref(route.query.resetID)
const userID = ref(route.query.userID)
const newPassword = ref<string>('')
const confirm = ref<string>('')
const showPassword = ref<boolean>(false)
const isModalOpen = ref<boolean>(false)
const passwordRegex = /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\S+$).{8,}$/
const isPasswordValid = computed(() => passwordRegex.test(newPassword.value))
const canResetPassword = computed(() => {
return isPasswordValid.value && newPassword.value === confirm.value && confirm.value !== ''
})
const resetPassword = async () => {
isModalOpen.value = true
const resetPasswordDTO = {
resetID: resetID.value,
userID: userID.value,
newPassword: newPassword.value
}
try {
await axios.post('http://localhost:8080/forgotPassword/resetPassword', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(resetPasswordDTO)
})
} catch (error) {
const err = error as Error
console.error(err.message)
}
}
const toggleShowPassword = () => {
showPassword.value = !showPassword.value
}
const goToLogin = () => {
isModalOpen.value = false
router.push('/logginn')
}
</script>
......@@ -22,7 +22,7 @@
>
<button
class="md:py-3 md:px-0 md:w-3/4 py-2 px-12 w-1/2 border border-[#95E35D] shadow-lg rounded-lg transition-all duration-500 bg-[#95E35D] hover:bg-white text-sm md:text-base"
@click="goToLogin"
@click="goToRegister"
>
Start her
</button>
......@@ -74,6 +74,10 @@ import { useRouter } from 'vue-router'
const router = useRouter()
const goToLogin = () => {
router.push('/login')
router.push('/logginn')
}
const goToRegister = () => {
router.push('/registrer')
}
</script>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment