Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from 'react'; | |
| import { | |
| Hammer, | |
| Sparkles, | |
| ShieldCheck, | |
| Terminal, | |
| Wallet, | |
| Key, | |
| Loader2, | |
| Eye, | |
| Image as ImageIcon, | |
| Zap, | |
| Globe, | |
| Share2, | |
| ExternalLink, | |
| Info, | |
| AlertCircle, | |
| // Added missing X icon import | |
| X | |
| } from 'lucide-react'; | |
| import { nftService, GeneratedNFT } from '../services/nftService.ts'; | |
| declare global { | |
| interface Window { | |
| ethereum?: any; | |
| } | |
| } | |
| const CryptView: React.FC = () => { | |
| const [prompt, setPrompt] = useState(''); | |
| const [isSynthesizing, setIsSynthesizing] = useState(false); | |
| const [isMinting, setIsMinting] = useState(false); | |
| const [nft, setNft] = useState<GeneratedNFT | null>(null); | |
| const [openSeaKey, setOpenSeaKey] = useState(''); | |
| const [walletAddress, setWalletAddress] = useState(''); | |
| const [mintLogs, setMintLogs] = useState<string[]>([]); | |
| const [walletConnected, setWalletConnected] = useState(false); | |
| const [isConnecting, setIsConnecting] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| const connectWallet = async () => { | |
| if (typeof window.ethereum === 'undefined') { | |
| setError("MetaMask not detected. Please install the browser extension to interact with the foundry."); | |
| return; | |
| } | |
| setIsConnecting(true); | |
| setError(null); | |
| try { | |
| const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); | |
| if (accounts.length > 0) { | |
| setWalletAddress(accounts[0]); | |
| setWalletConnected(true); | |
| } | |
| } catch (err: any) { | |
| console.error("Wallet Connection Failed:", err); | |
| setError(err.message || "Failed to establish neural handshake with wallet."); | |
| } finally { | |
| setIsConnecting(false); | |
| } | |
| }; | |
| useEffect(() => { | |
| const checkConnection = async () => { | |
| if (window.ethereum) { | |
| const accounts = await window.ethereum.request({ method: 'eth_accounts' }); | |
| if (accounts.length > 0) { | |
| setWalletAddress(accounts[0]); | |
| setWalletConnected(true); | |
| } | |
| } | |
| }; | |
| checkConnection(); | |
| if (window.ethereum) { | |
| window.ethereum.on('accountsChanged', (accounts: string[]) => { | |
| if (accounts.length > 0) { | |
| setWalletAddress(accounts[0]); | |
| setWalletConnected(true); | |
| } else { | |
| setWalletAddress(''); | |
| setWalletConnected(false); | |
| } | |
| }); | |
| } | |
| }, []); | |
| const handleSynthesize = async () => { | |
| if (!prompt.trim()) return; | |
| setIsSynthesizing(true); | |
| setNft(null); | |
| setMintLogs([]); | |
| setError(null); | |
| const [imageUrl, metadata] = await Promise.all([ | |
| nftService.generateImage(prompt), | |
| nftService.generateMetadata(prompt) | |
| ]); | |
| if (imageUrl) { | |
| setNft({ | |
| name: metadata.name || "Neural Artifact", | |
| description: metadata.description || "Synthesized by Lumina Oracle", | |
| imageUrl, | |
| traits: metadata.traits || [] | |
| }); | |
| } else { | |
| setError("Asset synthesis failed. Please adjust your prompt parameters."); | |
| } | |
| setIsSynthesizing(false); | |
| }; | |
| const handleMint = async () => { | |
| if (!nft || !openSeaKey || !walletConnected) return; | |
| setIsMinting(true); | |
| setMintLogs(["Starting deployment sequence..."]); | |
| const steps = await nftService.mintToOpenSea(nft, openSeaKey, walletAddress); | |
| for (const step of steps) { | |
| await new Promise(r => setTimeout(r, 800)); | |
| setMintLogs(prev => [...prev, step]); | |
| } | |
| setIsMinting(false); | |
| }; | |
| const shortenAddress = (addr: string) => `${addr.substring(0, 6)}...${addr.substring(addr.length - 4)}`; | |
| return ( | |
| <div className="space-y-10 animate-in fade-in duration-700 pb-20"> | |
| {/* Header */} | |
| <div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6"> | |
| <div> | |
| <h2 className="text-3xl font-black text-white italic tracking-tighter uppercase mb-2">Quantum <span className="text-blue-500 not-italic">Crypt</span></h2> | |
| <p className="text-zinc-500 text-[10px] font-black uppercase tracking-[0.3em]">Neural Asset Synthesis & Blockchain Deployment</p> | |
| </div> | |
| <div className="flex gap-4"> | |
| <div className="px-6 py-3 bg-zinc-950 border border-zinc-800 rounded-2xl flex items-center gap-4 shadow-xl"> | |
| <div className="flex items-center gap-2"> | |
| <div className={`w-2 h-2 rounded-full ${walletConnected ? 'bg-emerald-500 animate-pulse' : 'bg-rose-500'}`}></div> | |
| <span className="text-[10px] font-black text-zinc-500 uppercase tracking-widest"> | |
| Wallet: {walletConnected ? shortenAddress(walletAddress) : 'Disconnected'} | |
| </span> | |
| </div> | |
| </div> | |
| {!walletConnected && ( | |
| <button | |
| onClick={connectWallet} | |
| disabled={isConnecting} | |
| className="flex items-center space-x-2 px-6 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-2xl font-black text-[10px] uppercase tracking-widest transition-all shadow-lg shadow-blue-900/30 disabled:opacity-50" | |
| > | |
| {isConnecting ? <Loader2 size={14} className="animate-spin" /> : <Wallet size={14} />} | |
| <span>{isConnecting ? 'Initializing...' : 'Connect MetaMask'}</span> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| {error && ( | |
| <div className="p-4 bg-rose-500/10 border border-rose-500/20 rounded-2xl flex items-center gap-4 animate-in slide-in-from-top-2"> | |
| <AlertCircle size={20} className="text-rose-500 shrink-0" /> | |
| <p className="text-xs font-bold text-rose-200">{error}</p> | |
| <button onClick={() => setError(null)} className="ml-auto p-1 hover:bg-rose-500/10 rounded-lg text-rose-500"> | |
| <X size={16} /> | |
| </button> | |
| </div> | |
| )} | |
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-10"> | |
| {/* Left: Configuration & Foundry */} | |
| <div className="lg:col-span-1 space-y-8"> | |
| <div className="bg-zinc-950 border border-zinc-900 rounded-[2.5rem] p-8 shadow-2xl space-y-8"> | |
| <div className="flex items-center gap-3 mb-2"> | |
| <div className="p-3 bg-blue-600/10 text-blue-500 rounded-xl"> | |
| <Key size={18} /> | |
| </div> | |
| <h3 className="text-white font-black text-xs uppercase tracking-widest italic">Integration Layer</h3> | |
| </div> | |
| <div className="space-y-6"> | |
| <div className="space-y-2"> | |
| <label className="text-[9px] font-black text-zinc-600 uppercase tracking-widest ml-1">OpenSea API Key</label> | |
| <input | |
| type="password" | |
| value={openSeaKey} | |
| onChange={e => setOpenSeaKey(e.target.value)} | |
| placeholder="X-API-KEY-..." | |
| className="w-full bg-black border border-zinc-800 focus:border-blue-500/50 rounded-xl py-3 px-4 text-white text-xs outline-none transition-all placeholder:text-zinc-800 font-mono" | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <label className="text-[9px] font-black text-zinc-600 uppercase tracking-widest ml-1">Synthesis Prompt</label> | |
| <textarea | |
| value={prompt} | |
| onChange={e => setPrompt(e.target.value)} | |
| placeholder="e.g. Cybernetic obsidian eagle with neon circuits..." | |
| className="w-full bg-black border border-zinc-800 focus:border-blue-500/50 rounded-xl py-4 px-4 text-white text-xs outline-none transition-all placeholder:text-zinc-800 font-bold min-h-[120px] resize-none" | |
| /> | |
| </div> | |
| <button | |
| onClick={handleSynthesize} | |
| disabled={isSynthesizing || !prompt} | |
| className="w-full py-4 bg-zinc-900 hover:bg-zinc-100 hover:text-black text-white rounded-2xl font-black text-[10px] uppercase tracking-[0.2em] transition-all border border-zinc-800 flex items-center justify-center gap-3 disabled:opacity-50" | |
| > | |
| {isSynthesizing ? <Loader2 className="animate-spin" size={16} /> : <Sparkles size={16} />} | |
| <span>{isSynthesizing ? 'Synthesizing Neural Path...' : 'Synthesize Artifact'}</span> | |
| </button> | |
| </div> | |
| </div> | |
| {/* Minting Log Terminal */} | |
| <div className="bg-black border border-zinc-900 rounded-[2.5rem] overflow-hidden shadow-2xl h-[300px] flex flex-col"> | |
| <div className="bg-zinc-900/80 px-6 py-3 border-b border-zinc-800 flex justify-between items-center"> | |
| <div className="flex items-center gap-2"> | |
| <Terminal size={12} className="text-emerald-500" /> | |
| <span className="text-[9px] font-black uppercase tracking-widest text-zinc-500">Chain Trace Log</span> | |
| </div> | |
| <div className="flex gap-1.5"> | |
| <div className="w-1.5 h-1.5 rounded-full bg-zinc-800"></div> | |
| <div className="w-1.5 h-1.5 rounded-full bg-zinc-800"></div> | |
| <div className="w-1.5 h-1.5 rounded-full bg-zinc-800"></div> | |
| </div> | |
| </div> | |
| <div className="flex-1 p-6 font-mono text-[10px] overflow-y-auto custom-scrollbar space-y-1.5"> | |
| {mintLogs.length === 0 ? ( | |
| <div className="text-zinc-700 italic">Waiting for forge command...</div> | |
| ) : ( | |
| mintLogs.map((log, i) => ( | |
| <div key={i} className={`${log.includes('Successfully') ? 'text-emerald-500' : 'text-zinc-500'}`}> | |
| <span className="text-zinc-800 mr-2">[{new Date().toLocaleTimeString([], { hour12: false })}]</span> | |
| {log} | |
| </div> | |
| )) | |
| )} | |
| {isMinting && <div className="text-blue-500 animate-pulse font-black">Executing Quantum Block...</div>} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Center/Right: Preview & Metadata */} | |
| <div className="lg:col-span-2 space-y-8"> | |
| <div className="bg-zinc-950 border border-zinc-900 rounded-[3rem] p-10 shadow-2xl relative overflow-hidden group"> | |
| <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_0%,_#1e1b4b_0%,_transparent_50%)] opacity-30"></div> | |
| {!nft && !isSynthesizing ? ( | |
| <div className="h-[500px] flex flex-col items-center justify-center text-center space-y-6 relative z-10 border-2 border-dashed border-zinc-900 rounded-[2rem]"> | |
| <div className="w-20 h-20 bg-zinc-900 rounded-full flex items-center justify-center text-zinc-700"> | |
| <ImageIcon size={40} /> | |
| </div> | |
| <div> | |
| <h3 className="text-zinc-600 font-black uppercase tracking-[0.4em] text-sm">Foundry Idle</h3> | |
| <p className="text-zinc-700 text-xs mt-2 font-medium">Input parameters to initialize synthesis.</p> | |
| </div> | |
| </div> | |
| ) : isSynthesizing ? ( | |
| <div className="h-[500px] flex flex-col items-center justify-center text-center space-y-8 relative z-10"> | |
| <div className="relative"> | |
| <div className="w-48 h-48 rounded-full border-4 border-blue-500/20 border-t-blue-500 animate-spin"></div> | |
| <div className="absolute inset-0 flex items-center justify-center"> | |
| <Zap size={48} className="text-blue-500 animate-pulse" /> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <h3 className="text-white font-black uppercase tracking-[0.4em] italic animate-pulse">Forging Neural Layers</h3> | |
| <p className="text-zinc-500 text-[10px] font-black uppercase tracking-widest">Estimated completion: 8.2s</p> | |
| </div> | |
| </div> | |
| ) : nft ? ( | |
| <div className="flex flex-col lg:flex-row gap-12 relative z-10 animate-in zoom-in-95 duration-700"> | |
| <div className="lg:w-1/2"> | |
| <div className="relative group"> | |
| <div className="absolute -inset-1 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-[2.5rem] blur opacity-25 group-hover:opacity-75 transition duration-1000 group-hover:duration-200"></div> | |
| <img | |
| src={nft.imageUrl} | |
| className="relative w-full aspect-square rounded-[2rem] bg-black border border-zinc-800 object-cover shadow-2xl" | |
| alt="Synthesized NFT" | |
| /> | |
| <div className="absolute top-4 right-4 flex gap-2"> | |
| <div className="bg-black/60 backdrop-blur-md px-3 py-1.5 rounded-full border border-white/10 flex items-center gap-2"> | |
| <ShieldCheck size={12} className="text-emerald-500" /> | |
| <span className="text-[8px] font-black text-white uppercase tracking-widest">AI Verified</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="lg:w-1/2 flex flex-col justify-between"> | |
| <div className="space-y-8"> | |
| <div> | |
| <span className="px-3 py-1 bg-blue-600/10 text-blue-500 border border-blue-500/20 rounded-full text-[9px] font-black uppercase tracking-widest mb-4 inline-block">Unminted Asset</span> | |
| <h3 className="text-4xl font-black text-white italic tracking-tighter uppercase mb-2 leading-none">{nft.name}</h3> | |
| <p className="text-zinc-500 text-sm leading-relaxed font-medium italic">"{nft.description}"</p> | |
| </div> | |
| <div className="grid grid-cols-2 gap-4"> | |
| {nft.traits.map((trait, i) => ( | |
| <div key={i} className="p-4 bg-black/40 border border-zinc-800 rounded-2xl"> | |
| <p className="text-[8px] font-black text-zinc-600 uppercase tracking-widest mb-1">{trait.trait_type}</p> | |
| <p className="text-white font-bold text-xs uppercase">{trait.value}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="pt-10 flex gap-4"> | |
| <button | |
| onClick={handleMint} | |
| disabled={isMinting || !openSeaKey || !walletConnected} | |
| className="flex-1 py-5 bg-blue-600 hover:bg-blue-500 text-white rounded-[2rem] font-black text-xs uppercase tracking-[0.3em] transition-all flex items-center justify-center gap-4 shadow-xl shadow-blue-900/30 disabled:opacity-50" | |
| > | |
| {isMinting ? <Loader2 className="animate-spin" size={20} /> : <Hammer size={20} />} | |
| <span>{isMinting ? 'Casting Block...' : 'Forge to OpenSea'}</span> | |
| </button> | |
| <button className="p-5 bg-zinc-900 hover:bg-zinc-800 text-zinc-500 hover:text-white rounded-[2rem] transition-all border border-zinc-800"> | |
| <Share2 size={20} /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ) : null} | |
| </div> | |
| <div className="bg-zinc-950 border border-zinc-900 rounded-[3rem] p-10 relative overflow-hidden group shadow-2xl"> | |
| <div className="flex flex-col md:flex-row justify-between items-center gap-10"> | |
| <div className="flex items-center gap-8 flex-1 w-full"> | |
| <div className="w-20 h-20 bg-emerald-500/10 rounded-3xl flex items-center justify-center text-emerald-500 shadow-2xl shadow-emerald-500/5"> | |
| <ShieldCheck size={40} /> | |
| </div> | |
| <div className="flex-1"> | |
| <h4 className="text-xl font-black text-white italic tracking-tighter uppercase mb-1">OpenSea <span className="text-emerald-500 not-italic">Verified Foundry</span></h4> | |
| <p className="text-zinc-500 text-xs font-medium max-w-md">Lumina Quantum Foundry utilizes dedicated node architecture to ensure gas-optimized minting and instant marketplace visibility.</p> | |
| </div> | |
| </div> | |
| <button className="flex items-center gap-2 text-zinc-600 hover:text-white transition-colors"> | |
| <span className="text-[10px] font-black uppercase tracking-widest">Review Protocol</span> | |
| <ExternalLink size={14} /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default CryptView; |