From 700b341faaa3f9931a6104bbb1df985fd81370d5 Mon Sep 17 00:00:00 2001 From: Reece Date: Wed, 1 Oct 2025 17:03:35 +0100 Subject: [PATCH] Landing zones --- .../public/locales/en-GB/translation.json | 4 +- frontend/src/components/FileManager.tsx | 3 +- .../src/components/fileEditor/AddFileCard.tsx | 179 ++++++++++++++++++ .../fileEditor/FileEditor.module.css | 80 ++++++++ .../src/components/fileEditor/FileEditor.tsx | 11 ++ .../fileManager/EmptyFilesState.tsx | 115 +++++++++++ .../components/fileManager/FileListArea.tsx | 15 +- frontend/src/contexts/FileManagerContext.tsx | 5 + 8 files changed, 401 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/fileEditor/AddFileCard.tsx create mode 100644 frontend/src/components/fileManager/EmptyFilesState.tsx diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index dd9ff0400..5c0d44f23 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -3131,6 +3131,9 @@ "addFiles": "Add Files", "dragFilesInOrClick": "Drag files in or click \"Add Files\" to browse" }, + "fileEditor": { + "addFiles": "Add Files" + }, "fileManager": { "title": "Upload PDF Files", "subtitle": "Add files to your storage for easy access across tools", @@ -3166,7 +3169,6 @@ "googleDriveShort": "Drive", "myFiles": "My Files", "noRecentFiles": "No recent files found", - "dropFilesHint": "Drop files here to upload", "googleDriveNotAvailable": "Google Drive integration not available", "openFiles": "Open Files", "openFile": "Open File", diff --git a/frontend/src/components/FileManager.tsx b/frontend/src/components/FileManager.tsx index e75b95e28..e75e8e7b8 100644 --- a/frontend/src/components/FileManager.tsx +++ b/frontend/src/components/FileManager.tsx @@ -20,7 +20,7 @@ const FileManager: React.FC = ({ selectedTool }) => { const [isDragging, setIsDragging] = useState(false); const [isMobile, setIsMobile] = useState(false); - const { loadRecentFiles, handleRemoveFile } = useFileManager(); + const { loadRecentFiles, handleRemoveFile, loading } = useFileManager(); // File management handlers const isFileSupported = useCallback((fileName: string) => { @@ -147,6 +147,7 @@ const FileManager: React.FC = ({ selectedTool }) => { onFileRemove={handleRemoveFileByIndex} modalHeight={modalHeight} refreshRecentFiles={refreshRecentFiles} + isLoading={loading} > {isMobile ? : } diff --git a/frontend/src/components/fileEditor/AddFileCard.tsx b/frontend/src/components/fileEditor/AddFileCard.tsx new file mode 100644 index 000000000..8d04aac87 --- /dev/null +++ b/frontend/src/components/fileEditor/AddFileCard.tsx @@ -0,0 +1,179 @@ +import React, { useRef, useState } from 'react'; +import { Button, Group, useMantineColorScheme } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import AddIcon from '@mui/icons-material/Add'; +import { useFilesModalContext } from '../../contexts/FilesModalContext'; +import LocalIcon from '../shared/LocalIcon'; +import { BASE_PATH } from '../../constants/app'; +import styles from './FileEditor.module.css'; + +interface AddFileCardProps { + onFileSelect: (files: File[]) => void; + accept?: string; + multiple?: boolean; +} + +const AddFileCard = ({ + onFileSelect, + accept = ".pdf,.zip", + multiple = true +}: AddFileCardProps) => { + const { t } = useTranslation(); + const fileInputRef = useRef(null); + const { openFilesModal } = useFilesModalContext(); + const { colorScheme } = useMantineColorScheme(); + const [isUploadHover, setIsUploadHover] = useState(false); + + const handleNativeUploadClick = (e: React.MouseEvent) => { + e.stopPropagation(); + fileInputRef.current?.click(); + }; + + const handleOpenFilesModal = (e: React.MouseEvent) => { + e.stopPropagation(); + openFilesModal(); + }; + + const handleFileChange = (event: React.ChangeEvent) => { + const files = Array.from(event.target.files || []); + if (files.length > 0) { + onFileSelect(files); + } + // Reset input so same files can be selected again + event.target.value = ''; + }; + + // Prevent this card from interfering with drag and drop to parent Dropzone + const handleDragOver = (e: React.DragEvent) => { + // Don't prevent default - let the parent Dropzone handle it + // Don't stop propagation - let it bubble up + }; + + const handleDrop = (e: React.DragEvent) => { + // Don't prevent default or stop propagation + // Let the parent Dropzone handle the file drop + }; + + return ( + <> + + +
+ {/* Header bar - matches FileEditorThumbnail structure */} +
+
+ +
+
+ {t('fileEditor.addFiles', 'Add Files')} +
+
+
+ + {/* Main content area */} +
+ {/* Stirling PDF Branding */} + + Stirling PDF + + + {/* Add Files + Native Upload Buttons - styled like LandingPage */} +
setIsUploadHover(false)} + > + + +
+ + {/* Instruction Text */} + + {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + +
+
+ + ); +}; + +export default AddFileCard; \ No newline at end of file diff --git a/frontend/src/components/fileEditor/FileEditor.module.css b/frontend/src/components/fileEditor/FileEditor.module.css index ccabc2fa7..173738c29 100644 --- a/frontend/src/components/fileEditor/FileEditor.module.css +++ b/frontend/src/components/fileEditor/FileEditor.module.css @@ -304,4 +304,84 @@ /* Light mode selected header stroke override */ :global([data-mantine-color-scheme="light"]) .card[data-selected="true"] { outline-color: #3B4B6E; +} + +/* ========================= + Add File Card Styles + ========================= */ + +.addFileCard { + background: var(--file-card-bg); + border: 2px dashed var(--border-default); + border-radius: 0.0625rem; + cursor: pointer; + transition: all 0.18s ease; + max-width: 100%; + max-height: 100%; + overflow: hidden; + margin-left: 0.5rem; + margin-right: 0.5rem; + opacity: 0.7; +} + +.addFileCard:hover { + opacity: 1; + border-color: var(--color-blue-500); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.addFileCard:focus { + outline: 2px solid var(--color-blue-500); + outline-offset: 2px; +} + +.addFileHeader { + background: var(--bg-subtle); + color: var(--text-secondary); + border-bottom: 1px solid var(--border-default); +} + +.addFileCard:hover .addFileHeader { + background: var(--color-blue-500); + color: white; +} + +.addFileContent { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1.5rem 1rem; + gap: 0.5rem; +} + +.addFileIcon { + display: flex; + align-items: center; + justify-content: center; + width: 5rem; + height: 5rem; + border-radius: 50%; + background: var(--bg-subtle); + transition: background-color 0.18s ease; +} + +.addFileCard:hover .addFileIcon { + background: var(--color-blue-50); +} + +.addFileText { + font-weight: 500; + transition: color 0.18s ease; +} + +.addFileCard:hover .addFileText { + color: var(--text-primary); +} + +.addFileSubtext { + font-size: 0.875rem; + opacity: 0.8; } \ No newline at end of file diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 8dbd83480..0fe3023fb 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -8,6 +8,7 @@ import { useNavigationActions } from '../../contexts/NavigationContext'; import { zipFileService } from '../../services/zipFileService'; import { detectFileExtension } from '../../utils/fileUtils'; import FileEditorThumbnail from './FileEditorThumbnail'; +import AddFileCard from './AddFileCard'; import FilePickerModal from '../shared/FilePickerModal'; import SkeletonLoader from '../shared/SkeletonLoader'; import { FileId, StirlingFile } from '../../types/fileContext'; @@ -405,6 +406,16 @@ const FileEditor = ({ pointerEvents: 'auto' }} > + {/* Add File Card - only show when files exist */} + {activeStirlingFileStubs.length > 0 && ( + + )} + {activeStirlingFileStubs.map((record, index) => { return ( { + const { t } = useTranslation(); + const { colorScheme } = useMantineColorScheme(); + const { onLocalFileClick } = useFileManagerContext(); + const [isUploadHover, setIsUploadHover] = useState(false); + + const handleUploadClick = () => { + onLocalFileClick(); + }; + + return ( +
+ {/* Container */} +
+ {/* No Recent Files Message */} + + + + {t('fileManager.noRecentFiles', 'No recent files')} + + + + {/* Stirling PDF Logo */} + + Stirling PDF + + + {/* Upload Button */} +
setIsUploadHover(false)} + > + +
+ + {/* Instruction Text */} + + {t('fileUpload.dropFilesHere', 'Drop files here or click the upload button')} + +
+
+ ); +}; + +export default EmptyFilesState; diff --git a/frontend/src/components/fileManager/FileListArea.tsx b/frontend/src/components/fileManager/FileListArea.tsx index 842d0bf0e..aeb128daf 100644 --- a/frontend/src/components/fileManager/FileListArea.tsx +++ b/frontend/src/components/fileManager/FileListArea.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { Center, ScrollArea, Text, Stack } from '@mantine/core'; import CloudIcon from '@mui/icons-material/Cloud'; -import HistoryIcon from '@mui/icons-material/History'; import { useTranslation } from 'react-i18next'; import FileListItem from './FileListItem'; import FileHistoryGroup from './FileHistoryGroup'; +import EmptyFilesState from './EmptyFilesState'; import { useFileManagerContext } from '../../contexts/FileManagerContext'; interface FileListAreaProps { @@ -29,6 +29,7 @@ const FileListArea: React.FC = ({ onFileDoubleClick, onDownloadSingle, isFileSupported, + isLoading, } = useFileManagerContext(); const { t } = useTranslation(); @@ -43,15 +44,11 @@ const FileListArea: React.FC = ({ scrollbarSize={8} > - {recentFiles.length === 0 ? ( + {recentFiles.length === 0 && !isLoading ? ( + + ) : recentFiles.length === 0 && isLoading ? (
- - - {t('fileManager.noRecentFiles', 'No recent files')} - - {t('fileManager.dropFilesHint', 'Drop files anywhere to upload')} - - + Loading files...
) : ( filteredFiles.map((file, index) => { diff --git a/frontend/src/contexts/FileManagerContext.tsx b/frontend/src/contexts/FileManagerContext.tsx index 6f3afe21b..fb82e9071 100644 --- a/frontend/src/contexts/FileManagerContext.tsx +++ b/frontend/src/contexts/FileManagerContext.tsx @@ -18,6 +18,7 @@ interface FileManagerContextValue { expandedFileIds: Set; fileGroups: Map; loadedHistoryFiles: Map; + isLoading: boolean; // Handlers onSourceChange: (source: 'recent' | 'local' | 'drive') => void; @@ -58,6 +59,7 @@ interface FileManagerProviderProps { onFileRemove: (index: number) => void; modalHeight: string; refreshRecentFiles: () => Promise; + isLoading: boolean; } export const FileManagerProvider: React.FC = ({ @@ -71,6 +73,7 @@ export const FileManagerProvider: React.FC = ({ onFileRemove, modalHeight, refreshRecentFiles, + isLoading, }) => { const [activeSource, setActiveSource] = useState<'recent' | 'local' | 'drive'>('recent'); const [selectedFileIds, setSelectedFileIds] = useState([]); @@ -574,6 +577,7 @@ export const FileManagerProvider: React.FC = ({ expandedFileIds, fileGroups, loadedHistoryFiles, + isLoading, // Handlers onSourceChange: handleSourceChange, @@ -607,6 +611,7 @@ export const FileManagerProvider: React.FC = ({ expandedFileIds, fileGroups, loadedHistoryFiles, + isLoading, handleSourceChange, handleLocalFileClick, handleFileSelect,