import React, { useState, useRef, useEffect } from 'react';
import { Menu, X, Upload, ZoomIn, Undo2, Save, Search, PlusCircle, Image as ImageIcon, Lock, Globe, DollarSign, Store, LayoutGrid, Edit3 } from 'lucide-react';
// --- COMPONENTES PRINCIPALES ---
export default function App() {
const [activeTab, setActiveTab] = useState('editor'); // editor, catalog, store
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [cards, setCards] = useState([]); // Almacenará las cartas del usuario
// Navegación
const navItems = [
{ id: 'editor', label: 'Editor & Vista Previa', icon: Edit3 },
{ id: 'catalog', label: 'Mi Catálogo', icon: LayoutGrid },
{ id: 'store', label: 'Tienda Digital', icon: Store },
];
return (
{/* HEADER / NAVEGACIÓN */}
{/* Menú Móvil */}
{isMenuOpen && (
{/* ÁREA DE CONTENIDO PRINCIPAL */}
{activeTab === 'editor' && (
setCards([...cards, newCard])}
goToCatalog={() => setActiveTab('catalog')}
/>
)}
{activeTab === 'catalog' && (
setActiveTab('editor')}
/>
)}
{activeTab === 'store' && (
)}
);
}
// --- VISTA 1: EDITOR Y VISTA PREVIA ---
function EditorVistaPrevia({ onSave, goToCatalog }) {
const canvasRef = useRef(null);
const [imageObj, setImageObj] = useState(null);
const [zoom, setZoom] = useState(1);
const [zoomHistory, setZoomHistory] = useState([]);
// Lógica para cargar la imagen
const handleImageUpload = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
setImageObj(img);
setZoom(1);
setZoomHistory([]);
drawImage(img, 1);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
};
// Dibujar en el Canvas HTML5
const drawImage = (img, scale) => {
const canvas = canvasRef.current;
if (!canvas || !img) return;
const ctx = canvas.getContext('2d');
// Configurar el tamaño del canvas (fijo para vista previa, responsivo visualmente por CSS)
canvas.width = 600;
canvas.height = 800; // Proporción tipo carta
// Limpiar canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Calcular centro y escala
const scaledWidth = img.width * scale;
const scaledHeight = img.height * scale;
const x = (canvas.width - scaledWidth) / 2;
const y = (canvas.height - scaledHeight) / 2;
// Fondo oscuro para el canvas
ctx.fillStyle = '#1e293b';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Dibujar imagen
ctx.drawImage(img, x, y, scaledWidth, scaledHeight);
};
const handleZoomChange = (e) => {
const newZoom = parseFloat(e.target.value);
setZoomHistory([...zoomHistory, zoom]); // Guardar estado actual
setZoom(newZoom);
drawImage(imageObj, newZoom);
};
const handleUndo = () => {
if (zoomHistory.length > 0) {
const previousZoom = zoomHistory[zoomHistory.length - 1];
const newHistory = zoomHistory.slice(0, -1);
setZoomHistory(newHistory);
setZoom(previousZoom);
drawImage(imageObj, previousZoom);
}
};
const handleSaveToCatalog = async () => {
if (!imageObj) return;
const canvas = canvasRef.current;
// Extraer la imagen editada del canvas
const editedImageDataUrl = canvas.toDataURL('image/jpeg', 0.9);
// ---------------------------------------------------------
// SIMULACIÓN DE CONEXIÓN A BASE DE DATOS WORDPRESS
// ---------------------------------------------------------
/*
En un entorno real de WordPress, aquí haríamos una petición POST a la REST API:
const formData = new FormData();
// Convertir dataURL a Blob
const blob = await (await fetch(editedImageDataUrl)).blob();
formData.append('file', blob, 'carta-digital.jpg');
fetch('https://capricorniografx.com/wp-json/wp/v2/media', {
method: 'POST',
headers: {
'Authorization': 'Bearer TU_TOKEN_JWT',
},
body: formData
})
.then(res => res.json())
.then(data => {
// data.source_url contendría algo como:
// https://capricorniografx.com/wp-content/uploads/2026/07/carta-digital.jpg
});
*/
// Para esta demo funcional, guardamos en el estado local simulando la ruta
const newCard = {
id: Date.now().toString(),
name: 'Nueva Carta Digital',
image: editedImageDataUrl,
// Ruta simulada de WP multimedia
wpPathSimulated: `https://capricorniografx.com/wp-content/uploads/${new Date().getFullYear()}/${String(new Date().getMonth() + 1).padStart(2, '0')}/carta-${Date.now()}.jpg`,
variants: [], // Para futuras variantes
isForSale: false,
price: 0.00
};
onSave(newCard);
// Pequeño feedback visual y redirección
alert('¡Carta guardada en el catálogo exitosamente! (Ruta simulada WP: ' + newCard.wpPathSimulated + ')');
goToCatalog();
};
return (
{/* Logo y Branding */}
{/* Navegación Desktop */}
{/* Botón Menú Hamburguesa (Móvil) */}
{navItems.map((item) => (
))}
)}
{/* BARRA LATERAL IZQUIERDA - CONTROLES */}
{/* Instrucciones */}
{/* Controles de carga */}
{/* Controles de Zoom */}
{/* Botón Guardar */}
{/* ÁREA DE VISTA PREVIA (CENTRAL) */}
);
}
// --- VISTA 2: MI CATÁLOGO (ESTILO PINTEREST) ---
function MiCatalogo({ cards, setCards, goToEditor }) {
const [boardName, setBoardName] = useState("Colección Principal");
const [isEditingBoardName, setIsEditingBoardName] = useState(false);
const [isPublic, setIsPublic] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const filteredCards = cards.filter(card =>
card.name.toLowerCase().includes(searchQuery.toLowerCase())
);
const updateCardName = (id, newName) => {
setCards(cards.map(c => c.id === id ? { ...c, name: newName } : c));
};
return (
Editor
{/* Instrucciones */}
Instrucciones:
- Sube la imagen de tu carta.
- Ajusta el zoom para encuadrarla.
- Usa deshacer si te equivocas.
- Guarda en tu catálogo personal.
{!imageObj && (
)}
La vista previa de tu carta aparecerá aquí
{/* Cabecera del Tablero */}
setSearchQuery(e.target.value)}
/>
{/* Grid estilo Pinterest (Masonry-like con CSS Columns) */}
{filteredCards.length === 0 ? (
) : (
);
}
// --- VISTA 3: TIENDA DIGITAL ---
function TiendaDigital({ cards, setCards }) {
const updateStoreSettings = (id, field, value) => {
setCards(cards.map(c => c.id === id ? { ...c, [field]: value } : c));
};
const formatPrice = (value) => {
// Asegurar formato 0.00
const num = parseFloat(value);
return isNaN(num) ? "0.00" : num.toFixed(2);
};
return (
{/* Info del Tablero */}
{/* Controles del Tablero (Toggle Público/Privado) */}
Privado
{/* Toggle Switch Personalizado (Fondo azul y check blanco) */}
Público
{/* Buscador y Botón de Añadir */}
{isEditingBoardName ? (
setIsEditingBoardName(true)}
title="Clic para editar nombre"
>
{boardName}
)}
setBoardName(e.target.value)}
className="text-3xl font-bold border-b-2 border-blue-500 focus:outline-none bg-transparent px-1 py-1 text-gray-800"
autoFocus
/>
) : (
setIsEditingBoardName(true)}
title="Clic para editar nombre"
>
{boardName}
)}
{cards.length} {cards.length === 1 ? 'carta' : 'cartas'} en total
No hay cartas aquí
Agrega tu primera carta digital usando el editor.
{filteredCards.map((card) => (
)}
{/* Imagen de la carta */}
{/* Overlay on hover */}
))}
ID: {card.id.slice(-6)}
{/* Nombre Editable */}
updateCardName(card.id, e.target.value)}
className="w-full font-bold text-gray-800 text-lg border-b border-transparent hover:border-gray-300 focus:border-blue-500 focus:outline-none transition-colors bg-transparent mb-3"
placeholder="Nombre de la carta..."
/>
{/* Slider de Variantes a Futuro */}
Variantes (Próximamente)
{/* Mockups de variantes */}
{[1, 2, 3].map(i => (
V{i}
))}
{/* Banner Promocional Artículos */}
) : (
);
}
{/* Decoración de fondo */}
Promoción Exclusiva
Mercado de Cartas
Mercado de Cartas
Capricornio Grafx
Monetiza tu colección. Configura los precios de tus variantes digitales y ponlas a la venta directamente desde tu catálogo.
Gestión de Ventas
{cards.length === 0 ? (Tu catálogo está vacío. Sube cartas en el Editor para poder venderlas aquí.
{cards.map((card) => (
)}
{/* Imagen */}
{card.isForSale && (
{/* Controles de Tienda (Sin sobreponer elementos visuales sobre la imagen) */}
))}
EN VENTA
)}
{card.name}
{/* Control de Precio Formato $ 0.00 */}
{/* Botón Poner a la venta */}
$
updateStoreSettings(card.id, 'price', e.target.value)}
onBlur={(e) => updateStoreSettings(card.id, 'price', formatPrice(e.target.value))}
disabled={card.isForSale} // Prevenir cambio de precio si ya está a la venta
className={`block w-full pl-8 pr-12 py-2.5 sm:text-sm border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500 transition-colors ${
card.isForSale ? 'bg-gray-100 text-gray-500 cursor-not-allowed border-transparent' : 'bg-white border-gray-300'
}`}
placeholder="0.00"
/>
USD
{
e.target.onerror = null;
e.target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 12L12 2l10 10-10 10L2 12z'/%3E%3C/svg%3E";
}}
/>