import React, { useState, useEffect, useRef, useMemo, createContext, useContext } from 'react'; import { BrowserRouter, Routes, Route, useNavigate, Link } from 'react-router-dom'; import { Stage, Layer, Text, Image as KonvaImage, Transformer, Rect, Group } from 'react-konva'; import axios from 'axios'; import { Type, Image as ImageIcon, Settings, Trash2, Undo, Redo, ShoppingBag, Truck, LayoutDashboard, MessageCircle, ChevronRight, ArrowRight, Paintbrush, ShieldCheck, CheckCircle, Plus, Minus, Layers, Star, TrendingUp, Award, Save, X, Menu, Zap, Box, Fingerprint } from 'lucide-react'; const API_URL = 'http://localhost:5000/api'; // ========================================== // UTILS & ERROR HANDLING // ========================================== class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { return { hasError: true }; } render() { if (this.state.hasError) { return (

STUDIO ERROR

The design engine encountered a conflict. We've notified our technicians.

); } return this.props.children; } } const formatRs = (amount) => `Rs. ${Number(amount || 0).toLocaleString('en-LK', { minimumFractionDigits: 2 })}`; const DEFAULT_SETTINGS = { basePrices: { budget: 1500, corporate: 2500, premium: 3500 }, printFees: { front: 800, back: 800, left: 400, right: 400 }, rushFee: 1500, deliveryFee: 500, whatsappNumber: "94741336159", bankDetails: "Bank: Commercial Bank\nAccount: 0987654321", discountTiers: [{ minQty: 15, discountPercent: 5 }, { minQty: 50, discountPercent: 10 }] }; const SHIRT_COLORS = [ { name: 'Pure White', hex: '#ffffff', stroke: '#e2e8f0' }, { name: 'Onyx Black', hex: '#111827', stroke: '#000000' }, { name: 'Navy Blue', hex: '#1e3a8a', stroke: '#172554' }, { name: 'Heather Gray', hex: '#94a3b8', stroke: '#64748b' }, ]; const SIZES = ['S', 'M', 'L', 'XL', '2XL', '3XL']; const SettingsContext = createContext(DEFAULT_SETTINGS); const AppProvider = ({ children }) => { const [settings, setSettings] = useState(DEFAULT_SETTINGS); useEffect(() => { axios.get(`${API_URL}/settings`) .then(res => { if (res.data?.basePrices) setSettings(res.data); }) .catch(() => console.log("DesignLab: Using local defaults")); }, []); return {children}; }; // ========================================== // CANVAS ENGINE COMPONENTS // ========================================== const TShirtBackground = ({ color, stroke, view }) => { let path = "M25 10 Q 50 25 75 10 L 95 30 L 80 45 L 80 95 L 20 95 L 20 45 L 5 30 Z"; if (view === 'left' || view === 'right') path = "M30 10 Q 50 5 70 10 L 85 40 L 70 95 L 30 95 L 15 40 Z"; return ( ); }; const KonvaNodeImage = ({ node, onSelect, isReadOnly }) => { const [image, setImage] = useState(null); useEffect(() => { if (!node.url) return; const img = new window.Image(); img.src = node.url; img.crossOrigin = 'Anonymous'; img.onload = () => setImage(img); }, [node.url]); return ( ); }; // ========================================== // VIEW: PREMIUM HOMEPAGE // ========================================== const LandingPage = () => (
Precision Print Technology

WE BUILD,
YOU DESIGN.

The ultimate design lab for modern brands. High-performance fabrics meet an uncompromising mobile editor.

