Spaces:
Sleeping
Sleeping
Update web/src/App.tsx
Browse files- web/src/App.tsx +55 -47
web/src/App.tsx
CHANGED
|
@@ -348,7 +348,7 @@ function App() {
|
|
| 348 |
localStorage.setItem("theme", isDarkMode ? "dark" : "light");
|
| 349 |
}, [isDarkMode]);
|
| 350 |
|
| 351 |
-
// ✅
|
| 352 |
useEffect(() => {
|
| 353 |
const prev = document.body.style.overflow;
|
| 354 |
document.body.style.overflow = "hidden";
|
|
@@ -575,18 +575,19 @@ function App() {
|
|
| 575 |
setUploadedFiles((prev) => prev.filter((_, i) => i !== index));
|
| 576 |
};
|
| 577 |
|
|
|
|
| 578 |
const handleFileTypeChange = async (index: number, type: FileType) => {
|
| 579 |
-
setUploadedFiles((prev) => prev.map((file, i) => (i === index ? { ...file, type } : file)));
|
| 580 |
-
|
| 581 |
if (!user) return;
|
| 582 |
|
| 583 |
-
const
|
|
|
|
|
|
|
|
|
|
| 584 |
const file = target?.file;
|
| 585 |
if (!file) return;
|
| 586 |
|
| 587 |
const fp = `${file.name}::${file.size}::${file.lastModified}`;
|
| 588 |
if (uploadedFingerprintsRef.current.has(fp)) return;
|
| 589 |
-
|
| 590 |
uploadedFingerprintsRef.current.add(fp);
|
| 591 |
|
| 592 |
try {
|
|
@@ -858,17 +859,18 @@ function App() {
|
|
| 858 |
if (showOnboarding && user) return <Onboarding user={user} onComplete={handleOnboardingComplete} onSkip={handleOnboardingSkip} />;
|
| 859 |
|
| 860 |
return (
|
| 861 |
-
|
|
|
|
| 862 |
<Toaster />
|
| 863 |
|
| 864 |
-
{/* APP
|
| 865 |
-
<div className="
|
| 866 |
{/* Header fixed */}
|
| 867 |
-
<div className="
|
| 868 |
<Header
|
| 869 |
user={user}
|
| 870 |
onMenuClick={() => setLeftSidebarOpen(!leftSidebarOpen)}
|
| 871 |
-
onUserClick={() => setShowProfileEditor(true)}
|
| 872 |
isDarkMode={isDarkMode}
|
| 873 |
onToggleDarkMode={() => setIsDarkMode(!isDarkMode)}
|
| 874 |
language={language}
|
|
@@ -883,17 +885,19 @@ function App() {
|
|
| 883 |
/>
|
| 884 |
</div>
|
| 885 |
|
| 886 |
-
{showProfileEditor && user &&
|
|
|
|
|
|
|
| 887 |
|
| 888 |
{/* Review banner fixed */}
|
| 889 |
{showReviewBanner && (
|
| 890 |
-
<div className="
|
| 891 |
<ReviewBanner onReview={handleReviewClick} onDismiss={handleDismissReviewBanner} />
|
| 892 |
</div>
|
| 893 |
)}
|
| 894 |
|
| 895 |
{/* Main area: NO page scroll, only inner panes scroll */}
|
| 896 |
-
<div className="
|
| 897 |
{!leftPanelVisible && (
|
| 898 |
<Button
|
| 899 |
variant="secondary"
|
|
@@ -907,11 +911,13 @@ function App() {
|
|
| 907 |
</Button>
|
| 908 |
)}
|
| 909 |
|
| 910 |
-
{leftSidebarOpen &&
|
|
|
|
|
|
|
| 911 |
|
| 912 |
{/* Desktop left panel */}
|
| 913 |
{leftPanelVisible ? (
|
| 914 |
-
<aside className="hidden lg:flex w-80 bg-card border-r border-border
|
| 915 |
<Button
|
| 916 |
variant="secondary"
|
| 917 |
size="icon"
|
|
@@ -923,32 +929,34 @@ function App() {
|
|
| 923 |
<ChevronLeft className="h-3 w-3" />
|
| 924 |
</Button>
|
| 925 |
|
| 926 |
-
{/* LeftSidebar
|
| 927 |
-
<
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
|
| 934 |
-
|
| 935 |
-
|
| 936 |
-
|
| 937 |
-
|
| 938 |
-
|
| 939 |
-
|
| 940 |
-
|
| 941 |
-
|
| 942 |
-
|
| 943 |
-
|
| 944 |
-
|
| 945 |
-
|
| 946 |
-
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
|
| 951 |
-
|
|
|
|
|
|
|
| 952 |
</aside>
|
| 953 |
) : null}
|
| 954 |
|
|
@@ -959,21 +967,21 @@ function App() {
|
|
| 959 |
w-80 bg-card border-r border-border
|
| 960 |
transform transition-transform duration-300 ease-in-out
|
| 961 |
${leftSidebarOpen ? "translate-x-0" : "-translate-x-full"}
|
| 962 |
-
col
|
| 963 |
mt-16
|
| 964 |
h-[calc(100vh-4rem)]
|
| 965 |
min-h-0
|
| 966 |
overflow-hidden
|
|
|
|
| 967 |
`}
|
| 968 |
>
|
| 969 |
-
<div className="
|
| 970 |
<h3>Settings & Guide</h3>
|
| 971 |
<Button variant="ghost" size="icon" onClick={() => setLeftSidebarOpen(false)}>
|
| 972 |
<X className="h-5 w-5" />
|
| 973 |
</Button>
|
| 974 |
</div>
|
| 975 |
|
| 976 |
-
<div className="
|
| 977 |
<LeftSidebar
|
| 978 |
learningMode={learningMode}
|
| 979 |
language={language}
|
|
@@ -993,7 +1001,7 @@ function App() {
|
|
| 993 |
savedChats={savedChats}
|
| 994 |
onLoadChat={handleLoadChat}
|
| 995 |
onDeleteSavedChat={handleDeleteSavedChat}
|
| 996 |
-
onRenameSavedChat={handleRenameSavedChat}
|
| 997 |
currentWorkspaceId={currentWorkspaceId}
|
| 998 |
workspaces={workspaces}
|
| 999 |
selectedCourse={currentCourseId}
|
|
@@ -1003,8 +1011,8 @@ function App() {
|
|
| 1003 |
</aside>
|
| 1004 |
|
| 1005 |
{/* Chat column: outer never scroll, ChatArea controls internal scroll */}
|
| 1006 |
-
<main className="flex-1 min-w-0
|
| 1007 |
-
<div className="
|
| 1008 |
<ChatArea
|
| 1009 |
messages={messages}
|
| 1010 |
onSendMessage={handleSendMessage}
|
|
|
|
| 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";
|
|
|
|
| 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 |
|
| 582 |
+
const next = uploadedFiles.map((f, i) => (i === index ? { ...f, type } : f));
|
| 583 |
+
setUploadedFiles(next);
|
| 584 |
+
|
| 585 |
+
const target = next[index];
|
| 586 |
const file = target?.file;
|
| 587 |
if (!file) return;
|
| 588 |
|
| 589 |
const fp = `${file.name}::${file.size}::${file.lastModified}`;
|
| 590 |
if (uploadedFingerprintsRef.current.has(fp)) return;
|
|
|
|
| 591 |
uploadedFingerprintsRef.current.add(fp);
|
| 592 |
|
| 593 |
try {
|
|
|
|
| 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}
|
| 872 |
onMenuClick={() => setLeftSidebarOpen(!leftSidebarOpen)}
|
| 873 |
+
onUserClick={() => setShowProfileEditor(true)}
|
| 874 |
isDarkMode={isDarkMode}
|
| 875 |
onToggleDarkMode={() => setIsDarkMode(!isDarkMode)}
|
| 876 |
language={language}
|
|
|
|
| 885 |
/>
|
| 886 |
</div>
|
| 887 |
|
| 888 |
+
{showProfileEditor && user && (
|
| 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 |
</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 |
<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}
|
| 936 |
+
language={language}
|
| 937 |
+
onLearningModeChange={setLearningMode}
|
| 938 |
+
onLanguageChange={setLanguage}
|
| 939 |
+
spaceType={spaceType}
|
| 940 |
+
groupMembers={groupMembers}
|
| 941 |
+
user={user}
|
| 942 |
+
onLogin={setUser}
|
| 943 |
+
onLogout={() => setUser(null)}
|
| 944 |
+
isLoggedIn={!!user}
|
| 945 |
+
onEditProfile={() => setShowProfileEditor(true)}
|
| 946 |
+
savedItems={savedItems}
|
| 947 |
+
recentlySavedId={recentlySavedId}
|
| 948 |
+
onUnsave={handleUnsave}
|
| 949 |
+
onSave={handleSave}
|
| 950 |
+
savedChats={savedChats}
|
| 951 |
+
onLoadChat={handleLoadChat}
|
| 952 |
+
onDeleteSavedChat={handleDeleteSavedChat}
|
| 953 |
+
onRenameSavedChat={handleRenameSavedChat}
|
| 954 |
+
currentWorkspaceId={currentWorkspaceId}
|
| 955 |
+
workspaces={workspaces}
|
| 956 |
+
selectedCourse={currentCourseId}
|
| 957 |
+
availableCourses={availableCourses}
|
| 958 |
+
/>
|
| 959 |
+
</div>
|
| 960 |
</aside>
|
| 961 |
) : null}
|
| 962 |
|
|
|
|
| 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>
|
| 979 |
<Button variant="ghost" size="icon" onClick={() => setLeftSidebarOpen(false)}>
|
| 980 |
<X className="h-5 w-5" />
|
| 981 |
</Button>
|
| 982 |
</div>
|
| 983 |
|
| 984 |
+
<div className="flex-1 min-h-0 overflow-hidden">
|
| 985 |
<LeftSidebar
|
| 986 |
learningMode={learningMode}
|
| 987 |
language={language}
|
|
|
|
| 1001 |
savedChats={savedChats}
|
| 1002 |
onLoadChat={handleLoadChat}
|
| 1003 |
onDeleteSavedChat={handleDeleteSavedChat}
|
| 1004 |
+
onRenameSavedChat={handleRenameSavedChat}
|
| 1005 |
currentWorkspaceId={currentWorkspaceId}
|
| 1006 |
workspaces={workspaces}
|
| 1007 |
selectedCourse={currentCourseId}
|
|
|
|
| 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}
|