anotherath's picture
update space and room
57f5158
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import SharedFile from "./SharedFile";
import { getUserColor } from "../../utils/userColor";
import { dmService } from "../../services/dm.service";
import { updateUserProfile } from "../../store/slices/dmSlice";
import {
FiMail,
FiFileText,
FiInbox,
FiSearch,
FiMousePointer,
FiMessageCircle,
FiZap,
FiUsers,
} from "react-icons/fi";
function getInitials(name) {
if (!name) return "?";
return name
.split(" ")
.map((n) => n[0])
.join("")
.slice(0, 2)
.toUpperCase();
}
function isEmoji(str) {
if (!str) return false;
return /\p{Emoji}/u.test(str) && str.length <= 2;
}
function UserAvatar({ name, avatarUrl, isOnline, isDark, isBot, color }) {
const userColor = getUserColor(name, color);
const textColor = isDark ? "var(--bg-surface)" : "#fff";
const avatarEmoji = isEmoji(avatarUrl) ? avatarUrl : null;
const imageUrl = avatarUrl && !avatarEmoji ? avatarUrl : null;
const [imgError, setImgError] = useState(false);
return (
<div className="relative shrink-0">
<div
className="w-16 h-16 rounded-lg flex items-center justify-center text-2xl font-semibold overflow-hidden"
style={{
background: userColor,
color: textColor,
}}
>
{avatarEmoji ? (
<span className="text-3xl">{avatarEmoji}</span>
) : imageUrl && !imgError ? (
<img
src={imageUrl}
alt={name}
className="w-full h-full object-cover"
onError={() => setImgError(true)}
/>
) : (
getInitials(name)
)}
</div>
</div>
);
}
function DMProfile({ isDark, dmUser }) {
const dispatch = useDispatch();
const onlineUsers = useSelector((state) => state.dm.onlineUsers);
const [profileColor, setProfileColor] = useState(null);
const isOnlineRealtime = dmUser?.id && onlineUsers.includes(String(dmUser.id));
console.log("[DMProfile] dmUser.id:", dmUser?.id, "onlineUsers:", onlineUsers, "isOnlineRealtime:", isOnlineRealtime, "dmUser.isOnline:", dmUser?.isOnline);
const dmUserOnline = isOnlineRealtime || dmUser?.isOnline || false;
useEffect(() => {
if (!dmUser) {
setProfileColor(null);
return;
}
if (dmUser.color) {
setProfileColor(dmUser.color);
return;
}
let mounted = true;
dmService
.getUserProfile(dmUser.id)
.then(({ data }) => {
const color = data?.user?.color || data?.color || null;
const displayName =
data?.user?.display_name || data?.display_name || null;
const avatarUrl = data?.user?.avatar_url || data?.avatar_url || null;
if (mounted && color) {
setProfileColor(color);
}
// Update Redux store with user profile info
if (color || displayName || avatarUrl) {
dispatch(
updateUserProfile({
userId: dmUser.id,
updates: {
...(color && { color }),
...(displayName && { display_name: displayName }),
...(avatarUrl && { avatar_url: avatarUrl }),
},
}),
);
}
})
.catch(() => {});
return () => {
mounted = false;
};
}, [dmUser]);
const effectiveColor = dmUser?.color || profileColor || null;
if (!dmUser) {
return (
<div
className="w-60 min-w-60 flex flex-col h-screen overflow-y-auto border-l"
style={{
background: "var(--bg-surface-secondary)",
borderColor: "var(--border-primary)",
}}
>
<div className="p-4 pt-5">
<div
className="text-sm font-semibold uppercase tracking-wider mb-8"
style={{ color: "var(--text-primary)" }}
>
Hướng dẫn
</div>
<div className="flex flex-col gap-4">
<div className="flex gap-3 items-center">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0"
style={{
background: isDark
? "rgba(255,255,255,0.06)"
: "rgba(0,0,0,0.04)",
color: "var(--primary)",
}}
>
<FiSearch size={15} />
</div>
<div>
<div
className="text-sm font-medium"
style={{ color: "var(--text-primary)" }}
>
Tìm kiếm bạn bè
</div>
<div
className="text-xs mt-0.5"
style={{ color: "var(--text-muted)" }}
>
Nhập tên hoặc email để tìm
</div>
</div>
</div>
<div className="flex gap-3 items-center">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0"
style={{
background: isDark
? "rgba(255,255,255,0.06)"
: "rgba(0,0,0,0.04)",
color: "var(--primary)",
}}
>
<FiMousePointer size={15} />
</div>
<div>
<div
className="text-sm font-medium"
style={{ color: "var(--text-primary)" }}
>
Chọn để nhắn tin
</div>
<div
className="text-xs mt-0.5"
style={{ color: "var(--text-muted)" }}
>
Click vào tên để bắt đầu trò chuyện
</div>
</div>
</div>
<div className="flex gap-3 items-center">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0"
style={{
background: isDark
? "rgba(255,255,255,0.06)"
: "rgba(0,0,0,0.04)",
color: "var(--primary)",
}}
>
<FiUsers size={15} />
</div>
<div>
<div
className="text-sm font-medium"
style={{ color: "var(--text-primary)" }}
>
Tham gia space học tập
</div>
<div
className="text-xs mt-0.5"
style={{ color: "var(--text-muted)" }}
>
Vào các nhóm môn học để trao đổi
</div>
</div>
</div>
<div className="flex gap-3 items-center">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0"
style={{
background: isDark
? "rgba(255,255,255,0.06)"
: "rgba(0,0,0,0.04)",
color: "var(--primary)",
}}
>
<FiZap size={15} />
</div>
<div>
<div
className="text-sm font-medium"
style={{ color: "var(--text-primary)" }}
>
Hỏi StudyBot
</div>
<div
className="text-xs mt-0.5"
style={{ color: "var(--text-muted)" }}
>
Trợ lý AI sẵn sàng giải đáp 24/7
</div>
</div>
</div>
<div className="flex gap-3 items-center">
<div
className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0"
style={{
background: isDark
? "rgba(255,255,255,0.06)"
: "rgba(0,0,0,0.04)",
color: "var(--primary)",
}}
>
<FiMessageCircle size={15} />
</div>
<div>
<div
className="text-sm font-medium"
style={{ color: "var(--text-primary)" }}
>
Kết nối & học tập
</div>
<div
className="text-xs mt-0.5"
style={{ color: "var(--text-muted)" }}
>
Trao đổi bài tập và tài liệu
</div>
</div>
</div>
</div>
</div>
</div>
);
}
return (
<div
className="w-60 min-w-60 flex flex-col h-screen overflow-y-auto border-l"
style={{
background: "var(--bg-surface-secondary)",
borderColor: "var(--border-primary)",
}}
>
{/* Avatar + Name */}
<div className="p-4 text-center">
<div className="flex justify-center">
<UserAvatar
name={dmUser.name}
avatarUrl={dmUser.avatar}
color={effectiveColor}
isOnline={dmUserOnline}
isDark={isDark}
isBot={dmUser.isBot}
/>
</div>
<div
className="text-base font-semibold mt-3"
style={{ color: "var(--text-primary)" }}
>
{dmUser.name}
</div>
<div
className="text-xs mt-1"
style={{ color: "var(--text-secondary)" }}
>
{dmUserOnline ? "Đang hoạt động" : "Ngoại tuyến"}
</div>
</div>
{/* Info */}
<div
className="mx-4 py-3 border-t"
style={{ borderColor: "var(--border-primary)" }}
>
{dmUser.email && (
<div className="flex items-center gap-2 py-1.5">
<FiMail size={14} style={{ color: "var(--text-muted)" }} />
<span
className="text-sm truncate"
style={{ color: "var(--text-secondary)" }}
>
{dmUser.email}
</span>
</div>
)}
{dmUser.bio && (
<div className="flex items-start gap-2 py-1.5">
<FiFileText size={14} style={{ color: "var(--text-muted)" }} />
<span
className="text-sm"
style={{ color: "var(--text-secondary)" }}
>
{dmUser.bio}
</span>
</div>
)}
</div>
{/* Shared Files */}
<div
className="mx-4 mt-2 py-3 border-t"
style={{ borderColor: "var(--border-primary)" }}
>
<div
className="text-xs font-medium mb-2"
style={{ color: "var(--text-muted)" }}
>
Tệp chia sẻ
</div>
{dmUser.sharedFiles && dmUser.sharedFiles.length > 0 ? (
dmUser.sharedFiles.map((file, idx) => (
<SharedFile
key={idx}
isDark={isDark}
fileName={file.name}
time={file.time}
type={file.type}
/>
))
) : (
<div className="flex flex-col items-center justify-center py-6 text-center">
<FiInbox size={24} style={{ color: "var(--text-muted)" }} />
<div
className="text-xs mt-2"
style={{ color: "var(--text-muted)" }}
>
Chưa có tệp nào
</div>
</div>
)}
</div>
</div>
);
}
export default DMProfile;