insiderNFT / src /components /DeveloperHub.tsx
yonagush's picture
Update src/components/DeveloperHub.tsx
b973bea verified
import React, { useState, useRef } from 'react';
import { Layers, Database, Key, CheckCircle, FileJson, ArrowRight, ShieldCheck, Zap, Sparkles, Mail, Globe, Scale, Share2, Copy, FileText, MonitorPlay, Github, Terminal, Package, Rocket, Server, FolderTree, HelpCircle, AlertCircle, XCircle, Mic, Play, Pause, Loader2, Volume2, Info } from 'lucide-react';
import CodeBlock from './CodeBlock';
import { generateProposalScript, generateSpeech } from '../geminiService';
const DeveloperHub: React.FC = () => {
const [activeSection, setActiveSection] = useState<'architecture' | 'deployment' | 'compliance' | 'repo' | 'pitch'>('pitch');
const [presentationMode, setPresentationMode] = useState(false);
// Pitch Studio States
const [pitchScript, setPitchScript] = useState<string>('');
const [isGeneratingPitch, setIsGeneratingPitch] = useState(false);
const [isSpeaking, setIsSpeaking] = useState(false);
const [pitchType, setPitchType] = useState<'executive' | 'walkthrough'>('executive');
const handleCreatePitch = async () => {
setIsGeneratingPitch(true);
const script = await generateProposalScript(pitchType);
setPitchScript(script);
setIsGeneratingPitch(false);
};
const handlePlayPitch = async () => {
if (!pitchScript) return;
setIsSpeaking(true);
const base64Audio = await generateSpeech(pitchScript);
if (base64Audio) {
const audioData = decode(base64Audio);
const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 24000 });
const audioBuffer = await decodeAudioData(audioData, audioCtx, 24000, 1);
const source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioCtx.destination);
source.onended = () => setIsSpeaking(false);
source.start();
} else {
setIsSpeaking(false);
}
};
function decode(base64: string) {
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
async function decodeAudioData(
data: Uint8Array,
ctx: AudioContext,
sampleRate: number,
numChannels: number,
): Promise<AudioBuffer> {
const dataInt16 = new Int16Array(data.buffer);
const frameCount = dataInt16.length / numChannels;
const buffer = ctx.createBuffer(numChannels, frameCount, sampleRate);
for (let channel = 0; channel < numChannels; channel++) {
const channelData = buffer.getChannelData(channel);
for (let i = 0; i < frameCount; i++) {
channelData[i] = dataInt16[i * numChannels + channel] / 32768.0;
}
}
return buffer;
}
const dockerfileCode = `FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]`;
const renderSectionContent = (section: string) => {
switch(section) {
case 'pitch': return (
<div className="space-y-8 animate-in fade-in duration-500">
<div className="flex items-start justify-between">
<div>
<h2 className="text-3xl font-display font-bold text-white mb-2">Pitch Studio</h2>
<p className="text-gray-400 max-w-2xl">
Generate a professional narrative for your proposal video. Use the AI Narrator to provide the voiceover.
</p>
</div>
<div className="p-3 bg-brand-500/20 rounded-2xl border border-brand-500/30">
<Mic size={32} className="text-brand-500" />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/5 space-y-4">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<FileText size={18} className="text-brand-400" />
Script Config
</h3>
<div className="flex gap-2 p-1 bg-black/40 rounded-xl border border-white/5 mb-4">
<button
onClick={() => setPitchType('executive')}
className={`flex-1 py-2 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all ${pitchType === 'executive' ? 'bg-brand-500 text-white' : 'text-gray-500 hover:text-white'}`}
>
Executive Pitch
</button>
<button
onClick={() => setPitchType('walkthrough')}
className={`flex-1 py-2 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all ${pitchType === 'walkthrough' ? 'bg-brand-500 text-white' : 'text-gray-500 hover:text-white'}`}
>
Walkthrough V.O.
</button>
</div>
<button
onClick={handleCreatePitch}
disabled={isGeneratingPitch}
className="w-full py-4 bg-brand-600 hover:bg-brand-500 text-white rounded-xl font-black uppercase tracking-widest text-[10px] flex items-center justify-center gap-3 transition-all active:scale-95 disabled:opacity-50"
>
{isGeneratingPitch ? <Loader2 className="animate-spin" size={16} /> : <Sparkles size={16} />}
Generate {pitchType === 'executive' ? 'Proposal' : 'Walkthrough'} Script
</button>
{pitchScript && (
<div className="mt-6 space-y-4 animate-in slide-in-from-top-2">
<div className="p-4 bg-black/40 rounded-xl border border-white/10 max-h-[250px] overflow-y-auto">
<p className="text-xs text-gray-400 leading-relaxed whitespace-pre-wrap italic">
"{pitchScript}"
</p>
</div>
<button
onClick={handlePlayPitch}
disabled={isSpeaking}
className="w-full py-4 bg-white text-black hover:bg-brand-500 hover:text-white rounded-xl font-black uppercase tracking-widest text-[10px] flex items-center justify-center gap-3 transition-all disabled:opacity-50"
>
{isSpeaking ? <Loader2 className="animate-spin" size={16} /> : <Volume2 size={16} />}
Play Narrator
</button>
</div>
)}
</div>
<div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/5 flex flex-col justify-center items-center text-center space-y-4">
<div className="w-16 h-16 rounded-full bg-blue-500/20 flex items-center justify-center border border-blue-500/30 mb-2">
<MonitorPlay size={32} className="text-blue-500" />
</div>
<h3 className="text-lg font-bold text-white">Video Ready</h3>
<p className="text-xs text-gray-500 max-w-xs">
Turn on your screen recorder, activate "Presentation Mode", and let the AI handle the narration while you navigate the tabs.
</p>
<div className="flex gap-2">
<div className="px-3 py-1 bg-white/5 rounded text-[10px] text-gray-400 font-mono">1080p Export</div>
<div className="px-3 py-1 bg-white/5 rounded text-[10px] text-gray-400 font-mono">Dolby Audio</div>
</div>
</div>
</div>
</div>
);
case 'repo': return (
<div className="space-y-8 animate-in fade-in duration-500">
<div>
<h2 className="text-2xl font-display font-bold text-white mb-2">Repository Setup</h2>
<p className="text-gray-400">
GitHub is the standard for hosting the Shib Insider technical ecosystem.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
<h3 className="text-brand-400 font-bold mb-3 flex items-center gap-2">
<Package size={18} /> Frontend Hub
</h3>
<p className="text-xs text-gray-500 mb-4">React 18 + Vite. Optimized for L2 wallet interactions.</p>
<div className="text-[10px] font-mono bg-black/40 p-2 rounded border border-white/5 text-gray-400">
git init<br/>
git add .<br/>
git commit -m "initial protocol"
</div>
</div>
<div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
<h3 className="text-blue-400 font-bold mb-3 flex items-center gap-2">
<Terminal size={18} /> Backend Node
</h3>
<p className="text-xs text-gray-500 mb-4">Python + Flask. Handles Beehiiv RSS & DB verification.</p>
<div className="text-[10px] font-mono bg-black/40 p-2 rounded border border-white/5 text-gray-400">
python -m venv venv<br/>
pip install flask feedparser
</div>
</div>
</div>
</div>
);
case 'deployment': return (
<div className="space-y-8 animate-in fade-in duration-500">
<div>
<h2 className="text-2xl font-display font-bold text-white mb-2">Deploying to Hugging Face</h2>
<p className="text-gray-400">Host your dashboard on Hugging Face Spaces using the Docker SDK for maximum reliability.</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/10">
<h3 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
<FileCode2 size={18} className="text-blue-400" /> Dockerfile
</h3>
<CodeBlock language="dockerfile" code={dockerfileCode} filename="Dockerfile" />
</div>
<div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/10">
<h3 className="text-lg font-bold text-white mb-4">Step-by-Step Instructions</h3>
<ol className="space-y-4">
<li className="flex gap-4">
<div className="w-6 h-6 rounded-full bg-brand-500 flex items-center justify-center text-[10px] font-black shrink-0">1</div>
<div>
<p className="text-sm font-bold text-white">Create Space</p>
<p className="text-xs text-gray-500">Go to HF Spaces, select "New Space", choose <strong>Docker</strong> as the SDK.</p>
</div>
</li>
<li className="flex gap-4">
<div className="w-6 h-6 rounded-full bg-brand-500 flex items-center justify-center text-[10px] font-black shrink-0">2</div>
<div>
<p className="text-sm font-bold text-white">Config Secrets</p>
<p className="text-xs text-gray-500">Under "Settings" → "Variables and Secrets", add your <code>API_KEY</code> from Google AI Studio.</p>
</div>
</li>
<li className="flex gap-4">
<div className="w-6 h-6 rounded-full bg-brand-500 flex items-center justify-center text-[10px] font-black shrink-0">3</div>
<div>
<p className="text-sm font-bold text-white">Push Files</p>
<p className="text-xs text-gray-500">Push your <code>App.tsx</code>, <code>index.html</code>, and <code>Dockerfile</code>. HF will build automatically.</p>
</div>
</li>
</ol>
</div>
</div>
<div className="space-y-6">
<div className="p-6 bg-blue-900/10 border border-blue-500/20 rounded-2xl">
<h4 className="text-blue-400 font-black text-[10px] uppercase tracking-widest mb-3 flex items-center gap-2">
<Info size={14} /> Deployment Tips
</h4>
<ul className="space-y-3 text-[11px] text-gray-400">
<li className="flex gap-2"><div className="w-1 h-1 rounded-full bg-blue-500 mt-1.5 shrink-0"></div> Ensure <code>dist</code> folder matches Docker config.</li>
<li className="flex gap-2"><div className="w-1 h-1 rounded-full bg-blue-500 mt-1.5 shrink-0"></div> Set Space to "Public" for subscriber access.</li>
<li className="flex gap-2"><div className="w-1 h-1 rounded-full bg-blue-500 mt-1.5 shrink-0"></div> Port 7860 is the default HF port.</li>
</ul>
</div>
</div>
</div>
</div>
);
case 'compliance': return (
<div className="space-y-8 animate-in fade-in duration-500">
<h2 className="text-2xl font-display font-bold text-white">Platform Compliance Strategy</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
<h3 className="text-lg font-bold text-brand-400 mb-2">1. Gamified Discovery</h3>
<p className="text-sm text-gray-400 leading-relaxed">Frame activity as exploring the newsletter value rather than "pay-per-click" to maintain platform health.</p>
</div>
<div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
<h3 className="text-lg font-bold text-green-400 mb-2">2. On-Chain Verifiability</h3>
<p className="text-sm text-gray-400 leading-relaxed">Using Shibarium ensures that every reward given is transparent and traceable back to genuine engagement.</p>
</div>
</div>
</div>
);
default: return <div className="text-gray-500">Section details loaded in full codebase.</div>;
}
}
const SidebarButton = ({ section, icon: Icon, label }: { section: typeof activeSection, icon: any, label: string }) => (
<button
onClick={() => setActiveSection(section)}
className={`w-full text-left px-4 py-3 rounded-xl transition-all flex items-center gap-3 ${
activeSection === section
? 'bg-brand-500/10 text-brand-400 border border-brand-500/20'
: 'text-gray-400 hover:bg-gray-800'
}`}
>
<Icon size={18} />
<span className="font-semibold">{label}</span>
</button>
);
return (
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8 h-full">
<div className="lg:col-span-1 flex flex-col gap-6">
<div className="space-y-2">
{!presentationMode && (
<>
<SidebarButton section="pitch" icon={Mic} label="Pitch Studio" />
<SidebarButton section="repo" icon={Github} label="Repository Setup" />
<SidebarButton section="deployment" icon={Rocket} label="Deploy to HF" />
<SidebarButton section="compliance" icon={Scale} label="Compliance" />
</>
)}
<div className="pt-6 border-t border-gray-800 space-y-3">
<button
onClick={() => setPresentationMode(!presentationMode)}
className={`w-full py-3 px-4 rounded-xl font-bold flex items-center justify-center gap-2 transition-all ${
presentationMode
? 'bg-brand-600 text-white shadow-lg'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'
}`}
>
<MonitorPlay size={18} />
{presentationMode ? 'Exit Presentation' : 'Presentation Mode'}
</button>
</div>
</div>
</div>
<div className="lg:col-span-3">
<div className="glass-panel p-8 rounded-2xl min-h-[600px] relative overflow-y-auto max-h-[75vh]">
{presentationMode ? (
<div className="space-y-16">
<div className="pb-4 border-b border-gray-800">
<h1 className="text-3xl font-display font-bold text-white mb-2">Technical Specification: Weekly Trail</h1>
<p className="text-gray-400">Project Overview and Presentation Mode active.</p>
</div>
{renderSectionContent('pitch')}
<hr className="border-gray-800" />
{renderSectionContent('deployment')}
<hr className="border-gray-800" />
{renderSectionContent('repo')}
</div>
) : (
renderSectionContent(activeSection)
)}
</div>
</div>
</div>
);
};
// Internal icon fix for the file
const FileCode2 = (props: any) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v4"/><polyline points="14 2 14 8 20 8"/><path d="m3 15 2 2-2 2"/><path d="m9 15-2 2 2 2"/></svg>
);
export default DeveloperHub;