SarahXia0405 commited on
Commit
a537956
·
verified ·
1 Parent(s): 6ab9aff

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
- // --------- Group number/name sources ---------
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 (editable)
197
- const [groupName, setGroupName] = useState<string>(wsGroupName || "My Group");
198
  const [editingGroupName, setEditingGroupName] = useState(false);
199
- const [draftGroupName, setDraftGroupName] = useState<string>(wsGroupName || "My Group");
200
 
201
- // state: group no (editable)
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 : (wsGroupName || "My Group");
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() ? toIntOrFallback(storedNo, toIntOrFallback(wsGroupNo, 1)) : toIntOrFallback(wsGroupNo, 1);
 
 
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: keep simple
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 type="button" size="icon" variant="ghost" className="h-9 w-9" onClick={saveGroupName}>
341
  <Check className="w-4 h-4" />
342
  </Button>
343
- <Button type="button" size="icon" variant="ghost" className="h-9 w-9" onClick={cancelGroupName}>
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: exact structure per screenshot
352
  <div className="rounded-2xl border bg-white overflow-hidden">
353
- {/* Row 1: Group Name + (value) + pencil (NO invite here) */}
354
- <div className="px-4 pt-4 pb-2">
355
- <div className="flex items-center gap-3 min-w-0">
356
- <div className="text-[18px] font-semibold text-foreground whitespace-nowrap">
357
- Group Name
358
- </div>
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
- </div>
395
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
- {/* Row 2: Group # (2) + pencil(edit group no) | Invite button on right */}
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 min-w-0">
401
  <Users className="w-4 h-4 text-muted-foreground" />
402
 
403
  {!editingGroupNo ? (
404
- <>
405
- <div className="text-[16px] text-foreground font-medium whitespace-nowrap">
406
- Group # ({memberCount})
407
- </div>
408
- <div className="text-[16px] text-muted-foreground font-medium whitespace-nowrap">
409
- {String(groupNo)}
410
- </div>
411
- <button
412
- type="button"
413
- className="inline-flex items-center text-muted-foreground hover:text-foreground"
414
- onClick={() => setEditingGroupNo(true)}
415
- aria-label="Edit group number"
416
- >
417
- <Pencil className="w-4 h-4" />
418
- </button>
419
- </>
420
  ) : (
421
  <div className="flex items-center gap-2">
422
- <div className="text-[16px] text-foreground font-medium whitespace-nowrap">
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-[90px]"
433
  autoFocus
434
  />
435
- <Button type="button" size="icon" variant="ghost" className="h-8 w-8" onClick={saveGroupNo}>
 
436
  <Check className="w-4 h-4" />
437
  </Button>
438
- <Button type="button" size="icon" variant="ghost" className="h-8 w-8" onClick={cancelGroupNo}>
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
  );