SarahXia0405 commited on
Commit
e687a4a
Β·
verified Β·
1 Parent(s): dd7ec4b

Update web/src/App.tsx

Browse files
Files changed (1) hide show
  1. web/src/App.tsx +60 -41
web/src/App.tsx CHANGED
@@ -118,7 +118,9 @@ function App() {
118
 
119
  const [user, setUser] = useState<User | null>(null);
120
 
121
- const [currentCourseId, setCurrentCourseId] = useState<string>(() => localStorage.getItem("myspace_selected_course") || "course1");
 
 
122
 
123
  const availableCourses: CourseInfo[] = [
124
  {
@@ -161,7 +163,8 @@ function App() {
161
  {
162
  id: "review-1",
163
  role: "assistant",
164
- content: "πŸ“š Welcome to Review mode! I'll help you review and consolidate your learning. Let's go through what you've learned!",
 
165
  timestamp: new Date(),
166
  },
167
  ]);
@@ -170,7 +173,8 @@ function App() {
170
  {
171
  id: "quiz-1",
172
  role: "assistant",
173
- content: "🎯 Welcome to Quiz mode! I'll test your understanding with personalized questions based on your learning history. Ready to start?",
 
174
  timestamp: new Date(),
175
  },
176
  ]);
@@ -200,7 +204,9 @@ function App() {
200
 
201
  const hasUserMessages = currentMessages.some((msg) => msg.role === "user");
202
  const expectedWelcomeId = chatMode === "ask" ? "1" : chatMode === "review" ? "review-1" : "quiz-1";
203
- const hasWelcomeMessage = currentMessages.some((msg) => msg.id === expectedWelcomeId && msg.role === "assistant");
 
 
204
  const modeChanged = prevChatModeRef.current !== chatMode;
205
 
206
  if ((modeChanged || currentMessages.length === 0 || !hasWelcomeMessage) && !hasUserMessages) {
@@ -218,7 +224,8 @@ function App() {
218
  {
219
  id: "review-1",
220
  role: "assistant",
221
- content: "πŸ“š Welcome to Review mode! I'll help you review and consolidate your learning. Let's go through what you've learned!",
 
222
  timestamp: new Date(),
223
  },
224
  ],
@@ -226,7 +233,8 @@ function App() {
226
  {
227
  id: "quiz-1",
228
  role: "assistant",
229
- content: "🎯 Welcome to Quiz mode! I'll test your understanding with personalized questions based on your learning history. Ready to start?",
 
230
  timestamp: new Date(),
231
  },
232
  ],
@@ -309,7 +317,6 @@ function App() {
309
  }
310
  }, [user, groupMembers, availableCourses]);
311
 
312
- // βœ… stable fallback to avoid undefined props during the first render after login
313
  const fallbackWorkspace: Workspace = {
314
  id: "individual",
315
  name: "My Space",
@@ -348,7 +355,7 @@ function App() {
348
  localStorage.setItem("theme", isDarkMode ? "dark" : "light");
349
  }, [isDarkMode]);
350
 
351
- // βœ… lock body scroll (avoid outer page scrollbars)
352
  useEffect(() => {
353
  const prev = document.body.style.overflow;
354
  document.body.style.overflow = "hidden";
@@ -385,11 +392,13 @@ function App() {
385
  question: "Which of the following is NOT a principle of Responsible AI?",
386
  options: ["A) Fairness", "B) Transparency", "C) Profit Maximization", "D) Accountability"],
387
  correctAnswer: "C",
388
- explanation: "Profit Maximization is not a principle of Responsible AI. The key principles are Fairness, Transparency, and Accountability.",
 
389
  },
390
  {
391
  type: "fill-in-blank",
392
- question: "Algorithmic fairness ensures that AI systems do not discriminate against individuals based on their _____.",
 
393
  correctAnswer: "protected characteristics",
394
  explanation:
395
  "Protected characteristics include attributes like race, gender, age, religion, etc. AI systems should not discriminate based on these.",
@@ -575,7 +584,6 @@ function App() {
575
  setUploadedFiles((prev) => prev.filter((_, i) => i !== index));
576
  };
577
 
578
- // βœ… FIX: use the same "next" array for state update and for uploading; do NOT read stale `uploadedFiles`
579
  const handleFileTypeChange = async (index: number, type: FileType) => {
580
  if (!user) return;
581
 
@@ -612,7 +620,11 @@ function App() {
612
 
613
  return chat.messages.every((savedMsg, idx) => {
614
  const currentMsg = messages[idx];
615
- return savedMsg.id === currentMsg.id && savedMsg.role === currentMsg.role && savedMsg.content === currentMsg.content;
 
 
 
 
616
  });
617
  }) || null
618
  );
@@ -685,7 +697,8 @@ function App() {
685
  {
686
  id: "review-1",
687
  role: "assistant",
688
- content: "πŸ“š Welcome to Review mode! I'll help you review and consolidate your learning. Let's go through what you've learned!",
 
689
  timestamp: new Date(),
690
  },
691
  ],
@@ -693,7 +706,8 @@ function App() {
693
  {
694
  id: "quiz-1",
695
  role: "assistant",
696
- content: "🎯 Welcome to Quiz mode! I'll test your understanding with personalized questions based on your learning history. Ready to start?",
 
697
  timestamp: new Date(),
698
  },
699
  ],
@@ -774,7 +788,12 @@ function App() {
774
  toast.success("Removed from saved items");
775
  };
776
 
777
- const handleCreateWorkspace = (payload: { name: string; category: "course" | "personal"; courseId?: string; invites: string[] }) => {
 
 
 
 
 
778
  const id = `group-${Date.now()}`;
779
  const avatar = `https://api.dicebear.com/7.x/shapes/svg?seed=${encodeURIComponent(payload.name)}`;
780
 
@@ -856,16 +875,17 @@ function App() {
856
  const handleOnboardingSkip = () => setShowOnboarding(false);
857
 
858
  if (!user) return <LoginScreen onLogin={handleLogin} />;
859
- if (showOnboarding && user) return <Onboarding user={user} onComplete={handleOnboardingComplete} onSkip={handleOnboardingSkip} />;
 
860
 
861
  return (
862
- // βœ… CRITICAL: make the whole app fill viewport and avoid page scroll
863
- <div className="h-screen w-full overflow-hidden bg-background">
864
  <Toaster />
865
 
866
- {/* APP column layout (header + main) */}
867
- <div className="flex flex-col h-full min-h-0 min-w-0">
868
- {/* Header fixed */}
869
  <div className="flex-shrink-0">
870
  <Header
871
  user={user}
@@ -889,15 +909,16 @@ function App() {
889
  <ProfileEditor user={user} onSave={setUser} onClose={() => setShowProfileEditor(false)} />
890
  )}
891
 
892
- {/* Review banner fixed */}
893
  {showReviewBanner && (
894
  <div className="flex-shrink-0 w-full bg-background border-b border-border relative z-50">
895
  <ReviewBanner onReview={handleReviewClick} onDismiss={handleDismissReviewBanner} />
896
  </div>
897
  )}
898
 
899
- {/* Main area: NO page scroll, only inner panes scroll */}
900
- <div className="flex-1 min-h-0 min-w-0 flex overflow-hidden relative" style={{ overscrollBehavior: "none" }}>
 
901
  {!leftPanelVisible && (
902
  <Button
903
  variant="secondary"
@@ -911,13 +932,14 @@ function App() {
911
  </Button>
912
  )}
913
 
 
914
  {leftSidebarOpen && (
915
  <div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setLeftSidebarOpen(false)} />
916
  )}
917
 
918
  {/* Desktop left panel */}
919
  {leftPanelVisible ? (
920
- <aside className="hidden lg:flex w-80 h-full min-h-0 min-w-0 bg-card border-r border-border overflow-hidden relative">
921
  <Button
922
  variant="secondary"
923
  size="icon"
@@ -929,7 +951,7 @@ function App() {
929
  <ChevronLeft className="h-3 w-3" />
930
  </Button>
931
 
932
- {/* LeftSidebar controls internal scroll (Saved list), outer never scrolls */}
933
  <div className="flex-1 min-h-0 min-w-0 overflow-hidden">
934
  <LeftSidebar
935
  learningMode={learningMode}
@@ -960,19 +982,16 @@ function App() {
960
  </aside>
961
  ) : null}
962
 
963
- {/* Mobile left drawer */}
964
  <aside
965
- className={`
966
- fixed lg:hidden inset-y-0 left-0 z-50
967
- w-80 bg-card border-r border-border
968
- transform transition-transform duration-300 ease-in-out
969
- ${leftSidebarOpen ? "translate-x-0" : "-translate-x-full"}
970
- mt-16
971
- h-[calc(100vh-4rem)]
972
- min-h-0
973
- overflow-hidden
974
- flex flex-col
975
- `}
976
  >
977
  <div className="p-4 border-b border-border flex justify-between items-center flex-shrink-0">
978
  <h3>Settings & Guide</h3>
@@ -1010,9 +1029,9 @@ function App() {
1010
  </div>
1011
  </aside>
1012
 
1013
- {/* Chat column: outer never scroll, ChatArea controls internal scroll */}
1014
- <main className="flex-1 min-w-0 min-h-0 overflow-hidden flex flex-col">
1015
- <div className="flex-1 min-h-0 overflow-hidden">
1016
  <ChatArea
1017
  messages={messages}
1018
  onSendMessage={handleSendMessage}
 
118
 
119
  const [user, setUser] = useState<User | null>(null);
120
 
121
+ const [currentCourseId, setCurrentCourseId] = useState<string>(
122
+ () => localStorage.getItem("myspace_selected_course") || "course1"
123
+ );
124
 
125
  const availableCourses: CourseInfo[] = [
126
  {
 
163
  {
164
  id: "review-1",
165
  role: "assistant",
166
+ content:
167
+ "πŸ“š Welcome to Review mode! I'll help you review and consolidate your learning. Let's go through what you've learned!",
168
  timestamp: new Date(),
169
  },
170
  ]);
 
173
  {
174
  id: "quiz-1",
175
  role: "assistant",
176
+ content:
177
+ "🎯 Welcome to Quiz mode! I'll test your understanding with personalized questions based on your learning history. Ready to start?",
178
  timestamp: new Date(),
179
  },
180
  ]);
 
204
 
205
  const hasUserMessages = currentMessages.some((msg) => msg.role === "user");
206
  const expectedWelcomeId = chatMode === "ask" ? "1" : chatMode === "review" ? "review-1" : "quiz-1";
207
+ const hasWelcomeMessage = currentMessages.some(
208
+ (msg) => msg.id === expectedWelcomeId && msg.role === "assistant"
209
+ );
210
  const modeChanged = prevChatModeRef.current !== chatMode;
211
 
212
  if ((modeChanged || currentMessages.length === 0 || !hasWelcomeMessage) && !hasUserMessages) {
 
224
  {
225
  id: "review-1",
226
  role: "assistant",
227
+ content:
228
+ "πŸ“š Welcome to Review mode! I'll help you review and consolidate your learning. Let's go through what you've learned!",
229
  timestamp: new Date(),
230
  },
231
  ],
 
233
  {
234
  id: "quiz-1",
235
  role: "assistant",
236
+ content:
237
+ "🎯 Welcome to Quiz mode! I'll test your understanding with personalized questions based on your learning history. Ready to start?",
238
  timestamp: new Date(),
239
  },
240
  ],
 
317
  }
318
  }, [user, groupMembers, availableCourses]);
319
 
 
320
  const fallbackWorkspace: Workspace = {
321
  id: "individual",
322
  name: "My Space",
 
355
  localStorage.setItem("theme", isDarkMode ? "dark" : "light");
356
  }, [isDarkMode]);
357
 
358
+ // βœ… lock outer page scroll (defensive; your index.css already does this)
359
  useEffect(() => {
360
  const prev = document.body.style.overflow;
361
  document.body.style.overflow = "hidden";
 
392
  question: "Which of the following is NOT a principle of Responsible AI?",
393
  options: ["A) Fairness", "B) Transparency", "C) Profit Maximization", "D) Accountability"],
394
  correctAnswer: "C",
395
+ explanation:
396
+ "Profit Maximization is not a principle of Responsible AI. The key principles are Fairness, Transparency, and Accountability.",
397
  },
398
  {
399
  type: "fill-in-blank",
400
+ question:
401
+ "Algorithmic fairness ensures that AI systems do not discriminate against individuals based on their _____.",
402
  correctAnswer: "protected characteristics",
403
  explanation:
404
  "Protected characteristics include attributes like race, gender, age, religion, etc. AI systems should not discriminate based on these.",
 
584
  setUploadedFiles((prev) => prev.filter((_, i) => i !== index));
585
  };
586
 
 
587
  const handleFileTypeChange = async (index: number, type: FileType) => {
588
  if (!user) return;
589
 
 
620
 
621
  return chat.messages.every((savedMsg, idx) => {
622
  const currentMsg = messages[idx];
623
+ return (
624
+ savedMsg.id === currentMsg.id &&
625
+ savedMsg.role === currentMsg.role &&
626
+ savedMsg.content === currentMsg.content
627
+ );
628
  });
629
  }) || null
630
  );
 
