);
}
diff --git a/client/src/components/sidebar/UploadTab.jsx b/client/src/components/sidebar/UploadTab.jsx
index 4dbab0c..1526708 100644
--- a/client/src/components/sidebar/UploadTab.jsx
+++ b/client/src/components/sidebar/UploadTab.jsx
@@ -1,42 +1,61 @@
-import { useState } from 'react';
-import { PhotoPreEditor } from '../editor/PhotoPreEditor';
+import { useRef, useState } from 'react';
-export function UploadTab({ onUpload }) {
- const fileInputRef = useState(null)[0];
+export function UploadTab({ onAddImage }) {
+ const fileInputRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
- const [uploading, setUploading] = useState(false);
- const [editingImage, setEditingImage] = useState(null);
+ const [isUploading, setIsUploading] = useState(false);
- const handleFile = async (file) => {
- if (!file || !file.type.startsWith('image/')) return;
+ const handleFiles = async (files) => {
+ const file = files[0];
+ if (!file) return;
- setUploading(true);
- const formData = new FormData();
- formData.append('image', file);
+ // Validate file type
+ const validTypes = ['image/jpeg', 'image/png', 'image/webp'];
+ if (!validTypes.includes(file.type)) {
+ alert('Please upload a JPEG, PNG, or WebP image');
+ return;
+ }
+
+ // Validate file size (20MB)
+ if (file.size > 20 * 1024 * 1024) {
+ alert('File size must be under 20MB');
+ return;
+ }
+
+ setIsUploading(true);
try {
+ const formData = new FormData();
+ formData.append('image', file);
+
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
- if (response.ok) {
- const data = await response.json();
- // Open photo editor with uploaded image
- setEditingImage(data.original.url);
+ if (!response.ok) {
+ throw new Error('Upload failed');
}
- } catch (error) {
- console.error('Upload failed:', error);
- } finally {
- setUploading(false);
- }
- };
- const handleDrop = (e) => {
- e.preventDefault();
- setIsDragging(false);
- const file = e.dataTransfer.files[0];
- handleFile(file);
+ const data = await response.json();
+
+ // Add the uploaded image to canvas (use preview for canvas)
+ onAddImage({
+ type: 'image',
+ x: 75,
+ y: 75,
+ width: 150,
+ height: 150,
+ rotation: 0,
+ src: data.preview.url,
+ originalUrl: data.original.url,
+ });
+ } catch (error) {
+ console.error('Upload error:', error);
+ alert('Failed to upload image. Please try again.');
+ } finally {
+ setIsUploading(false);
+ }
};
const handleDragOver = (e) => {
@@ -44,61 +63,87 @@ export function UploadTab({ onUpload }) {
setIsDragging(true);
};
- const handleDragLeave = () => {
+ const handleDragLeave = (e) => {
+ e.preventDefault();
setIsDragging(false);
};
- const handleEditorComplete = (editedImageUrl) => {
- onUpload({ preview: { url: editedImageUrl } });
- setEditingImage(null);
+ const handleDrop = (e) => {
+ e.preventDefault();
+ setIsDragging(false);
+ handleFiles(e.dataTransfer.files);
};
- const handleEditorClose = () => {
- setEditingImage(null);
+ const handleClick = () => {
+ fileInputRef.current?.click();
};
- if (editingImage) {
- return (
-
- );
- }
+ const handleFileChange = (e) => {
+ handleFiles(e.target.files);
+ };
return (
-
-
Upload Image
+
+
+ Upload Image
+
+
fileInputRef?.click()}
+ onDrop={handleDrop}
+ style={{
+ border: `2px dashed ${isDragging ? 'var(--accent)' : 'var(--border)'}`,
+ borderRadius: 'var(--radius-md)',
+ padding: '2rem 1rem',
+ textAlign: 'center',
+ cursor: 'pointer',
+ background: isDragging ? 'var(--accent-bg)' : 'var(--bg-primary)',
+ transition: 'all 0.15s ease',
+ marginBottom: '1rem',
+ }}
>
- {uploading ? (
-
- ) : (
- <>
-
📁
-
Drop image here or click to upload
-
PNG, JPG, WEBP up to 20MB
-
- Edit with crop, filters, and effects before adding to design
-
- >
- )}
-
handleFile(e.target.files[0])}
- style={{ display: 'none' }}
- />
+
📁
+
+ Click to upload or drag and drop
+
+
+ JPEG, PNG, WebP (max 20MB)
+
+
+
+
+
+ {isUploading && (
+
+ Uploading...
+
+ )}
+
+
+ Tip: After uploading, you can remove the background from your image using the background removal tool.
);
diff --git a/client/src/constants/fonts.js b/client/src/constants/fonts.js
new file mode 100644
index 0000000..5eb8d12
--- /dev/null
+++ b/client/src/constants/fonts.js
@@ -0,0 +1,22 @@
+export const FONTS = [
+ { name: 'Roboto', family: 'Roboto' },
+ { name: 'Open Sans', family: 'Open Sans' },
+ { name: 'Lato', family: 'Lato' },
+ { name: 'Montserrat', family: 'Montserrat' },
+ { name: 'Oswald', family: 'Oswald' },
+ { name: 'Raleway', family: 'Raleway' },
+ { name: 'Poppins', family: 'Poppins' },
+ { name: 'Roboto Condensed', family: 'Roboto Condensed' },
+ { name: 'Source Sans 3', family: 'Source Sans 3' },
+ { name: 'Roboto Slab', family: 'Roboto Slab' },
+ { name: 'Merriweather', family: 'Merriweather' },
+ { name: 'Ubuntu', family: 'Ubuntu' },
+ { name: 'Playfair Display', family: 'Playfair Display' },
+ { name: 'Nunito', family: 'Nunito' },
+ { name: 'Rubik', family: 'Rubik' },
+ { name: 'Work Sans', family: 'Work Sans' },
+ { name: 'Lora', family: 'Lora' },
+ { name: 'Fira Sans', family: 'Fira Sans' },
+ { name: 'Barlow', family: 'Barlow' },
+ { name: 'Bebas Neue', family: 'Bebas Neue' },
+];
diff --git a/client/src/constants/stickers.js b/client/src/constants/stickers.js
new file mode 100644
index 0000000..a0fda73
--- /dev/null
+++ b/client/src/constants/stickers.js
@@ -0,0 +1,159 @@
+export const STICKER_CATEGORIES = ['all', 'faces', 'animals', 'food', 'sports', 'nature', 'objects'];
+
+export const STICKERS = [
+ // Faces
+ { emoji: '😀', category: 'faces' },
+ { emoji: '😁', category: 'faces' },
+ { emoji: '😂', category: 'faces' },
+ { emoji: '🤣', category: 'faces' },
+ { emoji: '😃', category: 'faces' },
+ { emoji: '😄', category: 'faces' },
+ { emoji: '😅', category: 'faces' },
+ { emoji: '😆', category: 'faces' },
+ { emoji: '😉', category: 'faces' },
+ { emoji: '😊', category: 'faces' },
+ { emoji: '😋', category: 'faces' },
+ { emoji: '😎', category: 'faces' },
+ { emoji: '😍', category: 'faces' },
+ { emoji: '😘', category: 'faces' },
+ { emoji: '🥰', category: 'faces' },
+ { emoji: '😗', category: 'faces' },
+ { emoji: '🤔', category: 'faces' },
+ { emoji: '🤨', category: 'faces' },
+ { emoji: '🧐', category: 'faces' },
+ { emoji: '🤓', category: 'faces' },
+ { emoji: '😈', category: 'faces' },
+ { emoji: '🤠', category: 'faces' },
+ { emoji: '🥳', category: 'faces' },
+ { emoji: '🤩', category: 'faces' },
+
+ // Animals
+ { emoji: '🐶', category: 'animals' },
+ { emoji: '🐱', category: 'animals' },
+ { emoji: '🐭', category: 'animals' },
+ { emoji: '🐹', category: 'animals' },
+ { emoji: '🐰', category: 'animals' },
+ { emoji: '🦊', category: 'animals' },
+ { emoji: '🐻', category: 'animals' },
+ { emoji: '🐼', category: 'animals' },
+ { emoji: '🐨', category: 'animals' },
+ { emoji: '🐯', category: 'animals' },
+ { emoji: '🦁', category: 'animals' },
+ { emoji: '🐮', category: 'animals' },
+ { emoji: '🐷', category: 'animals' },
+ { emoji: '🐸', category: 'animals' },
+ { emoji: '🐵', category: 'animals' },
+ { emoji: '🐔', category: 'animals' },
+ { emoji: '🐧', category: 'animals' },
+ { emoji: '🐦', category: 'animals' },
+ { emoji: '🦄', category: 'animals' },
+ { emoji: '🐝', category: 'animals' },
+ { emoji: '🦋', category: 'animals' },
+ { emoji: '🐌', category: 'animals' },
+ { emoji: '🐞', category: 'animals' },
+ { emoji: '🐢', category: 'animals' },
+
+ // Food
+ { emoji: '🍎', category: 'food' },
+ { emoji: '🍐', category: 'food' },
+ { emoji: '🍊', category: 'food' },
+ { emoji: '🍋', category: 'food' },
+ { emoji: '🍌', category: 'food' },
+ { emoji: '🍉', category: 'food' },
+ { emoji: '🍇', category: 'food' },
+ { emoji: '🍓', category: 'food' },
+ { emoji: '🍈', category: 'food' },
+ { emoji: '🍒', category: 'food' },
+ { emoji: '🍑', category: 'food' },
+ { emoji: '🍍', category: 'food' },
+ { emoji: '🥥', category: 'food' },
+ { emoji: '🥝', category: 'food' },
+ { emoji: '🍅', category: 'food' },
+ { emoji: '🥑', category: 'food' },
+ { emoji: '🍆', category: 'food' },
+ { emoji: '🥔', category: 'food' },
+ { emoji: '🥕', category: 'food' },
+ { emoji: '🌽', category: 'food' },
+ { emoji: '🍕', category: 'food' },
+ { emoji: '🍔', category: 'food' },
+ { emoji: '🍟', category: 'food' },
+ { emoji: '🌭', category: 'food' },
+
+ // Sports
+ { emoji: '⚽', category: 'sports' },
+ { emoji: '🏀', category: 'sports' },
+ { emoji: '🏈', category: 'sports' },
+ { emoji: '⚾', category: 'sports' },
+ { emoji: '🥎', category: 'sports' },
+ { emoji: '🎾', category: 'sports' },
+ { emoji: '🏐', category: 'sports' },
+ { emoji: '🏉', category: 'sports' },
+ { emoji: '🎱', category: 'sports' },
+ { emoji: '🏓', category: 'sports' },
+ { emoji: '🏸', category: 'sports' },
+ { emoji: '🥅', category: 'sports' },
+ { emoji: '⛳', category: 'sports' },
+ { emoji: '🥊', category: 'sports' },
+ { emoji: '🥋', category: 'sports' },
+ { emoji: '🎯', category: 'sports' },
+ { emoji: '⛹️', category: 'sports' },
+ { emoji: '🚴', category: 'sports' },
+ { emoji: '🏆', category: 'sports' },
+ { emoji: '🥇', category: 'sports' },
+ { emoji: '🥈', category: 'sports' },
+ { emoji: '🥉', category: 'sports' },
+ { emoji: '🏅', category: 'sports' },
+ { emoji: '🎖️', category: 'sports' },
+
+ // Nature
+ { emoji: '🌸', category: 'nature' },
+ { emoji: '💐', category: 'nature' },
+ { emoji: '🌹', category: 'nature' },
+ { emoji: '🌺', category: 'nature' },
+ { emoji: '🌻', category: 'nature' },
+ { emoji: '🌼', category: 'nature' },
+ { emoji: '🌷', category: 'nature' },
+ { emoji: '🌱', category: 'nature' },
+ { emoji: '🌲', category: 'nature' },
+ { emoji: '🌳', category: 'nature' },
+ { emoji: '🌴', category: 'nature' },
+ { emoji: '🌵', category: 'nature' },
+ { emoji: '🌾', category: 'nature' },
+ { emoji: '🌿', category: 'nature' },
+ { emoji: '☘️', category: 'nature' },
+ { emoji: '🍀', category: 'nature' },
+ { emoji: '🍁', category: 'nature' },
+ { emoji: '🍂', category: 'nature' },
+ { emoji: '🍃', category: 'nature' },
+ { emoji: '🌈', category: 'nature' },
+ { emoji: '☀️', category: 'nature' },
+ { emoji: '🌙', category: 'nature' },
+ { emoji: '⭐', category: 'nature' },
+ { emoji: '🔥', category: 'nature' },
+
+ // Objects
+ { emoji: '❤️', category: 'objects' },
+ { emoji: '💛', category: 'objects' },
+ { emoji: '💚', category: 'objects' },
+ { emoji: '💙', category: 'objects' },
+ { emoji: '💜', category: 'objects' },
+ { emoji: '🧡', category: 'objects' },
+ { emoji: '💔', category: 'objects' },
+ { emoji: '💯', category: 'objects' },
+ { emoji: '✨', category: 'objects' },
+ { emoji: '🌟', category: 'objects' },
+ { emoji: '💫', category: 'objects' },
+ { emoji: '🎵', category: 'objects' },
+ { emoji: '🎶', category: 'objects' },
+ { emoji: '🎸', category: 'objects' },
+ { emoji: '🎺', category: 'objects' },
+ { emoji: '🎷', category: 'objects' },
+ { emoji: '🎹', category: 'objects' },
+ { emoji: '👑', category: 'objects' },
+ { emoji: '💎', category: 'objects' },
+ { emoji: '🎁', category: 'objects' },
+ { emoji: '🎈', category: 'objects' },
+ { emoji: '🎉', category: 'objects' },
+ { emoji: '🎊', category: 'objects' },
+ { emoji: '🔮', category: 'objects' },
+];
diff --git a/client/src/index.css b/client/src/index.css
index de4e6b2..46e0483 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -56,11 +56,54 @@ body {
#root {
min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Three-column layout */
+.editor-layout {
+ display: flex;
+ flex: 1;
+ overflow: hidden;
+}
+
+.sidebar {
+ width: 320px;
+ background: var(--bg-secondary);
+ border-right: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.canvas-area {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg-tertiary);
+ overflow: auto;
+ padding: 2rem;
+}
+
+.properties-panel {
+ width: 280px;
+ background: var(--bg-secondary);
+ border-left: 1px solid var(--border);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
}
button {
font-family: inherit;
cursor: pointer;
+ outline: none;
+}
+
+button:focus-visible {
+ box-shadow: 0 0 0 2px var(--bg-primary), 0 0 0 4px var(--accent);
}
input, textarea, select {