Enter Design Lab
{[ { icon: , title: "Identity", desc: "Every order is a unique project. Our editor ensures your brand identity is translated perfectly to fabric." }, { icon: , title: "Precision", desc: "Automated production scheduling means consistent quality and 48-hour dispatch lead times." }, { icon: , title: "Logistics", desc: "Real-time tracking and insured delivery across Sri Lanka and international destinations." } ].map((item, i) => (
{item.icon}

{item.title}

{item.desc}

))}
); // ========================================== // VIEW: DESIGN LAB // ========================================== const DesignLab = () => { const navigate = useNavigate(); const settings = useContext(SettingsContext); const containerRef = useRef(null); const stageRef = useRef(null); const [nodes, setNodes] = useState({ front: [], back: [], left: [], right: [] }); const [activeView, setActiveView] = useState('front'); const [selectedId, setSelectedId] = useState(null); const [scale, setScale] = useState(1); const [config, setConfig] = useState({ material: 'corporate', color: SHIRT_COLORS[0], sizeQuantities: { 'S': 0, 'M': 1, 'L': 0, 'XL': 0, '2XL': 0 }, rush: false }); const activeNodes = nodes[activeView] || []; const selectedNode = activeNodes.find(n => n.id === selectedId); const totalQty = Object.values(config.sizeQuantities).reduce((a, b) => a + b, 0); useEffect(() => { const resizer = () => { if (!containerRef.current) return; const sX = (containerRef.current.clientWidth - 40) / 360; const sY = (containerRef.current.clientHeight - 120) / 480; setScale(Math.max(Math.min(sX, sY, 1.15), 0.45)); }; window.addEventListener('resize', resizer); setTimeout(resizer, 100); return () => window.removeEventListener('resize', resizer); }, []); const pricing = useMemo(() => { const baseP = settings.basePrices?.[config.material] || 2500; let pSideCount = Object.values(nodes).filter(v => v.length > 0).length; let costPerShirt = baseP + (pSideCount * 800); let disc = 0; const tier = (settings.discountTiers || []).sort((a,b)=>b.minQty-a.minQty).find(t => totalQty >= t.minQty); if (tier) disc = tier.discountPercent; const finalPerShirt = costPerShirt * (1 - (disc/100)); return { perShirt: finalPerShirt, total: finalPerShirt * totalQty + (config.rush ? 1500 : 0) }; }, [nodes, config, settings, totalQty]); const addText = () => { const id = `t_${Date.now()}`; const n = [...activeNodes, { id, type: 'text', text: 'NEW LAYER', x: 60, y: 150, fontSize: 26, fill: '#000', fontFamily: 'Arial', rotation: 0, scaleX: 1, scaleY: 1 }]; setNodes({...nodes, [activeView]: n}); setSelectedId(id); }; const handleFileUpload = (e) => { const file = e.target.files[0]; if (!file) return; const url = URL.createObjectURL(file); const id = `i_${Date.now()}`; const n = [...activeNodes, { id, type: 'image', url, x: 50, y: 100, width: 120, height: 120, rotation: 0, scaleX: 1, scaleY: 1 }]; setNodes({...nodes, [activeView]: n}); setSelectedId(id); }; return (
C
Studio
{/* Editor Sidebar */} {/* Workspace */}
{if(e.target.name()==='bg') setSelectedId(null)}}> {activeNodes.map(n => n.type === 'text' ? ( setSelectedId(n.id)} onDragEnd={e => { const u = activeNodes.map(nd => nd.id === n.id ? {...nd, x: e.target.x(), y: e.target.y()} : nd); setNodes({...nodes, [activeView]: u}); }} /> ) : ( setSelectedId(n.id)} isReadOnly={false} /> ))} {selectedId && }
{['front', 'back', 'left', 'right'].map(v => ( ))}
{/* Production Sidebar */}
); }; // ========================================== // VIEW: CHECKOUT // ========================================== const CheckoutView = () => { const navigate = useNavigate(); const settings = useContext(SettingsContext); const [orderData, setOrderData] = useState(null); const [dbOrder, setDbOrder] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { const data = localStorage.getItem('cenzi_current_order'); if (data) setOrderData(JSON.parse(data)); else navigate('/design'); }, [navigate]); const handlePlaceOrder = async (e) => { e.preventDefault(); setLoading(true); const fd = new FormData(e.target); const payload = { customer: { name: fd.get('name'), phone: fd.get('phone'), deliveryMethod: fd.get('delivery'), address: fd.get('address') || 'Store Pickup' }, design: { color: orderData.design.config.color.name, sizeQuantities: orderData.design.config.sizeQuantities, style: orderData.design.config.style, material: orderData.design.config.material }, pricing: orderData.design.pricing, projectJson: orderData.projectJson }; try { const res = await axios.post(`${API_URL}/orders`, payload); setDbOrder(res.data); localStorage.removeItem('cenzi_current_order'); } catch(err) { alert("Production server is currently offline. Please try again in 5 minutes."); } finally { setLoading(false); } }; if (!orderData) return null; return (
{!dbOrder ? (

Fulfillment Details

Please provide accurate contact information for order confirmation.