697
  {
698
  id: "review-1",
699
  role: "assistant",
700
+ content:
701
+ "πŸ“š Welcome to Review mode! I'll help you review and consolidate your learning. Let's go through what you've learned!",
702
  timestamp: new Date(),
703
  },
704
  ],
 
706
  {
707
  id: "quiz-1",
708
  role: "assistant",
709
+ content:
710
+ "🎯 Welcome to Quiz mode! I'll test your understanding with personalized questions based on your learning history. Ready to start?",
711
  timestamp: new Date(),
712
  },
713
  ],
 
788
  toast.success("Removed from saved items");
789
  };
790
 
791
+ const handleCreateWorkspace = (payload: {
792
+ name: string;
793
+ category: "course" | "personal";
794
+ courseId?: string;
795
+ invites: string[];
796
+ }) => {
797
  const id = `group-${Date.now()}`;
798
  const avatar = `https://api.dicebear.com/7.x/shapes/svg?seed=${encodeURIComponent(payload.name)}`;
799
 
 
875
  const handleOnboardingSkip = () => setShowOnboarding(false);
876
 
877
  if (!user) return <LoginScreen onLogin={handleLogin} />;
878
+ if (showOnboarding && user)
879
+ return <Onboarding user={user} onComplete={handleOnboardingComplete} onSkip={handleOnboardingSkip} />;
880
 
881
  return (
882
+ // βœ… Use fixed inset-0 instead of h-screen to avoid mobile/OS viewport quirks and ensure true viewport lock.
883
+ <div className="fixed inset-0 w-full bg-background overflow-hidden">
884
  <Toaster />
885
 
886
+ {/* App vertical layout: header + (optional) banner + main */}
887
+ <div className="flex h-full min-h-0 min-w-0 flex-col overflow-hidden">
888
+ {/* Header (never scrolls) */}
889
  <div className="flex-shrink-0">
890
  <Header
891
  user={user}
 
909
  <ProfileEditor user={user} onSave={setUser} onClose={() => setShowProfileEditor(false)} />
910
  )}
