Skip to content
Snippets Groups Projects
Commit 21490b5f authored by Ingrid Martinsheimen Egge's avatar Ingrid Martinsheimen Egge :cow2:
Browse files

merge with main

parents 541fceb8 39847f2e
No related branches found
No related tags found
1 merge request!21Merge profilinnstillinger into main
Showing
with 2622 additions and 189 deletions
describe('Correct navigation links', () => { describe('Correct navigation links', () => {
<<<<<<< HEAD
it("is sent to error page if page does not exist", () => { it("is sent to error page if page does not exist", () => {
cy.visit('/qwerty') cy.visit('/qwerty')
cy.contains('#msg', '404') cy.contains('#msg', '404')
...@@ -22,4 +23,10 @@ describe('Navbar on all pages', () => { ...@@ -22,4 +23,10 @@ describe('Navbar on all pages', () => {
cy.get('nav').should('exist') cy.get('nav').should('exist')
}) })
/*TODO: other pages*/ /*TODO: other pages*/
=======
/*TODO*/
})
describe('Navbar on all pages', () => {
/*TODO*/
>>>>>>> main
}) })
\ No newline at end of file
describe('Cannot register with wrong fields', () => {
it('passes', () => {
cy.visit('http://localhost:4173/registerAccount')
cy.get('#register-button').trigger('click')
cy.get('#error-message').contains("Vennligst fyll inn alle feltene")
cy.get('#email-input').type('en ugyldig epost')
cy.get('#register-button').trigger('click')
cy.get('#error-message').contains("Vennligst fyll inn alle feltene")
cy.get('#password-input').type('hei')
cy.get('#register-button').trigger('click')
cy.get('#error-message').contains("Vennligst fyll inn alle feltene")
})
})
\ No newline at end of file
This diff is collapsed.
...@@ -11,19 +11,23 @@ ...@@ -11,19 +11,23 @@
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress run --e2e'" "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress run --e2e'"
}, },
"dependencies": { "dependencies": {
"@iconify/vue": "^4.1.1", "@iconify/iconify": "^3.1.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"pinia": "^2.0.28", "pinia": "^2.0.28",
"pinia-plugin-persistedstate": "^3.1.0",
"sass": "^1.62.0", "sass": "^1.62.0",
"pinia-plugin-persistedstate": "^3.1.0",
"vue": "^3.2.45", "vue": "^3.2.45",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },
"devDependencies": { "devDependencies": {
"@iconify/vue": "^4.1.1",
"@pinia/testing": "^0.0.16",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"@vue/test-utils": "^2.2.6", "@vue/test-utils": "^2.2.6",
"cypress": "^12.0.2", "cypress": "^12.0.2",
"jsdom": "^20.0.3", "jsdom": "^20.0.3",
"node-sass": "^8.0.0",
"sass-loader": "^13.2.2",
"start-server-and-test": "^1.15.2", "start-server-and-test": "^1.15.2",
"vite": "^4.0.0", "vite": "^4.0.0",
"vitest": "^0.25.6" "vitest": "^0.25.6"
......
<script setup> <script setup>
import { RouterLink, RouterView } from 'vue-router' import { RouterLink, RouterView } from 'vue-router'
import Navbar from "@/components/Navbar.vue"; import Navbar from "@/components/Navbar.vue";
</script> </script>
<template> <template>
......
...@@ -29,7 +29,6 @@ a, ...@@ -29,7 +29,6 @@ a,
#app { #app {
display: grid; display: grid;
/*grid-template-columns: 1fr 1fr;*/
padding: 0 2rem; padding: 0 2rem;
} }
} }
...@@ -25,8 +25,13 @@ ...@@ -25,8 +25,13 @@
</RouterLink> </RouterLink>
</li> </li>
<li> <li>
<<<<<<< HEAD
<RouterLink :to="'/profileSettings'" :aria-label="'link to settings page'"> <RouterLink :to="'/profileSettings'" :aria-label="'link to settings page'">
<Icon id="settingsIcon" icon="mdi:cog" :color="iconColor" :style="{ fontSize: iconSize }"/> <Icon id="settingsIcon" icon="mdi:cog" :color="iconColor" :style="{ fontSize: iconSize }"/>
=======
<RouterLink :to="'/'" :aria-label="'link to settings page'">
<Icon icon="mdi:cog" :color="iconColor" :style="{ fontSize: iconSize }"/>
>>>>>>> main
</RouterLink> </RouterLink>
</li> </li>
</ul> </ul>
...@@ -48,30 +53,34 @@ export default { ...@@ -48,30 +53,34 @@ export default {
return `32px`; return `32px`;
}, },
logoSize() { logoSize() {
return '62px'; return '52px';
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
#logoContainer { #logoContainer {
background-color: white; background-color: white;
border-radius: 50%; border-radius: 50%;
display: flex;
justify-content: center;
align-items:center;
} }
#logoContainer img{ #logoContainer img{
width:70%; width:70%;
} }
nav { nav {
z-index: 999;
position: fixed; position: fixed;
top: auto; top: auto;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
background-color: base.$green; background-color: base.$green;
margin:0; margin:0;
padding:0; padding:5px;
z-index: 999;
} }
ul { ul {
...@@ -84,8 +93,6 @@ ul { ...@@ -84,8 +93,6 @@ ul {
margin-left: 1em; margin-left: 1em;
} }
@media only screen and (min-width: base.$desktop-min){ @media only screen and (min-width: base.$desktop-min){
nav { nav {
top:0; top:0;
......
...@@ -9,8 +9,8 @@ describe('Navbar', () => { ...@@ -9,8 +9,8 @@ describe('Navbar', () => {
expect(wrapper.findAll('RouterLink').length).toBe(5) expect(wrapper.findAll('RouterLink').length).toBe(5)
}) })
it('contains 5 icons', () => { it('contains 4 icons', () => {
const wrapper = mount(Navbar) const wrapper = mount(Navbar)
const icons = wrapper.findAllComponents(Icon) const icons = wrapper.findAllComponents(Icon)
expect(icons).toHaveLength(5)}) expect(icons).toHaveLength(4)})
}) })
...@@ -4,6 +4,7 @@ import MissingPage from "@/views/MissingPage.vue"; ...@@ -4,6 +4,7 @@ import MissingPage from "@/views/MissingPage.vue";
import HomeView from '../views/HomeView.vue' import HomeView from '../views/HomeView.vue'
import LoginView from '../views/LoginView.vue' import LoginView from '../views/LoginView.vue'
import SelectProfileView from '../views/SelectProfileView.vue' import SelectProfileView from '../views/SelectProfileView.vue'
import RegisterAccountView from '../views/RegisterAccountView.vue'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
...@@ -23,6 +24,12 @@ const router = createRouter({ ...@@ -23,6 +24,12 @@ const router = createRouter({
name: "selectProfile", name: "selectProfile",
component: SelectProfileView component: SelectProfileView
}, },
{
path: '/registerAccount',
name: 'registerAccount',
component: RegisterAccountView
},
{ {
path: '/profileSettings', path: '/profileSettings',
name: 'profileSettings', name: 'profileSettings',
......
...@@ -3,8 +3,9 @@ export const useAuthStore = defineStore("auth", { ...@@ -3,8 +3,9 @@ export const useAuthStore = defineStore("auth", {
state: () => { state: () => {
return { return {
token: "", token: "",
user: {}, account: {},
profile: {}, profile: {},
profiles: []
}; };
}, },
persist: { persist: {
...@@ -19,8 +20,8 @@ export const useAuthStore = defineStore("auth", { ...@@ -19,8 +20,8 @@ export const useAuthStore = defineStore("auth", {
setToken(token) { setToken(token) {
this.token = token; this.token = token;
}, },
setUser(user) { setAccount(account) {
this.user = user; this.account = account;
}, },
logout() { logout() {
this.$reset(); this.$reset();
...@@ -28,6 +29,9 @@ export const useAuthStore = defineStore("auth", { ...@@ -28,6 +29,9 @@ export const useAuthStore = defineStore("auth", {
setProfile(profile) { setProfile(profile) {
this.profile = profile; this.profile = profile;
}, },
setProfiles(profiles) {
this.profiles = profiles;
},
updateProfile(name, image, isRestricted){ updateProfile(name, image, isRestricted){
this.profile.name = name; this.profile.name = name;
this.profile.profileImageUrl = image; this.profile.profileImageUrl = image;
......
/*APP COLORS*/ /*APP COLORS*/
$green: #00663C; $green: #00663C;
$light-green: hsla(160, 100%, 37%, 1);
$white:#FFFFFF; $white:#FFFFFF;
$grey:#D9D9D9; $grey:#D9D9D9;
$red:#EE6D6D; $red:#EE6D6D;
......
...@@ -17,6 +17,7 @@ export const API = { ...@@ -17,6 +17,7 @@ export const API = {
login: async (request) => { login: async (request) => {
const authStore = useAuthStore(); const authStore = useAuthStore();
let token; let token;
authStore.logout(); //in case someone for some reason is logged in
return axios.post( return axios.post(
`${import.meta.env.VITE_BACKEND_URL}/login`, `${import.meta.env.VITE_BACKEND_URL}/login`,
...@@ -25,11 +26,14 @@ export const API = { ...@@ -25,11 +26,14 @@ export const API = {
.then(async (response) => { .then(async (response) => {
token = response.data; token = response.data;
const id = (jwt_decode(token)).id; const id = (jwt_decode(token)).id;
return API.getAccount(id, token) return API.getAccount(id, token)
.then((user) => { .then((user) => {
authStore.setUser(user); authStore.setAccount(user);
authStore.setToken(token); authStore.setToken(token);
API.getProfiles()
.then(response => {authStore.setProfiles(response)})
.catch(() => {throw new Error()})
return; return;
}) })
.catch(() => { .catch(() => {
...@@ -69,7 +73,7 @@ export const API = { ...@@ -69,7 +73,7 @@ export const API = {
.then((response) => { .then((response) => {
authStore.setProfile(response.data) authStore.setProfile(response.data)
router.push("/") router.push("/")
}) })
.catch(() => { .catch(() => {
throw new Error("Profile not found or not accessible") throw new Error("Profile not found or not accessible")
...@@ -78,7 +82,7 @@ export const API = { ...@@ -78,7 +82,7 @@ export const API = {
}, },
// Sends the user into the "register profile" view // Sends the user into the "register profile" view
addProfile: async () => { addProfile: async () => {
console.log("todo"); console.log("todo");
}, },
...@@ -90,20 +94,30 @@ export const API = { ...@@ -90,20 +94,30 @@ export const API = {
throw new Error(); throw new Error();
} }
return axios.get(import.meta.env.VITE_BACKEND_URL + '/profile', { return axios.get(import.meta.env.VITE_BACKEND_URL + '/profile', {
headers: { Authorization: "Bearer " + authStore.token }, headers: { Authorization: "Bearer " + authStore.token },
}, },
) )
.then(response => { .then(response => {
console.log(response.data)
return response.data return response.data
}).catch(() => { }).catch(() => {
throw new Error(); throw new Error();
}); });
}, },
// Registers a new account and logs into it
addAccount: async (request) => {
const authStore = useAuthStore();
axios.post(import.meta.env.VITE_BACKEND_URL + '/account', request)
.then(() => {
API.login({email: request.email, password: request.password})
.catch(err => {console.log(err)})
})
.catch(() => {throw new Error()})
}
},
/** /**
* Deletes account from the * Deletes account from the
* @param id * @param id
...@@ -182,4 +196,4 @@ export const API = { ...@@ -182,4 +196,4 @@ export const API = {
} }
\ No newline at end of file
<script setup> <script>
import { useAuthStore } from "@/stores/authStore.js";
import { mapState } from 'pinia'
export default {
data() {
return {
}
},
computed: {
...mapState(useAuthStore, ['profile'])
}
}
</script> </script>
<template> <template>
<main> <main>
<p>HALLO</p> <div class="content">
<div class="welcome">
<img id="logo" src="../components/icons/logo.png" alt="Logo">
<h1>Velkommen, {{ this.profile.name }}!</h1>
</div>
<div class="gamification">
</div>
<div class="tips">
<img id="tips-img" src="../components/icons/logo.png" alt="Logo">
<p id="tips-text">Her kommer tips du kanskje kan ha nytte av, trykk på meg for å gå til neste tips!</p>
</div>
</div>
</main> </main>
</template> </template>
<style scoped>
.container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
min-width: 300px;
margin-top: 40px;
}
.welcome {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 20px;
}
#logo {
width: 75px;
height: 75px;
}
#tips-img {
width: 40px;
height: 40px;
margin: auto 0;
}
.tips {
display:flex;
padding: 10px;
min-height: 75px;
max-height: 250px;
background-color: rgb(232, 232, 232);
margin-left: 10px;
margin-right: 10px;
}
#tips-text {
overflow: hidden;
padding-left: 10px;
}
@media (min-width: 1024px) {
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}
}
</style>
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<div class="login-container"> <div class="login-container">
<img id="logo" src="../components/icons/logo.png" alt="Logo"> <img id="logo" src="../components/icons/logo.png" alt="Logo">
<h1>{{ welcomemsg }}</h1> <h1>{{ welcomemsg }}</h1>
<form @submit.prevent="login"> <form @submit.prevent>
<div class="field-container"> <div class="field-container">
<label for="email">E-post</label> <label for="email">E-post</label>
<input id="email-input" name="email" type="text" v-model="email" /> <input id="email-input" name="email" type="text" v-model="email" />
...@@ -46,12 +46,12 @@ ...@@ -46,12 +46,12 @@
<button @click="login" id="login-button">Logg inn</button> <button @click="login" id="login-button">Logg inn</button>
</form> </form>
<p><RouterLink to="/newuser">Ny bruker</RouterLink> - <a href="#">Glemt passord?</a></p> <p><RouterLink to="/registerAccount">Ny bruker</RouterLink> - <a href="#">Glemt passord?</a></p>
</div> </div>
</main> </main>
</template> </template>
<style scoped> <style lang="scss" scoped>
.login-container { .login-container {
display: flex; display: flex;
...@@ -87,7 +87,7 @@ label { ...@@ -87,7 +87,7 @@ label {
} }
#login-button { #login-button {
background-color: #00663C; background-color: base.$green;
color: #FFFFFF; color: #FFFFFF;
border-radius: 5px; border-radius: 5px;
border-style: none; border-style: none;
...@@ -98,6 +98,11 @@ label { ...@@ -98,6 +98,11 @@ label {
margin: 20px; margin: 20px;
} }
#login-button:hover {
background-color: base.$light-green;
}
#logo { #logo {
width: 100px; width: 100px;
height: 100px; height: 100px;
......
<template>
<div class="content">
<img id="logo" src="../components/icons/logo.png" alt="Logo">
<h1>Registrer konto</h1>
<form @submit.prevent>
<div class="field-container">
<label for="name">Navn</label>
<input id="input" name="name" type="text" v-model="name" />
</div>
<div class="field-container">
<label for="email">E-post</label>
<input id="email-input" name="email" type="email" v-model="email" />
</div>
<div class="field-container">
<label for="password">Passord</label>
<input id="password-input" name="password" type="password" v-model="password" />
</div>
<p id="error-message">{{ errormsg }}</p>
<button @click="register" id="register-button">Registrer</button>
</form>
</div>
</template>
<script>
import { API } from '@/util/API.js';
import router from '@/router/index.js';
export default {
data() {
return {
email: "",
name: "",
password: "",
errormsg: ""
}
},
methods: {
register() {
if (this.name === "" || this.password === "") {
this.errormsg = "Vennligst fyll inn alle feltene"
}
else {
API.addAccount({firstname: this.name, email: this.email, password: this.password}).then(() => {
router.push("/selectProfile")
})
.catch(() => {
throw new Error()
});
}
}
}
}
</script>
<style scoped>
.content {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
min-width: 300px;
margin-top: 40px;
}
form {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.field-container {
padding: 10px;
display: flex;
flex-direction: column;
}
input {
height: 40px;
font-size: 16px;
padding-left: 10px;
}
label {
font-size: 18px;
}
#register-button {
background-color: #00663C;
color: #FFFFFF;
border-radius: 5px;
border-style: none;
width: 150px;
height: 40px;
font-size: 18px;
font-weight: bold;
margin: 20px;
}
#register-button:hover {
background-color: #04be80;
}
#logo {
width: 100px;
height: 100px;
}
@media (min-width: 1024px) {
.login-container {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}
}
</style>
\ No newline at end of file
<script> <script>
import { API } from '@/util/API.js'; import { API } from '@/util/API.js';
import { useAuthStore } from "@/stores/authStore.js";
import { mapState } from 'pinia'
export default { export default {
data() { data() {
return { return {
profiles: []
} }
}, },
computed: {
...mapState(useAuthStore, ['profiles'])
},
methods: { methods: {
// Sends the user into the home page logged in as the profile they clicked on // Sends the user into the home page logged in as the profile they clicked on
selectProfile(id) { selectProfile(id) {
...@@ -21,7 +28,6 @@ import { API } from '@/util/API.js'; ...@@ -21,7 +28,6 @@ import { API } from '@/util/API.js';
// Receives all profiles from this user // Receives all profiles from this user
async getProfiles() { async getProfiles() {
await API.getProfiles() await API.getProfiles()
.then(response => {this.profiles = response})
.catch(() => new Error()); .catch(() => new Error());
} }
}, },
...@@ -55,7 +61,7 @@ import { API } from '@/util/API.js'; ...@@ -55,7 +61,7 @@ import { API } from '@/util/API.js';
</template> </template>
<style scoped> <style scoped lang="scss">
.container { .container {
display: flex; display: flex;
...@@ -87,7 +93,7 @@ import { API } from '@/util/API.js'; ...@@ -87,7 +93,7 @@ import { API } from '@/util/API.js';
} }
.icon:hover { .icon:hover {
background-color: #d5d5d5; background-color: base.$grey;
border-radius: 10%; border-radius: 10%;
} }
...@@ -95,6 +101,7 @@ import { API } from '@/util/API.js'; ...@@ -95,6 +101,7 @@ import { API } from '@/util/API.js';
height: 130px; height: 130px;
width: 130px; width: 130px;
border-radius: 50%; border-radius: 50%;
padding: 10px;
} }
......
import { describe, it, expect, vi } from 'vitest'
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import HomeView from '../HomeView.vue'
describe('Home', () => {
it('renders properly', () => {
const wrapper = mount(HomeView, {
global: {
plugins: [createTestingPinia({
createSpy: vi.fn,
})],
},
})
expect(wrapper.text()).toContain('Velkommen,')
})
it('tips renders properly', () => {
const wrapper = mount(HomeView, {
global: {
plugins: [createTestingPinia({
createSpy: vi.fn,
})],
},
})
expect(wrapper.find('#tips-text').text()).not.toBe("");
})
})
\ No newline at end of file
...@@ -7,10 +7,11 @@ describe('Login', () => { ...@@ -7,10 +7,11 @@ describe('Login', () => {
it('renders properly', () => { it('renders properly', () => {
const wrapper = mount(LoginView) const wrapper = mount(LoginView)
expect(wrapper.text()).toContain('E-post') expect(wrapper.text()).toContain('E-post')
expect(wrapper.text()).toContain('Passord')
}) })
it('login button exists', () => { it('login button exists', () => {
const wrapper = mount(LoginView) const wrapper = mount(LoginView)
wrapper.find('#login-button').exists() expect(wrapper.find('#login-button').exists()).toBe(true);
}) })
}) })
\ No newline at end of file
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import RegisterAccountView from '../RegisterAccountView.vue'
describe('Login', () => {
it('renders properly', () => {
const wrapper = mount(RegisterAccountView)
expect(wrapper.text()).toContain('E-post')
expect(wrapper.text()).toContain('Navn')
expect(wrapper.text()).toContain('Passord')
})
it('register button exists', () => {
const wrapper = mount(RegisterAccountView)
expect(wrapper.find('#register-button').exists()).toBe(true)
})
})
\ No newline at end of file
import { describe, it, expect } from 'vitest' import { describe, it, expect, vi } from 'vitest'
import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import SelectProfileView from '../SelectProfileView.vue' import SelectProfileView from '../SelectProfileView.vue'
describe('Select profile', () => { describe('Select profile', () => {
it('renders properly', () => { it('renders properly', () => {
const wrapper = mount(SelectProfileView) const wrapper = mount(SelectProfileView, {
global: {
plugins: [createTestingPinia({
createSpy: vi.fn,
})],
},
})
expect(wrapper.text()).toContain('Hvem bruker appen?') expect(wrapper.text()).toContain('Hvem bruker appen?')
expect(wrapper.text()).toContain('+') expect(wrapper.text()).toContain('+')
}) })
it('loads with one profile', () => { it('loads with one profile', () => {
const wrapper = mount(SelectProfileView, { const wrapper = mount(SelectProfileView, {
global: {
plugins: [createTestingPinia({
createSpy: vi.fn,
})],
},
data() { data() {
return { return {
profiles: [{ profiles: [{
......
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