diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index de5001850..2943dd83a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider'; import { FileContextProvider } from './contexts/FileContext'; +import { FilesModalProvider } from './contexts/FilesModalContext'; +import FileUploadModal from './components/shared/FileUploadModal'; import HomePage from './pages/HomePage'; // Import global styles @@ -11,7 +13,10 @@ export default function App() { return ( - + + + + ); diff --git a/frontend/src/components/shared/FileUploadModal.tsx b/frontend/src/components/shared/FileUploadModal.tsx new file mode 100644 index 000000000..7d94a18c8 --- /dev/null +++ b/frontend/src/components/shared/FileUploadModal.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Modal } from '@mantine/core'; +import FileUploadSelector from './FileUploadSelector'; +import { useFilesModalContext } from '../../contexts/FilesModalContext'; + +const FileUploadModal: React.FC = () => { + const { isFilesModalOpen, closeFilesModal, onFileSelect, onFilesSelect } = useFilesModalContext(); + + return ( + + + + ); +}; + +export default FileUploadModal; \ No newline at end of file diff --git a/frontend/src/components/shared/LandingPage.tsx b/frontend/src/components/shared/LandingPage.tsx new file mode 100644 index 000000000..977f1f280 --- /dev/null +++ b/frontend/src/components/shared/LandingPage.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Container, Stack, Text, Button } from '@mantine/core'; +import FolderIcon from '@mui/icons-material/FolderRounded'; +import { useFilesModalContext } from '../../contexts/FilesModalContext'; + +interface LandingPageProps { + title: string; +} + +const LandingPage = ({ title }: LandingPageProps) => { + const { openFilesModal } = useFilesModalContext(); + return ( + + + + {title} + + + + + ); +}; + +export default LandingPage; \ No newline at end of file diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index 22a49617e..3d861db90 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -11,6 +11,7 @@ import { useRainbowThemeContext } from "./RainbowThemeProvider"; import rainbowStyles from '../../styles/rainbow.module.css'; import AppConfigModal from './AppConfigModal'; import { useIsOverflowing } from '../../hooks/useIsOverflowing'; +import { useFilesModalContext } from '../../contexts/FilesModalContext'; import './QuickAccessBar.css'; interface QuickAccessBarProps { @@ -30,6 +31,7 @@ interface ButtonConfig { isRound?: boolean; size?: 'sm' | 'md' | 'lg' | 'xl'; onClick: () => void; + type?: 'navigation' | 'modal' | 'action'; // navigation = main nav, modal = triggers modal, action = other actions } function NavHeader({ @@ -111,11 +113,16 @@ const QuickAccessBar = ({ readerMode, }: QuickAccessBarProps) => { const { isRainbowMode } = useRainbowThemeContext(); + const { openFilesModal, isFilesModalOpen } = useFilesModalContext(); const [configModalOpen, setConfigModalOpen] = useState(false); const [activeButton, setActiveButton] = useState('tools'); const scrollableRef = useRef(null); const isOverflow = useIsOverflowing(scrollableRef); + const handleFilesButtonClick = () => { + openFilesModal(); + }; + const buttonConfigs: ButtonConfig[] = [ { id: 'read', @@ -124,6 +131,7 @@ const QuickAccessBar = ({ tooltip: 'Read documents', size: 'lg', isRound: false, + type: 'navigation', onClick: () => { setActiveButton('read'); onReaderToggle(); @@ -139,6 +147,7 @@ const QuickAccessBar = ({ tooltip: 'Sign your document', size: 'lg', isRound: false, + type: 'navigation', onClick: () => setActiveButton('sign') }, { @@ -148,6 +157,7 @@ const QuickAccessBar = ({ tooltip: 'Automate workflows', size: 'lg', isRound: false, + type: 'navigation', onClick: () => setActiveButton('automate') }, { @@ -157,7 +167,8 @@ const QuickAccessBar = ({ tooltip: 'Manage files', isRound: true, size: 'lg', - onClick: () => setActiveButton('files') + type: 'modal', + onClick: handleFilesButtonClick }, { id: 'activity', @@ -169,6 +180,7 @@ const QuickAccessBar = ({ tooltip: 'View activity and analytics', isRound: true, size: 'lg', + type: 'navigation', onClick: () => setActiveButton('activity') }, { @@ -177,6 +189,7 @@ const QuickAccessBar = ({ icon: , tooltip: 'Configure settings', size: 'lg', + type: 'modal', onClick: () => { setConfigModalOpen(true); } @@ -190,8 +203,16 @@ const QuickAccessBar = ({ return config.isRound ? CIRCULAR_BORDER_RADIUS : ROUND_BORDER_RADIUS; }; + const isButtonActive = (config: ButtonConfig): boolean => { + return ( + (config.type === 'navigation' && activeButton === config.id) || + (config.type === 'modal' && config.id === 'files' && isFilesModalOpen) || + (config.type === 'modal' && config.id === 'config' && configModalOpen) + ); + }; + const getButtonStyle = (config: ButtonConfig) => { - const isActive = activeButton === config.id; + const isActive = isButtonActive(config); if (isActive) { return { @@ -202,7 +223,7 @@ const QuickAccessBar = ({ }; } - // Inactive state - use consistent inactive colors + // Inactive state for all buttons return { backgroundColor: 'var(--icon-inactive-bg)', color: 'var(--icon-inactive-color)', @@ -254,13 +275,13 @@ const QuickAccessBar = ({ variant="subtle" onClick={config.onClick} style={getButtonStyle(config)} - className={activeButton === config.id ? 'activeIconScale' : ''} + className={isButtonActive(config) ? 'activeIconScale' : ''} > {config.icon} - + {config.name} @@ -281,30 +302,28 @@ const QuickAccessBar = ({
{/* Config button at the bottom */} - -
- { - setConfigModalOpen(true); - }} - style={{ - backgroundColor: 'var(--icon-inactive-bg)', - color: 'var(--icon-inactive-color)', - border: 'none', - borderRadius: '8px', - }} - > - - - - - - Config - -
-
+ {buttonConfigs + .filter(config => config.id === 'config') + .map(config => ( + +
+ + + {config.icon} + + + + {config.name} + +
+
+ ))}
diff --git a/frontend/src/contexts/FilesModalContext.tsx b/frontend/src/contexts/FilesModalContext.tsx new file mode 100644 index 000000000..6940ab9e7 --- /dev/null +++ b/frontend/src/contexts/FilesModalContext.tsx @@ -0,0 +1,30 @@ +import React, { createContext, useContext } from 'react'; +import { useFilesModal, UseFilesModalReturn } from '../hooks/useFilesModal'; +import { useFileHandler } from '../hooks/useFileHandler'; + +interface FilesModalContextType extends UseFilesModalReturn {} + +const FilesModalContext = createContext(null); + +export const FilesModalProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const { addToActiveFiles, addMultipleFiles } = useFileHandler(); + + const filesModal = useFilesModal({ + onFileSelect: addToActiveFiles, + onFilesSelect: addMultipleFiles, + }); + + return ( + + {children} + + ); +}; + +export const useFilesModalContext = () => { + const context = useContext(FilesModalContext); + if (!context) { + throw new Error('useFilesModalContext must be used within FilesModalProvider'); + } + return context; +}; \ No newline at end of file diff --git a/frontend/src/hooks/useFileHandler.ts b/frontend/src/hooks/useFileHandler.ts new file mode 100644 index 000000000..efd988906 --- /dev/null +++ b/frontend/src/hooks/useFileHandler.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; +import { useFileContext } from '../contexts/FileContext'; + +export const useFileHandler = () => { + const { activeFiles, addFiles } = useFileContext(); + + const addToActiveFiles = useCallback(async (file: File) => { + const exists = activeFiles.some(f => f.name === file.name && f.size === file.size); + if (!exists) { + await addFiles([file]); + } + }, [activeFiles, addFiles]); + + const addMultipleFiles = useCallback(async (files: File[]) => { + const newFiles = files.filter(file => + !activeFiles.some(f => f.name === file.name && f.size === file.size) + ); + if (newFiles.length > 0) { + await addFiles(newFiles); + } + }, [activeFiles, addFiles]); + + return { + addToActiveFiles, + addMultipleFiles, + }; +}; \ No newline at end of file diff --git a/frontend/src/hooks/useFilesModal.ts b/frontend/src/hooks/useFilesModal.ts new file mode 100644 index 000000000..49e9f2c5e --- /dev/null +++ b/frontend/src/hooks/useFilesModal.ts @@ -0,0 +1,57 @@ +import { useState, useCallback } from 'react'; + +export interface UseFilesModalReturn { + isFilesModalOpen: boolean; + openFilesModal: () => void; + closeFilesModal: () => void; + onFileSelect?: (file: File) => void; + onFilesSelect?: (files: File[]) => void; + onModalClose?: () => void; + setOnModalClose: (callback: () => void) => void; +} + +interface UseFilesModalProps { + onFileSelect?: (file: File) => void; + onFilesSelect?: (files: File[]) => void; +} + +export const useFilesModal = ({ + onFileSelect, + onFilesSelect +}: UseFilesModalProps = {}): UseFilesModalReturn => { + const [isFilesModalOpen, setIsFilesModalOpen] = useState(false); + const [onModalClose, setOnModalClose] = useState<(() => void) | undefined>(); + + const openFilesModal = useCallback(() => { + setIsFilesModalOpen(true); + }, []); + + const closeFilesModal = useCallback(() => { + setIsFilesModalOpen(false); + onModalClose?.(); + }, [onModalClose]); + + const handleFileSelect = useCallback((file: File) => { + onFileSelect?.(file); + closeFilesModal(); + }, [onFileSelect, closeFilesModal]); + + const handleFilesSelect = useCallback((files: File[]) => { + onFilesSelect?.(files); + closeFilesModal(); + }, [onFilesSelect, closeFilesModal]); + + const setModalCloseCallback = useCallback((callback: () => void) => { + setOnModalClose(() => callback); + }, []); + + return { + isFilesModalOpen, + openFilesModal, + closeFilesModal, + onFileSelect: handleFileSelect, + onFilesSelect: handleFilesSelect, + onModalClose, + setOnModalClose: setModalCloseCallback, + }; +}; \ No newline at end of file diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index d24c58b44..4cc706845 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,9 +1,10 @@ -import React, { useState, useCallback, useEffect} from "react"; +import React, { useState, useCallback, useEffect, useRef } from "react"; import { useTranslation } from 'react-i18next'; import { useFileContext } from "../contexts/FileContext"; import { FileSelectionProvider, useFileSelection } from "../contexts/FileSelectionContext"; import { useToolManagement } from "../hooks/useToolManagement"; -import { Group, Box, Button, Container } from "@mantine/core"; +import { useFileHandler } from "../hooks/useFileHandler"; +import { Group, Box, Button } from "@mantine/core"; import { useRainbowThemeContext } from "../components/shared/RainbowThemeProvider"; import { PageEditorFunctions } from "../types/pageEditor"; import rainbowStyles from '../styles/rainbow.module.css'; @@ -14,17 +15,18 @@ import FileEditor from "../components/fileEditor/FileEditor"; import PageEditor from "../components/pageEditor/PageEditor"; import PageEditorControls from "../components/pageEditor/PageEditorControls"; import Viewer from "../components/viewer/Viewer"; -import FileUploadSelector from "../components/shared/FileUploadSelector"; import ToolRenderer from "../components/tools/ToolRenderer"; import QuickAccessBar from "../components/shared/QuickAccessBar"; +import LandingPage from "../components/shared/LandingPage"; function HomePageContent() { const { t } = useTranslation(); const { isRainbowMode } = useRainbowThemeContext(); const fileContext = useFileContext(); - const { activeFiles, currentView, currentMode, setCurrentView, addFiles } = fileContext; + const { activeFiles, currentView, setCurrentView } = fileContext; const { setMaxFiles, setIsToolMode, setSelectedFiles } = useFileSelection(); + const { addToActiveFiles } = useFileHandler(); const { selectedToolKey, @@ -77,12 +79,6 @@ function HomePageContent() { setCurrentView(view as any); }, [setCurrentView]); - const addToActiveFiles = useCallback(async (file: File) => { - const exists = activeFiles.some(f => f.name === file.name && f.size === file.size); - if (!exists) { - await addFiles([file]); - } - }, [activeFiles, addFiles]); @@ -183,25 +179,12 @@ function HomePageContent() { }} > {!activeFiles[0] ? ( - - { - addToActiveFiles(file); - }} - onFilesSelect={(files) => { - files.forEach(addToActiveFiles); - }} - accept={["application/pdf"]} - loading={false} - showRecentFiles={true} - maxRecentFiles={8} - /> - + ) : currentView === "fileEditor" ? ( ) : ( - - { - addToActiveFiles(file); - }} - onFilesSelect={(files) => { - files.forEach(addToActiveFiles); - }} - accept={["application/pdf"]} - loading={false} - showRecentFiles={true} - maxRecentFiles={8} - /> - + )}