,
- type: 'image' | 'video',
- ) => {
- const file = event.target.files?.[0];
- if (file) {
- const url = URL.createObjectURL(file);
- if (type === 'image') {
- updateStyle('backgroundImage', `url(${url})`);
- updateStyle('backgroundVideo', ''); // Clear video if an image is set
- } else {
- updateStyle('backgroundVideo', url);
- updateStyle('backgroundImage', ''); // Clear image if a video is set
- }
- }
- };
-
- /**
- * Renders a control input for customizing a specific style property.
- */
- const renderColorControl = (label: string, key: keyof CustomStyles) => (
-
-
-
updateStyle(key, e.target.value)}
- className="h-10 w-10 cursor-pointer rounded-md border-2 border-gray-200 shadow-sm dark:border-gray-600"
- />
-
-
-
-
-
- {styles[key]}
-
-
-
- );
-
- const renderRangeControl = (
- label: string,
- key: keyof CustomStyles,
- options?: { min?: number; max?: number; step?: number },
- ) => (
-
-
-
-
- {styles[key]}
-
-
-
updateStyle(key, parseInt(e.target.value))}
- className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-200 dark:bg-gray-700"
- />
-
- {options?.min}
- {options?.max}
-
-
- );
-
- const renderSelectControl = (
- label: string,
- key: keyof CustomStyles,
- options?: { choices?: string[] },
- ) => (
-
-
-
-
- );
-
- const renderFileControl = (
- label: string,
- key: keyof CustomStyles,
- options?: { accept?: string },
- ) => (
-
-
-
-
-
- {styles[key] && (
-
-
- {String(styles[key]).substring(0, 25)}...
-
-
-
- )}
-
-
- );
-
- const TabButton: React.FC<{
- active: boolean;
- onClick: () => void;
- icon: React.ReactNode;
- label: string;
- }> = ({ active, onClick, icon, label }) => (
-
- );
-
- const SectionHeader: React.FC<{ title: string }> = ({ title }) => (
-
- {title}
-
- );
-
- const renderPresetThemes = () => (
-
-
-
- {Object.entries(presetThemes).map(([name, theme]) => (
-
- ))}
-
-
- );
-
- const renderTabContent = () => {
- switch (activeTab) {
- case 'presets':
- return renderPresetThemes();
- case 'colors':
- return (
-
-
- {renderColorControl('Background Color', 'bgColor')}
- {renderColorControl('Text Color', 'textColor')}
- {renderColorControl('Accent Color', 'activeBackgroundColor')}
- {renderColorControl('Active Text Color', 'activeTextColor')}
- {renderColorControl('Icon Color', 'iconColor')}
- {renderColorControl('UI Background', 'uiBackgroundColor')}
-
- );
- case 'layout':
- return (
-
-
- {renderRangeControl('Sidebar Width', 'sidebarWidth', {
- min: 48,
- max: 128,
- step: 8,
- })}
- {renderRangeControl('Icon Size', 'iconSize', {
- min: 16,
- max: 32,
- step: 2,
- })}
-
- );
- case 'typography':
- return (
-
-
- {renderRangeControl('Font Size', 'fontSize', {
- min: 12,
- max: 20,
- step: 1,
- })}
- {renderSelectControl('Font Family', 'fontFamily', {
- choices: [
- 'Inter, sans-serif',
- "'SF Pro Display', sans-serif",
- "'Roboto', sans-serif",
- "'Open Sans', sans-serif",
- ],
- })}
-
- );
- case 'background':
- return (
-
-
- {renderFileControl('Background Image', 'backgroundImage', {
- accept: 'image/*',
- })}
- {renderFileControl('Background Video', 'backgroundVideo', {
- accept: 'video/*',
- })}
-
- );
- default:
- return Select a tab
;
- }
- };
-
- return (
-
- {styles.backgroundVideo && (
-
- )}
-
-
-
- Customize Theme
-
-
-
-
-
-
setActiveTab('presets')}
- icon={}
- label="Presets"
- />
- setActiveTab('colors')}
- icon={
-
- }
- label="Colors"
- />
- setActiveTab('layout')}
- icon={}
- label="Layout"
- />
- setActiveTab('typography')}
- icon={}
- label="Typography"
- />
- setActiveTab('background')}
- icon={}
- label="Background"
- />
-
-
-
- {renderTabContent()}
-
-
-
- );
-};
-
-export default CustomizationPopup;
diff --git a/frontend/src/components/Navigation/Sidebar/ImageCompressor.tsx b/frontend/src/components/Navigation/Sidebar/ImageCompressor.tsx
deleted file mode 100644
index 29c1cb7f4..000000000
--- a/frontend/src/components/Navigation/Sidebar/ImageCompressor.tsx
+++ /dev/null
@@ -1,363 +0,0 @@
-import React, { useState, useCallback } from 'react';
-import { Upload, Download, Trash2, RefreshCw } from 'lucide-react';
-
-// Define the CompressedImage interface
-interface CompressedImage {
- id: string;
- originalFile: File;
- originalSize: number;
- compressedSize: number;
- compressedBlob: Blob;
- previewUrl: string;
-}
-
-const ImageCompressor: React.FC = () => {
- const [compressedImages, setCompressedImages] = useState(
- [],
- );
- const [isCompressing, setIsCompressing] = useState(false);
- const [compressionLevel, setCompressionLevel] = useState(0.7);
- const [maxWidth, setMaxWidth] = useState(1920);
- const [maxHeight, setMaxHeight] = useState(1080);
-
- /**
- * Compresses an image file.
- * @param file - The image file to compress.
- * @returns A promise that resolves to the compressed image object.
- */
- const compressImage = useCallback(
- async (file: File): Promise => {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = (event) => {
- const img = new Image();
- img.onload = () => {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- if (!ctx) {
- reject('Failed to get canvas context');
- return;
- }
-
- let width = img.width;
- let height = img.height;
-
- // Adjust dimensions to fit within maxWidth and maxHeight
- if (width > maxWidth) {
- height *= maxWidth / width;
- width = maxWidth;
- }
- if (height > maxHeight) {
- width *= maxHeight / height;
- height = maxHeight;
- }
-
- canvas.width = width;
- canvas.height = height;
-
- ctx.drawImage(img, 0, 0, width, height);
-
- // Convert canvas to Blob
- canvas.toBlob(
- (blob) => {
- if (blob) {
- const compressedImage: CompressedImage = {
- id: Math.random().toString(36).substring(2, 9),
- originalFile: file,
- originalSize: file.size,
- compressedSize: blob.size,
- compressedBlob: blob,
- previewUrl: URL.createObjectURL(blob),
- };
- resolve(compressedImage);
- } else {
- reject('Failed to compress image');
- }
- },
- 'image/jpeg',
- compressionLevel,
- );
- };
- img.onerror = () => reject('Image load failed');
- img.src = event.target?.result as string;
- };
- reader.onerror = () => reject('File reading failed');
- reader.readAsDataURL(file);
- });
- },
- [compressionLevel, maxWidth, maxHeight],
- );
-
- /**
- * Handles file upload and compresses the selected images.
- * @param event - The file input change event.
- */
- const handleFileUpload = async (
- event: React.ChangeEvent,
- ) => {
- const files = event.target.files;
- if (files && files.length > 0) {
- setIsCompressing(true);
- try {
- const compressedFiles = await Promise.all(
- Array.from(files).map((file) =>
- compressImage(file).catch((err) => {
- console.error('Compression failed:', err);
- return null;
- }),
- ),
- );
- setCompressedImages((prev) => [
- ...prev,
- ...compressedFiles.filter(
- (file): file is CompressedImage => file !== null,
- ),
- ]);
- } catch (error) {
- console.error('Error compressing images:', error);
- } finally {
- setIsCompressing(false);
- }
- }
- };
-
- /**
- * Handles downloading a compressed image.
- * @param image - The compressed image to download.
- */
- const handleDownload = (image: CompressedImage) => {
- const url = URL.createObjectURL(image.compressedBlob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `compressed_${image.originalFile.name}`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- };
-
- /**
- * Handles removing a compressed image from the list.
- * @param id - The ID of the compressed image to remove.
- */
- const handleRemove = (id: string) => {
- setCompressedImages((prev) => prev.filter((img) => img.id !== id));
- };
-
- /**
- * Handles recompressing an image.
- * @param image - The compressed image to recompress.
- */
- const handleRecompress = async (image: CompressedImage) => {
- setIsCompressing(true);
- try {
- const recompressedImage = await compressImage(image.originalFile);
- setCompressedImages((prev) =>
- prev.map((img) => (img.id === image.id ? recompressedImage : img)),
- );
- } catch (error) {
- console.error('Error recompressing image:', error);
- } finally {
- setIsCompressing(false);
- }
- };
-
- return (
-
-
- Image Compressor
-
-
-
-
- {/* File Upload Button */}
-
-
-
-
-
- {/* Compressing Indicator */}
- {isCompressing && (
-
-
-
- Compressing images...
-
-
- )}
-
- {/* Compressed Images List */}
-
- {compressedImages.map((image) => (
- -
-
-
- {image.originalFile.name}
-
-
- {/* Download Button */}
-
-
- {/* Recompress Button */}
-
-
- {/* Remove Button */}
-
-
-
-
- {/* Image Preview and Details */}
-
-
-

-
-
-
-
-
- Original:
-
-
- {(image.originalSize / 1024).toFixed(2)} KB
-
-
-
-
- Compressed:
-
-
- {(image.compressedSize / 1024).toFixed(2)} KB
-
-
-
-
-
- Saved{' '}
- {(
- (image.originalSize - image.compressedSize) /
- 1024
- ).toFixed(2)}{' '}
- KB
-
- {(
- ((image.originalSize - image.compressedSize) /
- image.originalSize) *
- 100
- ).toFixed(1)}
- %
-
-
-
-
-
-
- ))}
-
-
- );
-};
-
-export default ImageCompressor;
diff --git a/frontend/src/components/Navigation/Sidebar/Sidebar.tsx b/frontend/src/components/Navigation/Sidebar/Sidebar.tsx
index ab93dbc99..9c4a498f1 100644
--- a/frontend/src/components/Navigation/Sidebar/Sidebar.tsx
+++ b/frontend/src/components/Navigation/Sidebar/Sidebar.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router';
import {
Home,
@@ -6,15 +6,9 @@ import {
Video,
Images,
Settings,
- Palette,
- FileArchiveIcon as FileCompress,
BookImage,
Lock,
- User,
} from 'lucide-react';
-import CustomizationPopup from './CustomizationPopup';
-import ImageCompressor from './ImageCompressor';
-import AvatarCropper from './AvatarCropper';
import { defaultStyles, type CustomStyles } from './styles';
import { ROUTES } from '@/constants/routes';
@@ -34,16 +28,7 @@ type CustomCSSProperties = React.CSSProperties & {
const Sidebar: React.FC = () => {
const location = useLocation();
- // const [] = useState(false);
- const [showCustomize, setShowCustomize] = useState(false);
- const [showImageCompressor, setShowImageCompressor] =
- useState(false);
- const [styles, setStyles] = useState(defaultStyles);
- const [avatar, setAvatar] = useState(null);
- const [isAvatarLoading, setIsAvatarLoading] = useState(false);
- const [avatarError, setAvatarError] = useState(null);
- const [showAvatarCropper, setShowAvatarCropper] = useState(false);
- const [croppedAvatar, setCroppedAvatar] = useState(null);
+ const [styles] = useState(defaultStyles);
// Check if the current path matches the given path
const isActive = (path: string): boolean => location.pathname === path;
@@ -53,54 +38,6 @@ const Sidebar: React.FC = () => {
document.body.style.backgroundColor = styles.uiBackgroundColor;
}, [styles.uiBackgroundColor]);
- // Handle keyboard events for accessibility
- const handleKeyDown = useCallback((event: React.KeyboardEvent): void => {
- if (event.key === 'Enter' || event.key === ' ') {
- event.preventDefault();
- (event.target as HTMLElement).click();
- }
- }, []);
-
- // Handle avatar file upload
- const handleAvatarChange = async (
- event: React.ChangeEvent,
- ): Promise => {
- const file = event.target.files?.[0];
- setAvatarError(null);
-
- if (!file) return;
-
- if (!file.type.startsWith('image/')) {
- setAvatarError('Please upload a valid image file (JPEG, PNG, GIF)');
- return;
- }
-
- setIsAvatarLoading(true);
-
- try {
- const reader = new FileReader();
- reader.onloadend = () => {
- setAvatar(reader.result as string);
- setIsAvatarLoading(false);
- setShowAvatarCropper(true);
- };
- reader.onerror = () => {
- setAvatarError('Error reading file. Please try again.');
- setIsAvatarLoading(false);
- };
- reader.readAsDataURL(file);
- } catch (error) {
- setAvatarError('An unexpected error occurred. Please try again.');
- setIsAvatarLoading(false);
- }
- };
-
- // Handle avatar crop completion
- const handleCropComplete = (croppedImage: string): void => {
- setCroppedAvatar(croppedImage);
- setShowAvatarCropper(false);
- };
-
// Define the sidebar style
const sidebarStyle: CustomCSSProperties = {
background: styles.bgColor,
@@ -136,22 +73,6 @@ const Sidebar: React.FC = () => {
return (
<>
- {/* Background Video */}
- {styles.backgroundVideo && (
-
-
-
- )}
-
{/* Sidebar */}
-
- {/* Customization Popup */}
-
- {showCustomize && (
-
-
- setShowCustomize(false)}
- />
-
-
- )}
-
- {/* Image Compressor Popup */}
-
- {showImageCompressor && (
-
-
-
-
- Image Compressor
-
-
-
-
-
-
- )}
-
- {/* Avatar Cropper Popup */}
- {showAvatarCropper && (
-
-
-
-
- Crop Avatar
-
-
-
-
-
- )}
>
);
};