feat: avatar upload

This commit is contained in:
Reto Aebersold 2024-02-06 11:03:55 +01:00
parent 2d2b5a86c3
commit a40f066279
3 changed files with 71 additions and 10 deletions

View File

@ -1,10 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import { useEntities } from "@/services/entities"; import { useEntities } from "@/services/entities";
import AvatarImage from "@/components/ui/AvatarImage.vue";
import { ref } from "vue";
import { useUserStore } from "@/stores/user";
const { countries, organisations } = useEntities(); const { countries, organisations } = useEntities();
const props = defineProps(["modelValue"]); const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]); const emit = defineEmits(["update:modelValue", "inlineEditComplete"]);
const user = useUserStore();
const avatarLoading = ref(false);
const avatarError = ref(false);
async function avatarUpload(e: Event) {
const { files } = e.target as HTMLInputElement;
if (!files?.length) return;
avatarLoading.value = true;
avatarError.value = false;
try {
await user.setUserAvatar(files[0]);
} catch (e) {
avatarError.value = true;
} finally {
avatarLoading.value = false;
emit("inlineEditComplete");
}
}
</script> </script>
<template> <template>
@ -70,6 +94,28 @@ const emit = defineEmits(["update:modelValue"]);
}) })
" "
/> />
<label class="block pb-1.5 leading-6">
{{ $t("a.Profilbild") }}
</label>
<div class="flex items-center space-x-4">
<AvatarImage
:image-size="64"
:loading="avatarLoading"
:image-url="user.avatar_url"
/>
<div class="btn-secondary relative inline-flex cursor-pointer items-center">
<input
id="upload"
type="file"
class="absolute bottom-0 left-0 right-0 top-0 opacity-0"
accept="image/*"
:disabled="avatarLoading"
@change="avatarUpload"
/>
{{ $t("a.Bild hochladen") }}
<it-icon-upload class="it-icon ml-2 h-6 w-6" />
</div>
</div>
</section> </section>
<section class="mt-8 border-t pt-8"> <section class="mt-8 border-t pt-8">
<h4 class="mb-4 text-lg font-bold"> <h4 class="mb-4 text-lg font-bold">

View File

@ -1,23 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue"; import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import { computed } from "vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
imageUrl?: string; imageUrl?: string;
loading?: boolean; loading?: boolean;
imageSize?: number;
}>(), }>(),
{ {
imageUrl: "", imageUrl: "",
loading: false, loading: false,
imageSize: 172,
} }
); );
const imageStyle = computed(() => {
return {
width: `${props.imageSize}px`,
height: `${props.imageSize}px`,
};
});
</script> </script>
<template> <template>
<div class="relative"> <div class="relative">
<img <img
v-if="props?.imageUrl" v-if="props?.imageUrl"
class="aspect-square w-[172px] rounded-full object-cover" class="aspect-square rounded-full object-cover"
:style="imageStyle"
:class="{ 'opacity-30': props.loading }" :class="{ 'opacity-30': props.loading }"
:src="props.imageUrl" :src="props.imageUrl"
alt="avatar" alt="avatar"
@ -26,8 +37,8 @@ const props = withDefaults(
v-else v-else
:class="{ 'opacity-30': props.loading }" :class="{ 'opacity-30': props.loading }"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="172" :width="imageSize"
height="172" :height="imageSize"
viewBox="0 0 172 172" viewBox="0 0 172 172"
fill="none" fill="none"
> >

View File

@ -36,6 +36,14 @@ function startEditMode() {
editMode.value = true; editMode.value = true;
} }
function exitEditMode() {
editMode.value = false;
saved.value = true;
setTimeout(() => {
saved.value = false;
}, 10 * 1000);
}
async function save() { async function save() {
const profileData = Object.assign({}, formData.value); const profileData = Object.assign({}, formData.value);
profileData.country = countries.value.find( profileData.country = countries.value.find(
@ -46,11 +54,7 @@ async function save() {
); );
profileData.organisation = parseInt(profileData.organisation); profileData.organisation = parseInt(profileData.organisation);
await user.updateUserProfile(profileData); await user.updateUserProfile(profileData);
editMode.value = false; exitEditMode();
saved.value = true;
setTimeout(() => {
saved.value = false;
}, 10 * 1000);
} }
</script> </script>
@ -89,7 +93,7 @@ async function save() {
<span>{{ $t("a.Deine Änderungen wurden gespeichert") }}.</span> <span>{{ $t("a.Deine Änderungen wurden gespeichert") }}.</span>
</div> </div>
<template v-if="editMode"> <template v-if="editMode">
<ProfileEdit v-model="formData" /> <ProfileEdit v-model="formData" @inline-edit-complete="exitEditMode" />
<div class="flex justify-end space-x-4"> <div class="flex justify-end space-x-4">
<button class="btn btn-secondary" @click="editMode = false"> <button class="btn btn-secondary" @click="editMode = false">
{{ $t("general.cancel") }} {{ $t("general.cancel") }}