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">
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 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>
<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 class="mt-8 border-t pt-8">
<h4 class="mb-4 text-lg font-bold">

View File

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

View File

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