feat: avatar upload
This commit is contained in:
parent
2d2b5a86c3
commit
a40f066279
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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") }}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue