Skip to content
Snippets Groups Projects
Commit d745cf50 authored by Valdemar Åstorp Beere's avatar Valdemar Åstorp Beere
Browse files

Merge branch 'chore/profileimage' into 'dev'

refactor(component)

See merge request !56
parents ce764402 3496fd3d
Branches enhancement/47/general-cleanup
No related tags found
3 merge requests!66Final merge,!56refactor(component),!4Pipeline fix
Pipeline #283973 failed with stages
in 1 minute and 41 seconds
public/avatar1.png

96.5 KiB

public/avatar2.png

70.3 KiB

public/avatar3.png

71.4 KiB

public/avatar4.png

108 KiB

public/avatar5.png

82.4 KiB

public/avatar6.png

63.4 KiB

public/avatar7.png

129 KiB

public/avatar8.png

121 KiB

public/avatar9.png

58.5 KiB

<template>
<div class="flex flex-col items-center absolute">
<span class="text-sm text-bold">STREAK</span>
<div class="flex flex-col items-center relative">
<button
@mouseover="display"
@mouseleave="hide"
......@@ -15,7 +14,7 @@
<div
v-if="displayStreakCard"
class="w-[30vh] h-[20vh] md:w-auto md:h-auto group z-50 bg-opacity-50 overflow-hidden absolute left-0 top-14 md:top-20 flex flex-col justify-evenly text-wrap"
class="w-[30vh] h-[20vh] md:w-auto md:h-auto group z-50 bg-opacity-50 overflow-hidden absolute right-[-4rem] top-14 md:top-20 flex flex-col justify-evenly text-wrap"
>
<div
class="flex flex-col justify-evenly w-full h-full py-2 px-4 md:py-0 bg-white rounded-2xl border-4 border-green-300"
......@@ -63,7 +62,7 @@
class="flex flex-row items-center mx-auto h-20 w-4/5 md:w-full bg-black-400 gap-4"
>
<div class="flex flex-1 overflow-x-auto">
<div v-for="index in 7" :key="index" class="min-w-max mx-auto">
<div v-for="index in 6" :key="index" class="min-w-max mx-auto">
<div class="flex flex-col justify-around items-center">
<!-- Display the current streak day number adjusted by index -->
<span class="text-black text-xs md:text-1xl font-bold">
......
......@@ -85,10 +85,6 @@ const challengeStore = useChallengeStore()
const challengeImageUrl = ref('/src/assets/star.png') // Default or placeholder image
const props = defineProps<{ challenge: Challenge }>()
interface Props {
challenge: Challenge
}
const emit = defineEmits(['update-challenge', 'complete-challenge'])
// Increment saved amount
......
<template>
<button @click="openModal" class="text-nowrap">Endre avatar</button>
<button @click="openModal" class="primary text-nowrap">Endre avatar</button>
<div
v-if="isModalOpen"
class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50"
>
<div class="bg-white p-6 rounded-lg shadow-lg max-w-[80vh] h-auto w-full text-center">
<div class="flex flex-row justify-end">
<button @click="closeModal" class="primary">X</button>
</div>
<h2 class="title">Endre avatar</h2>
<div class="avatar-container flex flex-row justify-between items-center my-8">
<div class="avatar-container flex flex-row justify-between gap-2 items-center my-8">
<button @click="cycleArray('prev')"></button>
<div class="flex flex-row items-center justify-around">
<img :src="previousAvatar" alt="avatar" class="avatar h-16 w-16" />
<div class="border-4 rounded-full border-green-600 p-8 mx-4">
<img :src="currentAvatar" alt="avatar" class="avatar h-40 w-40" />
</div>
<img
:src="currentAvatar"
alt="avatar"
class="avatar block mx-auto h-32 w-32 rounded-full border-green-600 border-2 sm:mx-0 sm:shrink-0"
/>
<img :src="nextAvatar" alt="avatar" class="avatar h-16 w-16" />
</div>
<button @click="cycleArray('next')"></button>
</div>
<button @click="saveAvatar" class="save-button">Lagre</button>
<div class="flex flex-row items-center gap-4 mx-auto">
<button @click="saveAvatar" class="primary save-button basis-1/2">Lagre</button>
<button @click="openFileExplorer" class="primary basis-1/2">
Upload New Avatar
</button>
</div>
<input type="file" ref="fileInput" @change="handleFileUpload" hidden />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, reactive, computed } from 'vue'
import { useUserStore } from '@/stores/userStore'
const userStore = useUserStore()
const state = reactive({
avatars: [
'/avatar1.png',
'/avatar2.png',
'/avatar3.png',
'/avatar4.png',
'/avatar5.png',
'/avatar6.png',
'/avatar7.png',
'/avatar8.png',
'/avatar9.png'
],
currentAvatarIndex: 0,
newFile: null, // To hold the new file object
selectedPublicImg: '' // Track blob URLs created for uploaded files
})
const isModalOpen = ref(false)
const avatars = [
'src/assets/coffee.png',
'src/assets/head.png',
'src/assets/nose.png',
'src/assets/penger.png',
'src/assets/pig.png'
]
let currentAvatarIndex = 0
const fileInput = ref<HTMLElement | null>(null)
const emit = defineEmits(['update-profile-picture'])
const openModal = () => {
isModalOpen.value = !isModalOpen.value
state.avatars = [
'/avatar1.png',
'/avatar2.png',
'/avatar3.png',
'/avatar4.png',
'/avatar5.png',
'/avatar6.png',
'/avatar7.png',
'/avatar8.png',
'/avatar9.png'
]
userStore.getProfilePicture()
const urlProfilePicture = userStore.profilePicture
// Check if a profile picture URL exists and append it to the avatars list
const img = localStorage.getItem('profilePicture') as string
console.log(state.avatars)
console.log(img)
if (state.avatars.includes(state.selectedPublicImg) || state.avatars.includes(img)) {
// Remove the public asset from the list if it's already selected
state.avatars = state.avatars.filter((avatar) => avatar !== state.selectedPublicImg)
console.log(state.avatars, 'state.avatars')
}
// Clear
console.log(state.avatars)
localStorage.removeItem('profilePicture')
state.selectedPublicImg = ''
if (urlProfilePicture) {
state.avatars.push(urlProfilePicture)
state.currentAvatarIndex = state.avatars.length - 1 // Set the current avatar to the profile picture
}
isModalOpen.value = true
}
const nextAvatar = ref(avatars[(currentAvatarIndex + 1) % avatars.length])
const currentAvatar = ref(avatars[currentAvatarIndex])
const previousAvatar = ref(avatars[(currentAvatarIndex - 1 + avatars.length) % avatars.length])
const closeModal = () => {
isModalOpen.value = false
//Remove the uploaded file if there is one.
state.avatars = []
state.newFile = null // Clear the new file reference
}
const cycleArray = (direction: string) => {
if (direction === 'prev') {
currentAvatarIndex = (currentAvatarIndex - 1 + avatars.length) % avatars.length
console.log(currentAvatarIndex)
currentAvatar.value = avatars[currentAvatarIndex]
previousAvatar.value = avatars[(currentAvatarIndex - 1 + avatars.length) % avatars.length]
nextAvatar.value = avatars[(currentAvatarIndex + 1) % avatars.length]
state.currentAvatarIndex =
(state.currentAvatarIndex - 1 + state.avatars.length) % state.avatars.length
} else {
currentAvatarIndex = (currentAvatarIndex + 1) % avatars.length
currentAvatar.value = avatars[currentAvatarIndex]
previousAvatar.value = avatars[(currentAvatarIndex - 1 + avatars.length) % avatars.length]
nextAvatar.value = avatars[(currentAvatarIndex + 1) % avatars.length]
state.currentAvatarIndex = (state.currentAvatarIndex + 1) % state.avatars.length
}
}
const saveAvatar = () => {
localStorage.setItem('avatar', currentAvatar.value)
isModalOpen.value = false
const handleFileUpload = async (event: any) => {
const input = event.target
if (input.files && input.files[0]) {
const file = input.files[0]
// Clear any existing temporary blob URLs
state.avatars = state.avatars.filter((avatar) => !avatar.startsWith('blob:'))
state.newFile = file // Save the new file object for later upload
state.avatars.push(URL.createObjectURL(file)) // Add the blob URL for preview
state.currentAvatarIndex = state.avatars.length - 1 // Set this new avatar as current
}
}
const saveAvatar = async () => {
if (state.newFile && currentAvatar.value.startsWith('blob:')) {
// If there's a new file selected, upload it
const formData = new FormData()
formData.append('file', state.newFile)
await userStore.uploadProfilePicture(formData)
} else if (currentAvatar.value.startsWith('/')) {
// If it's a public asset, fetch it as a blob and upload
state.selectedPublicImg = currentAvatar.value
const response = await fetch(currentAvatar.value)
const blob = await response.blob()
const file = new File([blob], 'public-avatar.png', { type: blob.type })
const formData = new FormData()
formData.append('file', file)
await userStore.uploadProfilePicture(formData)
localStorage.setItem('profilePicture', currentAvatar.value)
}
closeModal()
emit('update-profile-picture', currentAvatar.value)
}
const openFileExplorer = () => {
fileInput.value?.click()
}
const currentAvatar = computed(() => state.avatars[state.currentAvatarIndex])
const nextAvatar = computed(
() => state.avatars[(state.currentAvatarIndex + 1) % state.avatars.length]
)
const previousAvatar = computed(
() =>
state.avatars[(state.currentAvatarIndex - 1 + state.avatars.length) % state.avatars.length]
)
</script>
<template>
<nav class="flex justify-between items-center min-h-32 text-xl w-full px-3 my-0">
<div>
<div class="order-first basis-1/5">
<router-link to="/hjem" @click="hamburgerOpen = false">
<img
alt="logo"
......@@ -8,12 +8,8 @@
src="@/assets/spareSti.png"
/>
</router-link>
<div class="flex flex-row justify-center">
<ButtonDisplayStreak />
</div>
</div>
<div v-if="!isHamburger" class="flex flex-row gap-10">
<div v-if="!isHamburger" class="flex flex-row justify-center gap-10 mx-auto basis-3/5">
<router-link active-class="border-b-2" to="/hjem">🏠Hjem</router-link>
<router-link active-class="border-b-2" to="/sparemaal">🎯Sparemål</router-link>
<router-link active-class="border-b-2" to="/spareutfordringer"
......@@ -22,15 +18,19 @@
<router-link active-class="border-b-2" to="/profil">🤭Profil</router-link>
</div>
<div v-if="!isHamburger" class="flex justify-center w-40">
<div v-if="!isHamburger" class="flex-row flex gap-2 justify-end w-auto h-14 basis-1/5">
<ButtonDisplayStreak />
<button
class="primary bg-[#95e35d] logout focus:ring focus:ring-black-300"
class="primary basis-1/2 bg-[#95e35d] logout focus:ring focus:ring-black-300 text-nowrap"
@click="openModal"
>
Logg ut
</button>
</div>
<button class="primary logout" v-if="isHamburger" @click="toggleMenu"></button>
<div class="flex flex-row gap-2">
<ButtonDisplayStreak v-if="isHamburger" />
<button class="primary logout" v-if="isHamburger" @click="toggleMenu"></button>
</div>
</nav>
<div v-if="hamburgerOpen" class="flex flex-col bg-white border border-slate-300 z-50">
......
......@@ -19,6 +19,7 @@ export const useUserStore = defineStore('user', () => {
})
const errorMessage = ref<string>('')
const streak = ref<Streak>()
const profilePicture = ref<string>('')
const register = async (
firstName: string,
......@@ -244,6 +245,28 @@ export const useUserStore = defineStore('user', () => {
user.value.isConfigured = false
})
}
// Inside your store or component methods
const uploadProfilePicture = async (formData: FormData) => {
try {
const response = await authInterceptor.post('/profile/picture', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
console.log('Upload successful:', response.data)
} catch (error: any) {
console.error('Failed to upload profile picture:', error.response.data)
}
}
const getProfilePicture = async () => {
try {
const imageResponse = await authInterceptor.get('/profile/picture', {
responseType: 'blob'
})
profilePicture.value = URL.createObjectURL(imageResponse.data)
} catch (error: any) {
console.error('Failed to retrieve profile picture:', error.response.data)
}
}
return {
user,
......@@ -255,6 +278,9 @@ export const useUserStore = defineStore('user', () => {
bioRegister,
errorMessage,
getUserStreak,
streak
streak,
uploadProfilePicture,
getProfilePicture,
profilePicture
}
})
......@@ -28,6 +28,7 @@ const updatePassword = ref<boolean>(false)
const confirmPassword = ref<string>('')
const errorMessage = ref<string>('')
const isModalOpen = ref(false)
const image = ref<File>()
const nameRegex = /^[æÆøØåÅa-zA-Z,.'-][æÆøØåÅa-zA-Z ,.'-]{0,29}$/
const emailRegex =
......@@ -89,6 +90,24 @@ onMounted(async () => {
})
})
const selectImage = async () => {
const fileInput = document.getElementById('fileInput')! as HTMLInputElement
if (!fileInput) {
// Error handling
console.log('Vi klarte ikke å hente bildene dine. Prøv igjen!')
}
if (!fileInput.files) {
return
}
image.value = fileInput.files[0]
}
const uploadImage = async () => {
// bildet må lastes opp som en form. altså en body med form
// const formData = new FormData()
// authInterceptor.post("/profile/picture", formData)
}
const saveChanges = async () => {
if (isFormInvalid.value) {
errorMessage.value = 'Vennligst fyll ut alle feltene riktig'
......@@ -124,7 +143,17 @@ const saveChanges = async () => {
<button class="h-min bg-transparent text-4xl" v-text="'➡️'" />
</div>
</div>
<div class="flex flex-row justify-center">
<input
id="fileInput"
type="file"
style="display: none"
accept=".jpg, .jpeg, .png, .gif, .img"
/>
<button v-text="'Velg eget bilde!'" @click="selectImage()" />
<button v-text="'Send bilde'" @click="uploadImage()" />
</div>
<div class="flex flex-col">
<div class="flex flex-row justify-between mx-4">
<p>Fornavn*</p>
......
......@@ -9,11 +9,15 @@ import CardGoal from '@/components/CardGoal.vue'
import router from '@/router'
import SpareComponent from '@/components/SpareComponent.vue'
import { useUserStore } from '@/stores/userStore'
import ModalEditAvatar from '@/components/ModalEditAvatar.vue'
const profile = ref<Profile>()
const completedGoals = ref<Goal[]>([])
const completedChallenges = ref<Challenge[]>([])
const speech = ref<string[]>([])
const profilePicture = ref<string>()
const userStore = useUserStore()
const updateUser = async () => {
authInterceptor('/profile')
......@@ -45,6 +49,8 @@ onMounted(async () => {
return console.log(error)
})
await userStore.getProfilePicture()
profilePicture.value = userStore.profilePicture
openSpare()
})
const updateBiometrics = async () => {
......@@ -52,6 +58,12 @@ const updateBiometrics = async () => {
await updateUser()
}
const updateProfilePicture = async () => {
await updateUser()
await userStore.getProfilePicture()
profilePicture.value = userStore.profilePicture
}
const openSpare = () => {
speech.value = [
`Velkommen, ${profile.value?.firstName} ${profile.value?.lastName} !`,
......@@ -67,7 +79,14 @@ const openSpare = () => {
<div class="flex flex-col max-w-96 w-full gap-5">
<h1>Profil</h1>
<div class="flex flex-row gap-5">
<div class="w-32 h-32 border-slate-200 border-2 rounded-full shrink-0" />
<div class="flex flex-col gap-1">
<img
:src="profilePicture"
alt="could not load"
class="block mx-auto h-32 rounded-full border-green-600 border-2 sm:mx-0 sm:shrink-0"
/>
<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">
......
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