diff --git a/apps/desktop/main.js b/apps/desktop/main.js index c5de4cd423..cecf597a2b 100644 --- a/apps/desktop/main.js +++ b/apps/desktop/main.js @@ -116,6 +116,67 @@ if (isLocal) { app.commandLine.appendSwitch('ignore-ssl-errors'); } +// Circular loading animation HTML +const loadingHTML = ` + + + + + Kortix + + + +
+
+
Loading...
+
+ + +`; + function createWindow() { // Use .icns for macOS (proper styling), PNG for other platforms const iconPath = process.platform === 'darwin' @@ -133,6 +194,7 @@ function createWindow() { titleBarStyle: 'default', frame: true, transparent: false, + show: false, // Don't show until ready webPreferences: { nodeIntegration: false, contextIsolation: true, @@ -142,6 +204,12 @@ function createWindow() { const { webContents } = mainWindow; + // Show loading animation immediately + mainWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(loadingHTML)}`); + mainWindow.once('ready-to-show', () => { + mainWindow.show(); + }); + // Set custom user agent to identify Electron app webContents.setUserAgent(webContents.getUserAgent() + ' Electron/Kortix-Desktop'); @@ -241,7 +309,11 @@ function createWindow() { const authUrl = normalizedUrl.endsWith('/') ? normalizedUrl + 'auth' : normalizedUrl + '/auth'; - mainWindow.loadURL(authUrl); + + // Load the actual URL after the loading screen is shown + setTimeout(() => { + mainWindow.loadURL(authUrl); + }, 100); // Intercept navigation to prevent going to homepage and handle OAuth webContents.on('will-navigate', (event, navigationUrl) => { @@ -260,7 +332,7 @@ function createWindow() { console.log('✅ Opening OAuth in popup instead:', navigationUrl); event.preventDefault(); - // Create OAuth popup window + // Create OAuth popup window with loading animation const oauthWindow = new BrowserWindow({ width: 600, height: 800, @@ -268,13 +340,20 @@ function createWindow() { modal: false, autoHideMenuBar: true, title: 'Sign In', + backgroundColor: '#000000', + show: false, webPreferences: { nodeIntegration: false, contextIsolation: true, }, }); - oauthWindow.loadURL(navigationUrl); + // Show loading animation first, then load OAuth URL + oauthWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(loadingHTML)}`); + oauthWindow.once('ready-to-show', () => { + oauthWindow.show(); + oauthWindow.loadURL(navigationUrl); + }); // Handle OAuth callback - close popup and load callback in main window oauthWindow.webContents.on('will-navigate', (e, callbackUrl) => { @@ -414,6 +493,7 @@ function createWindow() { modal: false, autoHideMenuBar: true, title: 'Sign In', + backgroundColor: '#000000', webPreferences: { nodeIntegration: false, contextIsolation: true, diff --git a/apps/frontend/public/kortix-logomark-white.svg b/apps/frontend/public/kortix-logomark-white.svg index 448cdd27a1..a1715c58cb 100644 --- a/apps/frontend/public/kortix-logomark-white.svg +++ b/apps/frontend/public/kortix-logomark-white.svg @@ -11,3 +11,4 @@ + diff --git a/apps/frontend/src/app/(dashboard)/library/[projectId]/page.tsx b/apps/frontend/src/app/(dashboard)/library/[projectId]/page.tsx index 1148fa8fa7..d9bfd68315 100644 --- a/apps/frontend/src/app/(dashboard)/library/[projectId]/page.tsx +++ b/apps/frontend/src/app/(dashboard)/library/[projectId]/page.tsx @@ -1,371 +1,14 @@ 'use client'; -import React, { useCallback, useState, useMemo, useEffect } from 'react'; +import React, { useMemo, useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { - ArrowLeft, - Download, - MessageSquare, - Presentation, - Image, - FileText, - FileCode, - File, - FileSpreadsheet, - FileArchive, - Film, - Music, - MoreHorizontal, - Star, - SlidersHorizontal, - ChevronDown -} from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { ScrollArea } from '@/components/ui/scroll-area'; import { KortixLoader } from '@/components/ui/kortix-loader'; import { useProjectQuery } from '@/hooks/threads/use-project'; import { useThreads } from '@/hooks/threads/use-threads'; -import { useDirectoryQuery } from '@/hooks/files/use-file-queries'; -import { getFileUrl } from '@/lib/utils/file-utils'; -import { useAuth } from '@/components/AuthProvider'; -import { toast } from '@/lib/toast'; -import JSZip from 'jszip'; -import { listSandboxFiles, type FileInfo } from '@/lib/api/sandbox'; import { usePresentationViewerStore, PresentationViewerWrapper } from '@/stores/presentation-viewer-store'; import { useFileViewerStore, FileViewerWrapper } from '@/stores/file-viewer-store'; -import { constructHtmlPreviewUrl } from '@/lib/utils/url'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, -} from '@/components/ui/dropdown-menu'; - -interface PresentationThumbnail { - firstSlideUrl: string; - title: string; - hasMetadata: boolean; -} - -// Check if file is an image -function isImageFile(fileName: string): boolean { - const ext = fileName.split('.').pop()?.toLowerCase() || ''; - return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff'].includes(ext); -} - -// Get Lucide icon based on file extension -function getFileIcon(fileName: string) { - const ext = fileName.split('.').pop()?.toLowerCase() || ''; - - // Images - if (['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'ico', 'bmp', 'tiff'].includes(ext)) { - return Image; - } - - // Videos - if (['mp4', 'webm', 'mov', 'avi', 'mkv', 'flv', 'wmv'].includes(ext)) { - return Film; - } - - // Audio - if (['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma'].includes(ext)) { - return Music; - } - - // Archives - if (['zip', 'tar', 'gz', 'rar', '7z', 'bz2', 'xz', 'tgz'].includes(ext)) { - return FileArchive; - } - - // Spreadsheets - if (['csv', 'xls', 'xlsx', 'ods'].includes(ext)) { - return FileSpreadsheet; - } - - // Code files - if (['js', 'ts', 'jsx', 'tsx', 'py', 'rb', 'go', 'rs', 'java', 'c', 'cpp', 'h', 'hpp', - 'css', 'scss', 'sass', 'less', 'html', 'htm', 'vue', 'svelte', 'php', 'swift', - 'kt', 'scala', 'sh', 'bash', 'zsh', 'sql', 'json', 'yaml', 'yml', 'xml', 'toml'].includes(ext)) { - return FileCode; - } - - // Documents - if (['txt', 'md', 'rtf', 'doc', 'docx', 'odt', 'pdf'].includes(ext)) { - return FileText; - } - - // Default - return File; -} - -// Large preview card for presentations with title header -const PresentationCard = React.memo(function PresentationCard({ - thumbnailUrl, - thumbnailTitle, - onClick, - onDownload, -}: { - thumbnailUrl: string; - thumbnailTitle: string; - onClick: () => void; - onDownload?: (format: 'zip' | 'pptx' | 'pdf') => void; -}) { - const containerRef = React.useRef(null); - const [scale, setScale] = React.useState(0.1); - const [iframeError, setIframeError] = React.useState(false); - - React.useEffect(() => { - if (!containerRef.current) return; - - const updateScale = () => { - if (containerRef.current) { - const width = containerRef.current.offsetWidth; - setScale(width / 1920); - } - }; - - updateScale(); - - const resizeObserver = new ResizeObserver(updateScale); - resizeObserver.observe(containerRef.current); - - return () => resizeObserver.disconnect(); - }, []); - - return ( -
- {/* Header with icon, title and menu */} -
-
- -
- {thumbnailTitle} - - - - - - { e.stopPropagation(); onClick(); }}> - Open - - {onDownload && ( - - e.stopPropagation()}> - Download - - - { e.stopPropagation(); onDownload('zip'); }}> - Download as ZIP - - { e.stopPropagation(); onDownload('pptx'); }}> - Download as PPTX - - { e.stopPropagation(); onDownload('pdf'); }}> - Download as PDF - - - - )} - - -
- - {/* Large preview */} -
- {thumbnailUrl && !iframeError ? ( -