SarahXia0405 commited on
Commit
1577d6e
·
verified ·
1 Parent(s): d83a16b

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

Browse files
web/src/components/sidebar/LeftSidebar.tsx CHANGED
@@ -71,6 +71,7 @@ type Props = {
71
  selectedCourse: string;
72
  availableCourses: CourseDirectoryItem[];
73
 
 
74
  onRenameGroupName?: (workspaceId: string, newName: string) => Promise<void> | void;
75
  onRenameGroupNo?: (workspaceId: string, newNo: number) => Promise<void> | void;
76
  };
@@ -134,7 +135,13 @@ export function LeftSidebar(props: Props) {
134
  [workspaces, currentWorkspaceId]
135
  );
136
 
137
- // --------- CourseInfo resolution (same strategy as CourseInfoSection) ---------
 
 
 
 
 
 
138
  const courseInfo = useMemo((): CourseDirectoryItem | null => {
139
  const list = Array.isArray(availableCourses) ? availableCourses : [];
140
 
@@ -168,7 +175,7 @@ export function LeftSidebar(props: Props) {
168
  return (courseInfo as any)?.name ?? (selectedCourse || "Course");
169
  }, [courseInfo, selectedCourse]);
170
 
171
- // --------- Workspace-derived group fields ---------
172
  const wsGroupNo = useMemo(() => {
173
  const ws: any = currentWorkspace as any;
174
  return (
@@ -186,6 +193,9 @@ export function LeftSidebar(props: Props) {
186
  return pickAny(ws, ["groupName", "name", "title"]) || "My Group";
187
  }, [currentWorkspace]);
188
 
 
 
 
189
  const groupNameStorageKey = useMemo(
190
  () => `clare_group_name__${currentWorkspaceId}`,
191
  [currentWorkspaceId]
@@ -195,24 +205,20 @@ export function LeftSidebar(props: Props) {
195
  [currentWorkspaceId]
196
  );
197
 
198
- // group name
199
  const [groupName, setGroupName] = useState<string>(wsGroupName);
200
  const [editingGroupName, setEditingGroupName] = useState(false);
201
  const [draftGroupName, setDraftGroupName] = useState<string>(wsGroupName);
202
 
203
- // group no
204
  const [groupNo, setGroupNo] = useState<number>(toIntOrFallback(wsGroupNo, 1));
205
  const [editingGroupNo, setEditingGroupNo] = useState(false);
206
  const [draftGroupNo, setDraftGroupNo] = useState<string>(String(toIntOrFallback(wsGroupNo, 1)));
207
 
208
- // Invite dialog state (restored)
209
  const [inviteOpen, setInviteOpen] = useState(false);
210
  const [inviteEmail, setInviteEmail] = useState("");
211
 
212
- const handleInviteClick = () => {
213
- setInviteOpen(true);
214
- };
215
-
216
  const handleSendInvite = () => {
217
  if (!inviteEmail.trim()) {
218
  toast.error("Please enter an email to invite");
@@ -223,6 +229,7 @@ export function LeftSidebar(props: Props) {
223
  setInviteOpen(false);
224
  };
225
 
 
226
  useEffect(() => {
227
  const storedName =
228
  typeof window !== "undefined" ? window.localStorage.getItem(groupNameStorageKey) : null;
@@ -295,75 +302,39 @@ export function LeftSidebar(props: Props) {
295
 
296
  const displayName = useMemo(() => getUserName(user), [user]);
297
 
298
- const isTeamSpace = useMemo(() => {
299
- const st = String(spaceType || "").toLowerCase();
300
- return st === "group" || st === "team";
301
- }, [spaceType]);
302
-
303
- const memberCount = (groupMembers || []).length;
304
-
305
  return (
306
  <div className="h-full w-full flex flex-col min-h-0 bg-white">
307
  {/* ================= TOP (non-scroll) ================= */}
308
  <div className="flex-shrink-0">
309
- <div className="px-4 pt-6 pb-6 space-y-3">
 
310
  <div className="text-[34px] leading-tight font-semibold">
311
  Welcome, {displayName}!
312
  </div>
313
  </div>
314
 
 
315
  <Separator className="bg-[#ECECF1]" />
316
 
 
317
  <div className="px-4 pt-10 pb-10 space-y-4">
318
  <div className="text-[30px] leading-tight font-semibold">{courseName}</div>
319
 
 
320
  {!isTeamSpace ? (
321
- <div className="space-y-3">
322
- <div className="text-[30px] leading-tight font-semibold">
323
- Group {String(groupNo)}
324
  </div>
325
-
326
- <div className="flex items-center justify-between gap-2">
327
- {!editingGroupName ? (
328
- <>
329
- <div className="text-[22px] leading-tight font-medium">
330
- Group {groupName}
331
- </div>
332
- <button
333
- type="button"
334
- className="inline-flex items-center text-muted-foreground hover:text-foreground"
335
- onClick={() => setEditingGroupName(true)}
336
- aria-label="Edit group name"
337
- >
338
- <Pencil className="w-4 h-4" />
339
- </button>
340
- </>
341
- ) : (
342
- <div className="w-full flex items-center gap-2">
343
- <Input
344
- value={draftGroupName}
345
- onChange={(e) => setDraftGroupName(e.target.value)}
346
- onKeyDown={(e) => {
347
- if (e.key === "Enter") saveGroupName();
348
- if (e.key === "Escape") cancelGroupName();
349
- }}
350
- className="h-9"
351
- autoFocus
352
- />
353
- <Button size="icon" variant="ghost" className="h-9 w-9" onClick={saveGroupName}>
354
- <Check className="w-4 h-4" />
355
- </Button>
356
- <Button size="icon" variant="ghost" className="h-9 w-9" onClick={cancelGroupName}>
357
- <X className="w-4 h-4" />
358
- </Button>
359
- </div>
360
- )}
361
  </div>
362
  </div>
363
  ) : (
 
364
  <div className="rounded-2xl border bg-white overflow-hidden">
365
  <div className="px-4 pt-4 pb-3 space-y-3">
366
- {/* Line 1: group name (black) + pencil */}
367
  {!editingGroupName ? (
368
  <div className="flex items-center gap-2">
369
  <div className="text-[18px] font-semibold text-foreground truncate">
@@ -448,7 +419,7 @@ export function LeftSidebar(props: Props) {
448
  type="button"
449
  variant="secondary"
450
  className="h-9 px-3 text-[13px]"
451
- onClick={handleInviteClick}
452
  >
453
  <MailPlus className="w-4 h-4 mr-2" />
454
  Invite
@@ -456,7 +427,6 @@ export function LeftSidebar(props: Props) {
456
  </div>
457
  </div>
458
 
459
- {/* Member list */}
460
  <div className="px-3 pb-3 space-y-2">
461
  {(groupMembers || []).map((member) => {
462
  const isAI = !!(member as any).isAI;
@@ -509,6 +479,7 @@ export function LeftSidebar(props: Props) {
509
  )}
510
  </div>
511
 
 
512
  <Separator className="bg-[#ECECF1]" />
513
  </div>
514
 
@@ -527,7 +498,9 @@ export function LeftSidebar(props: Props) {
527
 
528
  {/* ================= BOTTOM (fixed, non-scroll) ================= */}
529
  <div className="flex-shrink-0">
 
530
  <Separator className="bg-[#ECECF1]" />
 
531
  <div className="px-4 py-4 space-y-2 text-[16px]">
532
  <div className="text-muted-foreground">
533
  Instructor:&nbsp;
@@ -571,7 +544,7 @@ export function LeftSidebar(props: Props) {
571
  </div>
572
  </div>
573
 
574
- {/* Invite Dialog (restored) */}
575
  <Dialog open={inviteOpen} onOpenChange={setInviteOpen}>
576
  <DialogContent className="w-[600px] max-w-[600px] sm:max-w-[600px]" style={{ maxWidth: 600 }}>
577
  <DialogHeader>
 
71
  selectedCourse: string;
72
  availableCourses: CourseDirectoryItem[];
73
 
74
+ // optional if you already wired backend
75
  onRenameGroupName?: (workspaceId: string, newName: string) => Promise<void> | void;
76
  onRenameGroupNo?: (workspaceId: string, newNo: number) => Promise<void> | void;
77
  };
 
135
  [workspaces, currentWorkspaceId]
136
  );
137
 
138
+ // Treat only "team"/"group" as Team space; otherwise it's My Space
139
+ const isTeamSpace = useMemo(() => {
140
+ const st = String(spaceType || "").toLowerCase();
141
+ return st === "group" || st === "team";
142
+ }, [spaceType]);
143
+
144
+ // --------- CourseInfo resolution ---------
145
  const courseInfo = useMemo((): CourseDirectoryItem | null => {
146
  const list = Array.isArray(availableCourses) ? availableCourses : [];
147
 
 
175
  return (courseInfo as any)?.name ?? (selectedCourse || "Course");
176
  }, [courseInfo, selectedCourse]);
177
 
178
+ // --------- Group fields ---------
179
  const wsGroupNo = useMemo(() => {
180
  const ws: any = currentWorkspace as any;
181
  return (
 
193
  return pickAny(ws, ["groupName", "name", "title"]) || "My Group";
194
  }, [currentWorkspace]);
195
 
196
+ const memberCount = (groupMembers || []).length;
197
+
198
+ // localStorage keys (only used in Team space editing)
199
  const groupNameStorageKey = useMemo(
200
  () => `clare_group_name__${currentWorkspaceId}`,
201
  [currentWorkspaceId]
 
205
  [currentWorkspaceId]
206
  );
207
 
208
+ // group name (Team space editable)
209
  const [groupName, setGroupName] = useState<string>(wsGroupName);
210
  const [editingGroupName, setEditingGroupName] = useState(false);
211
  const [draftGroupName, setDraftGroupName] = useState<string>(wsGroupName);
212
 
213
+ // group no (Team space editable)
214
  const [groupNo, setGroupNo] = useState<number>(toIntOrFallback(wsGroupNo, 1));
215
  const [editingGroupNo, setEditingGroupNo] = useState(false);
216
  const [draftGroupNo, setDraftGroupNo] = useState<string>(String(toIntOrFallback(wsGroupNo, 1)));
217
 
218
+ // Invite dialog state
219
  const [inviteOpen, setInviteOpen] = useState(false);
220
  const [inviteEmail, setInviteEmail] = useState("");
221
 
 
 
 
 
222
  const handleSendInvite = () => {
223
  if (!inviteEmail.trim()) {
224
  toast.error("Please enter an email to invite");
 
229
  setInviteOpen(false);
230
  };
231
 
232
+ // hydrate persisted editable values (only meaningful in Team space)
233
  useEffect(() => {
234
  const storedName =
235
  typeof window !== "undefined" ? window.localStorage.getItem(groupNameStorageKey) : null;
 
302
 
303
  const displayName = useMemo(() => getUserName(user), [user]);
304
 
 
 
 
 
 
 
 
305
  return (
306
  <div className="h-full w-full flex flex-col min-h-0 bg-white">
307
  {/* ================= TOP (non-scroll) ================= */}
308
  <div className="flex-shrink-0">
309
+ {/* Welcome */}
310
+ <div className="px-4 pt-6 pb-6">
311
  <div className="text-[34px] leading-tight font-semibold">
312
  Welcome, {displayName}!
313
  </div>
314
  </div>
315
 
316
+ {/* 1) Welcome ↔ Course divider */}
317
  <Separator className="bg-[#ECECF1]" />
318
 
319
+ {/* Course + Group */}
320
  <div className="px-4 pt-10 pb-10 space-y-4">
321
  <div className="text-[30px] leading-tight font-semibold">{courseName}</div>
322
 
323
+ {/* ===== My Space: show Group Name + Group Number (read-only) ===== */}
324
  {!isTeamSpace ? (
325
+ <div className="space-y-2">
326
+ <div className="text-[18px] font-semibold text-foreground truncate">
327
+ {wsGroupName || "My Group"}
328
  </div>
329
+ <div className="text-[16px] text-foreground font-medium">
330
+ Group {toIntOrFallback(wsGroupNo, 1)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  </div>
332
  </div>
333
  ) : (
334
+ /* ===== Team/Group: current style unchanged ===== */
335
  <div className="rounded-2xl border bg-white overflow-hidden">
336
  <div className="px-4 pt-4 pb-3 space-y-3">
337
+ {/* Line 1: group name editable */}
338
  {!editingGroupName ? (
339
  <div className="flex items-center gap-2">
340
  <div className="text-[18px] font-semibold text-foreground truncate">
 
419
  type="button"
420
  variant="secondary"
421
  className="h-9 px-3 text-[13px]"
422
+ onClick={() => setInviteOpen(true)}
423
  >
424
  <MailPlus className="w-4 h-4 mr-2" />
425
  Invite
 
427
  </div>
428
  </div>
429
 
 
430
  <div className="px-3 pb-3 space-y-2">
431
  {(groupMembers || []).map((member) => {
432
  const isAI = !!(member as any).isAI;
 
479
  )}
480
  </div>
481
 
482
+ {/* 2) Group ↔ Saved Chat divider */}
483
  <Separator className="bg-[#ECECF1]" />
484
  </div>
485
 
 
498
 
499
  {/* ================= BOTTOM (fixed, non-scroll) ================= */}
500
  <div className="flex-shrink-0">
501
+ {/* 3) Saved Chat ↔ Instructor divider */}
502
  <Separator className="bg-[#ECECF1]" />
503
+
504
  <div className="px-4 py-4 space-y-2 text-[16px]">
505
  <div className="text-muted-foreground">
506
  Instructor:&nbsp;
 
544
  </div>
545
  </div>
546
 
547
+ {/* Invite Dialog (Team space only; harmless if opened elsewhere but button only exists there) */}
548
  <Dialog open={inviteOpen} onOpenChange={setInviteOpen}>
549
  <DialogContent className="w-[600px] max-w-[600px] sm:max-w-[600px]" style={{ maxWidth: 600 }}>
550
  <DialogHeader>