diff --git a/cypress.config.ts b/cypress.config.ts index 0f66080fd0637080f5e2f5151146e89797be2e54..f80530a2a66e24534ac18223b7603fd3d0206f49 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,6 +3,6 @@ import { defineConfig } from 'cypress' export default defineConfig({ e2e: { specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}', - baseUrl: 'http://localhost:4173' + baseUrl: 'http://localhost:5173' } }) diff --git a/cypress/e2e/Authentication/LoginView.cy.ts b/cypress/e2e/Authentication/LoginView.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..c498a772125535ca7d217abe276a309ddd0b3d0c --- /dev/null +++ b/cypress/e2e/Authentication/LoginView.cy.ts @@ -0,0 +1,33 @@ +describe('Login Form Tests', () => { + beforeEach(() => { + cy.visit('/login'); + }); + + it('validates user input and displays error messages', () => { + cy.get('#emailInput input').type('test'); + cy.get('#passwordInput input').type('pass'); + cy.get('form').submit(); + cy.get('#invalid').should('contain', 'Invalid email'); + }); + + it('successfully logs in and redirects to the home page', () => { + cy.visit('/'); + cy.url().should('include', '/login'); + }); + + it('successfully logs in and redirects to the home page', () => { + cy.get('#emailInput input').type('user@example.com'); + cy.get('#passwordInput input').type('John1'); + cy.get('form').submit(); + cy.wait(1000); + cy.url().should('include', '/'); + }); + + it('shows an error message on login failure', () => { + cy.get('#emailInput input').type('wrong@example.com'); + cy.get('#passwordInput input').type('wrongPass1'); + cy.get('#confirmButton').click(); + cy.wait(1000); + cy.get('[data-cy="error"]').should('contain', 'User not found'); + }); +}); diff --git a/cypress/e2e/Authentication/SignUpView.cy.ts b/cypress/e2e/Authentication/SignUpView.cy.ts new file mode 100644 index 0000000000000000000000000000000000000000..652e6e3adafd4674478d3dd4bd63b9814b0fa32d --- /dev/null +++ b/cypress/e2e/Authentication/SignUpView.cy.ts @@ -0,0 +1,39 @@ +describe('Login Form Tests', () => { + beforeEach(() => { + cy.visit('/sign-up'); + }); + + it('validates user input and displays error messages', () => { + cy.get('#emailInput input').type('test'); + cy.get('#passwordInput input').type('pass'); + cy.get('form').submit(); + cy.get('#invalid').should('contain', 'Invalid email'); + }); + + it('successfully logs in and redirects to the home page', () => { + cy.visit('/'); + cy.url().should('include', '/login'); + }); + + it('successfully logs in and redirects to the home page', () => { + cy.get('#emailInput input').type('user@example.com'); + cy.get('#passwordInput input').type('John1'); + cy.get('form').submit(); + cy.wait(1000); + cy.url().should('include', '/'); + }); + + it('shows an error message on login failure', () => { + // Update the intercept to simulate a login failure + cy.intercept('POST', '/api/login', { + statusCode: 401, + body: { message: 'Invalid credentials' } + }).as('apiLoginFail'); + + cy.get('#emailInput input').type('wrong@example.com'); + cy.get('#passwordInput input').type('wrongPass1'); + cy.get('#confirmButton').click(); + cy.wait(1000); + cy.get('[data-cy="error"]').should('contain', 'User not found'); + }); +}); diff --git a/src/App.vue b/src/App.vue index f7a239c42135aae98c86203521fd01c1e98cd18a..d32330ffb583d487044a1e59f4703d7072a35ba5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,6 +15,5 @@ import { RouterView } from 'vue-router' main { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-weight: 600; - } </style> \ No newline at end of file diff --git a/src/components/Login/LoginForm.vue b/src/components/Login/LoginForm.vue index a6021798954054cd7aeb616733e6c03e8560b24c..9e58d96f32ddd9d57fdbf9d65d75b439aa56ace0 100644 --- a/src/components/Login/LoginForm.vue +++ b/src/components/Login/LoginForm.vue @@ -105,7 +105,7 @@ const handleSubmit = async () => { /> <p>Forgotten password? <RouterLink to="/forgotten-password">Reset password</RouterLink></p> - <p class="text-danger">{{ errorMsg }}</p> + <p class="text-danger" data-cy="error">{{ errorMsg }}</p> <button1 id="confirmButton" type="submit" @click="handleSubmit" button-text="Login"></button1> <SignUpLink/> </form> diff --git a/src/components/UserProfile/UserProfileForeignLayout.vue b/src/components/UserProfile/UserProfileForeignLayout.vue index 43ca37ef0103117e46ac94acdf73a5ee0498c152..df6ea8befbd76da3b997b92105fb51f64ce09029 100644 --- a/src/components/UserProfile/UserProfileForeignLayout.vue +++ b/src/components/UserProfile/UserProfileForeignLayout.vue @@ -2,7 +2,7 @@ import {useRoute, useRouter} from "vue-router"; import {onMounted, ref} from "vue"; -import {UserService} from "@/api"; +import {UserService, type ProfileDTO} from "@/api"; let numberOfHistory = 6; @@ -12,6 +12,10 @@ let username = ref() let friend = ref(false) +let profile: ProfileDTO;; + +const imageUrl = ref(`../src/assets/userprofile.png`); + let id = ref() @@ -33,7 +37,12 @@ onMounted(async () => { let response = await UserService.getProfile({ userId: id.value.id }) - username.value = response.firstName + profile = response; + console.log(profile) + username.value = profile.firstName + if (profile.profileImage){ + imageUrl.value = `http://localhost:8080/api/images/${profile.profileImage}` + } console.log(username) } catch (error) { console.error("Something went wrong getting the profile: ", error) @@ -69,7 +78,7 @@ function toUpdateUserSettings(){ <div class="card"> <div class="rounded-top text-white d-flex flex-row bg-primary" style="height:200px;"> <div class="ms-4 mt-5 d-flex flex-column" style="width: 150px;"> - <img src="https://bootdey.com/img/Content/avatar/avatar3.png" alt="Generic placeholder image" + <img :src="imageUrl" alt="Generic placeholder image" class="img-fluid img-thumbnail mt-4 mb-2" style="width: 150px; z-index: 1"> <button v-if="!friend" type="button" data-mdb-button-init data-mdb-ripple-init class="btn btn-outline-primary" data-mdb-ripple-color="dark" style="z-index: 1;" @click="addFriend"> diff --git a/src/components/UserProfile/UserProfileLayout.vue b/src/components/UserProfile/UserProfileLayout.vue index b9a6c8bc30a445b141863805d0637efe687d9524..500d9742cc22bfa0116a53f87e235684223fe764 100644 --- a/src/components/UserProfile/UserProfileLayout.vue +++ b/src/components/UserProfile/UserProfileLayout.vue @@ -9,6 +9,9 @@ let cardTitles = ["Spain tour", "Food waste", "Coffee", "Concert", "New book", " let firstname = ref(""); let lastname = ref(""); +let imageID = ref(12) +const imageUrl = ref(`http://localhost:8080/api/images/${imageID.value}`); + const router = useRouter(); const userStore = useUserInfoStore(); @@ -33,7 +36,7 @@ const toUpdateUserSettings = () => { <div class="card"> <div class="rounded-top text-white d-flex flex-row bg-primary" style="height:200px;"> <div class="ms-4 mt-5 d-flex flex-column" style="width: 150px;"> - <img src="https://bootdey.com/img/Content/avatar/avatar3.png" alt="Generic placeholder image" + <img :src="imageUrl" alt="Generic placeholder image" class="img-fluid img-thumbnail mt-4 mb-2" style="width: 150px; z-index: 1"> <button type="button" data-mdb-button-init data-mdb-ripple-init class="btn btn-outline-primary" data-mdb-ripple-color="dark" style="z-index: 1;" id="toUpdate" @click="toUpdateUserSettings"> diff --git a/src/views/Settings/SettingsProfileView.vue b/src/views/Settings/SettingsProfileView.vue index c572d7b627eda82cd7b917ddbc04101bdeb607d7..788676db282f7ed05a49ca7d45d35a2eb7a7f90e 100644 --- a/src/views/Settings/SettingsProfileView.vue +++ b/src/views/Settings/SettingsProfileView.vue @@ -2,7 +2,7 @@ import { ref, onMounted } from 'vue'; import BaseInput from '@/components/InputFields/BaseInput.vue'; import { useUserInfoStore } from "@/stores/UserStore"; -import { UserService } from '@/api'; +import { UserService, ImageService } from '@/api'; import type { UserUpdateDTO } from '@/api'; const firstNameRef = ref() @@ -12,6 +12,9 @@ const passwordRef = ref('') const formRef = ref() let samePasswords = ref(true) +const iconSrc = ref('https://bootdey.com/img/Content/avatar/avatar7.png'); +const fileInputRef = ref(); + const handleFirstNameInputEvent = (newValue: any) => { firstNameRef.value = newValue } @@ -21,6 +24,31 @@ const handleSurnameInputEvent = (newValue: any) => { surnameRef.value = newValue } +const triggerFileUpload = () => { + fileInputRef.value.click(); +}; + +const handleFileChange = (event: any) => { + const file = event.target.files[0]; + if (file) { + uploadImage(file); + } +}; + +const uploadImage = async (file: any) => { + const formData = { file: new Blob([file])} + + try { + const response = await ImageService.uploadImage({formData}); + + console.log('Image uploaded:', response); + + iconSrc.value = "http://localhost:8080/api/images/" + response; + } catch (error) { + console.error('Failed to upload image:', error); + } +}; + async function setupForm() { try { let response = await UserService.getUser(); @@ -66,11 +94,10 @@ onMounted(() => { <hr> <form @submit.prevent="handleSubmit" novalidate> <div class="user-avatar"> - <img id="icon" src="https://bootdey.com/img/Content/avatar/avatar7.png" alt="Maxwell Admin"> - </div> - <div class="btn"> + <input type="file" ref="fileInputRef" @change="handleFileChange" accept=".jpg, .jpeg, .png" style="display: none;" /> + <img :src="iconSrc" alt="User Avatar"> <div class="mt-2"> - <span class="btn btn-primary"><img src="@/assets/icons/download.svg"></span> + <button type="button" class="btn btn-primary" @click="triggerFileUpload"><img src="@/assets/icons/download.svg"> Upload Image</button> </div> </div> <div class="form-group"> diff --git a/src/views/Settings/SettingsSecurityView.vue b/src/views/Settings/SettingsSecurityView.vue index 62b021cc667c4785bb5f62d026b50e7ce35bd2c0..db55bb062446fec0738188c2da1a1c74e3e145b3 100644 --- a/src/views/Settings/SettingsSecurityView.vue +++ b/src/views/Settings/SettingsSecurityView.vue @@ -2,22 +2,76 @@ <div class="tab-pane active" id="security"> <h6>SECURITY SETTINGS</h6> <hr> - <form> + <form @submit.prevent="handleSubmit" novalidate> <div class="form-group"> <label class="d-block">Change Password</label> - <input type="text" class="form-control" placeholder="Enter your old password"> - <input type="text" class="form-control mt-3" placeholder="New password"> - <input type="text" class="form-control mt-3 mb-2" placeholder="Confirm new password"> + <BaseInput :model-value="oldPasswordRef" @input-change-event="handleOldPasswordInputEvent" + id="passwordInput-change" input-id="password-old" type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="Old Password" placeholder="Enter password" + invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number" /> + + <BaseInput :model-value="newPasswordRef" @input-change-event="handleNewPasswordInputEvent" + id="passwordInput-change" input-id="password-new" type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="New Password" placeholder="Enter password" + invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number" /> + + <BaseInput :model-value="confirmPasswordRef" @input-change-event="handleConfirmPasswordInputEvent" + id="passwordInput-change" input-id="password-confirm" type="password" + pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,16}" label="Confirm New Password" placeholder="Enter password" + invalid-message="Password must be between 4 and 16 characters and contain one capital letter, small letter and a number" /> </div> - <button type="button" class="btn btn-primary">Update Password</button> + <button type="submit" class="btn btn-primary">Update Password</button> <button type="reset" class="btn btn-light">Reset Changes</button> </form> <hr> </div> </template> + <script setup lang="ts"> -</script> + import { ref } from 'vue' + import BaseInput from '@/components/InputFields/BaseInput.vue' + import { type PasswordUpdateDTO, UserService } from '@/api' + + const oldPasswordRef = ref(''); + const newPasswordRef = ref(''); + const confirmPasswordRef = ref(''); + + + const handleOldPasswordInputEvent = (newValue: any) => { + oldPasswordRef.value = newValue +} + + const handleNewPasswordInputEvent = (newValue: any) => { + newPasswordRef.value = newValue +} + + const handleConfirmPasswordInputEvent = (newValue: any) => { + confirmPasswordRef.value = newValue +} -<style scoped> +const handleSubmit = async () => { + if (newPasswordRef.value !== confirmPasswordRef.value) { + console.error('Passwords do not match') + return + } + + + const updateUserPayload: PasswordUpdateDTO = { + oldPassword: oldPasswordRef.value, + newPassword: newPasswordRef.value, + }; + + try { + const response = UserService.updatePassword({ requestBody: updateUserPayload }) + console.log(response) + } catch (err) { + console.error(err) + } +} + + + + +</script> -</style> \ No newline at end of file +<style scoped></style> \ No newline at end of file