fix: Fix issue with profile picture not updating (#10532)

This PR resolves the issue with updating the profile picture in the profile settings.

**Cause of issue**
The issue can be reproduced with the old `ProfileAvatar.vue` component.
While the exact reason is unclear, it seems related to cases where the
file might be `null`.

**Solution**
Replaced the old `ProfileAvatar.vue` with `Avatar.vue` and tested it. It
works fine. I’ve attached a loom video below.

Fixes https://linear.app/chatwoot/issue/CW-3768/profile-picture-bug

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
This commit is contained in:
Sivin Varghese
2024-12-05 04:32:29 +05:30
committed by GitHub
parent bf58a18af4
commit 3edc0542cc
8 changed files with 16 additions and 100 deletions

View File

@@ -17,7 +17,7 @@ class Api::V1::ProfilesController < Api::BaseController
def avatar def avatar
@user.avatar.attachment.destroy! if @user.avatar.attached? @user.avatar.attachment.destroy! if @user.avatar.attached?
head :ok @user.reload
end end
def auto_offline def auto_offline

View File

@@ -132,7 +132,7 @@ const iconStyles = computed(() => ({
})); }));
const initialsStyles = computed(() => ({ const initialsStyles = computed(() => ({
fontSize: `${props.size > 32 ? 16 : props.size / 2}px`, fontSize: `${Math.min(props.size / 2.5, 24)}px`,
})); }));
const invalidateCurrentImage = () => { const invalidateCurrentImage = () => {

View File

@@ -190,7 +190,6 @@ export default {
<UserProfilePicture <UserProfilePicture
:src="avatarUrl" :src="avatarUrl"
:name="name" :name="name"
size="72px"
@change="updateProfilePicture" @change="updateProfilePicture"
@delete="deleteProfilePicture" @delete="deleteProfilePicture"
/> />

View File

@@ -1,8 +1,6 @@
<script setup> <script setup>
import { computed } from 'vue'; import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
import ProfileAvatar from 'v3/components/Form/ProfileAvatar.vue'; defineProps({
import { removeEmoji } from 'shared/helpers/emoji';
const props = defineProps({
src: { src: {
type: String, type: String,
default: '', default: '',
@@ -15,8 +13,6 @@ const props = defineProps({
const emit = defineEmits(['change', 'delete']); const emit = defineEmits(['change', 'delete']);
const userNameWithoutEmoji = computed(() => removeEmoji(props.name));
const updateProfilePicture = e => { const updateProfilePicture = e => {
emit('change', e); emit('change', e);
}; };
@@ -31,10 +27,12 @@ const deleteProfilePicture = () => {
<span class="text-sm font-medium text-ash-900"> <span class="text-sm font-medium text-ash-900">
{{ $t('PROFILE_SETTINGS.FORM.PICTURE') }} {{ $t('PROFILE_SETTINGS.FORM.PICTURE') }}
</span> </span>
<ProfileAvatar <Avatar
:src="src" :src="src || ''"
:name="userNameWithoutEmoji" :name="name || ''"
@change="updateProfilePicture" :size="72"
allow-upload
@upload="updateProfilePicture"
@delete="deleteProfilePicture" @delete="deleteProfilePicture"
/> />
</div> </div>

View File

@@ -133,9 +133,10 @@ export const actions = {
} }
}, },
deleteAvatar: async () => { deleteAvatar: async ({ commit }) => {
try { try {
await authAPI.deleteAvatar(); const response = await authAPI.deleteAvatar();
commit(types.SET_CURRENT_USER, response.data);
} catch (error) { } catch (error) {
// Ignore error // Ignore error
} }

View File

@@ -1,85 +0,0 @@
<script setup>
import { computed, ref } from 'vue';
import InitialsAvatar from './InitialsAvatar.vue';
const props = defineProps({
src: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
});
const emit = defineEmits(['change', 'delete']);
const hasImageLoaded = ref(false);
const imageLoadedError = ref(false);
const fileInputRef = ref(null);
const shouldShowImage = computed(() => props.src && !imageLoadedError.value);
const onImageLoadError = () => {
imageLoadedError.value = true;
};
const onImageLoad = () => {
hasImageLoaded.value = true;
imageLoadedError.value = false;
};
const openFileInput = () => {
fileInputRef.value.click();
};
const onImageUpload = event => {
const [file] = event.target.files;
emit('change', {
file,
url: file ? URL.createObjectURL(file) : null,
});
};
const onAvatarDelete = () => {
emit('delete');
};
</script>
<template>
<div class="relative rounded-xl h-[72px] w-[72px] cursor-pointe group">
<img
v-if="shouldShowImage"
class="rounded-xl h-[72px] w-[72px]"
:alt="name"
:src="src"
draggable="false"
@load="onImageLoad"
@error="onImageLoadError"
/>
<InitialsAvatar v-else-if="!shouldShowImage" :name="name" :size="72" />
<input
ref="fileInputRef"
type="file"
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
hidden
@change="onImageUpload"
/>
<div class="hidden group-hover:block">
<button
v-if="src"
class="absolute z-10 flex items-center justify-center w-6 h-6 p-1 border border-white rounded-full select-none dark:border-ash-75 reset-base -top-2 -right-2 bg-ash-300"
@click="onAvatarDelete"
>
<fluent-icon icon="dismiss" size="16" class="text-ash-900" />
</button>
<button
class="reset-base absolute h-[72px] w-[72px] top-0 left-0 rounded-xl select-none flex items-center justify-center bg-modal-backdrop-dark dark:bg-modal-backdrop-dark"
@click="openFileInput"
>
<fluent-icon icon="avatar-upload" size="32" class="text-white" />
</button>
</div>
</div>
</template>

View File

@@ -0,0 +1 @@
json.partial! 'api/v1/models/user', formats: [:json], resource: @user

View File

@@ -180,6 +180,8 @@ RSpec.describe 'Profile API', type: :request do
as: :json as: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
json_response = response.parsed_body
expect(json_response['avatar_url']).to be_empty
end end
end end
end end