SarahXia0405 commited on
Commit
574dd1f
·
verified ·
1 Parent(s): a77ae0b

Update web/src/components/sidebar/LeftSidebar.tsx

Browse files
web/src/components/sidebar/LeftSidebar.tsx CHANGED
@@ -1,6 +1,5 @@
1
  // web/src/components/sidebar/LeftSidebar.tsx
2
  import React, { useEffect, useMemo, useState } from "react";
3
- import { Separator } from "../ui/separator";
4
  import { Button } from "../ui/button";
5
  import { Input } from "../ui/input";
6
  import { Badge } from "../ui/badge";
@@ -33,9 +32,9 @@ import type { CourseDirectoryItem } from "../../lib/courseDirectory";
33
 
34
  import clareAvatar from "../../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png";
35
 
36
- // ✅ Theme-compatible divider (light/dark) + guaranteed visibility
37
  const Divider = () => (
38
- <div className="w-full border-t border-border/70 dark:border-border my-2 flex-shrink-0" />
39
  );
40
 
41
  type Props = {
@@ -77,10 +76,7 @@ type Props = {
77
  availableCourses: CourseDirectoryItem[];
78
 
79
  // optional if you already wired backend
80
- onRenameGroupName?: (
81
- workspaceId: string,
82
- newName: string
83
- ) => Promise<void> | void;
84
  onRenameGroupNo?: (workspaceId: string, newNo: number) => Promise<void> | void;
85
  };
86
 
@@ -146,13 +142,11 @@ export function LeftSidebar(props: Props) {
146
  const isMyWorkspace = useMemo(() => {
147
  const ws: any = currentWorkspace as any;
148
 
149
- // 常见 personal 标记(你后端 schema 之后可以对齐其中一个)
150
  if (ws?.isPersonal === true) return true;
151
  if (String(ws?.spaceType || "").toLowerCase().includes("my")) return true;
152
  if (String(ws?.type || "").toLowerCase().includes("my")) return true;
153
  if (String(ws?.kind || "").toLowerCase().includes("my")) return true;
154
 
155
- // 兜底:workspace id/name 里含 my/personal
156
  const id = String(ws?.id || "").toLowerCase();
157
  const name = String(ws?.name || "").toLowerCase();
158
  if (id.includes("my") || id.includes("personal")) return true;
@@ -162,28 +156,12 @@ export function LeftSidebar(props: Props) {
162
  }, [currentWorkspace]);
163
 
164
  const isTeamSpace = useMemo(() => {
165
- // 如果 workspace 明确是 My Space,则强制 false
166
  if (isMyWorkspace) return false;
167
-
168
  const st = String(spaceType || "").toLowerCase();
169
  return st === "group" || st === "team";
170
  }, [spaceType, isMyWorkspace]);
171
 
172
  // --------- CourseInfo resolution ---------
173
- const demoGroupMap: Record<string, { name: string; no: number }> = {
174
- "Introduction to AI": { name: "Foundations", no: 1 },
175
- "Machine Learning": { name: "Model Builders", no: 3 },
176
- "Data Visualization": { name: "Storytellers", no: 2 },
177
- };
178
-
179
- const demoGroup = useMemo(() => {
180
- return demoGroupMap[courseName] ?? {
181
- name: wsGroupName || "My Group",
182
- no: toIntOrFallback(wsGroupNo, 1),
183
- };
184
- }, [courseName, wsGroupName, wsGroupNo]);
185
-
186
-
187
  const courseInfo = useMemo((): CourseDirectoryItem | null => {
188
  const list = Array.isArray(availableCourses) ? availableCourses : [];
189
 
@@ -193,7 +171,7 @@ const demoGroup = useMemo(() => {
193
  const hit =
194
  list.find((c: any) => norm(c.id) === sel) ||
195
  list.find((c: any) => norm(c.name) === sel);
196
- if (hit) return hit;
197
  }
198
 
199
  const wsCourse = (currentWorkspace as any)?.courseInfo as
@@ -201,16 +179,10 @@ const demoGroup = useMemo(() => {
201
  | undefined;
202
 
203
  const wsId = norm(wsCourse?.id);
204
- if (wsId) {
205
- return (list.find((c: any) => norm(c.id) === wsId) ??
206
- (wsCourse as any)) as any;
207
- }
208
 
209
  const wsName = norm(wsCourse?.name);
210
- if (wsName) {
211
- return (list.find((c: any) => norm(c.name) === wsName) ??
212
- (wsCourse as any)) as any;
213
- }
214
 
215
  return null;
216
  }, [availableCourses, currentWorkspace, selectedCourse]);
@@ -222,14 +194,7 @@ const demoGroup = useMemo(() => {
222
  // --------- Group fields ---------
223
  const wsGroupNo = useMemo(() => {
224
  const ws: any = currentWorkspace as any;
225
- return (
226
- ws?.groupNo ??
227
- ws?.groupNumber ??
228
- ws?.groupIndex ??
229
- ws?.group_id ??
230
- ws?.groupId ??
231
- 1
232
- );
233
  }, [currentWorkspace]);
234
 
235
  const wsGroupName = useMemo(() => {
@@ -237,15 +202,28 @@ const demoGroup = useMemo(() => {
237
  return pickAny(ws, ["groupName", "name", "title"]) || "My Group";
238
  }, [currentWorkspace]);
239
 
240
- // My Space label displayed in the My Space view only
241
- const wsSpaceName = useMemo(() => {
242
- const ws: any = currentWorkspace as any;
243
- return pickAny(ws, ["spaceName", "spaceTitle", "workspaceName"]) || "My Space";
244
- }, [currentWorkspace]);
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
  const memberCount = (groupMembers || []).length;
247
 
248
- // localStorage keys (only used in Team space editing)
249
  const groupNameStorageKey = useMemo(
250
  () => `clare_group_name__${currentWorkspaceId}`,
251
  [currentWorkspaceId]
@@ -263,9 +241,7 @@ const demoGroup = useMemo(() => {
263
  // group no (Team space editable)
264
  const [groupNo, setGroupNo] = useState<number>(toIntOrFallback(wsGroupNo, 1));
265
  const [editingGroupNo, setEditingGroupNo] = useState(false);
266
- const [draftGroupNo, setDraftGroupNo] = useState<string>(
267
- String(toIntOrFallback(wsGroupNo, 1))
268
- );
269
 
270
  // Invite dialog state
271
  const [inviteOpen, setInviteOpen] = useState(false);
@@ -281,21 +257,17 @@ const demoGroup = useMemo(() => {
281
  setInviteOpen(false);
282
  };
283
 
284
- // hydrate persisted editable values (only meaningful in Team space)
285
  useEffect(() => {
286
  const storedName =
287
- typeof window !== "undefined"
288
- ? window.localStorage.getItem(groupNameStorageKey)
289
- : null;
290
  const name = storedName && storedName.trim() ? storedName : wsGroupName;
291
  setGroupName(name);
292
  setDraftGroupName(name);
293
  setEditingGroupName(false);
294
 
295
  const storedNo =
296
- typeof window !== "undefined"
297
- ? window.localStorage.getItem(groupNoStorageKey)
298
- : null;
299
  const no =
300
  storedNo && storedNo.trim()
301
  ? toIntOrFallback(storedNo, toIntOrFallback(wsGroupNo, 1))
@@ -349,7 +321,7 @@ const demoGroup = useMemo(() => {
349
  setEditingGroupNo(false);
350
  };
351
 
352
- // --------- Contacts (fixed bottom) ---------
353
  const instructorName = (courseInfo as any)?.instructor?.name ?? "N/A";
354
  const instructorEmail = String((courseInfo as any)?.instructor?.email ?? "").trim();
355
 
@@ -359,50 +331,37 @@ const demoGroup = useMemo(() => {
359
  const displayName = useMemo(() => getUserName(user), [user]);
360
 
361
  return (
362
- // ✅ Theme-safe background/text (fixes dark mode invisibility)
363
  <div className="h-full w-full flex flex-col min-h-0 bg-background text-foreground">
364
  {/* ================= TOP (non-scroll) ================= */}
365
  <div className="flex-shrink-0">
366
  {/* Welcome */}
367
  <div className="px-4 pt-6 pb-6">
368
- <div className="text-[34px] leading-tight font-semibold">
369
- Welcome, {displayName}!
370
- </div>
371
  </div>
372
 
373
- {/* 1) Welcome ↔ Course divider */}
374
- <div className="my-6">
375
  <Divider />
376
  </div>
377
 
378
-
379
  {/* Course + Group */}
380
- <div className="px-4 pt-10 pb-10 space-y-4">
381
  <div className="text-[30px] leading-tight font-semibold">{courseName}</div>
382
 
383
-
384
  {/* ===== My Space ===== */}
385
  {!isTeamSpace ? (
386
  <div className="space-y-2">
387
- <div className="text-[18px] font-semibold text-foreground truncate">
388
- {demoGroup.name}
389
- </div>
390
-
391
- <div className="text-[18px] font-semibold text-foreground">
392
- Group {demoGroup.no}
393
- </div>
394
  </div>
395
  ) : (
396
-
397
- /* ===== Team/Group: current style unchanged ===== */
398
  <div className="rounded-2xl border bg-background overflow-hidden">
399
  <div className="px-4 pt-4 pb-3 space-y-3">
400
  {/* Line 1: group name editable */}
401
  {!editingGroupName ? (
402
  <div className="flex items-center gap-2">
403
- <div className="text-[18px] font-semibold text-foreground truncate">
404
- {groupName || "My Group"}
405
- </div>
406
  <button
407
  type="button"
408
  className="inline-flex items-center text-muted-foreground hover:text-foreground"
@@ -424,20 +383,10 @@ const demoGroup = useMemo(() => {
424
  className="h-8 w-[220px]"
425
  autoFocus
426
  />
427
- <Button
428
- size="icon"
429
- variant="ghost"
430
- className="h-8 w-8"
431
- onClick={saveGroupName}
432
- >
433
  <Check className="w-4 h-4" />
434
  </Button>
435
- <Button
436
- size="icon"
437
- variant="ghost"
438
- className="h-8 w-8"
439
- onClick={cancelGroupName}
440
- >
441
  <X className="w-4 h-4" />
442
  </Button>
443
  </div>
@@ -449,7 +398,7 @@ const demoGroup = useMemo(() => {
449
  <Users className="w-4 h-4 text-muted-foreground" />
450
 
451
  {!editingGroupNo ? (
452
- <div className="text-[16px] text-foreground font-medium">
453
  Group{" "}
454
  <span className="inline-flex items-center gap-1">
455
  {groupNo}
@@ -466,9 +415,7 @@ const demoGroup = useMemo(() => {
466
  </div>
467
  ) : (
468
  <div className="flex items-center gap-2">
469
- <div className="text-[16px] text-foreground font-medium">
470
- Group
471
- </div>
472
  <Input
473
  value={draftGroupNo}
474
  onChange={(e) => setDraftGroupNo(e.target.value)}
@@ -479,23 +426,11 @@ const demoGroup = useMemo(() => {
479
  className="h-8 w-[80px]"
480
  autoFocus
481
  />
482
- <div className="text-[16px] text-foreground font-medium">
483
- ({memberCount})
484
- </div>
485
- <Button
486
- size="icon"
487
- variant="ghost"
488
- className="h-8 w-8"
489
- onClick={saveGroupNo}
490
- >
491
  <Check className="w-4 h-4" />
492
  </Button>
493
- <Button
494
- size="icon"
495
- variant="ghost"
496
- className="h-8 w-8"
497
- onClick={cancelGroupNo}
498
- >
499
  <X className="w-4 h-4" />
500
  </Button>
501
  </div>
@@ -539,11 +474,7 @@ const demoGroup = useMemo(() => {
539
  }`}
540
  >
541
  {isAI ? (
542
- <img
543
- src={clareAvatar}
544
- alt="Clare"
545
- className="w-full h-full object-cover"
546
- />
547
  ) : (
548
  <span className="text-sm">{initials}</span>
549
  )}
@@ -551,24 +482,17 @@ const demoGroup = useMemo(() => {
551
 
552
  <div className="flex-1 min-w-0">
553
  <div className="flex items-center gap-2">
554
- <p className="text-sm truncate text-foreground">
555
- {name}
556
- </p>
557
  {isAI && (
558
  <Badge variant="secondary" className="text-xs">
559
  AI
560
  </Badge>
561
  )}
562
  </div>
563
- <p className="text-xs text-muted-foreground truncate">
564
- {email}
565
- </p>
566
  </div>
567
 
568
- <div
569
- className="w-2 h-2 rounded-full bg-green-500 flex-shrink-0"
570
- title="Online"
571
- />
572
  </div>
573
  );
574
  })}
@@ -577,10 +501,11 @@ const demoGroup = useMemo(() => {
577
  )}
578
  </div>
579
 
580
- {/* 2) Group ↔ Saved Chat divider */}
581
- <div className="my-8">
582
  <Divider />
583
  </div>
 
584
 
585
  {/* ================= MIDDLE (only scroll) ================= */}
586
  <div className="flex-1 min-h-0 overflow-hidden">
@@ -597,12 +522,11 @@ const demoGroup = useMemo(() => {
597
 
598
  {/* ================= BOTTOM (fixed, non-scroll) ================= */}
599
  <div className="flex-shrink-0">
600
- {/* 3) Saved Chat ↔ Instructor divider */}
601
- <div className="my-6">
602
  <Divider />
603
  </div>
604
 
605
-
606
  <div className="px-4 py-4 space-y-2 text-[16px]">
607
  <div className="text-muted-foreground">
608
  Instructor:&nbsp;
@@ -646,17 +570,12 @@ const demoGroup = useMemo(() => {
646
  </div>
647
  </div>
648
 
649
- {/* Invite Dialog (Team space only; harmless if opened elsewhere but button only exists there) */}
650
  <Dialog open={inviteOpen} onOpenChange={setInviteOpen}>
651
- <DialogContent
652
- className="w-[600px] max-w-[600px] sm:max-w-[600px]"
653
- style={{ maxWidth: 600 }}
654
- >
655
  <DialogHeader>
656
  <DialogTitle>Invite member</DialogTitle>
657
- <DialogDescription>
658
- Send a quick email invite with the team details.
659
- </DialogDescription>
660
  </DialogHeader>
661
  <div className="space-y-3">
662
  <Input
 
1
  // web/src/components/sidebar/LeftSidebar.tsx
2
  import React, { useEffect, useMemo, useState } from "react";
 
3
  import { Button } from "../ui/button";
4
  import { Input } from "../ui/input";
5
  import { Badge } from "../ui/badge";
 
32
 
33
  import clareAvatar from "../../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png";
34
 
35
+ // ✅ Theme-compatible divider (light/dark) + visibility
36
  const Divider = () => (
37
+ <div className="w-full border-t border-border/70 dark:border-border flex-shrink-0" />
38
  );
39
 
40
  type Props = {
 
76
  availableCourses: CourseDirectoryItem[];
77
 
78
  // optional if you already wired backend
79
+ onRenameGroupName?: (workspaceId: string, newName: string) => Promise<void> | void;
 
 
 
80
  onRenameGroupNo?: (workspaceId: string, newNo: number) => Promise<void> | void;
81
  };
82
 
 
142
  const isMyWorkspace = useMemo(() => {
143
  const ws: any = currentWorkspace as any;
144
 
 
145
  if (ws?.isPersonal === true) return true;
146
  if (String(ws?.spaceType || "").toLowerCase().includes("my")) return true;
147
  if (String(ws?.type || "").toLowerCase().includes("my")) return true;
148
  if (String(ws?.kind || "").toLowerCase().includes("my")) return true;
149
 
 
150
  const id = String(ws?.id || "").toLowerCase();
151
  const name = String(ws?.name || "").toLowerCase();
152
  if (id.includes("my") || id.includes("personal")) return true;
 
156
  }, [currentWorkspace]);
157
 
158
  const isTeamSpace = useMemo(() => {
 
159
  if (isMyWorkspace) return false;
 
160
  const st = String(spaceType || "").toLowerCase();
161
  return st === "group" || st === "team";
162
  }, [spaceType, isMyWorkspace]);
163
 
164
  // --------- CourseInfo resolution ---------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  const courseInfo = useMemo((): CourseDirectoryItem | null => {
166
  const list = Array.isArray(availableCourses) ? availableCourses : [];
167
 
 
171
  const hit =
172
  list.find((c: any) => norm(c.id) === sel) ||
173
  list.find((c: any) => norm(c.name) === sel);
174
+ if (hit) return hit as any;
175
  }
176
 
177
  const wsCourse = (currentWorkspace as any)?.courseInfo as
 
179
  | undefined;
180
 
181
  const wsId = norm(wsCourse?.id);
182
+ if (wsId) return (list.find((c: any) => norm(c.id) === wsId) ?? (wsCourse as any)) as any;
 
 
 
183
 
184
  const wsName = norm(wsCourse?.name);
185
+ if (wsName) return (list.find((c: any) => norm(c.name) === wsName) ?? (wsCourse as any)) as any;
 
 
 
186
 
187
  return null;
188
  }, [availableCourses, currentWorkspace, selectedCourse]);
 
194
  // --------- Group fields ---------
195
  const wsGroupNo = useMemo(() => {
196
  const ws: any = currentWorkspace as any;
197
+ return ws?.groupNo ?? ws?.groupNumber ?? ws?.groupIndex ?? ws?.group_id ?? ws?.groupId ?? 1;
 
 
 
 
 
 
 
198
  }, [currentWorkspace]);
199
 
200
  const wsGroupName = useMemo(() => {
 
202
  return pickAny(ws, ["groupName", "name", "title"]) || "My Group";
203
  }, [currentWorkspace]);
204
 
205
+ // --------- Demo group mapping (My Space only) ---------
206
+ const demoGroupMap: Record<string, { name: string; no: number }> = useMemo(
207
+ () => ({
208
+ "Introduction to AI": { name: "My Space", no: 1 },
209
+ "Machine Learning": { name: "Study Sprint", no: 3 },
210
+ "Data Visualization": { name: "Design Lab", no: 2 },
211
+ }),
212
+ []
213
+ );
214
+
215
+ const demoGroup = useMemo(() => {
216
+ const hit = demoGroupMap[courseName];
217
+ if (hit) return hit;
218
+ return {
219
+ name: wsGroupName || "My Group",
220
+ no: toIntOrFallback(wsGroupNo, 1),
221
+ };
222
+ }, [demoGroupMap, courseName, wsGroupName, wsGroupNo]);
223
 
224
  const memberCount = (groupMembers || []).length;
225
 
226
+ // localStorage keys (Team space editing)
227
  const groupNameStorageKey = useMemo(
228
  () => `clare_group_name__${currentWorkspaceId}`,
229
  [currentWorkspaceId]
 
241
  // group no (Team space editable)
242
  const [groupNo, setGroupNo] = useState<number>(toIntOrFallback(wsGroupNo, 1));
243
  const [editingGroupNo, setEditingGroupNo] = useState(false);
244
+ const [draftGroupNo, setDraftGroupNo] = useState<string>(String(toIntOrFallback(wsGroupNo, 1)));
 
 
245
 
246
  // Invite dialog state
247
  const [inviteOpen, setInviteOpen] = useState(false);
 
257
  setInviteOpen(false);
258
  };
259
 
260
+ // hydrate persisted editable values (Team space)
261
  useEffect(() => {
262
  const storedName =
263
+ typeof window !== "undefined" ? window.localStorage.getItem(groupNameStorageKey) : null;
 
 
264
  const name = storedName && storedName.trim() ? storedName : wsGroupName;
265
  setGroupName(name);
266
  setDraftGroupName(name);
267
  setEditingGroupName(false);
268
 
269
  const storedNo =
270
+ typeof window !== "undefined" ? window.localStorage.getItem(groupNoStorageKey) : null;
 
 
271
  const no =
272
  storedNo && storedNo.trim()
273
  ? toIntOrFallback(storedNo, toIntOrFallback(wsGroupNo, 1))
 
321
  setEditingGroupNo(false);
322
  };
323
 
324
+ // --------- Contacts ---------
325
  const instructorName = (courseInfo as any)?.instructor?.name ?? "N/A";
326
  const instructorEmail = String((courseInfo as any)?.instructor?.email ?? "").trim();
327
 
 
331
  const displayName = useMemo(() => getUserName(user), [user]);
332
 
333
  return (
 
334
  <div className="h-full w-full flex flex-col min-h-0 bg-background text-foreground">
335
  {/* ================= TOP (non-scroll) ================= */}
336
  <div className="flex-shrink-0">
337
  {/* Welcome */}
338
  <div className="px-4 pt-6 pb-6">
339
+ <div className="text-[34px] leading-tight font-semibold">Welcome, {displayName}!</div>
 
 
340
  </div>
341
 
342
+ {/* Welcome ↔ Course divider + spacing */}
343
+ <div className="mt-4 mb-6">
344
  <Divider />
345
  </div>
346
 
 
347
  {/* Course + Group */}
348
+ <div className="px-4 pt-8 pb-10 space-y-4">
349
  <div className="text-[30px] leading-tight font-semibold">{courseName}</div>
350
 
 
351
  {/* ===== My Space ===== */}
352
  {!isTeamSpace ? (
353
  <div className="space-y-2">
354
+ <div className="text-[18px] font-semibold truncate">{demoGroup.name}</div>
355
+ <div className="text-[18px] font-semibold">Group {demoGroup.no}</div>
 
 
 
 
 
356
  </div>
357
  ) : (
358
+ /* ===== Team/Group ===== */
 
359
  <div className="rounded-2xl border bg-background overflow-hidden">
360
  <div className="px-4 pt-4 pb-3 space-y-3">
361
  {/* Line 1: group name editable */}
362
  {!editingGroupName ? (
363
  <div className="flex items-center gap-2">
364
+ <div className="text-[18px] font-semibold truncate">{groupName || "My Group"}</div>
 
 
365
  <button
366
  type="button"
367
  className="inline-flex items-center text-muted-foreground hover:text-foreground"
 
383
  className="h-8 w-[220px]"
384
  autoFocus
385
  />
386
+ <Button size="icon" variant="ghost" className="h-8 w-8" onClick={saveGroupName}>
 
 
 
 
 
387
  <Check className="w-4 h-4" />
388
  </Button>
389
+ <Button size="icon" variant="ghost" className="h-8 w-8" onClick={cancelGroupName}>
 
 
 
 
 
390
  <X className="w-4 h-4" />
391
  </Button>
392
  </div>
 
398
  <Users className="w-4 h-4 text-muted-foreground" />
399
 
400
  {!editingGroupNo ? (
401
+ <div className="text-[16px] font-medium">
402
  Group{" "}
403
  <span className="inline-flex items-center gap-1">
404
  {groupNo}
 
415
  </div>
416
  ) : (
417
  <div className="flex items-center gap-2">
418
+ <div className="text-[16px] font-medium">Group</div>
 
 
419
  <Input
420
  value={draftGroupNo}
421
  onChange={(e) => setDraftGroupNo(e.target.value)}
 
426
  className="h-8 w-[80px]"
427
  autoFocus
428
  />
429
+ <div className="text-[16px] font-medium">({memberCount})</div>
430
+ <Button size="icon" variant="ghost" className="h-8 w-8" onClick={saveGroupNo}>
 
 
 
 
 
 
 
431
  <Check className="w-4 h-4" />
432
  </Button>
433
+ <Button size="icon" variant="ghost" className="h-8 w-8" onClick={cancelGroupNo}>
 
 
 
 
 
434
  <X className="w-4 h-4" />
435
  </Button>
436
  </div>
 
474
  }`}
475
  >
476
  {isAI ? (
477
+ <img src={clareAvatar} alt="Clare" className="w-full h-full object-cover" />
 
 
 
 
478
  ) : (
479
  <span className="text-sm">{initials}</span>
480
  )}
 
482
 
483
  <div className="flex-1 min-w-0">
484
  <div className="flex items-center gap-2">
485
+ <p className="text-sm truncate">{name}</p>
 
 
486
  {isAI && (
487
  <Badge variant="secondary" className="text-xs">
488
  AI
489
  </Badge>
490
  )}
491
  </div>
492
+ <p className="text-xs text-muted-foreground truncate">{email}</p>
 
 
493
  </div>
494
 
495
+ <div className="w-2 h-2 rounded-full bg-green-500 flex-shrink-0" title="Online" />
 
 
 
496
  </div>
497
  );
498
  })}
 
501
  )}
502
  </div>
503
 
504
+ {/* Course/Group ↔ Saved Chat divider + spacing */}
505
+ <div className="mt-2 mb-8">
506
  <Divider />
507
  </div>
508
+ </div>
509
 
510
  {/* ================= MIDDLE (only scroll) ================= */}
511
  <div className="flex-1 min-h-0 overflow-hidden">
 
522
 
523
  {/* ================= BOTTOM (fixed, non-scroll) ================= */}
524
  <div className="flex-shrink-0">
525
+ {/* Saved Chat ↔ Instructor divider + spacing */}
526
+ <div className="mt-6 mb-4">
527
  <Divider />
528
  </div>
529
 
 
530
  <div className="px-4 py-4 space-y-2 text-[16px]">
531
  <div className="text-muted-foreground">
532
  Instructor:&nbsp;
 
570
  </div>
571
  </div>
572
 
573
+ {/* Invite Dialog */}
574
  <Dialog open={inviteOpen} onOpenChange={setInviteOpen}>
575
+ <DialogContent className="w-[600px] max-w-[600px] sm:max-w-[600px]" style={{ maxWidth: 600 }}>
 
 
 
576
  <DialogHeader>
577
  <DialogTitle>Invite member</DialogTitle>
578
+ <DialogDescription>Send a quick email invite with the team details.</DialogDescription>
 
 
579
  </DialogHeader>
580
  <div className="space-y-3">
581
  <Input