mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-24 16:37:55 +00:00
feat(web): add network subpages and subdomains listing page
- Add /subdomains page listing all 20 PezkuwiChain subdomains - Add Back to Home button to Subdomains page - Create NetworkPage reusable component for network details - Add 7 network subpages: /mainnet, /staging, /testnet, /beta, /alfa, /development, /local - Update ChainSpecs network cards to navigate to network subpages - Add i18n translations for chainSpecs section in en.ts - Add SDK docs with rebranding support (rebrand-rustdoc.cjs) - Add generate-docs-structure.cjs for automatic docs generation - Update shared libs: endpoints, polkadot, wallet, xcm-bridge - Add new token logos: TYR, ZGR, pezkuwi_icon - Add new pages: Explorer, Docs, Wallet, Api, Faucet, Developers, Grants, Wiki, Forum, Telemetry
This commit is contained in:
+40
-1
@@ -42,6 +42,26 @@ const PresaleList = lazy(() => import('./pages/launchpad/PresaleList'));
|
||||
const PresaleDetail = lazy(() => import('./pages/launchpad/PresaleDetail'));
|
||||
const CreatePresale = lazy(() => import('./pages/launchpad/CreatePresale'));
|
||||
const NotFound = lazy(() => import('@/pages/NotFound'));
|
||||
const Explorer = lazy(() => import('@/pages/Explorer'));
|
||||
const Docs = lazy(() => import('@/pages/Docs'));
|
||||
const Wallet = lazy(() => import('@/pages/Wallet'));
|
||||
const Api = lazy(() => import('@/pages/Api'));
|
||||
const Faucet = lazy(() => import('@/pages/Faucet'));
|
||||
const Developers = lazy(() => import('@/pages/Developers'));
|
||||
const Grants = lazy(() => import('@/pages/Grants'));
|
||||
const Wiki = lazy(() => import('@/pages/Wiki'));
|
||||
const Forum = lazy(() => import('@/pages/Forum'));
|
||||
const Telemetry = lazy(() => import('@/pages/Telemetry'));
|
||||
const Subdomains = lazy(() => import('@/pages/Subdomains'));
|
||||
|
||||
// Network pages
|
||||
const Mainnet = lazy(() => import('@/pages/networks/Mainnet'));
|
||||
const Staging = lazy(() => import('@/pages/networks/Staging'));
|
||||
const Testnet = lazy(() => import('@/pages/networks/Testnet'));
|
||||
const Beta = lazy(() => import('@/pages/networks/Beta'));
|
||||
const Alfa = lazy(() => import('@/pages/networks/Alfa'));
|
||||
const Development = lazy(() => import('@/pages/networks/Development'));
|
||||
const Local = lazy(() => import('@/pages/networks/Local'));
|
||||
|
||||
// Loading component
|
||||
const PageLoader = () => (
|
||||
@@ -92,6 +112,25 @@ function App() {
|
||||
<Route path="/email-verification" element={<EmailVerification />} />
|
||||
<Route path="/reset-password" element={<PasswordReset />} />
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/explorer" element={<Explorer />} />
|
||||
<Route path="/docs/*" element={<Docs />} />
|
||||
<Route path="/wallet" element={<Wallet />} />
|
||||
<Route path="/api" element={<Api />} />
|
||||
<Route path="/faucet" element={<Faucet />} />
|
||||
<Route path="/developers" element={<Developers />} />
|
||||
<Route path="/grants" element={<Grants />} />
|
||||
<Route path="/wiki" element={<Wiki />} />
|
||||
<Route path="/forum" element={<Forum />} />
|
||||
<Route path="/telemetry" element={<Telemetry />} />
|
||||
<Route path="/subdomains" element={<Subdomains />} />
|
||||
{/* Network pages */}
|
||||
<Route path="/mainnet" element={<Mainnet />} />
|
||||
<Route path="/staging" element={<Staging />} />
|
||||
<Route path="/testnet" element={<Testnet />} />
|
||||
<Route path="/beta" element={<Beta />} />
|
||||
<Route path="/alfa" element={<Alfa />} />
|
||||
<Route path="/development" element={<Development />} />
|
||||
<Route path="/local" element={<Local />} />
|
||||
<Route path="/be-citizen" element={<BeCitizen />} />
|
||||
<Route path="/citizens" element={<Citizens />} />
|
||||
<Route path="/citizens/issues" element={<CitizensIssues />} />
|
||||
@@ -111,7 +150,7 @@ function App() {
|
||||
<AdminPanel />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path="/wallet" element={
|
||||
<Route path="/wallet-dashboard" element={
|
||||
<ProtectedRoute>
|
||||
<WalletDashboard />
|
||||
</ProtectedRoute>
|
||||
|
||||
@@ -266,8 +266,7 @@ const AppLayout: React.FC = () => {
|
||||
)}
|
||||
|
||||
<a
|
||||
href="https://raw.githubusercontent.com/pezkuwichain/DKSweb/main/public/Whitepaper.pdf"
|
||||
download="Pezkuwi_Whitepaper.pdf"
|
||||
href="/docs"
|
||||
className="text-gray-300 hover:text-white transition-colors text-sm"
|
||||
>
|
||||
Docs
|
||||
@@ -518,13 +517,13 @@ const AppLayout: React.FC = () => {
|
||||
<h4 className="text-white font-semibold mb-4 text-left">Developers</h4>
|
||||
<ul className="space-y-2 text-left">
|
||||
<li>
|
||||
<a href="https://explorer.pezkuwichain.io" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">
|
||||
<a href="/api" className="text-gray-400 hover:text-white text-sm inline-flex items-center">
|
||||
API
|
||||
<ExternalLink className="w-3 h-3 ml-1" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://sdk.pezkuwichain.io" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm inline-flex items-center">
|
||||
<a href="/developers" className="text-gray-400 hover:text-white text-sm inline-flex items-center">
|
||||
SDK
|
||||
<ExternalLink className="w-3 h-3 ml-1" />
|
||||
</a>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Server, Globe, TestTube, Code, Wifi, Copy, Check } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Server, Globe, TestTube, Code, Wifi, Copy, Check, ExternalLink, Compass, Book, Briefcase, FileCode, HandCoins, Users, Wrench, MessageCircle, GitFork } from 'lucide-react';
|
||||
|
||||
// ... (interface and const arrays remain the same) ...
|
||||
|
||||
interface ChainSpec {
|
||||
id: string;
|
||||
@@ -58,6 +62,17 @@ const chainSpecs: ChainSpec[] = [
|
||||
features: ['Experimental', 'New Features', 'Limited Access'],
|
||||
color: 'from-orange-500 to-orange-600'
|
||||
},
|
||||
{
|
||||
id: 'alfa',
|
||||
name: 'PezkuwiChain Alfa Testnet',
|
||||
type: 'Development',
|
||||
icon: <TestTube className="w-5 h-5" />,
|
||||
endpoint: 'ws://127.0.0.1:8844',
|
||||
chainId: 'pezkuwichain_alfa_testnet',
|
||||
validators: 4,
|
||||
features: ['4 Validators', 'Staking Active', 'Full Features'],
|
||||
color: 'from-purple-500 to-pink-600'
|
||||
},
|
||||
{
|
||||
id: 'development',
|
||||
name: 'Development',
|
||||
@@ -82,9 +97,24 @@ const chainSpecs: ChainSpec[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const subdomains = [
|
||||
{ name: 'Explorer', href: '/explorer', icon: <Compass />, external: false },
|
||||
{ name: 'Docs', href: '/docs', icon: <Book />, external: false },
|
||||
{ name: 'Wallet', href: '/wallet', icon: <Briefcase />, external: false },
|
||||
{ name: 'API', href: '/api', icon: <FileCode />, external: false },
|
||||
{ name: 'Faucet', href: '/faucet', icon: <HandCoins />, external: false },
|
||||
{ name: 'Developers', href: '/developers', icon: <Users />, external: false },
|
||||
{ name: 'Grants', href: '/grants', icon: <Wrench />, external: false },
|
||||
{ name: 'Wiki', href: '/wiki', icon: <MessageCircle />, external: false },
|
||||
{ name: 'Forum', href: '/forum', icon: <GitFork />, external: false },
|
||||
{ name: 'Telemetry', href: '/telemetry', icon: <Server />, external: false },
|
||||
]
|
||||
|
||||
const ChainSpecs: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null);
|
||||
const [selectedSpec, setSelectedSpec] = useState<ChainSpec>(chainSpecs[0]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const copyToClipboard = (text: string, id: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
@@ -97,18 +127,18 @@ const ChainSpecs: React.FC = () => {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-purple-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
Chain Specifications
|
||||
{t('chainSpecs.title')}
|
||||
</h2>
|
||||
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
|
||||
Multiple network environments for development, testing, and production
|
||||
{t('chainSpecs.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
{chainSpecs.map((spec) => (
|
||||
<div
|
||||
key={spec.id}
|
||||
onClick={() => setSelectedSpec(spec)}
|
||||
onClick={() => navigate(`/${spec.id}`)}
|
||||
className={`cursor-pointer p-4 rounded-xl border transition-all ${
|
||||
selectedSpec.id === spec.id
|
||||
? 'bg-gray-900 border-purple-500'
|
||||
@@ -136,6 +166,26 @@ const ChainSpecs: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Subdomains Box */}
|
||||
<div
|
||||
onClick={() => navigate('/subdomains')}
|
||||
className="md:col-span-2 lg:col-span-1 cursor-pointer p-4 rounded-xl border transition-all bg-gray-950/50 border-gray-800 hover:border-gray-700"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="p-2 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 bg-opacity-20">
|
||||
<Compass className="w-5 h-5" />
|
||||
</div>
|
||||
<span className="px-2 py-1 text-xs rounded-full bg-gray-900/30 text-gray-400">
|
||||
{t('chainSpecs.services')}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-white font-semibold mb-2">{t('chainSpecs.subdomainsTitle')}</h3>
|
||||
<div className="flex items-center text-sm text-gray-400">
|
||||
<ExternalLink className="w-3 h-3 mr-1" />
|
||||
<span>{t('chainSpecs.availableServices', { count: subdomains.length })}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Selected Chain Details */}
|
||||
@@ -151,7 +201,7 @@ const ChainSpecs: React.FC = () => {
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-gray-400 text-sm">WebSocket Endpoint</label>
|
||||
<label className="text-gray-400 text-sm">{t('chainSpecs.websocketEndpoint')}</label>
|
||||
<div className="flex items-center mt-1">
|
||||
<code className="flex-1 p-3 bg-gray-900 rounded-lg text-cyan-400 font-mono text-sm">
|
||||
{selectedSpec.endpoint}
|
||||
@@ -169,7 +219,7 @@ const ChainSpecs: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-gray-400 text-sm">Chain ID</label>
|
||||
<label className="text-gray-400 text-sm">{t('chainSpecs.chainId')}</label>
|
||||
<div className="flex items-center mt-1">
|
||||
<code className="flex-1 p-3 bg-gray-900 rounded-lg text-purple-400 font-mono text-sm">
|
||||
{selectedSpec.chainId}
|
||||
@@ -187,64 +237,26 @@ const ChainSpecs: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-gray-400 text-sm mb-2 block">Features</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedSpec.features.map((feature) => (
|
||||
<span
|
||||
key={feature}
|
||||
className="px-3 py-1 bg-gray-900 text-gray-300 text-sm rounded-full"
|
||||
>
|
||||
{feature}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigate('/explorer')}
|
||||
className="w-full mt-2 bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg flex items-center justify-center transition-colors"
|
||||
>
|
||||
<Compass className="w-4 h-4 mr-2" />
|
||||
{t('chainSpecs.viewExplorer')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold text-white mb-4">Connection Example</h4>
|
||||
<div className="bg-gray-900 rounded-lg p-4 font-mono text-sm">
|
||||
<div className="text-gray-400 mb-2">{`// Using @polkadot/api`}</div>
|
||||
<div className="text-cyan-400">import</div>
|
||||
<div className="text-white ml-2">{'{ ApiPromise, WsProvider }'}</div>
|
||||
<div className="text-cyan-400">from</div>
|
||||
<div className="text-green-400 mb-3">'@polkadot/api';</div>
|
||||
|
||||
<div className="text-cyan-400">const</div>
|
||||
<div className="text-white ml-2">provider =</div>
|
||||
<div className="text-cyan-400 ml-2">new</div>
|
||||
<div className="text-yellow-400 ml-2">WsProvider(</div>
|
||||
<div className="text-green-400 ml-4">'{selectedSpec.endpoint}'</div>
|
||||
<div className="text-yellow-400">);</div>
|
||||
|
||||
<div className="text-cyan-400 mt-2">const</div>
|
||||
<div className="text-white ml-2">api =</div>
|
||||
<div className="text-cyan-400 ml-2">await</div>
|
||||
<div className="text-yellow-400 ml-2">ApiPromise.create</div>
|
||||
<div className="text-white">({'{ provider }'});</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-4 bg-kurdish-green/20 rounded-lg border border-kurdish-green/30">
|
||||
<h5 className="text-kurdish-green font-semibold mb-2">Network Stats</h5>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-400">Block Time:</span>
|
||||
<span className="text-white ml-2">6s</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-400">Finality:</span>
|
||||
<span className="text-white ml-2">GRANDPA</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-400">Consensus:</span>
|
||||
<span className="text-white ml-2">Aura</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-400">Runtime:</span>
|
||||
<span className="text-white ml-2">v1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
<h4 className="text-lg font-semibold text-white mb-4">{t('chainSpecs.availableSubdomains')}</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{subdomains.map(subdomain => (
|
||||
<div key={subdomain.name} onClick={() => navigate(subdomain.href)} className="flex items-center p-3 bg-gray-900 rounded-lg cursor-pointer hover:bg-gray-800 transition-colors">
|
||||
<div className="mr-3 text-cyan-400">{subdomain.icon}</div>
|
||||
<span className="font-semibold">{subdomain.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
|
||||
const PezkuwiChainLogo: React.FC = () => {
|
||||
return (
|
||||
<img src="/PezkuwiChain_Logo_Horizontal_Green_White.png" alt="PezkuwiChain Logo" className="h-8" />
|
||||
);
|
||||
};
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const linkStyle = "text-white hover:text-green-400 transition-colors";
|
||||
const activeLinkStyle = { color: '#34D399' }; // green-400
|
||||
|
||||
return (
|
||||
<header className="bg-gray-900 text-white p-4 fixed top-0 left-0 right-0 z-[1000]">
|
||||
<div className="container mx-auto flex justify-between items-center">
|
||||
<Link to="/">
|
||||
<PezkuwiChainLogo />
|
||||
</Link>
|
||||
<nav>
|
||||
<ul className="flex space-x-4">
|
||||
<li><NavLink to="/explorer" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Explorer</NavLink></li>
|
||||
<li><NavLink to="/docs" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Docs</NavLink></li>
|
||||
<li><NavLink to="/wallet" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Wallet</NavLink></li>
|
||||
<li><NavLink to="/api" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>API</NavLink></li>
|
||||
<li><NavLink to="/faucet" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Faucet</NavLink></li>
|
||||
<li><NavLink to="/developers" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Developers</NavLink></li>
|
||||
<li><NavLink to="/grants" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Grants</NavLink></li>
|
||||
<li><NavLink to="/wiki" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Wiki</NavLink></li>
|
||||
<li><NavLink to="/forum" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Forum</NavLink></li>
|
||||
<li><NavLink to="/telemetry" className={linkStyle} style={({ isActive }) => isActive ? activeLinkStyle : undefined}>Telemetry</NavLink></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
return (
|
||||
<footer className="bg-gray-900 text-white p-4">
|
||||
<div className="container mx-auto text-center">
|
||||
<p>© {new Date().getFullYear()} PezkuwiChain. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Header />
|
||||
<div className="flex-grow overflow-auto pt-16"> {/* Add padding-top equal to header height */}
|
||||
<main className="container mx-auto p-4">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
@@ -10,6 +10,7 @@ export const NetworkStats: React.FC = () => {
|
||||
const [blockHash, setBlockHash] = useState<string>('');
|
||||
const [finalizedBlock, setFinalizedBlock] = useState<number>(0);
|
||||
const [validatorCount, setValidatorCount] = useState<number>(0);
|
||||
const [collatorCount, setCollatorCount] = useState<number>(0);
|
||||
const [nominatorCount, setNominatorCount] = useState<number>(0);
|
||||
const [peers, setPeers] = useState<number>(0);
|
||||
|
||||
@@ -33,23 +34,51 @@ export const NetworkStats: React.FC = () => {
|
||||
setFinalizedBlock(header.number.toNumber());
|
||||
});
|
||||
|
||||
// Update validator count, nominator count, and peer count every 3 seconds
|
||||
// Update validator count, collator count, nominator count, and peer count every 3 seconds
|
||||
const updateNetworkStats = async () => {
|
||||
try {
|
||||
const validators = await api.query.session.validators();
|
||||
const health = await api.rpc.system.health();
|
||||
|
||||
// Count nominators
|
||||
let nominatorCount = 0;
|
||||
// 1. Fetch Validators
|
||||
let vCount = 0;
|
||||
try {
|
||||
const nominators = await api.query.staking.nominators.entries();
|
||||
nominatorCount = nominators.length;
|
||||
if (api.query.session?.validators) {
|
||||
const validators = await api.query.session.validators();
|
||||
if (validators) {
|
||||
vCount = validators.length;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (import.meta.env.DEV) console.warn('Failed to fetch validators', err);
|
||||
}
|
||||
|
||||
// 2. Fetch Collators (Invulnerables)
|
||||
let cCount = 0;
|
||||
try {
|
||||
if (api.query.collatorSelection?.invulnerables) {
|
||||
const invulnerables = await api.query.collatorSelection.invulnerables();
|
||||
if (invulnerables) {
|
||||
cCount = invulnerables.length;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (import.meta.env.DEV) console.warn('Failed to fetch collators', err);
|
||||
}
|
||||
|
||||
// 3. Count Nominators
|
||||
let nCount = 0;
|
||||
try {
|
||||
const nominators = await api.query.staking?.nominators.entries();
|
||||
if (nominators) {
|
||||
nCount = nominators.length;
|
||||
}
|
||||
} catch {
|
||||
if (import.meta.env.DEV) console.warn('Staking pallet not available, nominators = 0');
|
||||
}
|
||||
|
||||
setValidatorCount(validators.length);
|
||||
setNominatorCount(nominatorCount);
|
||||
setValidatorCount(vCount);
|
||||
setCollatorCount(cCount);
|
||||
setNominatorCount(nCount);
|
||||
setPeers(health.peers.toNumber());
|
||||
} catch (err) {
|
||||
if (import.meta.env.DEV) console.error('Failed to update network stats:', err);
|
||||
@@ -109,7 +138,7 @@ export const NetworkStats: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4">
|
||||
{/* Connection Status */}
|
||||
<Card className="bg-gray-900 border-gray-800">
|
||||
<CardHeader className="pb-3">
|
||||
@@ -179,7 +208,25 @@ export const NetworkStats: React.FC = () => {
|
||||
{validatorCount}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
Securing the network - LIVE
|
||||
Validating blocks
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Collators */}
|
||||
<Card className="bg-gray-900 border-gray-800">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm font-medium text-gray-400 flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-orange-500" />
|
||||
Active Collators
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{collatorCount}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
Producing blocks
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -54,7 +54,7 @@ const PoolDashboard = () => {
|
||||
|
||||
// Pool selection state
|
||||
const [availablePools, setAvailablePools] = useState<Array<[number, number]>>([]);
|
||||
const [selectedPool, setSelectedPool] = useState<string>('1-2'); // Default: PEZ/wUSDT
|
||||
const [selectedPool, setSelectedPool] = useState<string>('0-1'); // Default: wHEZ/PEZ
|
||||
|
||||
// Discover available pools
|
||||
useEffect(() => {
|
||||
@@ -62,25 +62,44 @@ const PoolDashboard = () => {
|
||||
|
||||
const discoverPools = async () => {
|
||||
try {
|
||||
// Check all possible pool combinations
|
||||
// Check all possible pool combinations in both directions
|
||||
// Pools can be stored as [A,B] or [B,A] depending on creation order
|
||||
// Note: .env sets WUSDT to Asset ID based on VITE_ASSET_WUSDT
|
||||
const possiblePools: Array<[number, number]> = [
|
||||
[ASSET_IDS.WHEZ, ASSET_IDS.PEZ], // wHEZ/PEZ
|
||||
[ASSET_IDS.WHEZ, ASSET_IDS.WUSDT], // wHEZ/wUSDT
|
||||
[ASSET_IDS.PEZ, ASSET_IDS.WUSDT], // PEZ/wUSDT
|
||||
[ASSET_IDS.WHEZ, ASSET_IDS.PEZ], // wHEZ(0) / PEZ(1) -> Shows as HEZ-PEZ
|
||||
[ASSET_IDS.PEZ, ASSET_IDS.WHEZ], // PEZ(1) / wHEZ(0) -> Shows as HEZ-PEZ (reverse)
|
||||
[ASSET_IDS.WHEZ, ASSET_IDS.WUSDT], // wHEZ(0) / wUSDT -> Shows as HEZ-USDT
|
||||
[ASSET_IDS.WUSDT, ASSET_IDS.WHEZ], // wUSDT / wHEZ(0) -> Shows as HEZ-USDT (reverse)
|
||||
[ASSET_IDS.PEZ, ASSET_IDS.WUSDT], // PEZ(1) / wUSDT -> Shows as PEZ-USDT
|
||||
[ASSET_IDS.WUSDT, ASSET_IDS.PEZ], // wUSDT / PEZ(1) -> Shows as PEZ-USDT (reverse)
|
||||
];
|
||||
|
||||
const existingPools: Array<[number, number]> = [];
|
||||
|
||||
for (const [asset0, asset1] of possiblePools) {
|
||||
const poolInfo = await api.query.assetConversion.pools([asset0, asset1]);
|
||||
if (poolInfo.isSome) {
|
||||
existingPools.push([asset0, asset1]);
|
||||
try {
|
||||
const poolInfo = await api.query.assetConversion.pools([asset0, asset1]);
|
||||
if (poolInfo.isSome) {
|
||||
existingPools.push([asset0, asset1]);
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`✅ Found pool: ${asset0}-${asset1}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip pools that error out (likely don't exist)
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`❌ Pool ${asset0}-${asset1} not found or error:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('📊 Total pools found:', existingPools.length, existingPools);
|
||||
}
|
||||
|
||||
setAvailablePools(existingPools);
|
||||
|
||||
// Set default pool to first available if current selection doesn't exist
|
||||
// Set default pool to first available if current selection doesn't exist
|
||||
if (existingPools.length > 0) {
|
||||
const currentPoolKey = selectedPool;
|
||||
const poolExists = existingPools.some(
|
||||
|
||||
@@ -430,8 +430,8 @@ const TokenSwap = () => {
|
||||
if (import.meta.env.DEV) console.warn('Failed to parse swap path:', err);
|
||||
}
|
||||
|
||||
const fromTokenSymbol = fromAssetId === 0 ? 'wHEZ' : fromAssetId === 1 ? 'PEZ' : fromAssetId === 2 ? 'USDT' : `Asset${fromAssetId}`;
|
||||
const toTokenSymbol = toAssetId === 0 ? 'wHEZ' : toAssetId === 1 ? 'PEZ' : toAssetId === 2 ? 'USDT' : `Asset${toAssetId}`;
|
||||
const fromTokenSymbol = fromAssetId === 0 ? 'wHEZ' : fromAssetId === 1 ? 'PEZ' : fromAssetId === 1000 ? 'USDT' : `Asset${fromAssetId}`;
|
||||
const toTokenSymbol = toAssetId === 0 ? 'wHEZ' : toAssetId === 1 ? 'PEZ' : toAssetId === 1000 ? 'USDT' : `Asset${toAssetId}`;
|
||||
|
||||
// Only show transactions from current user
|
||||
if (who.toString() === selectedAccount.address) {
|
||||
@@ -712,7 +712,7 @@ const TokenSwap = () => {
|
||||
|
||||
if (Array.isArray(pathArray) && pathArray.length >= 2) {
|
||||
const asset0 = pathArray[0];
|
||||
//const _asset1 = pathArray[1];
|
||||
const asset1 = pathArray[1];
|
||||
|
||||
// Each element is a tuple where index 0 is the asset ID
|
||||
if (Array.isArray(asset0) && asset0.length >= 1) {
|
||||
@@ -726,8 +726,8 @@ const TokenSwap = () => {
|
||||
if (import.meta.env.DEV) console.warn('Failed to parse swap path in refresh:', err);
|
||||
}
|
||||
|
||||
const fromTokenSymbol = fromAssetId === 0 ? 'wHEZ' : fromAssetId === 1 ? 'PEZ' : fromAssetId === 2 ? 'USDT' : `Asset${fromAssetId}`;
|
||||
const toTokenSymbol = toAssetId === 0 ? 'wHEZ' : toAssetId === 1 ? 'PEZ' : toAssetId === 2 ? 'USDT' : `Asset${toAssetId}`;
|
||||
const fromTokenSymbol = fromAssetId === 0 ? 'wHEZ' : fromAssetId === 1 ? 'PEZ' : fromAssetId === 1000 ? 'USDT' : `Asset${fromAssetId}`;
|
||||
const toTokenSymbol = toAssetId === 0 ? 'wHEZ' : toAssetId === 1 ? 'PEZ' : toAssetId === 1000 ? 'USDT' : `Asset${toAssetId}`;
|
||||
|
||||
if (who.toString() === selectedAccount.address) {
|
||||
transactions.push({
|
||||
|
||||
@@ -0,0 +1,875 @@
|
||||
/**
|
||||
* XCM Configuration Wizard - Multi-Step Parachain Setup
|
||||
*
|
||||
* Guides admin through complete XCM integration:
|
||||
* 1. Reserve ParaId
|
||||
* 2. Generate Chain Artifacts (genesis + WASM)
|
||||
* 3. Register Parachain
|
||||
* 4. Open HRMP Channels
|
||||
* 5. Register Foreign Assets
|
||||
* 6. Test XCM Transfer
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import {
|
||||
X,
|
||||
CheckCircle,
|
||||
Circle,
|
||||
Loader2,
|
||||
AlertCircle,
|
||||
Download,
|
||||
Upload,
|
||||
Send,
|
||||
Link2,
|
||||
Coins,
|
||||
TestTube,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import {
|
||||
reserveParaId,
|
||||
generateChainArtifacts,
|
||||
registerParachain,
|
||||
openHRMPChannels,
|
||||
registerForeignAssets,
|
||||
testXCMTransfer,
|
||||
getRelayChainEndpoint,
|
||||
getAssetHubParaId,
|
||||
type RelayChain,
|
||||
type ChainArtifacts,
|
||||
type HRMPChannel,
|
||||
type RegisteredAsset,
|
||||
type ForeignAsset,
|
||||
} from '@pezkuwi/lib/xcm-wizard';
|
||||
import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
|
||||
interface XCMConfigurationWizardProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
interface StepStatus {
|
||||
completed: boolean;
|
||||
data?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { api, isApiReady } = usePolkadot();
|
||||
const { account, signer } = useWallet();
|
||||
const { toast } = useToast();
|
||||
|
||||
// Wizard state
|
||||
const [currentStep, setCurrentStep] = useState<number>(1);
|
||||
const [steps, setSteps] = useState<Record<number, StepStatus>>({
|
||||
1: { completed: false },
|
||||
2: { completed: false },
|
||||
3: { completed: false },
|
||||
4: { completed: false },
|
||||
5: { completed: false },
|
||||
6: { completed: false },
|
||||
});
|
||||
|
||||
// Step 1: Reserve ParaId
|
||||
const [relayChain, setRelayChain] = useState<RelayChain>('westend');
|
||||
const [reservedParaId, setReservedParaId] = useState<number | null>(null);
|
||||
const [reserving, setReserving] = useState(false);
|
||||
|
||||
// Step 2: Generate Artifacts
|
||||
const [artifacts, setArtifacts] = useState<ChainArtifacts | null>(null);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
|
||||
// Step 3: Register Parachain
|
||||
const [genesisFile, setGenesisFile] = useState<File | null>(null);
|
||||
const [wasmFile, setWasmFile] = useState<File | null>(null);
|
||||
const [registering, setRegistering] = useState(false);
|
||||
const [registrationTxHash, setRegistrationTxHash] = useState<string>('');
|
||||
|
||||
// Step 4: HRMP Channels
|
||||
const [openingChannels, setOpeningChannels] = useState(false);
|
||||
const [openedChannels, setOpenedChannels] = useState<HRMPChannel[]>([]);
|
||||
|
||||
// Step 5: Foreign Assets
|
||||
const [registeringAssets, setRegisteringAssets] = useState(false);
|
||||
const [registeredAssets, setRegisteredAssets] = useState<RegisteredAsset[]>([]);
|
||||
|
||||
// Step 6: XCM Test
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState<{ success: boolean; balance: string } | null>(null);
|
||||
|
||||
const totalSteps = 6;
|
||||
const progress = (Object.values(steps).filter(s => s.completed).length / totalSteps) * 100;
|
||||
|
||||
// Reset state when modal opens/closes
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setCurrentStep(1);
|
||||
setSteps({
|
||||
1: { completed: false },
|
||||
2: { completed: false },
|
||||
3: { completed: false },
|
||||
4: { completed: false },
|
||||
5: { completed: false },
|
||||
6: { completed: false },
|
||||
});
|
||||
setReservedParaId(null);
|
||||
setArtifacts(null);
|
||||
setOpenedChannels([]);
|
||||
setRegisteredAssets([]);
|
||||
setTestResult(null);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// ========================================
|
||||
// STEP 1: RESERVE PARAID
|
||||
// ========================================
|
||||
const handleReserveParaId = async () => {
|
||||
if (!account || !signer) {
|
||||
toast({
|
||||
title: 'Wallet not connected',
|
||||
description: 'Please connect your wallet first',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setReserving(true);
|
||||
try {
|
||||
// Connect to relay chain
|
||||
const endpoint = getRelayChainEndpoint(relayChain);
|
||||
const provider = new WsProvider(endpoint);
|
||||
const relayApi = await ApiPromise.create({ provider });
|
||||
|
||||
// Reserve ParaId
|
||||
const paraId = await reserveParaId(relayApi, relayChain, account);
|
||||
setReservedParaId(paraId);
|
||||
|
||||
// Mark step as completed
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
1: { completed: true, data: { paraId, relayChain } },
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'ParaId Reserved!',
|
||||
description: `Successfully reserved ParaId ${paraId} on ${relayChain}`,
|
||||
});
|
||||
|
||||
// Auto-advance to next step
|
||||
setCurrentStep(2);
|
||||
|
||||
await relayApi.disconnect();
|
||||
} catch (error) {
|
||||
console.error('Failed to reserve ParaId:', error);
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
1: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Reservation Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to reserve ParaId',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setReserving(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// STEP 2: GENERATE CHAIN ARTIFACTS
|
||||
// ========================================
|
||||
const handleGenerateArtifacts = async () => {
|
||||
if (!reservedParaId) return;
|
||||
|
||||
setGenerating(true);
|
||||
try {
|
||||
const chainName = `pezkuwichain-${relayChain}`;
|
||||
const artifactData = await generateChainArtifacts(chainName);
|
||||
setArtifacts(artifactData);
|
||||
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
2: { completed: true, data: artifactData },
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'Artifacts Generated!',
|
||||
description: 'Genesis state and runtime WASM are ready for download',
|
||||
});
|
||||
|
||||
setCurrentStep(3);
|
||||
} catch (error) {
|
||||
console.error('Failed to generate artifacts:', error);
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
2: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Generation Failed',
|
||||
description: 'Failed to generate chain artifacts',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// STEP 3: REGISTER PARACHAIN
|
||||
// ========================================
|
||||
const handleRegisterParachain = async () => {
|
||||
if (!reservedParaId || !genesisFile || !wasmFile || !account || !signer) {
|
||||
toast({
|
||||
title: 'Missing Data',
|
||||
description: 'Please upload both genesis and WASM files',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setRegistering(true);
|
||||
try {
|
||||
const endpoint = getRelayChainEndpoint(relayChain);
|
||||
const provider = new WsProvider(endpoint);
|
||||
const relayApi = await ApiPromise.create({ provider });
|
||||
|
||||
const txHash = await registerParachain(relayApi, reservedParaId, genesisFile, wasmFile, account);
|
||||
setRegistrationTxHash(txHash);
|
||||
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
3: { completed: true, data: { txHash, paraId: reservedParaId } },
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'Parachain Registered!',
|
||||
description: `ParaId ${reservedParaId} registered on ${relayChain}`,
|
||||
});
|
||||
|
||||
setCurrentStep(4);
|
||||
|
||||
await relayApi.disconnect();
|
||||
} catch (error) {
|
||||
console.error('Failed to register parachain:', error);
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
3: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Registration Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to register parachain',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setRegistering(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// STEP 4: OPEN HRMP CHANNELS
|
||||
// ========================================
|
||||
const handleOpenHRMPChannels = async () => {
|
||||
if (!reservedParaId || !account || !signer) return;
|
||||
|
||||
setOpeningChannels(true);
|
||||
try {
|
||||
const endpoint = getRelayChainEndpoint(relayChain);
|
||||
const provider = new WsProvider(endpoint);
|
||||
const relayApi = await ApiPromise.create({ provider });
|
||||
|
||||
// Get Asset Hub ParaId
|
||||
const assetHubParaId = getAssetHubParaId(relayChain);
|
||||
|
||||
// Open channels with Asset Hub
|
||||
const channels = await openHRMPChannels(relayApi, reservedParaId, [assetHubParaId], account);
|
||||
setOpenedChannels(channels);
|
||||
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
4: { completed: true, data: { channels } },
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'HRMP Channels Opened!',
|
||||
description: `Opened ${channels.length} channel(s) with Asset Hub`,
|
||||
});
|
||||
|
||||
setCurrentStep(5);
|
||||
|
||||
await relayApi.disconnect();
|
||||
} catch (error) {
|
||||
console.error('Failed to open HRMP channels:', error);
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
4: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Channel Opening Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to open HRMP channels',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setOpeningChannels(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// STEP 5: REGISTER FOREIGN ASSETS
|
||||
// ========================================
|
||||
const handleRegisterAssets = async () => {
|
||||
if (!api || !isApiReady || !account || !signer) return;
|
||||
|
||||
setRegisteringAssets(true);
|
||||
try {
|
||||
// Define foreign assets to register (USDT, DOT, etc.)
|
||||
const foreignAssets: ForeignAsset[] = [
|
||||
{
|
||||
symbol: 'USDT',
|
||||
location: {
|
||||
parents: 1,
|
||||
interior: {
|
||||
X3: [{ Parachain: 1000 }, { PalletInstance: 50 }, { GeneralIndex: 1984 }],
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
name: 'Tether USD',
|
||||
symbol: 'USDT',
|
||||
decimals: 6,
|
||||
minBalance: '1000',
|
||||
},
|
||||
},
|
||||
{
|
||||
symbol: 'DOT',
|
||||
location: {
|
||||
parents: 1,
|
||||
interior: { Here: null },
|
||||
},
|
||||
metadata: {
|
||||
name: 'Polkadot',
|
||||
symbol: 'DOT',
|
||||
decimals: 10,
|
||||
minBalance: '10000000000',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const registered = await registerForeignAssets(api, foreignAssets, account);
|
||||
setRegisteredAssets(registered);
|
||||
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
5: { completed: true, data: { assets: registered } },
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'Assets Registered!',
|
||||
description: `Registered ${registered.length} foreign asset(s)`,
|
||||
});
|
||||
|
||||
setCurrentStep(6);
|
||||
} catch (error) {
|
||||
console.error('Failed to register assets:', error);
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
5: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Asset Registration Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to register foreign assets',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setRegisteringAssets(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// STEP 6: TEST XCM TRANSFER
|
||||
// ========================================
|
||||
const handleTestXCMTransfer = async () => {
|
||||
if (!api || !isApiReady || !account || !signer) return;
|
||||
|
||||
setTesting(true);
|
||||
try {
|
||||
const result = await testXCMTransfer(api, '1000000', account); // 1 USDT (6 decimals)
|
||||
|
||||
setTestResult(result);
|
||||
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
6: { completed: result.success, data: result },
|
||||
}));
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'XCM Test Successful!',
|
||||
description: `Received ${result.balance} wUSDT`,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'XCM Test Failed',
|
||||
description: result.error || 'Test transfer failed',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to test XCM transfer:', error);
|
||||
setSteps(prev => ({
|
||||
...prev,
|
||||
6: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Test Failed',
|
||||
description: error instanceof Error ? error.message : 'XCM test failed',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// RENDER STEP CONTENT
|
||||
// ========================================
|
||||
const renderStepContent = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Select Relay Chain</Label>
|
||||
<Select value={relayChain} onValueChange={(value: RelayChain) => setRelayChain(value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="westend">Westend (Testnet)</SelectItem>
|
||||
<SelectItem value="rococo">Rococo (Testnet)</SelectItem>
|
||||
<SelectItem value="polkadot">Polkadot (Mainnet)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{reservedParaId && (
|
||||
<Alert>
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
ParaId <strong>{reservedParaId}</strong> reserved on {relayChain}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{steps[1].error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{steps[1].error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button onClick={handleReserveParaId} disabled={reserving || steps[1].completed} className="w-full">
|
||||
{reserving ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Reserving ParaId...
|
||||
</>
|
||||
) : steps[1].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
ParaId Reserved
|
||||
</>
|
||||
) : (
|
||||
'Reserve ParaId'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{artifacts && (
|
||||
<div className="space-y-2">
|
||||
<Alert>
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Artifacts generated. Download files for registration.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href={artifacts.genesisPath} download>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Genesis ({artifacts.genesisSize} bytes)
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href={artifacts.wasmPath} download>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
WASM ({artifacts.wasmSize} bytes)
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{steps[2].error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{steps[2].error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button onClick={handleGenerateArtifacts} disabled={generating || steps[2].completed} className="w-full">
|
||||
{generating ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Generating Artifacts...
|
||||
</>
|
||||
) : steps[2].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Artifacts Generated
|
||||
</>
|
||||
) : (
|
||||
'Generate Chain Artifacts'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 3:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Upload Genesis State</Label>
|
||||
<Input
|
||||
type="file"
|
||||
accept=".hex,.txt"
|
||||
onChange={(e) => setGenesisFile(e.target.files?.[0] || null)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Upload Runtime WASM</Label>
|
||||
<Input
|
||||
type="file"
|
||||
accept=".wasm"
|
||||
onChange={(e) => setWasmFile(e.target.files?.[0] || null)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{registrationTxHash && (
|
||||
<Alert>
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Registered! TX: <code className="text-xs">{registrationTxHash.slice(0, 20)}...</code>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{steps[3].error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{steps[3].error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleRegisterParachain}
|
||||
disabled={!genesisFile || !wasmFile || registering || steps[3].completed}
|
||||
className="w-full"
|
||||
>
|
||||
{registering ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Registering Parachain...
|
||||
</>
|
||||
) : steps[3].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Parachain Registered
|
||||
</>
|
||||
) : (
|
||||
'Register Parachain'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 4:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Opening HRMP channels with Asset Hub (ParaId {getAssetHubParaId(relayChain)})
|
||||
</p>
|
||||
|
||||
{openedChannels.length > 0 && (
|
||||
<Alert>
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Opened {openedChannels.length} channel(s):
|
||||
<ul className="mt-2 space-y-1 text-xs">
|
||||
{openedChannels.map((ch, idx) => (
|
||||
<li key={idx}>
|
||||
{ch.sender} → {ch.receiver} (ID: {ch.channelId.slice(0, 10)}...)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{steps[4].error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{steps[4].error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button onClick={handleOpenHRMPChannels} disabled={openingChannels || steps[4].completed} className="w-full">
|
||||
{openingChannels ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Opening HRMP Channels...
|
||||
</>
|
||||
) : steps[4].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Channels Opened
|
||||
</>
|
||||
) : (
|
||||
'Open HRMP Channels'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 5:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Register foreign assets: USDT, DOT, and other cross-chain tokens
|
||||
</p>
|
||||
|
||||
{registeredAssets.length > 0 && (
|
||||
<Alert>
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Registered {registeredAssets.length} asset(s):
|
||||
<ul className="mt-2 space-y-1 text-xs">
|
||||
{registeredAssets.map((asset, idx) => (
|
||||
<li key={idx}>
|
||||
{asset.symbol} (Asset ID: {asset.assetId})
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{steps[5].error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{steps[5].error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button onClick={handleRegisterAssets} disabled={registeringAssets || steps[5].completed} className="w-full">
|
||||
{registeringAssets ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Registering Assets...
|
||||
</>
|
||||
) : steps[5].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Assets Registered
|
||||
</>
|
||||
) : (
|
||||
'Register Foreign Assets'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 6:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Test XCM transfer from Asset Hub to verify bridge functionality
|
||||
</p>
|
||||
|
||||
{testResult && (
|
||||
<Alert variant={testResult.success ? 'default' : 'destructive'}>
|
||||
{testResult.success ? <CheckCircle className="h-4 w-4" /> : <AlertCircle className="h-4 w-4" />}
|
||||
<AlertDescription>
|
||||
{testResult.success
|
||||
? `Test successful! Balance: ${testResult.balance} wUSDT`
|
||||
: `Test failed: ${testResult.error}`}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{steps[6].error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{steps[6].error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button onClick={handleTestXCMTransfer} disabled={testing || steps[6].completed} className="w-full">
|
||||
{testing ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Testing XCM Transfer...
|
||||
</>
|
||||
) : steps[6].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
XCM Test Passed
|
||||
</>
|
||||
) : (
|
||||
'Test XCM Transfer'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if all steps are completed
|
||||
const allStepsCompleted = Object.values(steps).every(s => s.completed);
|
||||
|
||||
// Handle Finish Configuration
|
||||
const handleFinishConfiguration = () => {
|
||||
toast({
|
||||
title: 'XCM Configuration Complete!',
|
||||
description: 'Your parachain is fully configured and ready for cross-chain transfers',
|
||||
});
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
<Card className="w-full max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>XCM Configuration Wizard</CardTitle>
|
||||
<CardDescription>
|
||||
Complete parachain setup and cross-chain integration
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<Progress value={progress} className="h-2" />
|
||||
<p className="mt-2 text-xs text-muted-foreground text-center">
|
||||
{Object.values(steps).filter(s => s.completed).length} / {totalSteps} steps completed
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6">
|
||||
{/* Step Navigation */}
|
||||
<div className="grid grid-cols-6 gap-2">
|
||||
{[1, 2, 3, 4, 5, 6].map((step) => (
|
||||
<button
|
||||
key={step}
|
||||
onClick={() => setCurrentStep(step)}
|
||||
className={`flex flex-col items-center gap-1 p-2 rounded-lg transition-colors ${
|
||||
currentStep === step
|
||||
? 'bg-kurdish-green text-white'
|
||||
: steps[step].completed
|
||||
? 'bg-green-100 text-green-700'
|
||||
: 'bg-gray-100 text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{steps[step].completed ? (
|
||||
<CheckCircle className="h-5 w-5" />
|
||||
) : currentStep === step ? (
|
||||
<Circle className="h-5 w-5 fill-current" />
|
||||
) : (
|
||||
<Circle className="h-5 w-5" />
|
||||
)}
|
||||
<span className="text-xs font-medium">{step}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Current Step Content */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">Step {currentStep}</Badge>
|
||||
<h3 className="font-semibold">
|
||||
{currentStep === 1 && 'Reserve ParaId'}
|
||||
{currentStep === 2 && 'Generate Chain Artifacts'}
|
||||
{currentStep === 3 && 'Register Parachain'}
|
||||
{currentStep === 4 && 'Open HRMP Channels'}
|
||||
{currentStep === 5 && 'Register Foreign Assets'}
|
||||
{currentStep === 6 && 'Test XCM Transfer'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{renderStepContent()}
|
||||
</div>
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
<div className="flex items-center justify-between pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setCurrentStep(Math.max(1, currentStep - 1))}
|
||||
disabled={currentStep === 1}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
{allStepsCompleted ? (
|
||||
<Button onClick={handleFinishConfiguration} className="bg-kurdish-green hover:bg-kurdish-green-dark">
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Finish Configuration
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => setCurrentStep(Math.min(totalSteps, currentStep + 1))}
|
||||
disabled={currentStep === totalSteps || !steps[currentStep].completed}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import PoolDashboard from '@/components/PoolDashboard';
|
||||
import { CreatePoolModal } from './CreatePoolModal';
|
||||
import { InitializeHezPoolModal } from './InitializeHezPoolModal';
|
||||
import { InitializeUsdtModal } from './InitializeUsdtModal';
|
||||
import { XCMBridgeSetupModal } from './XCMBridgeSetupModal';
|
||||
import { XCMConfigurationWizard } from '@/components/admin/XCMConfigurationWizard';
|
||||
import { ArrowRightLeft, Droplet, Settings } from 'lucide-react';
|
||||
import { isFounderWallet } from '@pezkuwi/utils/auth';
|
||||
|
||||
@@ -138,15 +138,15 @@ export const DEXDashboard: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="p-6 bg-gray-900 border border-purple-900/30 rounded-lg">
|
||||
<h3 className="text-xl font-bold text-white mb-2">XCM Bridge Setup</h3>
|
||||
<h3 className="text-xl font-bold text-white mb-2">XCM Configuration Wizard</h3>
|
||||
<p className="text-gray-400 mb-6">
|
||||
Configure Asset Hub USDT → wUSDT bridge with one click. Enables cross-chain USDT transfers from Westend Asset Hub.
|
||||
Complete 6-step parachain setup: Reserve ParaId, generate artifacts, register parachain, open HRMP channels, register foreign assets, and test XCM transfers.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowXcmBridgeModal(true)}
|
||||
className="w-full px-6 py-3 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
Configure XCM Bridge
|
||||
Open XCM Configuration Wizard
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -195,7 +195,7 @@ export const DEXDashboard: React.FC = () => {
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
<XCMBridgeSetupModal
|
||||
<XCMConfigurationWizard
|
||||
isOpen={showXcmBridgeModal}
|
||||
onClose={handleModalClose}
|
||||
onSuccess={handleSuccess}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
// Force reload for mock XCM update
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { X, AlertCircle, Loader2, CheckCircle, Info, ExternalLink, Zap } from 'lucide-react';
|
||||
|
||||
@@ -235,10 +235,29 @@
|
||||
"proposalUpdate": "Proposal status changed"
|
||||
},
|
||||
"websocket": {
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected",
|
||||
"reconnecting": "Reconnecting...",
|
||||
"liveUpdates": "Live updates enabled",
|
||||
"offlineMode": "Offline mode"
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected",
|
||||
"reconnecting": "Reconnecting...",
|
||||
"liveUpdates": "Live updates enabled",
|
||||
"offlineMode": "Offline mode"
|
||||
},
|
||||
"chainSpecs": {
|
||||
"title": "Chain Specifications",
|
||||
"subtitle": "Multiple network environments for development, testing, and production",
|
||||
"services": "Services",
|
||||
"subdomainsTitle": "Subdomains",
|
||||
"availableServices": "{{count}} available services",
|
||||
"viewExplorer": "View Explorer",
|
||||
"connectionExample": "Connection Example",
|
||||
"networkStats": "Network Stats",
|
||||
"blockTime": "Block Time",
|
||||
"finality": "Finality",
|
||||
"consensus": "Consensus",
|
||||
"runtime": "Runtime",
|
||||
"websocketEndpoint": "WebSocket Endpoint",
|
||||
"chainId": "Chain ID",
|
||||
"features": "Features",
|
||||
"availableSubdomains": "Available Subdomains"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +245,24 @@ export default {
|
||||
'websocket.liveUpdates': 'Live updates enabled',
|
||||
'websocket.offlineMode': 'Offline mode',
|
||||
|
||||
// Chain Specs
|
||||
'chainSpecs.title': 'Chain Specifications',
|
||||
'chainSpecs.subtitle': 'Multiple network environments for development, testing, and production',
|
||||
'chainSpecs.services': 'Services',
|
||||
'chainSpecs.subdomainsTitle': 'Subdomains',
|
||||
'chainSpecs.availableServices': '{{count}} available services',
|
||||
'chainSpecs.viewExplorer': 'View Explorer',
|
||||
'chainSpecs.connectionExample': 'Connection Example',
|
||||
'chainSpecs.networkStats': 'Network Stats',
|
||||
'chainSpecs.blockTime': 'Block Time',
|
||||
'chainSpecs.finality': 'Finality',
|
||||
'chainSpecs.consensus': 'Consensus',
|
||||
'chainSpecs.runtime': 'Runtime',
|
||||
'chainSpecs.websocketEndpoint': 'WebSocket Endpoint',
|
||||
'chainSpecs.chainId': 'Chain ID',
|
||||
'chainSpecs.features': 'Features',
|
||||
'chainSpecs.availableSubdomains': 'Available Services',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Login',
|
||||
'auth.logout': 'Logout',
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
|
||||
const CodeSnippet = ({ language, code }: { language: string, code: string }) => (
|
||||
<SyntaxHighlighter language={language} style={vscDarkPlus} customStyle={{ margin: 0, padding: '1rem', backgroundColor: '#1E1E1E', borderRadius: '0.5rem' }}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
|
||||
const Api: React.FC = () => {
|
||||
const getBlockResponse = `{
|
||||
"blockNumber": 123456,
|
||||
"hash": "0xabcde12345fghij67890klmno12345pqrst67890uvwxyz12345abcde12345",
|
||||
"parentHash": "0x12345abcde12345fghij67890klmno12345pqrst67890uvwxyz12345abc",
|
||||
"timestamp": "2025-12-10T10:30:00Z",
|
||||
"transactions": [
|
||||
"0x98765fedcba..."
|
||||
]
|
||||
}`;
|
||||
|
||||
const getTxResponse = `{
|
||||
"txHash": "0x98765fedcba...",
|
||||
"blockNumber": 123456,
|
||||
"from": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
|
||||
"to": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
|
||||
"amount": "100.00 HEZ",
|
||||
"fee": "0.01 HEZ"
|
||||
}`;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<h1 className="text-4xl font-bold mb-8">API Documentation</h1>
|
||||
|
||||
<div className="space-y-12">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4 border-b-2 border-gray-700 pb-2">Endpoints</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center">
|
||||
<span className="text-sm font-bold bg-green-600 text-white rounded px-2 py-1 mr-4">GET</span>
|
||||
<span className="font-mono text-lg">/api/blocks/latest</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-sm font-bold bg-green-600 text-white rounded px-2 py-1 mr-4">GET</span>
|
||||
<span className="font-mono text-lg">/api/blocks/{'{blockNumber}'}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="text-sm font-bold bg-green-600 text-white rounded px-2 py-1 mr-4">GET</span>
|
||||
<span className="font-mono text-lg">/api/transactions/{'{txHash}'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
<h2 className="text-2xl font-bold mb-4 border-b-2 border-gray-700 pb-2">Examples</h2>
|
||||
|
||||
{/* Get Block Example */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-2 font-mono">GET /api/blocks/{'{blockNumber}'}</h3>
|
||||
<p className="text-gray-400 mb-4">Retrieve a specific block by its number.</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="font-bold mb-2 text-gray-300">Request</h4>
|
||||
<CodeSnippet language="bash" code={'curl https://api.pezkuwichain.io/api/blocks/123456'} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold mb-2 text-gray-300">Response</h4>
|
||||
<CodeSnippet language="json" code={getBlockResponse} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Get Transaction Example */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-2 font-mono">GET /api/transactions/{'{txHash}'}</h3>
|
||||
<p className="text-gray-400 mb-4">Retrieve a specific transaction by its hash.</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="font-bold mb-2 text-gray-300">Request</h4>
|
||||
<CodeSnippet language="bash" code={'curl https://api.pezkuwichain.io/api/transactions/0x98765...'} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold mb-2 text-gray-300">Response</h4>
|
||||
<CodeSnippet language="json" code={getTxResponse} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Api;
|
||||
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import { Download, Book, MessageCircle, Github } from 'lucide-react';
|
||||
|
||||
const CodeSnippet = ({ language, code }: { language: string, code: string }) => (
|
||||
<SyntaxHighlighter language={language} style={vscDarkPlus} customStyle={{ margin: 0, padding: '1rem', backgroundColor: '#1E1E1E', borderRadius: '0.5rem' }}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
|
||||
const Developers: React.FC = () => {
|
||||
const connectCode = `import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
|
||||
// Connect to the PezkuwiChain node
|
||||
const wsProvider = new WsProvider('wss://rpc.pezkuwichain.io');
|
||||
const api = await ApiPromise.create({ provider: wsProvider });
|
||||
|
||||
console.log('API is connected:', api.isConnected);`;
|
||||
|
||||
const transferCode = `// Example: Transfer HEZ tokens
|
||||
const keyring = new Keyring({ type: 'sr25519' });
|
||||
const alice = keyring.addFromUri('//Alice');
|
||||
const bob = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty';
|
||||
|
||||
const unsub = await api.tx.balances
|
||||
.transfer(bob, 12345)
|
||||
.signAndSend(alice, (result) => {
|
||||
console.log(\`Current status is \${result.status}\`);
|
||||
if (result.isFinalized) {
|
||||
console.log(\`Transaction finalized at blockHash \${result.status.asFinalized}\`);
|
||||
unsub();
|
||||
}
|
||||
});`;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-5xl font-bold mb-2">Developer Portal</h1>
|
||||
<p className="text-xl text-gray-400">Everything you need to build on PezkuwiChain.</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{/* Quick Start Guide */}
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<Book className="text-blue-400 mb-4" size={32} />
|
||||
<h2 className="text-2xl font-bold mb-2">Quick Start Guide</h2>
|
||||
<p className="text-gray-400 mb-4">Your first steps to building a dApp on PezkuwiChain.</p>
|
||||
<ol className="list-decimal list-inside space-y-2">
|
||||
<li>Install the Polkadot.js extension.</li>
|
||||
<li>Get some testnet HEZ from the Faucet.</li> <li>Clone a starter project from our GitHub.</li>
|
||||
<li>Start building!</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{/* SDKs */}
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<Download className="text-green-400 mb-4" size={32} />
|
||||
<h2 className="text-2xl font-bold mb-2">SDK Downloads</h2>
|
||||
<p className="text-gray-400 mb-4">Libraries to interact with the chain.</p>
|
||||
<div className="space-y-3">
|
||||
<a href="#" className="flex items-center text-blue-400 hover:text-white">
|
||||
<Github className="mr-2" size={20} />
|
||||
<span>Javascript/Typescript SDK</span>
|
||||
</a>
|
||||
<a href="#" className="flex items-center text-blue-400 hover:text-white">
|
||||
<Github className="mr-2" size={20} />
|
||||
<span>Rust SDK</span>
|
||||
</a>
|
||||
<a href="#" className="flex items-center text-blue-400 hover:text-white">
|
||||
<Github className="mr-2" size={20} />
|
||||
<span>Python SDK</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Community */}
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<MessageCircle className="text-purple-400 mb-4" size={32} />
|
||||
<h2 className="text-2xl font-bold mb-2">Community</h2>
|
||||
<p className="text-gray-400 mb-4">Get help and connect with other developers.</p>
|
||||
<div className="space-y-3">
|
||||
<a href="#" className="flex items-center text-blue-400 hover:text-white">
|
||||
<Github className="mr-2" size={20} />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
<a href="#" className="flex items-center text-blue-400 hover:text-white">
|
||||
<MessageCircle className="mr-2" size={20} />
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">Code Examples</h2>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-2">Connect to the Network</h3>
|
||||
<CodeSnippet language="javascript" code={connectCode} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-2">Make a Transfer</h3>
|
||||
<CodeSnippet language="javascript" code={transferCode} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Developers;
|
||||
@@ -0,0 +1,309 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||
import Layout from '@/components/Layout';
|
||||
import { marked } from 'marked';
|
||||
import { ChevronRight, Book, ExternalLink } from 'lucide-react';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
// SDK Embedded View - shown inline in the content area (window in window style)
|
||||
const SDKEmbeddedView: React.FC = () => {
|
||||
const sdkUrl = '/sdk_docs/pezkuwi_sdk_docs/index.html';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full min-h-[600px]">
|
||||
{/* SDK Panel Header */}
|
||||
<div className="flex items-center gap-3 px-4 py-3 bg-gray-800 rounded-t-lg border border-gray-700 border-b-0">
|
||||
<img
|
||||
src="/pezkuwi_icon.png"
|
||||
alt="Pezkuwi"
|
||||
className="w-8 h-8 rounded"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-white font-semibold">pezkuwi_sdk_docs</h3>
|
||||
<span className="text-gray-400 text-xs">0.0.1</span>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<a
|
||||
href={sdkUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2 hover:bg-gray-700 rounded-md transition-colors text-gray-400 hover:text-white"
|
||||
title="Open in new tab"
|
||||
>
|
||||
<ExternalLink size={16} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{/* SDK Docs iframe */}
|
||||
<div className="flex-1 border border-gray-700 rounded-b-lg overflow-hidden bg-white">
|
||||
<iframe
|
||||
src={sdkUrl}
|
||||
title="Pezkuwi SDK Documentation"
|
||||
className="w-full h-full border-0"
|
||||
style={{ minHeight: '550px' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarNav: React.FC<{ structure: object, onLinkClick: () => void, onSDKClick: () => void }> = ({ structure, onLinkClick, onSDKClick }) => {
|
||||
const [openCategories, setOpenCategories] = useState<string[]>(['Getting Started', 'SDK Reference', 'General Docs', 'Contributor Guide']);
|
||||
|
||||
const toggleCategory = (category: string) => {
|
||||
setOpenCategories(prev =>
|
||||
prev.includes(category)
|
||||
? prev.filter(c => c !== category)
|
||||
: [...prev, category]
|
||||
);
|
||||
};
|
||||
|
||||
const renderNav = (struct: any) => {
|
||||
return Object.entries(struct).map(([key, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
// Check if it's the SDK docs special link
|
||||
const isSDKLink = value === 'sdk://open';
|
||||
|
||||
if (isSDKLink) {
|
||||
return (
|
||||
<li key={key}>
|
||||
<button
|
||||
onClick={() => {
|
||||
onSDKClick();
|
||||
}}
|
||||
className="w-full text-left block py-1 px-2 rounded-md hover:bg-gray-700 transition-colors font-bold text-green-400 hover:text-green-300 flex items-center gap-2"
|
||||
>
|
||||
{key}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
const path = value.replace(/\.(md|rs)$/, '');
|
||||
return (
|
||||
<li key={path}>
|
||||
<Link
|
||||
to={`/docs/${path}`}
|
||||
onClick={onLinkClick}
|
||||
className="block py-1 px-2 rounded-md hover:bg-gray-700 transition-colors text-gray-300 hover:text-white"
|
||||
>
|
||||
{key}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
const isExpanded = openCategories.includes(key);
|
||||
return (
|
||||
<li key={key}>
|
||||
<div
|
||||
onClick={() => toggleCategory(key)}
|
||||
className="flex justify-between items-center cursor-pointer py-2 px-2 rounded-md hover:bg-gray-700"
|
||||
>
|
||||
<span className="font-semibold text-white">{key}</span>
|
||||
<ChevronRight size={16} className={`transform transition-transform text-gray-400 ${isExpanded ? 'rotate-90' : ''}`} />
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<ul className="pl-4 border-l border-gray-600 ml-2">
|
||||
{renderNav(value)}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return <nav><ul className="space-y-1">{renderNav(structure)}</ul></nav>;
|
||||
};
|
||||
|
||||
|
||||
const Docs: React.FC = () => {
|
||||
const { '*': splat } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [docStructure, setDocStructure] = useState<object | null>(null);
|
||||
const [content, setContent] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const [showSDKLanding, setShowSDKLanding] = useState(false);
|
||||
|
||||
// Fetch the documentation structure
|
||||
useEffect(() => {
|
||||
fetch('/docs-structure.json')
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to load documentation structure.');
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => setDocStructure(data))
|
||||
.catch(e => setError(e.message));
|
||||
}, []);
|
||||
|
||||
const filePath = useMemo(() => {
|
||||
// If no splat, and the structure is loaded, default to the introduction markdown
|
||||
if (!splat && docStructure) {
|
||||
const defaultEntry = docStructure['Introduction'];
|
||||
if (typeof defaultEntry === 'string') {
|
||||
return defaultEntry;
|
||||
}
|
||||
} else if (splat) {
|
||||
// Check if it's an SDK link which is an HTML file
|
||||
if (splat.startsWith('sdk_docs/') && splat.endsWith('html')) {
|
||||
return splat; // Treat as direct path, no .md or .rs append
|
||||
}
|
||||
return `${splat}.md`; // For .md or .rs files
|
||||
}
|
||||
return null; // No file selected, no default provided yet
|
||||
}, [splat, docStructure]);
|
||||
|
||||
// If no splat and no default, avoid fetching content
|
||||
const shouldFetchContent = !!filePath && !filePath.startsWith('sdk_docs/'); // Do not fetch content if it's an external SDK link
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldFetchContent) {
|
||||
setContent(''); // Clear content if not fetching
|
||||
setError(null);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchContent = async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch(`/docs/${filePath}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Documentation file not found: ${filePath}`);
|
||||
}
|
||||
let text = await response.text();
|
||||
|
||||
// If the file is a Rust file, wrap it in a markdown code block
|
||||
if (filePath.endsWith('.rs')) {
|
||||
text = '```rust\n' + text + '\n```';
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.image = (href, title, text) => {
|
||||
try {
|
||||
// The base URL for the markdown file itself
|
||||
const base = new URL(`/docs/${filePath}`, window.location.origin);
|
||||
// Resolve the image's relative path against the markdown file's path
|
||||
const imageUrl = new URL(href, base);
|
||||
// Return the final path part of the URL
|
||||
return `<img src="${imageUrl.pathname}" alt="${text}" title="${title || ''}" />`;
|
||||
} catch (e) {
|
||||
console.error("Error processing image URL:", e);
|
||||
// Fallback to the original href if URL construction fails
|
||||
return `<img src="${href}" alt="${text}" title="${title || ''}" />`;
|
||||
}
|
||||
};
|
||||
marked.setOptions({ renderer });
|
||||
|
||||
const parsed = await marked.parse(text);
|
||||
const sanitized = DOMPurify.sanitize(parsed);
|
||||
setContent(sanitized);
|
||||
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
setContent('');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchContent();
|
||||
}, [filePath, shouldFetchContent]); // Dependency array
|
||||
|
||||
// Check if we're on SDK route
|
||||
const isSDKRoute = splat === 'sdk';
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="flex h-full overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`fixed lg:static top-16 left-0 h-full lg:h-auto z-30 w-64 bg-gray-800 lg:bg-transparent lg:w-1/4 lg:pr-8 py-4 transition-transform transform ${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'} lg:translate-x-0`}
|
||||
>
|
||||
<div className="px-4">
|
||||
{docStructure ? (
|
||||
<SidebarNav
|
||||
structure={docStructure}
|
||||
onLinkClick={() => {
|
||||
setIsSidebarOpen(false);
|
||||
setShowSDKLanding(false); // Clear SDK landing when navigating to other docs
|
||||
}}
|
||||
onSDKClick={() => {
|
||||
setIsSidebarOpen(false);
|
||||
setShowSDKLanding(true); // Show SDK landing
|
||||
setContent(''); // Clear any markdown content
|
||||
navigate('/docs/sdk');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-gray-400">Loading navigation...</p>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Mobile Sidebar Toggle */}
|
||||
<button
|
||||
className="fixed bottom-4 right-4 lg:hidden w-12 h-12 bg-green-600 rounded-full z-40 flex items-center justify-center text-white shadow-lg"
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
>
|
||||
<Book size={24} />
|
||||
</button>
|
||||
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="w-full lg:w-3/4 lg:pl-8 flex flex-col">
|
||||
<div className="prose prose-invert prose-headings:text-cyan-400 prose-a:text-blue-400 hover:prose-a:text-blue-300 prose-code:text-yellow-400 prose-pre:bg-gray-800 prose-pre:p-4 prose-pre:rounded-md max-w-none flex-1 min-h-0">
|
||||
{isLoading && <p className="text-gray-400">Loading...</p>}
|
||||
{error && <p className="text-red-400">Error: {error}</p>}
|
||||
|
||||
{/* SDK Embedded View - window in window style */}
|
||||
{(showSDKLanding || isSDKRoute) && (
|
||||
<SDKEmbeddedView />
|
||||
)}
|
||||
|
||||
{/* Regular Markdown Content */}
|
||||
{!isLoading && !error && content && !showSDKLanding && !isSDKRoute && (
|
||||
<div dangerouslySetInnerHTML={{ __html: content }} />
|
||||
)}
|
||||
|
||||
{/* Default Welcome */}
|
||||
{!isLoading && !error && !content && !splat && !showSDKLanding && (
|
||||
<div className="text-center py-12">
|
||||
<div className="mb-8">
|
||||
<div className="text-6xl mb-4">📖</div>
|
||||
<h1 className="text-3xl font-bold text-white mb-2">PezkuwiChain Documentation</h1>
|
||||
<p className="text-lg text-gray-400">Learn how to build on PezkuwiChain</p>
|
||||
</div>
|
||||
<p className="text-xl text-gray-400 mb-4">
|
||||
Select a document from the sidebar to get started.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4 justify-center mt-8">
|
||||
<Link to="/docs/GENESIS_ENGINEERING_PLAN" className="px-4 py-2 bg-green-600 hover:bg-green-500 text-white rounded-lg transition-colors">
|
||||
📋 Introduction
|
||||
</Link>
|
||||
<Link
|
||||
to="/docs/sdk"
|
||||
onClick={() => setShowSDKLanding(true)}
|
||||
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors"
|
||||
>
|
||||
📚 SDK Docs
|
||||
</Link>
|
||||
<Link to="/docs/whitepaper/whitepaper" className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors">
|
||||
📄 Whitepaper
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Docs;
|
||||
@@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
|
||||
const Explorer: React.FC = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-blue-400">Block Explorer</h1>
|
||||
<div className="w-full md:w-1/2 mt-4 md:mt-0">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by Block / Tx / Address"
|
||||
className="w-full px-4 py-2 rounded-lg bg-gray-800 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-400">Total Blocks</h3>
|
||||
<p className="text-2xl font-bold">123,456</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-400">Transactions per Second (TPS)</h3>
|
||||
<p className="text-2xl font-bold">15</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-400">Active Validators</h3>
|
||||
<p className="text-2xl font-bold">42</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">Latest Blocks</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-bold text-blue-400">Block #123456</span>
|
||||
<span className="text-gray-400">10 secs ago</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span>Includes <span className="font-semibold text-green-400">5</span> transactions</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-bold text-blue-400">Block #123455</span>
|
||||
<span className="text-gray-400">25 secs ago</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span>Includes <span className="font-semibold text-green-400">12</span> transactions</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-bold text-blue-400">Block #123454</span>
|
||||
<span className="text-gray-400">45 secs ago</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span>Includes <span className="font-semibold text-green-400">8</span> transactions</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">Latest Transactions</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-mono text-sm text-purple-400 truncate">0xabcdef123...</span>
|
||||
<span className="text-gray-400">12 secs ago</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="font-semibold">From:</span> <span className="font-mono text-xs text-gray-300">5Grwva...</span>
|
||||
<span className="font-semibold ml-2">To:</span> <span className="font-mono text-xs text-gray-300">5FHne...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-mono text-sm text-purple-400 truncate">0x123456abc...</span>
|
||||
<span className="text-gray-400">30 secs ago</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="font-semibold">From:</span> <span className="font-mono text-xs text-gray-300">5DAAn...</span>
|
||||
<span className="font-semibold ml-2">To:</span> <span className="font-mono text-xs text-gray-300">5G6s6...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg">
|
||||
<div className="flex justify-between">
|
||||
<span className="font-mono text-sm text-purple-400 truncate">0x7890ab123...</span>
|
||||
<span className="text-gray-400">50 secs ago</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="font-semibold">From:</span> <span className="font-mono text-xs text-gray-300">5Hp2d...</span>
|
||||
<span className="font-semibold ml-2">To:</span> <span className="font-mono text-xs text-gray-300">5E5s3...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Explorer;
|
||||
@@ -0,0 +1,104 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { Clock, Send } from 'lucide-react';
|
||||
|
||||
const Faucet: React.FC = () => {
|
||||
const [address, setAddress] = useState('');
|
||||
const [cooldown, setCooldown] = useState(0);
|
||||
const [token, setToken] = useState('HEZ');
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
if (cooldown > 0) {
|
||||
timer = setTimeout(() => setCooldown(cooldown - 1), 1000);
|
||||
}
|
||||
return () => clearTimeout(timer);
|
||||
}, [cooldown]);
|
||||
|
||||
const handleRequest = () => {
|
||||
// Mock request logic
|
||||
if (address) {
|
||||
console.log(`Requesting ${token} for address:`, address);
|
||||
setCooldown(300); // 5 minutes cooldown
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`;
|
||||
};
|
||||
|
||||
const recentDistributions = [
|
||||
{ token: 'HEZ', address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', txHash: '0xabcde...', time: '2 minutes ago' },
|
||||
{ token: 'PEZ', address: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty', txHash: '0x12345...', time: '5 minutes ago' },
|
||||
{ token: 'wHEZ', address: '5DAAn...c1s', txHash: '0x789ab...', time: '15 minutes ago' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white flex flex-col items-center">
|
||||
<div className="w-full max-w-2xl text-center">
|
||||
<h1 className="text-4xl font-bold mb-2 text-yellow-400">Testnet Faucet</h1>
|
||||
<p className="text-gray-400 mb-8">Get testnet tokens to build and test your dApps on PezkuwiChain.</p>
|
||||
|
||||
<div className="bg-gray-800 p-8 rounded-lg shadow-lg">
|
||||
<div className="flex mb-4">
|
||||
<select
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
className="p-3 rounded-l-lg bg-gray-700 text-white focus:outline-none"
|
||||
>
|
||||
<option>HEZ</option>
|
||||
<option>PEZ</option>
|
||||
<option>wHEZ</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your wallet address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
className="w-full p-3 bg-gray-700 text-white focus:outline-none rounded-r-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleRequest}
|
||||
disabled={cooldown > 0 || !address}
|
||||
className="w-full bg-yellow-500 hover:bg-yellow-600 text-black font-bold py-3 px-4 rounded-lg transition-colors disabled:bg-gray-600 disabled:cursor-not-allowed flex items-center justify-center"
|
||||
>
|
||||
{cooldown > 0 ? (
|
||||
<>
|
||||
<Clock className="mr-2" size={20} />
|
||||
<span>Wait for {formatTime(cooldown)}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="mr-2" size={20} />
|
||||
<span>Request Tokens</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 w-full">
|
||||
<h2 className="text-2xl font-bold mb-4 text-left">Recent Distributions</h2>
|
||||
<div className="space-y-3">
|
||||
{recentDistributions.map((dist, index) => (
|
||||
<div key={index} className="bg-gray-800 p-3 rounded-lg flex justify-between items-center text-sm">
|
||||
<div className="flex flex-col text-left">
|
||||
<span><span className="font-bold text-yellow-400">{dist.token}</span> sent to <span className="font-mono text-gray-300">{dist.address.substring(0, 12)}...</span></span>
|
||||
<span className="font-mono text-xs text-gray-500">{dist.txHash}</span>
|
||||
</div>
|
||||
<span className="text-gray-400">{dist.time}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Faucet;
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { Plus, MessageSquare, Eye, TrendingUp, Search } from 'lucide-react';
|
||||
|
||||
const Forum: React.FC = () => {
|
||||
|
||||
const topics = [
|
||||
{ title: 'Proposal: New Treasury Spend for Marketing', user: 'Alice', avatar: '/avatars/avatar-1.png', category: 'Proposals', replies: 42, views: 1200, activity: '1h' },
|
||||
{ title: 'Help with setting up a validator node', user: 'Bob', avatar: '/avatars/avatar-2.png', category: 'Technical Support', replies: 15, views: 850, activity: '3h' },
|
||||
{ title: 'PezkuwiChain 2.0 Vision', user: 'Charlie', avatar: '/avatars/avatar-3.png', category: 'General Discussion', replies: 128, views: 5600, activity: '1d' },
|
||||
{ title: 'Feature Request: Integrated NFT creator', user: 'David', avatar: '/avatars/avatar-4.png', category: 'Feature Requests', replies: 3, views: 250, activity: '2d' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center mb-8">
|
||||
<h1 className="text-4xl font-bold">Community Forum</h1>
|
||||
<div className="flex items-center space-x-4 mt-4 md:mt-0">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={20} />
|
||||
<input type="text" placeholder="Search forum..." className="w-full pl-10 pr-4 py-2 rounded-lg bg-gray-800 text-white focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
<button className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg flex items-center transition-colors">
|
||||
<Plus className="mr-2" size={20} /> New Topic
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-800 rounded-lg">
|
||||
<div className="hidden md:grid grid-cols-12 gap-4 px-6 py-3 border-b border-gray-700 font-bold text-gray-400">
|
||||
<div className="col-span-6">Topic</div>
|
||||
<div className="col-span-2 text-center">Category</div>
|
||||
<div className="col-span-1 text-center">Replies</div>
|
||||
<div className="col-span-1 text-center">Views</div>
|
||||
<div className="col-span-2 text-right">Activity</div>
|
||||
</div>
|
||||
|
||||
{topics.map((topic, index) => (
|
||||
<div key={index} className="grid grid-cols-12 gap-4 px-6 py-4 border-b border-gray-700 hover:bg-gray-700/50 transition-colors items-center">
|
||||
<div className="col-span-12 md:col-span-6">
|
||||
<a href="#" className="font-bold text-lg text-blue-400 hover:underline">{topic.title}</a>
|
||||
<div className="flex items-center mt-1 text-sm text-gray-400">
|
||||
<img src={topic.avatar} alt={topic.user} className="w-6 h-6 rounded-full mr-2" />
|
||||
<span>{topic.user}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-6 md:col-span-2 text-left md:text-center">
|
||||
<span className="font-semibold text-sm" style={{color: '#'+(Math.random()*0xFFFFFF<<0).toString(16)}}>{topic.category}</span>
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-1 text-left md:text-center flex items-center justify-start md:justify-center">
|
||||
<MessageSquare size={16} className="mr-1 text-gray-500" /> {topic.replies}
|
||||
</div>
|
||||
<div className="col-span-2 md:col-span-1 text-left md:text-center flex items-center justify-start md:justify-center">
|
||||
<Eye size={16} className="mr-1 text-gray-500" /> {topic.views}
|
||||
</div>
|
||||
<div className="col-span-4 md:col-span-2 text-left md:text-right text-gray-400">
|
||||
{topic.activity}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Forum;
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { Award, Lightbulb, CheckCircle } from 'lucide-react';
|
||||
|
||||
const Grants: React.FC = () => {
|
||||
|
||||
const fundedProjects = [
|
||||
{ name: 'Pezkuwi DEX', description: 'A fast and secure decentralized exchange built on PezkuwiChain.', logo: '/pezkuwimarket.png' },
|
||||
{ name: 'NFT Marketplace', description: 'A platform for creating and trading NFTs with low transaction fees.', logo: '/PezkuwiExchange.png' },
|
||||
{ name: 'DAO Governance Tool', description: 'A tool for decentralized autonomous organizations to manage their governance.', logo: '/governance.png' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-5xl font-bold mb-2 text-purple-400">PezkuwiChain Grants Program</h1>
|
||||
<p className="text-xl text-gray-400">Funding the future of the PezkuwiChain ecosystem.</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center mb-12">
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<Lightbulb className="mx-auto text-purple-400 mb-4" size={32} />
|
||||
<h3 className="text-xl font-bold">Bring Your Idea</h3>
|
||||
<p className="text-gray-400">We support innovative projects that bring value to the ecosystem.</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<Award className="mx-auto text-purple-400 mb-4" size={32} />
|
||||
<h3 className="text-xl font-bold">Get Funded</h3>
|
||||
<p className="text-gray-400">Receive funding and support to turn your idea into reality.</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<CheckCircle className="mx-auto text-purple-400 mb-4" size={32} />
|
||||
<h3 className="text-xl font-bold">Grow the Ecosystem</h3>
|
||||
<p className="text-gray-400">Contribute to the growth and decentralization of PezkuwiChain.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<div className="bg-gray-800 p-8 rounded-lg">
|
||||
<h2 className="text-3xl font-bold mb-6">Apply for a Grant</h2>
|
||||
<form>
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2 font-semibold">Project Name</label>
|
||||
<input type="text" placeholder="Your awesome project" className="w-full p-3 rounded bg-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-purple-500" />
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2 font-semibold">Team Information</label>
|
||||
<textarea placeholder="Tell us about your team" rows={3} className="w-full p-3 rounded bg-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-purple-500"></textarea>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<label className="block mb-2 font-semibold">Requested Amount (USD)</label>
|
||||
<input type="number" placeholder="10000" className="w-full p-3 rounded bg-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-purple-500" />
|
||||
</div>
|
||||
<button className="w-full bg-purple-500 hover:bg-purple-600 text-white font-bold py-3 px-4 rounded-lg transition-colors">
|
||||
Submit Application
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold mb-6">Our Focus Areas</h2>
|
||||
<ul className="list-disc list-inside space-y-3 text-lg">
|
||||
<li>Decentralized Finance (DeFi)</li>
|
||||
<li>NFTs and Gaming</li>
|
||||
<li>Infrastructure and Tooling</li>
|
||||
<li>Governance and DAOs</li>
|
||||
<li>Privacy and Identity</li>
|
||||
<li>Mobile and Web3 Applications</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">Funded Projects</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{fundedProjects.map((project, index) => (
|
||||
<div key={index} className="bg-gray-800 rounded-lg overflow-hidden">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center mb-4">
|
||||
<img src={project.logo} alt={`${project.name} logo`} className="w-12 h-12 mr-4"/>
|
||||
<h3 className="text-xl font-bold">{project.name}</h3>
|
||||
</div>
|
||||
<p className="text-gray-400">{project.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Grants;
|
||||
@@ -0,0 +1,160 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLink, Globe, Book, Compass, FileCode, HandCoins, Users, Wrench, MessageCircle, GitFork, Server, Activity, Zap, Database, Radio, Layers, ArrowLeft } from 'lucide-react';
|
||||
|
||||
interface SubdomainItem {
|
||||
name: string;
|
||||
url: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
status: 'active' | 'coming-soon';
|
||||
category: 'main' | 'rpc' | 'tools';
|
||||
}
|
||||
|
||||
const subdomainList: SubdomainItem[] = [
|
||||
// Main Services
|
||||
{ name: 'Explorer', url: 'https://explorer.pezkuwichain.io', description: 'Block explorer and chain analytics', icon: <Compass className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'Documentation', url: 'https://docs.pezkuwichain.io', description: 'SDK documentation and guides', icon: <Book className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'Wiki', url: 'https://wiki.pezkuwichain.io', description: 'Community knowledge base', icon: <Globe className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'Forum', url: 'https://forum.pezkuwichain.io', description: 'Community discussions and proposals', icon: <MessageCircle className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'Faucet', url: 'https://faucet.pezkuwichain.io', description: 'Get testnet tokens for development', icon: <HandCoins className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'Telemetry', url: 'https://telemetry.pezkuwichain.io', description: 'Network monitoring and node stats', icon: <Activity className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'Developers', url: 'https://developers.pezkuwichain.io', description: 'Developer portal and resources', icon: <Users className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'Grants', url: 'https://grants.pezkuwichain.io', description: 'Ecosystem grants program', icon: <Wrench className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
{ name: 'API', url: 'https://api.pezkuwichain.io', description: 'REST API for chain data', icon: <FileCode className="w-6 h-6" />, status: 'active', category: 'main' },
|
||||
|
||||
// RPC Endpoints
|
||||
{ name: 'Pezkuwi RPC', url: 'https://pezkuwichain-rpc.pezkuwichain.io', description: 'Primary mainnet RPC endpoint', icon: <Radio className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
{ name: 'Beta RPC', url: 'https://beta-rpc.pezkuwichain.io', description: 'Beta testnet RPC endpoint', icon: <Zap className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
{ name: 'Zagros Network', url: 'https://zagros.pezkuwichain.io', description: 'Zagros canary network', icon: <Globe className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
{ name: 'Zagros RPC', url: 'https://zagros-rpc.pezkuwichain.io', description: 'Zagros relay chain RPC', icon: <Radio className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
{ name: 'Asset Hub RPC', url: 'https://zagros-asset-hub-rpc.pezkuwichain.io', description: 'Zagros Asset Hub parachain RPC', icon: <Database className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
{ name: 'Bridge Hub RPC', url: 'https://zagros-bridge-hub-rpc.pezkuwichain.io', description: 'Zagros Bridge Hub parachain RPC', icon: <Layers className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
{ name: 'Collectives RPC', url: 'https://zagros-collectives-rpc.pezkuwichain.io', description: 'Zagros Collectives parachain RPC', icon: <Users className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
{ name: 'Coretime RPC', url: 'https://zagros-coretime-rpc.pezkuwichain.io', description: 'Zagros Coretime parachain RPC', icon: <Server className="w-6 h-6" />, status: 'active', category: 'rpc' },
|
||||
|
||||
// Tools
|
||||
{ name: 'Try Runtime', url: 'https://try-runtime.pezkuwichain.io', description: 'Test runtime upgrades safely', icon: <Zap className="w-6 h-6" />, status: 'active', category: 'tools' },
|
||||
{ name: 'Try Runtime Zagros', url: 'https://try-runtime-zagros.pezkuwichain.io', description: 'Test runtime on Zagros canary', icon: <Zap className="w-6 h-6" />, status: 'active', category: 'tools' },
|
||||
{ name: 'Network Wiki', url: 'https://wiki.network.pezkuwichain.io', description: 'Technical network documentation', icon: <Book className="w-6 h-6" />, status: 'active', category: 'tools' },
|
||||
];
|
||||
|
||||
const Subdomains: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const mainServices = subdomainList.filter(s => s.category === 'main');
|
||||
const rpcEndpoints = subdomainList.filter(s => s.category === 'rpc');
|
||||
const tools = subdomainList.filter(s => s.category === 'tools');
|
||||
|
||||
const renderCard = (item: SubdomainItem) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-start p-4 bg-gray-900/50 border border-gray-800 rounded-xl hover:border-green-500/50 hover:bg-gray-900 transition-all duration-300"
|
||||
>
|
||||
<div className="p-3 rounded-lg bg-gradient-to-br from-green-500/20 to-cyan-500/20 text-green-400 group-hover:from-green-500/30 group-hover:to-cyan-500/30 transition-all">
|
||||
{item.icon}
|
||||
</div>
|
||||
<div className="ml-4 flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-white font-semibold group-hover:text-green-400 transition-colors">
|
||||
{item.name}
|
||||
</h3>
|
||||
<ExternalLink className="w-4 h-4 text-gray-500 group-hover:text-green-400 transition-colors" />
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm mt-1">{item.description}</p>
|
||||
<code className="text-xs text-cyan-400/70 mt-2 block truncate">{item.url}</code>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-950 py-12">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Back Button */}
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="flex items-center text-gray-400 hover:text-white mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 mr-2" />
|
||||
Back to Home
|
||||
</button>
|
||||
|
||||
{/* Header */}
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-green-400 via-cyan-400 to-purple-400 bg-clip-text text-transparent">
|
||||
PezkuwiChain Subdomains
|
||||
</h1>
|
||||
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
|
||||
Access all PezkuwiChain services, RPC endpoints, and developer tools
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Main Services */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-white mb-6 flex items-center">
|
||||
<Globe className="w-6 h-6 mr-3 text-green-400" />
|
||||
Main Services
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{mainServices.map(renderCard)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* RPC Endpoints */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-white mb-6 flex items-center">
|
||||
<Radio className="w-6 h-6 mr-3 text-cyan-400" />
|
||||
RPC Endpoints
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{rpcEndpoints.map(renderCard)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Developer Tools */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-white mb-6 flex items-center">
|
||||
<Wrench className="w-6 h-6 mr-3 text-purple-400" />
|
||||
Developer Tools
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{tools.map(renderCard)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Info Box */}
|
||||
<div className="bg-gradient-to-r from-green-500/10 to-cyan-500/10 border border-green-500/20 rounded-xl p-6 text-center">
|
||||
<h3 className="text-xl font-semibold text-white mb-2">Need Help?</h3>
|
||||
<p className="text-gray-400 mb-4">
|
||||
Check our documentation or join the community forum for support
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<a
|
||||
href="https://docs.pezkuwichain.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-6 py-2 bg-green-500 hover:bg-green-600 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
Read Docs
|
||||
</a>
|
||||
<a
|
||||
href="https://forum.pezkuwichain.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-6 py-2 bg-gray-800 hover:bg-gray-700 text-white font-semibold rounded-lg border border-gray-700 transition-colors"
|
||||
>
|
||||
Join Forum
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Subdomains;
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { CheckCircle, Zap, Server, Globe } from 'lucide-react';
|
||||
|
||||
const Telemetry: React.FC = () => {
|
||||
|
||||
const validators = [
|
||||
{ name: 'Validator A', stake: '1.2M HEZ', uptime: 99.98, status: 'Active' },
|
||||
{ name: 'Validator B', stake: '1.1M HEZ', uptime: 99.95, status: 'Active' },
|
||||
{ name: 'Validator C', stake: '1.0M HEZ', uptime: 99.92, status: 'Active' },
|
||||
{ name: 'Validator D', stake: '0.9M HEZ', uptime: 99.80, status: 'Active' },
|
||||
{ name: 'Validator E', stake: '0.8M HEZ', uptime: 100, status: 'Waiting' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<h1 className="text-4xl font-bold mb-8">Network Telemetry</h1>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-400">Best Finalized Block</h3>
|
||||
<p className="text-2xl font-bold">1,234,567</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-400">Average Block Time</h3>
|
||||
<p className="text-2xl font-bold">6.0s</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-400">Active Nodes</h3>
|
||||
<p className="text-2xl font-bold">1,234</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-400">Validators</h3>
|
||||
<p className="text-2xl font-bold">42</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2 bg-gray-800 p-6 rounded-lg">
|
||||
<h2 className="text-2xl font-bold mb-4 flex items-center">
|
||||
<Server className="mr-3 text-blue-400" />
|
||||
Validators
|
||||
</h2>
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-700">
|
||||
<th className="py-2">Name</th>
|
||||
<th className="py-2 text-right">Total Stake</th>
|
||||
<th className="py-2 text-right">Uptime</th>
|
||||
<th className="py-2 text-right">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{validators.map((validator, index) => (
|
||||
<tr key={index} className="border-b border-gray-700">
|
||||
<td className="py-3 font-semibold">{validator.name}</td>
|
||||
<td className="py-3 text-right">{validator.stake}</td>
|
||||
<td className="py-3 text-right">{validator.uptime}%</td>
|
||||
<td className="py-3 text-right">
|
||||
<span className={`px-2 py-1 text-xs font-bold rounded-full ${validator.status === 'Active' ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'}`}>
|
||||
{validator.status}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<h2 className="text-2xl font-bold mb-4 flex items-center">
|
||||
<Globe className="mr-3 text-green-400" />
|
||||
Node Map
|
||||
</h2>
|
||||
<div className="h-64 bg-gray-700 rounded-lg flex items-center justify-center">
|
||||
<p className="text-gray-500">Node map visualization placeholder</p>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 mt-4">
|
||||
Note: A fully functional telemetry site already exists at <a href="https://telemetry.pezkuwichain.io/" target="_blank" rel="noopener noreferrer" className="text-blue-400 underline">telemetry.pezkuwichain.io</a>.
|
||||
This page is a placeholder for a new, integrated design.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Telemetry;
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { ArrowUpRight, ArrowDownLeft, Send, QrCode } from 'lucide-react';
|
||||
|
||||
const Wallet: React.FC = () => {
|
||||
const isConnected = false; // Mock connection status
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h1 className="text-4xl font-bold text-green-400">Web Wallet</h1>
|
||||
{!isConnected && (
|
||||
<button className="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded-lg transition-colors">
|
||||
Connect Wallet
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-800 p-6 rounded-lg mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4">My Balance</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<img src="/hez_logo_kurdistangunesi.png" alt="HEZ" className="w-10 h-10" />
|
||||
<div>
|
||||
<p className="text-gray-400">HEZ</p>
|
||||
<p className="text-xl font-bold">1,234.56</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<img src="/pez_logo.jpg" alt="PEZ" className="w-10 h-10" />
|
||||
<div>
|
||||
<p className="text-gray-400">PEZ</p>
|
||||
<p className="text-xl font-bold">5,000.00</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<img src="/usdt(hez)logo.png" alt="wHEZ" className="w-10 h-10" />
|
||||
<div>
|
||||
<p className="text-gray-400">wHEZ</p>
|
||||
<p className="text-xl font-bold">100.00</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-4 mt-6">
|
||||
<button className="flex items-center justify-center w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-4 rounded-lg transition-colors">
|
||||
<Send className="mr-2" size={20} /> Send
|
||||
</button>
|
||||
<button className="flex items-center justify-center w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-4 rounded-lg transition-colors">
|
||||
<QrCode className="mr-2" size={20} /> Receive
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">Transaction History</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-gray-800 p-4 rounded-lg flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="bg-red-500/20 p-2 rounded-full mr-4">
|
||||
<ArrowUpRight className="text-red-400" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold">Sent HEZ</p>
|
||||
<p className="text-sm text-gray-400">To: 5FHne...w1s</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-bold">-10.00 HEZ</p>
|
||||
<p className="text-sm text-gray-400">3 hours ago</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="bg-green-500/20 p-2 rounded-full mr-4">
|
||||
<ArrowDownLeft className="text-green-400" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold">Received PEZ</p>
|
||||
<p className="text-sm text-gray-400">From: 5Grwva...c1s</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-bold">+50.00 PEZ</p>
|
||||
<p className="text-sm text-gray-400">1 day ago</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-800 p-4 rounded-lg flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="bg-red-500/20 p-2 rounded-full mr-4">
|
||||
<ArrowUpRight className="text-red-400" size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold">Sent wHEZ</p>
|
||||
<p className="text-sm text-gray-400">To: 5E5s3...e3s</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-bold">-5.00 wHEZ</p>
|
||||
<p className="text-sm text-gray-400">2 days ago</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Wallet;
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/Layout';
|
||||
import { Search, BookOpen, Star } from 'lucide-react';
|
||||
|
||||
const Wiki: React.FC = () => {
|
||||
|
||||
const categories = [
|
||||
{ name: 'General', description: 'Learn the basics of PezkuwiChain.' },
|
||||
{ name: 'Validators', description: 'How to run a validator node and secure the network.' },
|
||||
{ name: 'Developers', description: 'Guides and resources for building on PezkuwiChain.' },
|
||||
{ name: 'Tokenomics', description: 'Understand the economics of the HEZ and PEZ tokens.' },
|
||||
{ name: 'Governance', description: 'Participate in the decentralized governance of the network.' },
|
||||
{ name: 'Wallets', description: 'Learn how to use the official and third-party wallets.' },
|
||||
];
|
||||
|
||||
const popularArticles = [
|
||||
'How to become a validator',
|
||||
'PezkuwiChain tokenomics explained',
|
||||
'Connecting to the network with Polkadot.js',
|
||||
'Understanding the governance process',
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8 text-white">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-5xl font-bold mb-2">Community Wiki</h1>
|
||||
<p className="text-xl text-gray-400">Your community-driven knowledge base for all things PezkuwiChain.</p>
|
||||
<div className="mt-6 max-w-2xl mx-auto">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" size={24} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search the wiki..."
|
||||
className="w-full pl-14 pr-4 py-3 rounded-lg bg-gray-800 text-white text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2">
|
||||
<h2 className="text-3xl font-bold mb-6 flex items-center">
|
||||
<BookOpen className="mr-3 text-blue-400" />
|
||||
Categories
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{categories.map((category, index) => (
|
||||
<div key={index} className="bg-gray-800 p-6 rounded-lg hover:bg-gray-700 transition-colors cursor-pointer">
|
||||
<h3 className="text-xl font-bold mb-2 text-blue-400">{category.name}</h3>
|
||||
<p className="text-gray-400">{category.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold mb-6 flex items-center">
|
||||
<Star className="mr-3 text-yellow-400" />
|
||||
Popular Articles
|
||||
</h2>
|
||||
<div className="bg-gray-800 p-6 rounded-lg">
|
||||
<ul className="space-y-4">
|
||||
{popularArticles.map((article, index) => (
|
||||
<li key={index}>
|
||||
<a href="#" className="hover:text-blue-400 transition-colors">{article}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Wiki;
|
||||
@@ -19,7 +19,7 @@ export default function CreatePresale() {
|
||||
|
||||
// Form state
|
||||
const [formData, setFormData] = useState({
|
||||
paymentAsset: '2', // wUSDT
|
||||
paymentAsset: '1000', // wUSDT
|
||||
rewardAsset: '1', // PEZ (or custom)
|
||||
tokensForSale: '10000000', // 10M tokens for sale (with 6 decimals = 10M)
|
||||
durationDays: '45',
|
||||
|
||||
@@ -227,7 +227,7 @@ export default function PresaleDetail() {
|
||||
return currentBlock <= graceEnd;
|
||||
};
|
||||
|
||||
const wusdtBalance = balances.find((b) => b.assetId === 2)?.balance || '0';
|
||||
const wusdtBalance = balances.find((b) => b.assetId === 1000)?.balance || '0';
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8 max-w-6xl">
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import NetworkPage from './NetworkPage';
|
||||
|
||||
const Alfa: React.FC = () => {
|
||||
return (
|
||||
<NetworkPage
|
||||
id="alfa"
|
||||
name="Alfa Testnet"
|
||||
type="Development"
|
||||
description="Early-stage testing network for experimental features and internal testing"
|
||||
endpoint="wss://alfa.pezkuwichain.io"
|
||||
chainId="pezkuwichain-alfa"
|
||||
validators={5}
|
||||
features={[
|
||||
'Experimental Features',
|
||||
'Early Access',
|
||||
'Internal Testing',
|
||||
'Rapid Changes',
|
||||
'Developer Preview',
|
||||
'Unstable'
|
||||
]}
|
||||
color="from-red-600 to-red-800"
|
||||
status="coming-soon"
|
||||
blockTime="~6s"
|
||||
finality="~12s"
|
||||
consensus="TNPoS"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Alfa;
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import NetworkPage from './NetworkPage';
|
||||
|
||||
const Beta: React.FC = () => {
|
||||
return (
|
||||
<NetworkPage
|
||||
id="beta"
|
||||
name="Beta Testnet"
|
||||
type="Development"
|
||||
description="Active development testnet with the latest features and improvements"
|
||||
endpoint="wss://rpc.pezkuwichain.io:9944"
|
||||
chainId="pezkuwichain-beta"
|
||||
validators={7}
|
||||
features={[
|
||||
'Latest Features',
|
||||
'Active Development',
|
||||
'Fast Iterations',
|
||||
'Community Access',
|
||||
'Bug Hunting',
|
||||
'Feature Feedback'
|
||||
]}
|
||||
color="from-orange-600 to-orange-800"
|
||||
status="active"
|
||||
blockTime="~6s"
|
||||
finality="~12s"
|
||||
consensus="TNPoS"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Beta;
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import NetworkPage from './NetworkPage';
|
||||
|
||||
const Development: React.FC = () => {
|
||||
return (
|
||||
<NetworkPage
|
||||
id="development"
|
||||
name="Development Environment"
|
||||
type="Development"
|
||||
description="Internal development network for core team feature development"
|
||||
endpoint="wss://dev.pezkuwichain.io"
|
||||
chainId="pezkuwichain-dev"
|
||||
validators={3}
|
||||
features={[
|
||||
'Core Development',
|
||||
'Feature Prototyping',
|
||||
'Internal Only',
|
||||
'Frequent Resets',
|
||||
'Debug Mode',
|
||||
'Hot Reloading'
|
||||
]}
|
||||
color="from-yellow-600 to-yellow-800"
|
||||
status="coming-soon"
|
||||
blockTime="~6s"
|
||||
finality="~12s"
|
||||
consensus="TNPoS"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Development;
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import NetworkPage from './NetworkPage';
|
||||
|
||||
const Local: React.FC = () => {
|
||||
return (
|
||||
<NetworkPage
|
||||
id="local"
|
||||
name="Local Testnet"
|
||||
type="Local"
|
||||
description="Run your own local PezkuwiChain node for development and testing"
|
||||
endpoint="ws://127.0.0.1:9944"
|
||||
chainId="pezkuwichain-local"
|
||||
validators={1}
|
||||
features={[
|
||||
'Local Development',
|
||||
'Instant Blocks',
|
||||
'Full Control',
|
||||
'No Network Latency',
|
||||
'Custom Genesis',
|
||||
'Unlimited Funds'
|
||||
]}
|
||||
color="from-blue-600 to-blue-800"
|
||||
status="active"
|
||||
blockTime="Instant"
|
||||
finality="Instant"
|
||||
consensus="Dev"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Local;
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import NetworkPage from './NetworkPage';
|
||||
|
||||
const Mainnet: React.FC = () => {
|
||||
return (
|
||||
<NetworkPage
|
||||
id="mainnet"
|
||||
name="PezkuwiChain Mainnet"
|
||||
type="Live"
|
||||
description="The production PezkuwiChain network with real value transactions"
|
||||
endpoint="wss://mainnet.pezkuwichain.io"
|
||||
chainId="pezkuwichain-mainnet"
|
||||
validators={21}
|
||||
features={[
|
||||
'HEZ Native Token',
|
||||
'Production Ready',
|
||||
'Full Security',
|
||||
'Governance',
|
||||
'Staking',
|
||||
'TNPoS Consensus',
|
||||
'Identity & KYC',
|
||||
'Asset Hub'
|
||||
]}
|
||||
color="from-green-600 to-green-800"
|
||||
status="coming-soon"
|
||||
blockTime="~6s"
|
||||
finality="~12s"
|
||||
consensus="TNPoS"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Mainnet;
|
||||
@@ -0,0 +1,236 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ArrowLeft, Copy, Check, Server, Globe, Zap, Clock, Shield, ExternalLink, Activity } from 'lucide-react';
|
||||
|
||||
interface NetworkPageProps {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'Live' | 'Development' | 'Local';
|
||||
description: string;
|
||||
endpoint: string;
|
||||
chainId: string;
|
||||
validators: number;
|
||||
features: string[];
|
||||
color: string;
|
||||
status: 'active' | 'coming-soon' | 'maintenance';
|
||||
blockTime?: string;
|
||||
finality?: string;
|
||||
consensus?: string;
|
||||
}
|
||||
|
||||
const NetworkPage: React.FC<NetworkPageProps> = ({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
endpoint,
|
||||
chainId,
|
||||
validators,
|
||||
features,
|
||||
color,
|
||||
status,
|
||||
blockTime = '~6s',
|
||||
finality = '~12s',
|
||||
consensus = 'TNPoS'
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const [copiedField, setCopiedField] = useState<string | null>(null);
|
||||
|
||||
const copyToClipboard = (text: string, field: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopiedField(field);
|
||||
setTimeout(() => setCopiedField(null), 2000);
|
||||
};
|
||||
|
||||
const statusColors = {
|
||||
'active': 'bg-green-500/20 text-green-400 border-green-500/30',
|
||||
'coming-soon': 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
|
||||
'maintenance': 'bg-red-500/20 text-red-400 border-red-500/30'
|
||||
};
|
||||
|
||||
const statusLabels = {
|
||||
'active': 'Active',
|
||||
'coming-soon': 'Coming Soon',
|
||||
'maintenance': 'Maintenance'
|
||||
};
|
||||
|
||||
const typeColors = {
|
||||
'Live': 'bg-green-900/30 text-green-400',
|
||||
'Development': 'bg-yellow-900/30 text-yellow-400',
|
||||
'Local': 'bg-blue-900/30 text-blue-400'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-950 py-12">
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Back Button */}
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="flex items-center text-gray-400 hover:text-white mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 mr-2" />
|
||||
Back to Home
|
||||
</button>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-10">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className={`p-4 rounded-xl bg-gradient-to-br ${color}`}>
|
||||
<Globe className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-white">{name}</h1>
|
||||
<span className={`px-3 py-1 text-sm rounded-full ${typeColors[type]}`}>
|
||||
{type}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-400 mt-1">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`inline-flex items-center px-4 py-2 rounded-full border ${statusColors[status]}`}>
|
||||
<span className="w-2 h-2 rounded-full bg-current mr-2 animate-pulse"></span>
|
||||
{statusLabels[status]}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-10">
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-5 text-center">
|
||||
<Server className="w-6 h-6 text-cyan-400 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-white">{validators}</div>
|
||||
<div className="text-gray-400 text-sm">Validators</div>
|
||||
</div>
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-5 text-center">
|
||||
<Clock className="w-6 h-6 text-green-400 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-white">{blockTime}</div>
|
||||
<div className="text-gray-400 text-sm">Block Time</div>
|
||||
</div>
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-5 text-center">
|
||||
<Zap className="w-6 h-6 text-yellow-400 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-white">{finality}</div>
|
||||
<div className="text-gray-400 text-sm">Finality</div>
|
||||
</div>
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-5 text-center">
|
||||
<Shield className="w-6 h-6 text-purple-400 mx-auto mb-2" />
|
||||
<div className="text-2xl font-bold text-white">{consensus}</div>
|
||||
<div className="text-gray-400 text-sm">Consensus</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connection Details */}
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-6 mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-6 flex items-center">
|
||||
<Activity className="w-5 h-5 mr-2 text-cyan-400" />
|
||||
Connection Details
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-gray-400 text-sm block mb-2">WebSocket Endpoint</label>
|
||||
<div className="flex items-center">
|
||||
<code className="flex-1 p-4 bg-gray-950 rounded-lg text-cyan-400 font-mono text-sm border border-gray-800">
|
||||
{endpoint}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard(endpoint, 'endpoint')}
|
||||
className="ml-3 p-3 text-gray-400 hover:text-white bg-gray-800 rounded-lg transition-colors"
|
||||
>
|
||||
{copiedField === 'endpoint' ?
|
||||
<Check className="w-5 h-5 text-green-400" /> :
|
||||
<Copy className="w-5 h-5" />
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-gray-400 text-sm block mb-2">Chain ID</label>
|
||||
<div className="flex items-center">
|
||||
<code className="flex-1 p-4 bg-gray-950 rounded-lg text-purple-400 font-mono text-sm border border-gray-800">
|
||||
{chainId}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard(chainId, 'chainId')}
|
||||
className="ml-3 p-3 text-gray-400 hover:text-white bg-gray-800 rounded-lg transition-colors"
|
||||
>
|
||||
{copiedField === 'chainId' ?
|
||||
<Check className="w-5 h-5 text-green-400" /> :
|
||||
<Copy className="w-5 h-5" />
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-6 mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-4">Features</h2>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{features.map((feature, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={`px-4 py-2 rounded-full bg-gradient-to-r ${color} text-white text-sm font-medium`}
|
||||
>
|
||||
{feature}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Code Example */}
|
||||
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-6 mb-8">
|
||||
<h2 className="text-xl font-semibold text-white mb-4">Quick Start</h2>
|
||||
<div className="bg-gray-950 rounded-lg p-4 border border-gray-800">
|
||||
<pre className="text-sm overflow-x-auto">
|
||||
<code className="text-gray-300">
|
||||
{`import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
|
||||
const provider = new WsProvider('${endpoint}');
|
||||
const api = await ApiPromise.create({ provider });
|
||||
|
||||
// Get chain info
|
||||
const chain = await api.rpc.system.chain();
|
||||
console.log('Connected to:', chain.toString());`}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<a
|
||||
href="https://explorer.pezkuwichain.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`flex items-center px-6 py-3 rounded-lg bg-gradient-to-r ${color} text-white font-semibold hover:opacity-90 transition-opacity`}
|
||||
>
|
||||
<ExternalLink className="w-5 h-5 mr-2" />
|
||||
Open Explorer
|
||||
</a>
|
||||
<a
|
||||
href="https://docs.pezkuwichain.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center px-6 py-3 rounded-lg bg-gray-800 text-white font-semibold hover:bg-gray-700 transition-colors border border-gray-700"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
{type !== 'Live' && (
|
||||
<a
|
||||
href="https://faucet.pezkuwichain.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center px-6 py-3 rounded-lg bg-cyan-600 text-white font-semibold hover:bg-cyan-700 transition-colors"
|
||||
>
|
||||
Get Test Tokens
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkPage;
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import NetworkPage from './NetworkPage';
|
||||
|
||||
const Staging: React.FC = () => {
|
||||
return (
|
||||
<NetworkPage
|
||||
id="staging"
|
||||
name="Staging Network"
|
||||
type="Development"
|
||||
description="Pre-production environment for final testing before mainnet deployment"
|
||||
endpoint="wss://staging.pezkuwichain.io"
|
||||
chainId="pezkuwichain-staging"
|
||||
validators={14}
|
||||
features={[
|
||||
'Pre-Production Testing',
|
||||
'Runtime Upgrades',
|
||||
'Feature Validation',
|
||||
'Security Audits',
|
||||
'Stress Testing',
|
||||
'Migration Testing'
|
||||
]}
|
||||
color="from-purple-600 to-purple-800"
|
||||
status="coming-soon"
|
||||
blockTime="~6s"
|
||||
finality="~12s"
|
||||
consensus="TNPoS"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Staging;
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import NetworkPage from './NetworkPage';
|
||||
|
||||
const Testnet: React.FC = () => {
|
||||
return (
|
||||
<NetworkPage
|
||||
id="testnet"
|
||||
name="Public Testnet"
|
||||
type="Development"
|
||||
description="Public testing network for community and developers to test applications"
|
||||
endpoint="wss://testnet.pezkuwichain.io"
|
||||
chainId="pezkuwichain-testnet"
|
||||
validators={10}
|
||||
features={[
|
||||
'Free Test Tokens',
|
||||
'Public Access',
|
||||
'Full Feature Set',
|
||||
'Community Testing',
|
||||
'dApp Development',
|
||||
'No Real Value'
|
||||
]}
|
||||
color="from-cyan-600 to-cyan-800"
|
||||
status="coming-soon"
|
||||
blockTime="~6s"
|
||||
finality="~12s"
|
||||
consensus="TNPoS"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Testnet;
|
||||
Reference in New Issue
Block a user