911
 
912
+ {/* Review banner (never scrolls) */}
913
  {showReviewBanner && (
914
  <div className="flex-shrink-0 w-full bg-background border-b border-border relative z-50">
915
  <ReviewBanner onReview={handleReviewClick} onDismiss={handleDismissReviewBanner} />
916
  </div>
917
  )}
918
 
919
+ {/* Main row: ONLY children scroll; never this container */}
920
+ <div className="flex flex-1 min-h-0 min-w-0 overflow-hidden relative">
921
+ {/* Left panel open button (desktop) */}
922
  {!leftPanelVisible && (
923
  <Button
924
  variant="secondary"
 
932
  </Button>
933
  )}
934
 
935
+ {/* Mobile overlay */}
936
  {leftSidebarOpen && (
937
  <div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setLeftSidebarOpen(false)} />
938
  )}
939
 
940
  {/* Desktop left panel */}
941
  {leftPanelVisible ? (
942
+ <aside className="hidden lg:flex w-80 h-full min-h-0 min-w-0 bg-card border-r border-border overflow-hidden relative flex-col">
943
  <Button
944
  variant="secondary"
945
  size="icon"
 
951
  <ChevronLeft className="h-3 w-3" />
952
  </Button>
953
 
954
+ {/* IMPORTANT: do not add overflow-y here; let LeftSidebar decide its internal scrolling */}
955
  <div className="flex-1 min-h-0 min-w-0 overflow-hidden">
956
  <LeftSidebar
957
  learningMode={learningMode}
 
982
  </aside>
983
  ) : null}
984
 
985
+ {/* Mobile left drawer: height derives from viewport, not from header */}
986
  <aside
987
+ className={[
988
+ "fixed lg:hidden z-50",
989
+ "left-0 top-0 bottom-0",
990
+ "w-80 bg-card border-r border-border",
991
+ "transform transition-transform duration-300 ease-in-out",
992
+ leftSidebarOpen ? "translate-x-0" : "-translate-x-full",
993
+ "overflow-hidden flex flex-col",
994
+ ].join(" ")}
 
 
 
995
  >
996
  <div className="p-4 border-b border-border flex justify-between items-center flex-shrink-0">
997
  <h3>Settings & Guide</h3>
 
1029
  </div>
1030
  </aside>
1031
 
1032
+ {/* Chat column: must be flex-col + min-h-0 + overflow-hidden so ChatArea can manage internal scroll */}
1033
+ <main className="flex flex-1 min-w-0 min-h-0 overflow-hidden flex-col">
1034
+ <div className="flex-1 min-h-0 min-w-0 overflow-hidden">
1035
  <ChatArea
1036
  messages={messages}
1037
  onSendMessage={handleSendMessage}