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

Merge remote-tracking branch 'origin/profilinnstillinger' into profilinnstillinger

# Conflicts:
#	src/components/EditProfile.vue
#	src/components/Navbar.vue
parents 18c68ba3 3fba3e17
No related branches found
No related tags found
1 merge request!21Merge profilinnstillinger into main
public/profile/default_avatar.png

4.03 KiB

<template> <template>
<h1>Konto-innstillinger</h1> <h1>Konto-innstillinger</h1>
<form @submit.prevent="submit"> <form @submit.prevent="submit">
<p class="infoText">OBS: Kontakt admin dersom du ønsker å oppdatere epost</p><br> <p class="infoText">OBS: Kontakt admin dersom du ønsker å oppdatere epost</p><br>
<p>Epost: {{this.account.email}}</p><br> <p>Epost: {{this.account.email}}</p><br>
...@@ -13,8 +12,9 @@ ...@@ -13,8 +12,9 @@
<input type="password" id="password" v-model="updatedAccount.upPassword"> <input type="password" id="password" v-model="updatedAccount.upPassword">
<button class="saveBtn" @click="saveAccountSettings">Lagre profilendringer</button> <button class="saveBtn" @click="saveAccountSettings">Lagre profilendringer</button>
<p :style={color:alertMsgColor} id="alert">{{alertMsg}}</p>
</form> </form>
<br> <br>
<br> <br>
<hr> <hr>
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
<input type="checkbox" id="deletionCheckbox" v-model="deletionConfirmation"> <input type="checkbox" id="deletionCheckbox" v-model="deletionConfirmation">
<label for="deletionCheckbox"> Jeg bekrefter jeg skjønner dette, og ønsker å slette kontoen min SmartMat-konto for alltid.</label><br> <label for="deletionCheckbox"> Jeg bekrefter jeg skjønner dette, og ønsker å slette kontoen min SmartMat-konto for alltid.</label><br>
<button class="delBtn" @click="deleteAccount">SLETT KONTO</button> <button class="delBtn" @click="deleteAccount">SLETT KONTO</button>
<p :style={color:alertMsgColor} id="alert">{{delAlertMsg}}</p>
</form> </form>
</template> </template>
...@@ -53,6 +54,9 @@ export default { ...@@ -53,6 +54,9 @@ export default {
data() { data() {
return { return {
deletionConfirmation: false, deletionConfirmation: false,
alertMsg:'',
alertMsgColor:'black',
delAlertMsg:'',
} }
}, },
methods: { methods: {
...@@ -67,8 +71,9 @@ export default { ...@@ -67,8 +71,9 @@ export default {
} }
).then((savedAccount)=>{ ).then((savedAccount)=>{
useAuthStore().setAccount(savedAccount); useAuthStore().setAccount(savedAccount);
alert("Bruker oppdatert.") this.setAlertText("Melding: Bruker oppdatert.",'light-green');
}).catch((error)=> { }).catch((error)=> {
this.setAlertText("Melding: Det oppsto en feil. ",'red');
console.log(error) console.log(error)
}) })
} else { } else {
...@@ -81,15 +86,16 @@ export default { ...@@ -81,15 +86,16 @@ export default {
} }
).then((savedAccount)=>{ ).then((savedAccount)=>{
useAuthStore().setAccount(savedAccount); useAuthStore().setAccount(savedAccount);
alert("Konto oppdatert.") this.setAlertText("Melding: Konto oppdatert.",'light-green')
}).catch((error)=> { }).catch((error)=> {
this.setAlertText("Melding: Det oppsto en feil. ",'red');
console.log(error) console.log(error)
}) })
} }
}, },
deleteAccount(){ deleteAccount(){
if(this.deletionConfirmation===false){ if(this.deletionConfirmation===false){
alert("Du må bekrefte at du vil slette konto ved å huke av boksen") this.setDeleationAlertText("Du må bekrefte at du vil slette konto ved å huke av boksen", 'red')
} }
else { else {
const id = this.account.id; const id = this.account.id;
...@@ -98,11 +104,43 @@ export default { ...@@ -98,11 +104,43 @@ export default {
).then(()=>{ ).then(()=>{
router.push('/login') router.push('/login')
}).catch((error)=> { }).catch((error)=> {
alert("Det oppsto en feil ved sletting av bruker") this.setDeleationAlertText("Melding: Det oppsto en feil ved sletting av bruker", 'red')
console.log(error)
}) })
} }
}, },
setAlertText(text, color){
switch (color) {
case 'red':
this.alertMsgColor ='#EE6D6D';
this.alertMsg = text;
break;
case 'light-green':
this.alertMsgColor ='hsla(160, 100%, 37%, 1);';
this.alertMsg = text;
break;
default:
this.alertMsgColor ='black';
this.alertMsg = text;
break;
}
},
setDeleationAlertText(text,color){
switch (color) {
case 'red':
this.alertMsgColor ='#EE6D6D';
this.delAlertMsg = text;
break;
case 'light-green':
this.alertMsgColor ='hsla(160, 100%, 37%, 1);';
this.delAlertMsg = text;
break;
default:
this.alertMsgColor ='black';
this.delAlertMsg = text;
break;
}
}
} }
} }
</script> </script>
...@@ -142,6 +180,7 @@ input[type="password"]{ ...@@ -142,6 +180,7 @@ input[type="password"]{
button { button {
background-color: base.$red; background-color: base.$red;
color: black; color: black;
border: 1px solid black; border: 1px solid black;
...@@ -168,4 +207,12 @@ button:hover{ ...@@ -168,4 +207,12 @@ button:hover{
color: darkred; color: darkred;
} }
#alert {
display: flex;
width:100%;
justify-content: center;
color: base.$light-green;
font-weight: bold;
}
</style> </style>
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<h1><br><br>Profilinnstillinger <br></h1> <h1><br><br>Profilinnstillinger <br></h1>
<div v-if="hasProfileImage" id = "profilepicture-container"> <div v-if="hasProfileImage" id = "profilepicture-container">
<img width="100" :src="getProfileImage" alt="profile picture"> <img width="100" :src="this.updatedProfile.upImage" alt="profile picture">
</div> </div>
<div v-else id = "profilepicture-container"> <div v-else id = "profilepicture-container">
<Icon icon="material-symbols:person" :color=iconColor :style="{ fontSize: '500px'}" /> <Icon icon="material-symbols:person" :color=iconColor :style="{ fontSize: '500px'}" />
...@@ -42,6 +42,8 @@ ...@@ -42,6 +42,8 @@
<button class="delBtn" @click="deleteProfile">Slett brukerprofil</button> <button class="delBtn" @click="deleteProfile">Slett brukerprofil</button>
</div> </div>
<p :style={color:alertMsgColor} id="alert">{{alertMsg}}</p>
</form> </form>
</template> </template>
...@@ -55,6 +57,12 @@ import router from "../router"; ...@@ -55,6 +57,12 @@ import router from "../router";
export default { export default {
name: "EditProfile", name: "EditProfile",
components: {Icon}, components: {Icon},
data() {
return {
alertMsg:'',
alertMsgColor:'black',
}
},
computed: { computed: {
...mapState(useAuthStore, ['profile']), ...mapState(useAuthStore, ['profile']),
...@@ -71,9 +79,6 @@ export default { ...@@ -71,9 +79,6 @@ export default {
}, },
hasProfileImage() { hasProfileImage() {
return this.updatedProfile.upImage.length > 0; return this.updatedProfile.upImage.length > 0;
},
getProfileImage(){
return this.updatedProfile.upImage;
} }
}, },
methods: { methods: {
...@@ -81,9 +86,8 @@ export default { ...@@ -81,9 +86,8 @@ export default {
const id = this.profile.id; const id = this.profile.id;
const numOfProfiles = API.getProfiles().length const numOfProfiles = API.getProfiles().length
console.log("antall profiler: " + numOfProfiles) //todo if(numOfProfiles===1 && this.updatedProfile.upRestricted===true){
if(numOfProfiles===1 && this.updatedProfile.upRestricted==true){ this.setAlertText("Du må ha minst en standardprofil per konto. (ingen endringer er gjort)",'red')
alert("Du må ha minst en standardprofil per konto. (ingen endringer er gjort)")
} }
else { else {
API.updateProfile( API.updateProfile(
...@@ -94,14 +98,15 @@ export default { ...@@ -94,14 +98,15 @@ export default {
} }
).then((savedProfile)=>{ ).then((savedProfile)=>{
useAuthStore().setProfile(savedProfile); useAuthStore().setProfile(savedProfile);
alert("profil oppdatert.") this.setAlertText("profil oppdatert.",'light-green')
}).catch((error)=> { }).catch((error)=> {
this.setAlertText("Det oppsto en feil",'red')
console.log(error) console.log(error)
}) })
} }
}, },
chooseProfilePicture(){ chooseProfilePicture(){
alert("skriv inn bildelenke i feltet, og oppdater innstillinger") this.setAlertText("skriv inn bildelenke i feltet, og oppdater innstillinger",'black')
}, },
changeProfile(){ changeProfile(){
router.push("/selectProfile"); router.push("/selectProfile");
...@@ -109,16 +114,32 @@ export default { ...@@ -109,16 +114,32 @@ export default {
deleteProfile(){ deleteProfile(){
const numOfProfiles = API.getProfiles().length const numOfProfiles = API.getProfiles().length
if(numOfProfiles===1){ if(numOfProfiles===1){
alert("Du kan ikke slette profilen. Hver Konto må ha minst en profil") this.setAlertText("Du kan ikke slette profilen. Hver Konto må ha minst en profil",'red')
}else { }else {
const id = this.profile.id; const id = this.profile.id;
API.deleteProfile(id).then(()=>{ API.deleteProfile(id).then(()=>{
router.push('/selectProfile') router.push('/selectProfile')
}).catch((error)=> { }).catch((error)=> {
alert("Det oppsto en feil ved sletting profil: " + error) this.setAlertText("Det oppsto en feil ved sletting profil", 'red')
}) })
} }
} },
setAlertText(text, color){
switch (color) {
case 'red':
this.alertMsgColor ='#EE6D6D';
this.alertMsg = text;
break;
case 'light-green':
this.alertMsgColor ='hsla(160, 100%, 37%, 1);';
this.alertMsg = text;
break;
default:
this.alertMsgColor ='black';
this.alertMsg = text;
break;
}
},
} }
} }
...@@ -219,5 +240,13 @@ button:hover{ ...@@ -219,5 +240,13 @@ button:hover{
#dangerZone { #dangerZone {
color: darkred; color: darkred;
} }
#alert {
display: flex;
width:100%;
justify-content: center;
color: base.$light-green;
font-weight: bold;
}
</style> </style>
\ No newline at end of file
<template> <template>
<div> <div>
<nav v-if="isLoggedIn"> <nav>
<ul :aria-label = "'Navigation links'"> <ul :aria-label = "'Navigation links'">
<li> <li>
<RouterLink :to="'/'" :aria-label="'link to home page'"> <RouterLink :to="'/'" :aria-label="'link to home page'">
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
</RouterLink> </RouterLink>
</li> </li>
<li> <li>
<RouterLink :to="'/'" :aria-label="'link to fridge page'"> <RouterLink :to="'/'" :aria-label="'link to week menu'">
<Icon icon="ic:baseline-calendar-month" :color="iconColor" :style="{ fontSize: logoSize }"/> <Icon icon="ic:baseline-calendar-month" :color="iconColor" :style="{ fontSize: iconSize }"/>
</RouterLink> </RouterLink>
</li> </li>
<li> <li>
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
<script> <script>
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import Logo from "@/components/Logo.vue"; import Logo from "@/components/Logo.vue";
import {useAuthStore} from "@/stores/authStore";
export default { export default {
name: "Navbar", name: "Navbar",
...@@ -47,14 +46,8 @@ export default { ...@@ -47,14 +46,8 @@ export default {
return `32px`; return `32px`;
}, },
logoSize() { logoSize() {
return '45px'; return '52px';
}, }
logocolor() {
return '#00663C'
},
isLoggedIn(){
return useAuthStore().isLoggedIn;
}
} }
} }
</script> </script>
...@@ -68,7 +61,7 @@ export default { ...@@ -68,7 +61,7 @@ export default {
justify-content: center; justify-content: center;
align-items:center; align-items:center;
} }
#logoContainer Icon{ #logoContainer img{
width:70%; width:70%;
} }
...@@ -80,7 +73,9 @@ nav { ...@@ -80,7 +73,9 @@ nav {
width: 100%; width: 100%;
background-color: base.$green; background-color: base.$green;
margin:0; margin:0;
padding:5px; qpadding:5px;
align-items: center;
} }
ul { ul {
...@@ -89,8 +84,12 @@ ul { ...@@ -89,8 +84,12 @@ ul {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0; padding: 0;
margin-top: .4em;
margin-right: 1em; margin-right: 1em;
margin-left: 1em; margin-left: 1em;
} }
@media only screen and (min-width: base.$desktop-min){ @media only screen and (min-width: base.$desktop-min){
......
<script>
export default {
data: () => {
return {
state: false,
}
},
emits: ['toggled'],
methods: {
emitUpdate() {
this.state = !this.state;
this.$emit('toggled');
}
}
}
</script>
<template>
<label class="toggle">
<input type="checkbox" @change="emitUpdate">
<span class="toggle_slider"></span>
</label>
</template>
<style scoped lang="scss">
$width: 40px;
$height: calc(2 * $width / 3);
$on-color: green;
$off-color: #cccccc;
.toggle {
position: relative;
display: inline-block;
border-radius: calc($height / 2);
width: $width;
height: $height;
background-color: $off-color;
input {
display: none;
}
}
.toggle_slider {
position: absolute;
cursor: pointer;
border-radius: calc($height / 2);
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
&:before {
position: absolute;
content: "";
border-radius: 50%;
height: calc(0.8 * $height);
width: calc(0.8 * $height);
left: calc(0.1 * $height);
top: calc(0.1 * $height);
background-color: white;
transition: 200ms;
}
}
input:checked+.toggle_slider {
background-color: $on-color;
}
input:focus+.toggle_slider {
box-shadow: 0 0 1px white;
}
input:checked+.toggle_slider:before {
transform: translateX(calc($width / 3));
}
</style>
\ No newline at end of file
...@@ -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 4 icons', () => { it('contains 5 icons', () => {
const wrapper = mount(Navbar) const wrapper = mount(Navbar)
const icons = wrapper.findAllComponents(Icon) const icons = wrapper.findAllComponents(Icon)
expect(icons).toHaveLength(4)}) expect(icons).toHaveLength(5)})
}) })
...@@ -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 ProfileCreationView from '../views/ProfileCreationView.vue'
import RegisterAccountView from '../views/RegisterAccountView.vue' import RegisterAccountView from '../views/RegisterAccountView.vue'
const router = createRouter({ const router = createRouter({
...@@ -24,6 +25,11 @@ const router = createRouter({ ...@@ -24,6 +25,11 @@ const router = createRouter({
name: "selectProfile", name: "selectProfile",
component: SelectProfileView component: SelectProfileView
}, },
{
path: '/newProfile',
name: "newProfile",
component: ProfileCreationView
},
{ {
path: '/registerAccount', path: '/registerAccount',
name: 'registerAccount', name: 'registerAccount',
......
...@@ -82,9 +82,28 @@ export const API = { ...@@ -82,9 +82,28 @@ export const API = {
}, },
// Sends the user into the "register profile" view /**
addProfile: async () => { * Sends a request to create a new profile on the currently logged in account
console.log("todo"); *
* @typedef {{name: string, profileImageUrl: string, isRestricted: boolean}} ProfileType
* @param {ProfileType} profile
* @returns
*/
addProfile: async (profile) => {
const authStore = useAuthStore();
if (!authStore.isLoggedIn) {
throw new Error();
}
return axios.post(import.meta.env.VITE_BACKEND_URL + '/profile', {
headers: { Authorization: "Bearer " + authStore.token },
body: profile
})
.then((response) => {
return response.data;
}).catch(() => {
throw new Error();
})
}, },
// Returns all profiles to the logged in user // Returns all profiles to the logged in user
......
<script>
import Toggle from '../components/Toggle.vue';
import { API } from '../util/API';
export default {
data: () => {
return {
image: "profile/default_avatar.png",
profile: {
profileImageUrl: "",
name: "",
isRestricted: false
}
};
},
methods: {
submit() {
this.profile.isRestricted = this.$refs.toggle.state;
API.addProfile(this.profile);
},
updateImg() {
let file = document.getElementById("avatar").files[0];
let reader = new FileReader();
reader.onload = function (ev) {
document.getElementById("avatar_preview").src = ev.target.result;
};
reader.readAsDataURL(file);
}
},
components: { Toggle }
}
</script>
<template>
<main>
<h1>Ny profil</h1>
<form action="todo" method="post">
<label for="avatar" id="avatar_label">
<div class="img_hover">
<img :src="image" alt="fjes" id="avatar_preview">
<p class="hover_text">
Klikk for å endre
</p>
</div>
</label>
<input type="file" name="avatar" id="avatar" @change="updateImg">
<label for="name">Navn</label>
<input name="name" type="text" v-model="profile.name">
<div class="check_container">
<label for="child">Begrenset:</label>
<Toggle ref="toggle" />
</div>
</form>
<button @click="submit">Opprett</button>
</main>
</template>
<style scoped lang="scss">
@import '../style.scss';
$gap: 1em;
$img-size: 150px;
input[type="file"] {
display: none;
}
main {
display: flex;
flex-direction: column;
padding-top: 15%;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: $gap;
form {
gap: $gap;
}
}
#avatar_label {
display: flex;
justify-content: center;
width: 100%;
}
.check_container {
display: flex;
gap: .5em;
}
.img_hover {
display: flex;
justify-content: center;
align-items: center;
&:hover .hover_text {
visibility: visible;
opacity: 1;
}
&:hover img {
filter: brightness(70%);
border-radius: 25%;
}
img {
height: $img-size;
width: $img-size;
border-radius: 50%;
object-fit: cover;
transition: all 300ms ease;
}
}
.hover_text {
position: absolute;
font-weight: bold;
color: #fff;
visibility: hidden;
opacity: 0;
transition: all 350ms eases;
}
form {
display: flex;
flex-direction: column;
max-width: 20em;
}
button {
padding: .5rem 1rem;
border: 2px $green solid;
border-radius: .5rem;
background-color: $light-green;
color: white;
font-weight: bold;
}
</style>
\ No newline at end of file
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