platform/lms-adm-fe release promotion snapshot

This commit is contained in:
2026-07-09 06:30:14 +00:00
parent 3b4bed7089
commit 61dd5d25e7
+9 -35
View File
@@ -179,7 +179,7 @@ export function InstructorChatPanel({ session, open, initialThreadId = null, ini
) : threads.length > 0 ? (
<div className="space-y-2.5">
{threads.map((thread) => (
<ThreadCard key={thread.id} accessToken={session.accessToken} thread={thread} onOpen={() => handleOpenThread(thread.id)} />
<ThreadCard key={thread.id} thread={thread} onOpen={() => handleOpenThread(thread.id)} />
))}
</div>
) : (
@@ -192,7 +192,7 @@ export function InstructorChatPanel({ session, open, initialThreadId = null, ini
);
}
function ThreadCard({ accessToken, thread, onOpen }: { accessToken: string; thread: InstructorChatThread; onOpen: () => void }) {
function ThreadCard({ thread, onOpen }: { thread: InstructorChatThread; onOpen: () => void }) {
return (
<button
type="button"
@@ -201,7 +201,7 @@ function ThreadCard({ accessToken, thread, onOpen }: { accessToken: string; thre
onClick={onOpen}
>
<div className="flex items-start gap-3">
<StudentAvatar accessToken={accessToken} thread={thread} />
<StudentAvatar thread={thread} />
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<p className="truncate text-[14px] font-black text-[#111827]">{thread.studentName}</p>
@@ -222,43 +222,17 @@ function ThreadCard({ accessToken, thread, onOpen }: { accessToken: string; thre
);
}
function StudentAvatar({ accessToken, thread }: { accessToken: string; thread: InstructorChatThread }) {
const [avatarSource, setAvatarSource] = useState<string | null>(null);
function StudentAvatar({ thread }: { thread: InstructorChatThread }) {
const [avatarFailed, setAvatarFailed] = useState(false);
useEffect(() => {
if (!thread.studentAvatarUrl) {
setAvatarSource(null);
return;
}
let revoked = false;
let objectUrl: string | null = null;
fetch(thread.studentAvatarUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => (response.ok ? response.blob() : null))
.then((blob) => {
if (!blob || revoked) return;
objectUrl = URL.createObjectURL(blob);
setAvatarSource(objectUrl);
})
.catch(() => {
if (!revoked) setAvatarSource(null);
});
return () => {
revoked = true;
if (objectUrl) URL.revokeObjectURL(objectUrl);
};
}, [accessToken, thread.studentAvatarUrl]);
setAvatarFailed(false);
}, [thread.studentAvatarUrl]);
return (
<span className="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-full bg-[#eef2ff] text-[#4f46e5] transition group-hover:bg-[#4f46e5] group-hover:text-white">
{avatarSource ? (
<img className="h-full w-full object-cover" src={avatarSource} alt="" />
{thread.studentAvatarUrl && !avatarFailed ? (
<img className="h-full w-full object-cover" src={thread.studentAvatarUrl} alt="" onError={() => setAvatarFailed(true)} />
) : (
<UserRound size={18} aria-hidden="true" />
)}