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:
@@ -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
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
1
app/views/api/v1/profiles/avatar.json.jbuilder
Normal file
1
app/views/api/v1/profiles/avatar.json.jbuilder
Normal file
@@ -0,0 +1 @@
|
|||||||
|
json.partial! 'api/v1/models/user', formats: [:json], resource: @user
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user