diff --git a/frontend/index.html b/frontend/index.html index 8408550..8e4f623 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,24 +3,7 @@ - - - - - - - - - - - - - - - - - - + diff --git a/frontend/package.json b/frontend/package.json index db8399c..1ebe754 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,8 +55,6 @@ "tailwindcss": "^4.0.0", "typescript": "^5.8.3", "vite": "^7.0.0", - "vite-plugin-pwa": "^1.1.0", - "vitest": "^0.28.0", - "workbox-window": "^7.3.0" + "vitest": "^0.28.0" } } diff --git a/frontend/public/icons/apple-touch-icon.png b/frontend/public/icons/apple-touch-icon.png deleted file mode 100644 index 2f42a4e..0000000 Binary files a/frontend/public/icons/apple-touch-icon.png and /dev/null differ diff --git a/frontend/public/icons/icon-192-maskable.png b/frontend/public/icons/icon-192-maskable.png deleted file mode 100644 index 466d378..0000000 Binary files a/frontend/public/icons/icon-192-maskable.png and /dev/null differ diff --git a/frontend/public/icons/icon-192.png b/frontend/public/icons/icon-192.png deleted file mode 100644 index c4007f4..0000000 Binary files a/frontend/public/icons/icon-192.png and /dev/null differ diff --git a/frontend/public/icons/icon-512-maskable.png b/frontend/public/icons/icon-512-maskable.png deleted file mode 100644 index b676002..0000000 Binary files a/frontend/public/icons/icon-512-maskable.png and /dev/null differ diff --git a/frontend/public/icons/icon-512.png b/frontend/public/icons/icon-512.png deleted file mode 100644 index 2a41f4c..0000000 Binary files a/frontend/public/icons/icon-512.png and /dev/null differ diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json deleted file mode 100644 index 41baa66..0000000 --- a/frontend/public/manifest.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "Readur - Document Intelligence Platform", - "short_name": "Readur", - "description": "AI-powered document management with OCR and intelligent search", - "start_url": "/", - "scope": "/", - "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#6366f1", - "orientation": "portrait-primary", - "icons": [ - { - "src": "/readur-32.png", - "sizes": "32x32", - "type": "image/png" - }, - { - "src": "/readur-64.png", - "sizes": "64x64", - "type": "image/png" - }, - { - "src": "/icons/icon-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any" - }, - { - "src": "/icons/icon-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "any" - }, - { - "src": "/icons/icon-192-maskable.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "/icons/icon-512-maskable.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ], - "categories": ["productivity", "utilities", "business"], - "shortcuts": [ - { - "name": "Upload Document", - "short_name": "Upload", - "description": "Upload a new document", - "url": "/upload", - "icons": [ - { - "src": "/icons/icon-192.png", - "sizes": "192x192" - } - ] - }, - { - "name": "Search Documents", - "short_name": "Search", - "description": "Search your documents", - "url": "/search", - "icons": [ - { - "src": "/icons/icon-192.png", - "sizes": "192x192" - } - ] - } - ], - "screenshots": [], - "display_override": ["standalone", "minimal-ui"], - "prefer_related_applications": false -} diff --git a/frontend/public/offline.html b/frontend/public/offline.html deleted file mode 100644 index 36cbb76..0000000 --- a/frontend/public/offline.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - Offline - Readur - - - -
-
- - - - -
- -

You're Offline

-

It looks like you've lost your internet connection. Don't worry, Readur will be back once you're online again.

- -
-

Checking connection...

-
- - -
- - - - diff --git a/frontend/src/components/DocumentViewer.tsx b/frontend/src/components/DocumentViewer.tsx index 2645be8..9081c72 100644 --- a/frontend/src/components/DocumentViewer.tsx +++ b/frontend/src/components/DocumentViewer.tsx @@ -1,19 +1,11 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect } from 'react'; import { Box, Typography, CircularProgress, Alert, Paper, - IconButton, - useTheme, - useMediaQuery, } from '@mui/material'; -import { - ZoomIn as ZoomInIcon, - ZoomOut as ZoomOutIcon, - RestartAlt as ResetIcon, -} from '@mui/icons-material'; import { documentService } from '../services/api'; interface DocumentViewerProps { @@ -63,7 +55,29 @@ const DocumentViewer: React.FC = ({ // Handle images if (mimeType.startsWith('image/')) { - return ; + return ( + + {filename} + + ); } // Handle PDFs @@ -138,200 +152,6 @@ const DocumentViewer: React.FC = ({ ); }; -// Component for viewing images with touch gestures -const ImageViewer: React.FC<{ documentUrl: string; filename: string }> = ({ - documentUrl, - filename, -}) => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - const [scale, setScale] = useState(1); - const [position, setPosition] = useState({ x: 0, y: 0 }); - const imageContainerRef = useRef(null); - const imageRef = useRef(null); - const lastTouchDistanceRef = useRef(null); - const lastTapTimeRef = useRef(0); - - // Reset zoom and position - const handleReset = () => { - setScale(1); - setPosition({ x: 0, y: 0 }); - }; - - // Zoom in - const handleZoomIn = () => { - setScale((prev) => Math.min(prev + 0.5, 5)); - }; - - // Zoom out - const handleZoomOut = () => { - setScale((prev) => Math.max(prev - 0.5, 0.5)); - }; - - // Handle double tap to zoom - const handleDoubleClick = (e: React.MouseEvent) => { - const now = Date.now(); - const timeSinceLastTap = now - lastTapTimeRef.current; - - if (timeSinceLastTap < 300) { - // Double tap detected - if (scale === 1) { - setScale(2); - } else { - handleReset(); - } - } - lastTapTimeRef.current = now; - }; - - // Handle pinch-to-zoom - const handleTouchStart = (e: React.TouchEvent) => { - if (e.touches.length === 2) { - const distance = Math.hypot( - e.touches[0].clientX - e.touches[1].clientX, - e.touches[0].clientY - e.touches[1].clientY - ); - lastTouchDistanceRef.current = distance; - } - }; - - const handleTouchMove = (e: React.TouchEvent) => { - if (e.touches.length === 2 && lastTouchDistanceRef.current) { - e.preventDefault(); - const distance = Math.hypot( - e.touches[0].clientX - e.touches[1].clientX, - e.touches[0].clientY - e.touches[1].clientY - ); - const scaleDelta = distance / lastTouchDistanceRef.current; - setScale((prev) => Math.max(0.5, Math.min(5, prev * scaleDelta))); - lastTouchDistanceRef.current = distance; - } - }; - - const handleTouchEnd = () => { - lastTouchDistanceRef.current = null; - }; - - // Handle wheel zoom - const handleWheel = (e: React.WheelEvent) => { - e.preventDefault(); - const delta = e.deltaY > 0 ? -0.1 : 0.1; - setScale((prev) => Math.max(0.5, Math.min(5, prev + delta))); - }; - - return ( - - {/* Zoom Controls */} - {isMobile && ( - - - - - - - - = 5} - sx={{ - color: 'white', - '&:disabled': { color: 'rgba(255,255,255,0.3)' }, - }} - > - - - - )} - - {/* Image Container */} - 1 ? 'move' : 'zoom-in', - touchAction: 'none', - userSelect: 'none', - WebkitUserSelect: 'none', - }} - > - {filename} - - - {/* Zoom Indicator */} - {isMobile && scale !== 1 && ( - - {Math.round(scale * 100)}% - - )} - - ); -}; - // Component for viewing text files const TextFileViewer: React.FC<{ documentUrl: string; filename: string }> = ({ documentUrl, diff --git a/frontend/src/components/GlobalSearchBar/GlobalSearchBar.tsx b/frontend/src/components/GlobalSearchBar/GlobalSearchBar.tsx index 5bf20fe..509d2a8 100644 --- a/frontend/src/components/GlobalSearchBar/GlobalSearchBar.tsx +++ b/frontend/src/components/GlobalSearchBar/GlobalSearchBar.tsx @@ -367,8 +367,8 @@ const GlobalSearchBar: React.FC = ({ sx, ...props }) => { }} sx={{ width: '100%', - minWidth: { xs: 0, sm: 400, md: 600 }, - maxWidth: { xs: '100%', sm: 600, md: 800, lg: 1200 }, + minWidth: 600, + maxWidth: 1200, '& .MuiOutlinedInput-root': { background: theme.palette.mode === 'light' ? 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(248,250,252,0.90) 100%)' diff --git a/frontend/src/components/Layout/AppLayout.tsx b/frontend/src/components/Layout/AppLayout.tsx index 0d70762..03a2a55 100644 --- a/frontend/src/components/Layout/AppLayout.tsx +++ b/frontend/src/components/Layout/AppLayout.tsx @@ -46,7 +46,6 @@ import GlobalSearchBar from '../GlobalSearchBar'; import ThemeToggle from '../ThemeToggle/ThemeToggle'; import NotificationPanel from '../Notifications/NotificationPanel'; import LanguageSwitcher from '../LanguageSwitcher'; -import BottomNavigation from './BottomNavigation'; import { useTranslation } from 'react-i18next'; const drawerWidth = 280; @@ -422,15 +421,9 @@ const AppLayout: React.FC = ({ children }) => { boxShadow: theme.palette.mode === 'light' ? '0 4px 32px rgba(0,0,0,0.04)' : '0 4px 32px rgba(0,0,0,0.2)', - // iOS safe area support - paddingTop: 'env(safe-area-inset-top, 0px)', }} > - + = ({ children }) => { : t('navigation.dashboard')} - {/* Global Search Bar - Hidden on mobile, use search page instead */} - + {/* Global Search Bar */} + @@ -671,37 +657,18 @@ const AppLayout: React.FC = ({ children }) => { width: { md: `calc(100% - ${drawerWidth}px)` }, minHeight: '100vh', backgroundColor: 'background.default', - // Add padding for bottom navigation on mobile - paddingBottom: { - xs: 'calc(64px + env(safe-area-inset-bottom, 0px))', - md: 0, - }, }} > - + {children} - {/* Bottom Navigation (Mobile Only) */} - - {/* Notification Panel */} - ); diff --git a/frontend/src/components/Layout/BottomNavigation.tsx b/frontend/src/components/Layout/BottomNavigation.tsx deleted file mode 100644 index 780fb35..0000000 --- a/frontend/src/components/Layout/BottomNavigation.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import React from 'react'; -import { - BottomNavigation as MuiBottomNavigation, - BottomNavigationAction, - Paper, - useTheme, -} from '@mui/material'; -import { - Dashboard as DashboardIcon, - CloudUpload as UploadIcon, - Search as SearchIcon, - Settings as SettingsIcon, -} from '@mui/icons-material'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; - -const BottomNavigation: React.FC = () => { - const navigate = useNavigate(); - const location = useLocation(); - const theme = useTheme(); - const { t } = useTranslation(); - - // Map paths to nav values - const getNavValue = (pathname: string): string => { - if (pathname === '/dashboard') return 'dashboard'; - if (pathname === '/upload') return 'upload'; - if (pathname === '/search' || pathname === '/documents') return 'search'; - if (pathname === '/settings' || pathname === '/profile') return 'settings'; - return 'dashboard'; - }; - - const handleNavigation = (_event: React.SyntheticEvent, newValue: string) => { - switch (newValue) { - case 'dashboard': - navigate('/dashboard'); - break; - case 'upload': - navigate('/upload'); - break; - case 'search': - navigate('/documents'); - break; - case 'settings': - navigate('/settings'); - break; - } - }; - - return ( - - - } - sx={{ - '&.Mui-selected': { - '& .MuiBottomNavigationAction-label': { - background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - backgroundClip: 'text', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - fontWeight: 600, - }, - }, - }} - /> - } - sx={{ - '&.Mui-selected': { - '& .MuiBottomNavigationAction-label': { - background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - backgroundClip: 'text', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - fontWeight: 600, - }, - }, - }} - /> - } - sx={{ - '&.Mui-selected': { - '& .MuiBottomNavigationAction-label': { - background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - backgroundClip: 'text', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - fontWeight: 600, - }, - }, - }} - /> - } - sx={{ - '&.Mui-selected': { - '& .MuiBottomNavigationAction-label': { - background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', - backgroundClip: 'text', - WebkitBackgroundClip: 'text', - WebkitTextFillColor: 'transparent', - fontWeight: 600, - }, - }, - }} - /> - - - ); -}; - -export default BottomNavigation; diff --git a/frontend/src/index.css b/frontend/src/index.css index c42fd0e..5e001a1 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,97 +2,6 @@ @tailwind components; @tailwind utilities; -/* ============================================ - PWA & iOS Safe Area Support - ============================================ */ - -/* Ensure the entire app respects iOS safe areas */ -:root { - /* Define safe area insets for iOS devices */ - --safe-area-inset-top: env(safe-area-inset-top, 0px); - --safe-area-inset-right: env(safe-area-inset-right, 0px); - --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px); - --safe-area-inset-left: env(safe-area-inset-left, 0px); -} - -/* PWA-specific styles */ -@media all and (display-mode: standalone) { - /* Remove system tap highlight on iOS PWA */ - * { - -webkit-tap-highlight-color: transparent; - } - - /* Prevent pull-to-refresh on iOS */ - body { - overscroll-behavior-y: contain; - } - - /* Improve iOS PWA scrolling performance */ - html { - -webkit-overflow-scrolling: touch; - } -} - -/* iOS-specific optimizations */ -@supports (-webkit-touch-callout: none) { - /* Disable callout on iOS when long-pressing */ - * { - -webkit-touch-callout: none; - } - - /* Allow callout on text content */ - p, span, div[contenteditable="true"] { - -webkit-touch-callout: default; - } - - /* Smooth momentum scrolling on iOS */ - .scrollable-content { - -webkit-overflow-scrolling: touch; - } - - /* Fix iOS input zoom issue */ - input, textarea, select { - font-size: 16px !important; - } -} - -/* Touch-optimized tap targets (minimum 44x44px for iOS) */ -@media (pointer: coarse) { - button, - a, - .MuiIconButton-root, - .MuiButton-root, - .MuiChip-root.MuiChip-clickable { - min-height: 44px; - min-width: 44px; - } - - /* Bottom navigation touch targets */ - .MuiBottomNavigationAction-root { - min-height: 56px; - } -} - -/* Prevent iOS double-tap zoom on buttons */ -button, -input[type="button"], -input[type="submit"] { - touch-action: manipulation; -} - -/* iOS status bar color adaptation */ -@media (prefers-color-scheme: dark) { - html { - background-color: #1e1e1e; - } -} - -@media (prefers-color-scheme: light) { - html { - background-color: #ffffff; - } -} - /* Enhanced search responsiveness styles */ .search-input-responsive { transition: all 0.2s ease-in-out; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 861e63d..7b89620 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,6 +1,5 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -import { VitePWA } from 'vite-plugin-pwa' // Support environment variables for development const BACKEND_PORT = process.env.BACKEND_PORT || '8000' @@ -9,113 +8,7 @@ const CLIENT_PORT = process.env.CLIENT_PORT || '5173' const PROXY_TARGET = process.env.VITE_API_PROXY_TARGET || `http://localhost:${BACKEND_PORT}` export default defineConfig({ - plugins: [ - react(), - VitePWA({ - registerType: 'autoUpdate', - includeAssets: ['favicon.ico', 'readur-32.png', 'readur-64.png', 'icons/*.png', 'offline.html'], - manifest: { - name: 'Readur - Document Intelligence Platform', - short_name: 'Readur', - description: 'AI-powered document management with OCR and intelligent search', - theme_color: '#6366f1', - background_color: '#ffffff', - display: 'standalone', - orientation: 'portrait-primary', - scope: '/', - start_url: '/', - icons: [ - { - src: '/readur-32.png', - sizes: '32x32', - type: 'image/png' - }, - { - src: '/readur-64.png', - sizes: '64x64', - type: 'image/png' - }, - { - src: '/icons/icon-192.png', - sizes: '192x192', - type: 'image/png', - purpose: 'any' - }, - { - src: '/icons/icon-512.png', - sizes: '512x512', - type: 'image/png', - purpose: 'any' - }, - { - src: '/icons/icon-192-maskable.png', - sizes: '192x192', - type: 'image/png', - purpose: 'maskable' - }, - { - src: '/icons/icon-512-maskable.png', - sizes: '512x512', - type: 'image/png', - purpose: 'maskable' - } - ] - }, - workbox: { - globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'], - globIgnores: ['**/readur.png'], - maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // 3 MB limit - // Exclude auth routes from navigation fallback and caching - navigateFallbackDenylist: [/^\/api\/auth\//], - runtimeCaching: [ - { - urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i, - handler: 'CacheFirst', - options: { - cacheName: 'google-fonts-cache', - expiration: { - maxEntries: 10, - maxAgeSeconds: 60 * 60 * 24 * 365 // 1 year - }, - cacheableResponse: { - statuses: [0, 200] - } - } - }, - { - urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i, - handler: 'CacheFirst', - options: { - cacheName: 'gstatic-fonts-cache', - expiration: { - maxEntries: 10, - maxAgeSeconds: 60 * 60 * 24 * 365 // 1 year - }, - cacheableResponse: { - statuses: [0, 200] - } - } - }, - { - // Only cache non-auth API routes - urlPattern: /^\/api\/(?!auth\/).*/, - handler: 'NetworkFirst', - options: { - cacheName: 'api-cache', - expiration: { - maxEntries: 100, - maxAgeSeconds: 60 * 5 // 5 minutes - }, - networkTimeoutSeconds: 10 - } - } - ] - }, - devOptions: { - enabled: false // Disable PWA in dev mode for faster development - } - }) - ], + plugins: [react()], test: { environment: 'jsdom', setupFiles: ['src/test/setup.ts'],