Profile picture display has been standardized across the board.
This commit is contained in:
@@ -188,6 +188,8 @@ export function mapBoardToStages(
|
||||
stageId: deal.stageId,
|
||||
currencyUnit: currencyUnitLabel(deal.currency),
|
||||
assigneeNames,
|
||||
ownerId: deal.owner?.id ?? null,
|
||||
assigneeIds: (deal.assignees ?? []).map((a) => a.id),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { reactive } from 'vue';
|
||||
import type { OrgUserDto } from '@mento/shared';
|
||||
import { api } from '@/lib/api';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
export const DEFAULT_USER_AVATAR = '/brand/user-avatar.png';
|
||||
|
||||
/** userId → آیا عکس دارد (از فهرست کاربران سازمان) */
|
||||
const hasAvatarById = reactive<Record<string, boolean>>({});
|
||||
/** userId → object URL عکس */
|
||||
const urlById = reactive<Record<string, string>>({});
|
||||
const pending = new Map<string, Promise<void>>();
|
||||
let usersLoad: Promise<void> | null = null;
|
||||
let usersScope: string | null = null;
|
||||
|
||||
function resetCache() {
|
||||
for (const url of Object.values(urlById)) URL.revokeObjectURL(url);
|
||||
for (const key of Object.keys(urlById)) delete urlById[key];
|
||||
for (const key of Object.keys(hasAvatarById)) delete hasAvatarById[key];
|
||||
pending.clear();
|
||||
usersLoad = null;
|
||||
}
|
||||
|
||||
function ensureScope() {
|
||||
const auth = useAuthStore();
|
||||
const scope = auth.user ? `${auth.user.id}:${auth.user.organizationId}` : null;
|
||||
if (scope !== usersScope) {
|
||||
resetCache();
|
||||
usersScope = scope;
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
|
||||
function loadUserFlags(): Promise<void> {
|
||||
if (usersLoad) return usersLoad;
|
||||
usersLoad = api
|
||||
.get<OrgUserDto[]>('/users')
|
||||
.then(({ data }) => {
|
||||
for (const u of data ?? []) hasAvatarById[u.id] = Boolean(u.hasAvatar);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return usersLoad;
|
||||
}
|
||||
|
||||
function fetchAvatar(userId: string): Promise<void> {
|
||||
const existing = pending.get(userId);
|
||||
if (existing) return existing;
|
||||
const job = api
|
||||
.get<Blob>(`/users/${encodeURIComponent(userId)}/avatar`, { responseType: 'blob' })
|
||||
.then(({ data }) => {
|
||||
if (!data || data.size === 0 || data.type.includes('json')) return;
|
||||
urlById[userId] = URL.createObjectURL(data);
|
||||
})
|
||||
.catch(() => {
|
||||
hasAvatarById[userId] = false;
|
||||
});
|
||||
pending.set(userId, job);
|
||||
return job;
|
||||
}
|
||||
|
||||
/** اطلاع از وضعیت عکس کاربرانی که API برگردانده (مثلاً اعضای گفتگو). */
|
||||
export function primeUserAvatar(userId: string, hasAvatar: boolean | undefined) {
|
||||
ensureScope();
|
||||
if (hasAvatar === undefined) return;
|
||||
hasAvatarById[userId] = hasAvatar;
|
||||
}
|
||||
|
||||
/** آدرس عکس پروفایل کاربر؛ تا بارگذاری نشده عکس پیشفرض برمیگردد. */
|
||||
export function userAvatarSrc(userId: string | null | undefined): string {
|
||||
if (!userId) return DEFAULT_USER_AVATAR;
|
||||
const auth = ensureScope();
|
||||
if (auth.user?.id === userId) return auth.avatarSrc;
|
||||
|
||||
const url = urlById[userId];
|
||||
if (url) return url;
|
||||
|
||||
const flag = hasAvatarById[userId];
|
||||
if (flag === undefined) {
|
||||
void loadUserFlags().then(() => {
|
||||
if (hasAvatarById[userId]) void fetchAvatar(userId);
|
||||
});
|
||||
} else if (flag) {
|
||||
void fetchAvatar(userId);
|
||||
}
|
||||
return DEFAULT_USER_AVATAR;
|
||||
}
|
||||
|
||||
/** بعد از تغییر عکس یک کاربر (مثلاً در تنظیمات تیم) کش را تازه کن. */
|
||||
export function invalidateUserAvatar(userId: string) {
|
||||
const url = urlById[userId];
|
||||
if (url) URL.revokeObjectURL(url);
|
||||
delete urlById[userId];
|
||||
delete hasAvatarById[userId];
|
||||
pending.delete(userId);
|
||||
usersLoad = null;
|
||||
}
|
||||
Reference in New Issue
Block a user