diff --git a/components/pages/create-video/CreateInput/AddItemModal.tsx b/components/pages/create-video/CreateInput/AddItemModal.tsx new file mode 100644 index 0000000..2104055 --- /dev/null +++ b/components/pages/create-video/CreateInput/AddItemModal.tsx @@ -0,0 +1,334 @@ +"use client"; + +import { useState, useRef, useEffect } from 'react'; +import { Modal, Upload, message } from 'antd'; +import NextImage from 'next/image'; +import { + CloseOutlined, + UserOutlined, + CameraOutlined, + BulbOutlined, + CloudUploadOutlined, + CheckOutlined +} from '@ant-design/icons'; +import type { PhotoType } from '../PhotoPreview/types'; + +interface AddItemModalProps { + /** Modal visibility state */ + visible: boolean; + /** Close handler */ + onClose: () => void; + /** Type of item to add */ + itemType: PhotoType | null; + /** Submit handler */ + onSubmit: (data: { name: string; description: string; file: File; index?: number }) => void; + /** Edit mode - initial data for editing */ + editData?: { + index: number; + name?: string; + description?: string; + imageUrl?: string; + }; +} + +/** + * Modern modal for adding/editing items (character, scene, prop) with name, description, and image. + * Features a card-based, non-traditional form design matching the app theme. + */ +export function AddItemModal({ + visible, + onClose, + itemType, + onSubmit, + editData, +}: AddItemModalProps) { + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [imageFile, setImageFile] = useState(null); + const [imagePreview, setImagePreview] = useState(null); + const [isDragging, setIsDragging] = useState(false); + + const fileInputRef = useRef(null); + const isEditMode = !!editData; + + /** Initialize form with edit data */ + useEffect(() => { + if (visible && editData) { + setName(editData.name || ''); + setDescription(editData.description || ''); + setImagePreview(editData.imageUrl || null); + setImageFile(null); + } else if (visible) { + setName(''); + setDescription(''); + setImagePreview(null); + setImageFile(null); + } + }, [visible, editData]); + + /** Get item configuration based on type */ + const getItemConfig = () => { + const prefix = isEditMode ? 'Edit' : 'Add'; + switch (itemType) { + case 'character': + return { + icon: , + title: `${prefix} Character`, + namePlaceholder: 'Character name', + descriptionPlaceholder: 'Describe the character\'s appearance, personality, or key features...', + }; + case 'scene': + return { + icon: , + title: `${prefix} Scene`, + namePlaceholder: 'Scene name', + descriptionPlaceholder: 'Describe the location, atmosphere, or visual elements...', + }; + case 'prop': + return { + icon: , + title: `${prefix} Prop`, + namePlaceholder: 'Prop name', + descriptionPlaceholder: 'Describe the object\'s appearance, function, or significance...', + }; + default: + return { + icon: null, + title: `${prefix} Item`, + namePlaceholder: 'Item name', + descriptionPlaceholder: 'Add a description...', + }; + } + }; + + const config = getItemConfig(); + + /** Handle file selection */ + const handleFileSelect = (file: File) => { + if (!file.type.startsWith('image/')) { + message.error('Please upload an image file'); + return; + } + + setImageFile(file); + const reader = new FileReader(); + reader.onload = (e) => { + setImagePreview(e.target?.result as string); + }; + reader.readAsDataURL(file); + }; + + /** Handle drag and drop */ + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + + const file = e.dataTransfer.files[0]; + if (file) { + handleFileSelect(file); + } + }; + + /** Handle form submission */ + const handleSubmit = () => { + if (!name.trim()) { + message.warning('Please enter a name'); + return; + } + + // In edit mode, if no new file is selected, we need to use existing image + if (!imageFile && !imagePreview) { + message.warning('Please upload an image'); + return; + } + + // If editing and no new file, create a dummy file for the existing image + const submitFile = imageFile || new File([], 'existing-image'); + + onSubmit({ + name: name.trim(), + description: description.trim(), + file: submitFile, + ...(isEditMode && editData && { index: editData.index }), + }); + + // Reset form + setName(''); + setDescription(''); + setImageFile(null); + setImagePreview(null); + onClose(); + }; + + /** Handle modal close */ + const handleClose = () => { + setName(''); + setDescription(''); + setImageFile(null); + setImagePreview(null); + onClose(); + }; + + return ( + } + width="90%" + style={{ maxWidth: '480px' }} + styles={{ + mask: { backdropFilter: 'blur(4px)', backgroundColor: 'rgba(0, 0, 0, 0.5)' }, + content: { + padding: 0, + backdropFilter: 'blur(12px)', + backgroundColor: 'rgba(0, 0, 0, 0.85)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '24px', + overflow: 'hidden', + }, + }} + > +
+ {/* Header with gradient */} +
+
+
+ {config.icon} +
+

{config.title}

+
+
+ + {/* Content */} +
+ {/* Image Upload Area */} +
+
+ + Image +
+ +
{ + e.preventDefault(); + setIsDragging(true); + }} + onDragLeave={() => setIsDragging(false)} + onDrop={handleDrop} + onClick={() => fileInputRef.current?.click()} + style={{ cursor: 'pointer', aspectRatio: '16/9' }} + > + {imagePreview ? ( +
+ +
+ Click to change +
+
+ +
+
+ ) : ( +
+ +
+

+ Click to upload or drag & drop +

+

+ PNG, JPG, WEBP up to 10MB +

+
+
+ )} + { + const file = e.target.files?.[0]; + if (file) handleFileSelect(file); + }} + /> +
+
+ + {/* Name Input */} +
+
+
+ Name +
+ setName(e.target.value)} + placeholder={config.namePlaceholder} + className="w-full px-3 py-2 bg-black/30 border border-white/10 rounded-lg text-gray-200 placeholder-gray-500 outline-none focus:border-cyan-400/60 focus:bg-black/40 transition-all duration-200 text-sm" + maxLength={50} + /> +
+ + {/* Description Input */} +
+
+
+ Description + (Optional) +
+