Spaces:
Sleeping
Sleeping
Update web/src/components/sidebar/LeftSidebar.tsx
Browse files
web/src/components/sidebar/LeftSidebar.tsx
CHANGED
|
@@ -61,13 +61,9 @@ type Props = {
|
|
| 61 |
selectedCourse: string;
|
| 62 |
availableCourses: CourseDirectoryItem[];
|
| 63 |
|
| 64 |
-
// invite flow (optional)
|
| 65 |
onInviteGroupMembers?: () => void;
|
| 66 |
|
| 67 |
-
// rename group name (optional -> fallback localStorage)
|
| 68 |
onRenameGroupName?: (workspaceId: string, newName: string) => Promise<void> | void;
|
| 69 |
-
|
| 70 |
-
// rename group number (optional -> fallback localStorage)
|
| 71 |
onRenameGroupNo?: (workspaceId: string, newNo: number) => Promise<void> | void;
|
| 72 |
};
|
| 73 |
|
|
@@ -165,7 +161,7 @@ export function LeftSidebar(props: Props) {
|
|
| 165 |
return (courseInfo as any)?.name ?? (selectedCourse || "Course");
|
| 166 |
}, [courseInfo, selectedCourse]);
|
| 167 |
|
| 168 |
-
// ---------
|
| 169 |
const wsGroupNo = useMemo(() => {
|
| 170 |
const ws: any = currentWorkspace as any;
|
| 171 |
return (
|
|
@@ -180,10 +176,9 @@ export function LeftSidebar(props: Props) {
|
|
| 180 |
|
| 181 |
const wsGroupName = useMemo(() => {
|
| 182 |
const ws: any = currentWorkspace as any;
|
| 183 |
-
return pickAny(ws, ["groupName", "name", "title"]) || "";
|
| 184 |
}, [currentWorkspace]);
|
| 185 |
|
| 186 |
-
// local storage keys
|
| 187 |
const groupNameStorageKey = useMemo(
|
| 188 |
() => `clare_group_name__${currentWorkspaceId}`,
|
| 189 |
[currentWorkspaceId]
|
|
@@ -193,30 +188,29 @@ export function LeftSidebar(props: Props) {
|
|
| 193 |
[currentWorkspaceId]
|
| 194 |
);
|
| 195 |
|
| 196 |
-
// state: group name
|
| 197 |
-
const [groupName, setGroupName] = useState<string>(wsGroupName
|
| 198 |
const [editingGroupName, setEditingGroupName] = useState(false);
|
| 199 |
-
const [draftGroupName, setDraftGroupName] = useState<string>(wsGroupName
|
| 200 |
|
| 201 |
-
// state: group no
|
| 202 |
const [groupNo, setGroupNo] = useState<number>(toIntOrFallback(wsGroupNo, 1));
|
| 203 |
const [editingGroupNo, setEditingGroupNo] = useState(false);
|
| 204 |
const [draftGroupNo, setDraftGroupNo] = useState<string>(String(toIntOrFallback(wsGroupNo, 1)));
|
| 205 |
|
| 206 |
-
// hydrate from localStorage (prefer persisted values)
|
| 207 |
useEffect(() => {
|
| 208 |
-
// group name
|
| 209 |
const storedName =
|
| 210 |
typeof window !== "undefined" ? window.localStorage.getItem(groupNameStorageKey) : null;
|
| 211 |
-
const name = storedName && storedName.trim() ? storedName :
|
| 212 |
setGroupName(name);
|
| 213 |
setDraftGroupName(name);
|
| 214 |
setEditingGroupName(false);
|
| 215 |
|
| 216 |
-
// group no
|
| 217 |
const storedNo =
|
| 218 |
typeof window !== "undefined" ? window.localStorage.getItem(groupNoStorageKey) : null;
|
| 219 |
-
const no = storedNo && storedNo.trim()
|
|
|
|
|
|
|
| 220 |
setGroupNo(no);
|
| 221 |
setDraftGroupNo(String(no));
|
| 222 |
setEditingGroupNo(false);
|
|
@@ -232,9 +226,7 @@ export function LeftSidebar(props: Props) {
|
|
| 232 |
try {
|
| 233 |
await onRenameGroupName(currentWorkspaceId, next);
|
| 234 |
return;
|
| 235 |
-
} catch {
|
| 236 |
-
// fallback
|
| 237 |
-
}
|
| 238 |
}
|
| 239 |
try {
|
| 240 |
window.localStorage.setItem(groupNameStorageKey, next);
|
|
@@ -256,9 +248,7 @@ export function LeftSidebar(props: Props) {
|
|
| 256 |
try {
|
| 257 |
await onRenameGroupNo(currentWorkspaceId, nextNo);
|
| 258 |
return;
|
| 259 |
-
} catch {
|
| 260 |
-
// fallback
|
| 261 |
-
}
|
| 262 |
}
|
| 263 |
try {
|
| 264 |
window.localStorage.setItem(groupNoStorageKey, String(nextNo));
|
|
@@ -290,7 +280,6 @@ export function LeftSidebar(props: Props) {
|
|
| 290 |
<div className="h-full w-full flex flex-col min-h-0 bg-white">
|
| 291 |
{/* ================= TOP (non-scroll) ================= */}
|
| 292 |
<div className="flex-shrink-0">
|
| 293 |
-
{/* Welcome */}
|
| 294 |
<div className="px-4 pt-6 pb-6 space-y-3">
|
| 295 |
<div className="text-[34px] leading-tight font-semibold">
|
| 296 |
Welcome, {displayName}!
|
|
@@ -299,12 +288,11 @@ export function LeftSidebar(props: Props) {
|
|
| 299 |
|
| 300 |
<Separator className="bg-[#ECECF1]" />
|
| 301 |
|
| 302 |
-
{/* Course + Group block */}
|
| 303 |
<div className="px-4 pt-10 pb-10 space-y-4">
|
| 304 |
<div className="text-[30px] leading-tight font-semibold">{courseName}</div>
|
| 305 |
|
| 306 |
{!isTeamSpace ? (
|
| 307 |
-
// My Space
|
| 308 |
<div className="space-y-3">
|
| 309 |
<div className="text-[30px] leading-tight font-semibold">
|
| 310 |
Group {String(groupNo)}
|
|
@@ -337,10 +325,10 @@ export function LeftSidebar(props: Props) {
|
|
| 337 |
className="h-9"
|
| 338 |
autoFocus
|
| 339 |
/>
|
| 340 |
-
<Button
|
| 341 |
<Check className="w-4 h-4" />
|
| 342 |
</Button>
|
| 343 |
-
<Button
|
| 344 |
<X className="w-4 h-4" />
|
| 345 |
</Button>
|
| 346 |
</div>
|
|
@@ -348,80 +336,69 @@ export function LeftSidebar(props: Props) {
|
|
| 348 |
</div>
|
| 349 |
</div>
|
| 350 |
) : (
|
| 351 |
-
// Team/Group Space
|
| 352 |
<div className="rounded-2xl border bg-white overflow-hidden">
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
<div className="
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
{!editingGroupName ? (
|
| 361 |
-
<>
|
| 362 |
-
<div className="text-[18px] font-medium text-muted-foreground truncate max-w-[180px]">
|
| 363 |
-
{groupName}
|
| 364 |
-
</div>
|
| 365 |
-
<button
|
| 366 |
-
type="button"
|
| 367 |
-
className="inline-flex items-center text-muted-foreground hover:text-foreground"
|
| 368 |
-
onClick={() => setEditingGroupName(true)}
|
| 369 |
-
aria-label="Edit group name"
|
| 370 |
-
>
|
| 371 |
-
<Pencil className="w-4 h-4" />
|
| 372 |
-
</button>
|
| 373 |
-
</>
|
| 374 |
-
) : (
|
| 375 |
-
<div className="flex items-center gap-2 min-w-0">
|
| 376 |
-
<Input
|
| 377 |
-
value={draftGroupName}
|
| 378 |
-
onChange={(e) => setDraftGroupName(e.target.value)}
|
| 379 |
-
onKeyDown={(e) => {
|
| 380 |
-
if (e.key === "Enter") saveGroupName();
|
| 381 |
-
if (e.key === "Escape") cancelGroupName();
|
| 382 |
-
}}
|
| 383 |
-
className="h-8 w-[180px]"
|
| 384 |
-
autoFocus
|
| 385 |
-
/>
|
| 386 |
-
<Button type="button" size="icon" variant="ghost" className="h-8 w-8" onClick={saveGroupName}>
|
| 387 |
-
<Check className="w-4 h-4" />
|
| 388 |
-
</Button>
|
| 389 |
-
<Button type="button" size="icon" variant="ghost" className="h-8 w-8" onClick={cancelGroupName}>
|
| 390 |
-
<X className="w-4 h-4" />
|
| 391 |
-
</Button>
|
| 392 |
</div>
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
-
|
| 398 |
-
<div className="px-4 pb-3">
|
| 399 |
<div className="flex items-center justify-between gap-3">
|
| 400 |
-
<div className="flex items-center gap-2
|
| 401 |
<Users className="w-4 h-4 text-muted-foreground" />
|
| 402 |
|
| 403 |
{!editingGroupNo ? (
|
| 404 |
-
<>
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
>
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
</>
|
| 420 |
) : (
|
| 421 |
<div className="flex items-center gap-2">
|
| 422 |
-
<div className="text-[16px] text-foreground font-medium
|
| 423 |
-
Group # ({memberCount})
|
| 424 |
-
</div>
|
| 425 |
<Input
|
| 426 |
value={draftGroupNo}
|
| 427 |
onChange={(e) => setDraftGroupNo(e.target.value)}
|
|
@@ -429,13 +406,14 @@ export function LeftSidebar(props: Props) {
|
|
| 429 |
if (e.key === "Enter") saveGroupNo();
|
| 430 |
if (e.key === "Escape") cancelGroupNo();
|
| 431 |
}}
|
| 432 |
-
className="h-8 w-[
|
| 433 |
autoFocus
|
| 434 |
/>
|
| 435 |
-
<
|
|
|
|
| 436 |
<Check className="w-4 h-4" />
|
| 437 |
</Button>
|
| 438 |
-
<Button
|
| 439 |
<X className="w-4 h-4" />
|
| 440 |
</Button>
|
| 441 |
</div>
|
|
@@ -474,24 +452,18 @@ export function LeftSidebar(props: Props) {
|
|
| 474 |
key={(member as any).id}
|
| 475 |
className="flex items-center gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors"
|
| 476 |
>
|
| 477 |
-
{/* Avatar */}
|
| 478 |
<div
|
| 479 |
className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
|
| 480 |
isAI ? "overflow-hidden bg-white" : "bg-muted"
|
| 481 |
}`}
|
| 482 |
>
|
| 483 |
{isAI ? (
|
| 484 |
-
<img
|
| 485 |
-
src={clareAvatar}
|
| 486 |
-
alt="Clare"
|
| 487 |
-
className="w-full h-full object-cover"
|
| 488 |
-
/>
|
| 489 |
) : (
|
| 490 |
<span className="text-sm">{initials}</span>
|
| 491 |
)}
|
| 492 |
</div>
|
| 493 |
|
| 494 |
-
{/* Member Info */}
|
| 495 |
<div className="flex-1 min-w-0">
|
| 496 |
<div className="flex items-center gap-2">
|
| 497 |
<p className="text-sm truncate text-foreground">{name}</p>
|
|
@@ -504,7 +476,6 @@ export function LeftSidebar(props: Props) {
|
|
| 504 |
<p className="text-xs text-muted-foreground truncate">{email}</p>
|
| 505 |
</div>
|
| 506 |
|
| 507 |
-
{/* Online Status */}
|
| 508 |
<div className="w-2 h-2 rounded-full bg-green-500 flex-shrink-0" title="Online" />
|
| 509 |
</div>
|
| 510 |
);
|
|
|
|
| 61 |
selectedCourse: string;
|
| 62 |
availableCourses: CourseDirectoryItem[];
|
| 63 |
|
|
|
|
| 64 |
onInviteGroupMembers?: () => void;
|
| 65 |
|
|
|
|
| 66 |
onRenameGroupName?: (workspaceId: string, newName: string) => Promise<void> | void;
|
|
|
|
|
|
|
| 67 |
onRenameGroupNo?: (workspaceId: string, newNo: number) => Promise<void> | void;
|
| 68 |
};
|
| 69 |
|
|
|
|
| 161 |
return (courseInfo as any)?.name ?? (selectedCourse || "Course");
|
| 162 |
}, [courseInfo, selectedCourse]);
|
| 163 |
|
| 164 |
+
// --------- Workspace-derived group fields ---------
|
| 165 |
const wsGroupNo = useMemo(() => {
|
| 166 |
const ws: any = currentWorkspace as any;
|
| 167 |
return (
|
|
|
|
| 176 |
|
| 177 |
const wsGroupName = useMemo(() => {
|
| 178 |
const ws: any = currentWorkspace as any;
|
| 179 |
+
return pickAny(ws, ["groupName", "name", "title"]) || "My Group";
|
| 180 |
}, [currentWorkspace]);
|
| 181 |
|
|
|
|
| 182 |
const groupNameStorageKey = useMemo(
|
| 183 |
() => `clare_group_name__${currentWorkspaceId}`,
|
| 184 |
[currentWorkspaceId]
|
|
|
|
| 188 |
[currentWorkspaceId]
|
| 189 |
);
|
| 190 |
|
| 191 |
+
// state: group name
|
| 192 |
+
const [groupName, setGroupName] = useState<string>(wsGroupName);
|
| 193 |
const [editingGroupName, setEditingGroupName] = useState(false);
|
| 194 |
+
const [draftGroupName, setDraftGroupName] = useState<string>(wsGroupName);
|
| 195 |
|
| 196 |
+
// state: group no
|
| 197 |
const [groupNo, setGroupNo] = useState<number>(toIntOrFallback(wsGroupNo, 1));
|
| 198 |
const [editingGroupNo, setEditingGroupNo] = useState(false);
|
| 199 |
const [draftGroupNo, setDraftGroupNo] = useState<string>(String(toIntOrFallback(wsGroupNo, 1)));
|
| 200 |
|
|
|
|
| 201 |
useEffect(() => {
|
|
|
|
| 202 |
const storedName =
|
| 203 |
typeof window !== "undefined" ? window.localStorage.getItem(groupNameStorageKey) : null;
|
| 204 |
+
const name = storedName && storedName.trim() ? storedName : wsGroupName;
|
| 205 |
setGroupName(name);
|
| 206 |
setDraftGroupName(name);
|
| 207 |
setEditingGroupName(false);
|
| 208 |
|
|
|
|
| 209 |
const storedNo =
|
| 210 |
typeof window !== "undefined" ? window.localStorage.getItem(groupNoStorageKey) : null;
|
| 211 |
+
const no = storedNo && storedNo.trim()
|
| 212 |
+
? toIntOrFallback(storedNo, toIntOrFallback(wsGroupNo, 1))
|
| 213 |
+
: toIntOrFallback(wsGroupNo, 1);
|
| 214 |
setGroupNo(no);
|
| 215 |
setDraftGroupNo(String(no));
|
| 216 |
setEditingGroupNo(false);
|
|
|
|
| 226 |
try {
|
| 227 |
await onRenameGroupName(currentWorkspaceId, next);
|
| 228 |
return;
|
| 229 |
+
} catch {}
|
|
|
|
|
|
|
| 230 |
}
|
| 231 |
try {
|
| 232 |
window.localStorage.setItem(groupNameStorageKey, next);
|
|
|
|
| 248 |
try {
|
| 249 |
await onRenameGroupNo(currentWorkspaceId, nextNo);
|
| 250 |
return;
|
| 251 |
+
} catch {}
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
try {
|
| 254 |
window.localStorage.setItem(groupNoStorageKey, String(nextNo));
|
|
|
|
| 280 |
<div className="h-full w-full flex flex-col min-h-0 bg-white">
|
| 281 |
{/* ================= TOP (non-scroll) ================= */}
|
| 282 |
<div className="flex-shrink-0">
|
|
|
|
| 283 |
<div className="px-4 pt-6 pb-6 space-y-3">
|
| 284 |
<div className="text-[34px] leading-tight font-semibold">
|
| 285 |
Welcome, {displayName}!
|
|
|
|
| 288 |
|
| 289 |
<Separator className="bg-[#ECECF1]" />
|
| 290 |
|
|
|
|
| 291 |
<div className="px-4 pt-10 pb-10 space-y-4">
|
| 292 |
<div className="text-[30px] leading-tight font-semibold">{courseName}</div>
|
| 293 |
|
| 294 |
{!isTeamSpace ? (
|
| 295 |
+
// My Space unchanged
|
| 296 |
<div className="space-y-3">
|
| 297 |
<div className="text-[30px] leading-tight font-semibold">
|
| 298 |
Group {String(groupNo)}
|
|
|
|
| 325 |
className="h-9"
|
| 326 |
autoFocus
|
| 327 |
/>
|
| 328 |
+
<Button size="icon" variant="ghost" className="h-9 w-9" onClick={saveGroupName}>
|
| 329 |
<Check className="w-4 h-4" />
|
| 330 |
</Button>
|
| 331 |
+
<Button size="icon" variant="ghost" className="h-9 w-9" onClick={cancelGroupName}>
|
| 332 |
<X className="w-4 h-4" />
|
| 333 |
</Button>
|
| 334 |
</div>
|
|
|
|
| 336 |
</div>
|
| 337 |
</div>
|
| 338 |
) : (
|
| 339 |
+
// Team/Group Space (your requested structure)
|
| 340 |
<div className="rounded-2xl border bg-white overflow-hidden">
|
| 341 |
+
<div className="px-4 pt-4 pb-3 space-y-3">
|
| 342 |
+
{/* Line 1: group name in BLACK + pencil (editable) */}
|
| 343 |
+
{!editingGroupName ? (
|
| 344 |
+
<div className="flex items-center gap-2">
|
| 345 |
+
<div className="text-[18px] font-semibold text-foreground truncate">
|
| 346 |
+
{groupName || "My Group"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
</div>
|
| 348 |
+
<button
|
| 349 |
+
type="button"
|
| 350 |
+
className="inline-flex items-center text-muted-foreground hover:text-foreground"
|
| 351 |
+
onClick={() => setEditingGroupName(true)}
|
| 352 |
+
aria-label="Edit group name"
|
| 353 |
+
>
|
| 354 |
+
<Pencil className="w-4 h-4" />
|
| 355 |
+
</button>
|
| 356 |
+
</div>
|
| 357 |
+
) : (
|
| 358 |
+
<div className="flex items-center gap-2">
|
| 359 |
+
<Input
|
| 360 |
+
value={draftGroupName}
|
| 361 |
+
onChange={(e) => setDraftGroupName(e.target.value)}
|
| 362 |
+
onKeyDown={(e) => {
|
| 363 |
+
if (e.key === "Enter") saveGroupName();
|
| 364 |
+
if (e.key === "Escape") cancelGroupName();
|
| 365 |
+
}}
|
| 366 |
+
className="h-8 w-[220px]"
|
| 367 |
+
autoFocus
|
| 368 |
+
/>
|
| 369 |
+
<Button size="icon" variant="ghost" className="h-8 w-8" onClick={saveGroupName}>
|
| 370 |
+
<Check className="w-4 h-4" />
|
| 371 |
+
</Button>
|
| 372 |
+
<Button size="icon" variant="ghost" className="h-8 w-8" onClick={cancelGroupName}>
|
| 373 |
+
<X className="w-4 h-4" />
|
| 374 |
+
</Button>
|
| 375 |
+
</div>
|
| 376 |
+
)}
|
| 377 |
|
| 378 |
+
{/* Line 2: "Group {no} (count)" where {no} is editable via pencil */}
|
|
|
|
| 379 |
<div className="flex items-center justify-between gap-3">
|
| 380 |
+
<div className="flex items-center gap-2">
|
| 381 |
<Users className="w-4 h-4 text-muted-foreground" />
|
| 382 |
|
| 383 |
{!editingGroupNo ? (
|
| 384 |
+
<div className="text-[16px] text-foreground font-medium">
|
| 385 |
+
Group{" "}
|
| 386 |
+
<span className="inline-flex items-center gap-1">
|
| 387 |
+
{groupNo}
|
| 388 |
+
<button
|
| 389 |
+
type="button"
|
| 390 |
+
className="inline-flex items-center text-muted-foreground hover:text-foreground"
|
| 391 |
+
onClick={() => setEditingGroupNo(true)}
|
| 392 |
+
aria-label="Edit group number"
|
| 393 |
+
>
|
| 394 |
+
<Pencil className="w-4 h-4" />
|
| 395 |
+
</button>
|
| 396 |
+
</span>{" "}
|
| 397 |
+
({memberCount})
|
| 398 |
+
</div>
|
|
|
|
| 399 |
) : (
|
| 400 |
<div className="flex items-center gap-2">
|
| 401 |
+
<div className="text-[16px] text-foreground font-medium">Group</div>
|
|
|
|
|
|
|
| 402 |
<Input
|
| 403 |
value={draftGroupNo}
|
| 404 |
onChange={(e) => setDraftGroupNo(e.target.value)}
|
|
|
|
| 406 |
if (e.key === "Enter") saveGroupNo();
|
| 407 |
if (e.key === "Escape") cancelGroupNo();
|
| 408 |
}}
|
| 409 |
+
className="h-8 w-[80px]"
|
| 410 |
autoFocus
|
| 411 |
/>
|
| 412 |
+
<div className="text-[16px] text-foreground font-medium">({memberCount})</div>
|
| 413 |
+
<Button size="icon" variant="ghost" className="h-8 w-8" onClick={saveGroupNo}>
|
| 414 |
<Check className="w-4 h-4" />
|
| 415 |
</Button>
|
| 416 |
+
<Button size="icon" variant="ghost" className="h-8 w-8" onClick={cancelGroupNo}>
|
| 417 |
<X className="w-4 h-4" />
|
| 418 |
</Button>
|
| 419 |
</div>
|
|
|
|
| 452 |
key={(member as any).id}
|
| 453 |
className="flex items-center gap-3 p-2 rounded-lg hover:bg-muted/50 transition-colors"
|
| 454 |
>
|
|
|
|
| 455 |
<div
|
| 456 |
className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
|
| 457 |
isAI ? "overflow-hidden bg-white" : "bg-muted"
|
| 458 |
}`}
|
| 459 |
>
|
| 460 |
{isAI ? (
|
| 461 |
+
<img src={clareAvatar} alt="Clare" className="w-full h-full object-cover" />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
) : (
|
| 463 |
<span className="text-sm">{initials}</span>
|
| 464 |
)}
|
| 465 |
</div>
|
| 466 |
|
|
|
|
| 467 |
<div className="flex-1 min-w-0">
|
| 468 |
<div className="flex items-center gap-2">
|
| 469 |
<p className="text-sm truncate text-foreground">{name}</p>
|
|
|
|
| 476 |
<p className="text-xs text-muted-foreground truncate">{email}</p>
|
| 477 |
</div>
|
| 478 |
|
|
|
|
| 479 |
<div className="w-2 h-2 rounded-full bg-green-500 flex-shrink-0" title="Online" />
|
| 480 |
</div>
|
| 481 |
);
|