diff --git a/apps/server/src/routes/fs/routes/save-board-background.ts b/apps/server/src/routes/fs/routes/save-board-background.ts
index e8988c6c..a0c2164a 100644
--- a/apps/server/src/routes/fs/routes/save-board-background.ts
+++ b/apps/server/src/routes/fs/routes/save-board-background.ts
@@ -31,7 +31,9 @@ export function createSaveBoardBackgroundHandler() {
await secureFs.mkdir(boardDir, { recursive: true });
// Decode base64 data (remove data URL prefix if present)
- const base64Data = data.replace(/^data:image\/\w+;base64,/, '');
+ // Use a regex that handles all data URL formats including those with extra params
+ // e.g., data:image/gif;charset=utf-8;base64,R0lGOD...
+ const base64Data = data.replace(/^data:[^,]+,/, '');
const buffer = Buffer.from(base64Data, 'base64');
// Use a fixed filename for the board background (overwrite previous)
diff --git a/apps/server/src/routes/fs/routes/save-image.ts b/apps/server/src/routes/fs/routes/save-image.ts
index 059abfaf..c8cfdda7 100644
--- a/apps/server/src/routes/fs/routes/save-image.ts
+++ b/apps/server/src/routes/fs/routes/save-image.ts
@@ -31,7 +31,9 @@ export function createSaveImageHandler() {
await secureFs.mkdir(imagesDir, { recursive: true });
// Decode base64 data (remove data URL prefix if present)
- const base64Data = data.replace(/^data:image\/\w+;base64,/, '');
+ // Use a regex that handles all data URL formats including those with extra params
+ // e.g., data:image/gif;charset=utf-8;base64,R0lGOD...
+ const base64Data = data.replace(/^data:[^,]+,/, '');
const buffer = Buffer.from(base64Data, 'base64');
// Generate unique filename with timestamp
diff --git a/apps/ui/src/components/layout/project-switcher/components/edit-project-dialog.tsx b/apps/ui/src/components/layout/project-switcher/components/edit-project-dialog.tsx
index 0cb598b2..70ada5ee 100644
--- a/apps/ui/src/components/layout/project-switcher/components/edit-project-dialog.tsx
+++ b/apps/ui/src/components/layout/project-switcher/components/edit-project-dialog.tsx
@@ -15,6 +15,7 @@ import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
import { getHttpApiClient } from '@/lib/http-api-client';
import type { Project } from '@/lib/electron';
import { IconPicker } from './icon-picker';
+import { toast } from 'sonner';
interface EditProjectDialogProps {
project: Project;
@@ -52,11 +53,18 @@ export function EditProjectDialog({ project, open, onOpenChange }: EditProjectDi
// Validate file type
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!validTypes.includes(file.type)) {
+ toast.error(
+ `Invalid file type: ${file.type || 'unknown'}. Please use JPG, PNG, GIF or WebP.`
+ );
return;
}
- // Validate file size (max 2MB for icons)
- if (file.size > 2 * 1024 * 1024) {
+ // Validate file size (max 5MB for icons - allows animated GIFs)
+ const maxSize = 5 * 1024 * 1024;
+ if (file.size > maxSize) {
+ toast.error(
+ `File too large (${(file.size / 1024 / 1024).toFixed(2)} MB). Maximum size is 5 MB.`
+ );
return;
}
@@ -72,15 +80,24 @@ export function EditProjectDialog({ project, open, onOpenChange }: EditProjectDi
file.type,
project.path
);
+
if (result.success && result.path) {
setCustomIconPath(result.path);
// Clear the Lucide icon when custom icon is set
setIcon(null);
+ toast.success('Icon uploaded successfully');
+ } else {
+ toast.error('Failed to upload icon');
}
setIsUploadingIcon(false);
};
+ reader.onerror = () => {
+ toast.error('Failed to read file');
+ setIsUploadingIcon(false);
+ };
reader.readAsDataURL(file);
} catch {
+ toast.error('Failed to upload icon');
setIsUploadingIcon(false);
}
};
@@ -162,7 +179,7 @@ export function EditProjectDialog({ project, open, onOpenChange }: EditProjectDi
{isUploadingIcon ? 'Uploading...' : 'Upload Custom Icon'}
- PNG, JPG, GIF or WebP. Max 2MB.
+ PNG, JPG, GIF or WebP. Max 5MB.
diff --git a/apps/ui/src/components/layout/project-switcher/components/project-context-menu.tsx b/apps/ui/src/components/layout/project-switcher/components/project-context-menu.tsx
index 249aa6a1..e8cf2f3f 100644
--- a/apps/ui/src/components/layout/project-switcher/components/project-context-menu.tsx
+++ b/apps/ui/src/components/layout/project-switcher/components/project-context-menu.tsx
@@ -59,7 +59,7 @@ interface ThemeButtonProps {
/** Handler for pointer leave events (used to clear preview) */
onPointerLeave: (e: React.PointerEvent) => void;
/** Handler for click events (used to select theme) */
- onClick: () => void;
+ onClick: (e: React.MouseEvent) => void;
}
/**
@@ -77,6 +77,7 @@ const ThemeButton = memo(function ThemeButton({
const Icon = option.icon;
return (