98 lines
2.6 KiB
TypeScript
98 lines
2.6 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { introspectAccessToken } from '@/lib/api/auth';
|
|
import { iamUser } from '@/lib/api/iam';
|
|
import type { IamUser } from '@/lib/api/types/user';
|
|
import { useAuthStore } from '@/stores/auth-store';
|
|
import { useTenantStore } from '@/stores/tenant-store';
|
|
|
|
type UserProfileState = {
|
|
profile: IamUser | null;
|
|
userSub: string | null;
|
|
loading: boolean;
|
|
label: string;
|
|
};
|
|
|
|
function displayLabel(profile: IamUser | null, userSub: string | null, loading: boolean): string {
|
|
if (loading) return '加载中…';
|
|
if (profile) {
|
|
const n = profile.real_name?.trim() || profile.user_name?.trim();
|
|
if (n) return n;
|
|
}
|
|
if (userSub) return userSub.length > 12 ? `${userSub.slice(0, 10)}…` : userSub;
|
|
return '用户';
|
|
}
|
|
|
|
/** 从 UserMenu 提取的用户资料获取逻辑 */
|
|
export function useUserProfile(): UserProfileState {
|
|
const accessToken = useAuthStore((s) => s.accessToken);
|
|
const [profile, setProfile] = useState<IamUser | null>(null);
|
|
const [userSub, setUserSub] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!accessToken) {
|
|
setProfile(null);
|
|
setUserSub(null);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
setLoading(true);
|
|
setProfile(null);
|
|
setUserSub(null);
|
|
|
|
(async () => {
|
|
try {
|
|
const intro = await introspectAccessToken(accessToken);
|
|
if (cancelled) return;
|
|
if (!intro.active || !intro.sub) {
|
|
setUserSub(null);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
setUserSub(intro.sub);
|
|
try {
|
|
const u = await iamUser.get(intro.sub);
|
|
if (!cancelled) {
|
|
setProfile(u);
|
|
useTenantStore.getState().hydrateFromUserTenant(u.tenant_id);
|
|
}
|
|
} catch {
|
|
if (!cancelled) setProfile(null);
|
|
}
|
|
} catch {
|
|
if (!cancelled) {
|
|
setUserSub(null);
|
|
setProfile(null);
|
|
}
|
|
} finally {
|
|
if (!cancelled) setLoading(false);
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [accessToken]);
|
|
|
|
return {
|
|
profile,
|
|
userSub,
|
|
loading,
|
|
label: displayLabel(profile, userSub, loading),
|
|
};
|
|
}
|
|
|
|
export function avatarInitials(profile: IamUser | null, userSub: string | null): string {
|
|
const name = profile?.real_name?.trim() || profile?.user_name?.trim();
|
|
if (name) {
|
|
const arr = [...name];
|
|
if (arr.length >= 2) return (arr[0] + arr[1]).toUpperCase();
|
|
return name.slice(0, 2).toUpperCase();
|
|
}
|
|
if (userSub) return userSub.replace(/-/g, '').slice(0, 2).toUpperCase() || '?';
|
|
return '?';
|
|
}
|