Reorganize repository into monorepo structure

Restructured the project to support multiple frontend applications:
- Move web app to web/ directory
- Create pezkuwi-sdk-ui/ for Polkadot SDK clone (planned)
- Create mobile/ directory for mobile app development
- Add shared/ directory with common utilities, types, and blockchain code
- Update README.md with comprehensive documentation
- Remove obsolete DKSweb/ directory

This monorepo structure enables better code sharing and organized
development across web, mobile, and SDK UI projects.
This commit is contained in:
Claude
2025-11-14 00:46:35 +00:00
parent bb3d9aeb29
commit c48ded7ff2
206 changed files with 502 additions and 4 deletions
@@ -0,0 +1,176 @@
import React, { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import { Shield, Users, Key, CheckCircle, XCircle, Clock, Send } from 'lucide-react';
interface MultiSigTransaction {
id: string;
to: string;
amount: number;
token: string;
description: string;
requiredSignatures: number;
currentSignatures: number;
status: 'pending' | 'executed' | 'rejected';
signers: string[];
}
export const MultiSigWallet: React.FC = () => {
const [amount, setAmount] = useState('');
const [recipient, setRecipient] = useState('');
const [description, setDescription] = useState('');
const transactions: MultiSigTransaction[] = [
{
id: '1',
to: '0x742d...29Bb',
amount: 5000,
token: 'HEZ',
description: 'Development fund payment',
requiredSignatures: 3,
currentSignatures: 2,
status: 'pending',
signers: ['Alice', 'Bob']
},
{
id: '2',
to: '0x891a...45Cc',
amount: 10000,
token: 'PEZ',
description: 'Marketing campaign',
requiredSignatures: 3,
currentSignatures: 3,
status: 'executed',
signers: ['Alice', 'Bob', 'Charlie']
}
];
const handleCreateTransaction = () => {
console.log('Creating multi-sig transaction:', { amount, recipient, description });
};
const handleSign = (txId: string) => {
console.log('Signing transaction:', txId);
};
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="bg-gray-900 border-gray-800">
<CardHeader className="pb-3">
<CardTitle className="text-sm text-gray-400">Wallet Balance</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">50,000 HEZ</div>
<p className="text-xs text-gray-500 mt-1">25,000 PEZ</p>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-800">
<CardHeader className="pb-3">
<CardTitle className="text-sm text-gray-400">Required Signatures</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">3 of 5</div>
<p className="text-xs text-gray-500 mt-1">Signers required</p>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-800">
<CardHeader className="pb-3">
<CardTitle className="text-sm text-gray-400">Pending Transactions</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-yellow-500">2</div>
<p className="text-xs text-gray-500 mt-1">Awaiting signatures</p>
</CardContent>
</Card>
</div>
<Card className="bg-gray-900 border-gray-800">
<CardHeader>
<CardTitle>Create Transaction</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Recipient Address</Label>
<Input
placeholder="0x..."
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
className="bg-gray-800 border-gray-700"
/>
</div>
<div>
<Label>Amount</Label>
<Input
type="number"
placeholder="0"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="bg-gray-800 border-gray-700"
/>
</div>
</div>
<div>
<Label>Description</Label>
<Input
placeholder="Transaction purpose"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="bg-gray-800 border-gray-700"
/>
</div>
<Button onClick={handleCreateTransaction} className="bg-green-600 hover:bg-green-700">
<Send className="w-4 h-4 mr-2" />
Create Transaction
</Button>
</CardContent>
</Card>
<Card className="bg-gray-900 border-gray-800">
<CardHeader>
<CardTitle>Pending Transactions</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{transactions.map((tx) => (
<Card key={tx.id} className="bg-gray-800 border-gray-700">
<CardContent className="p-4">
<div className="flex justify-between items-start">
<div className="space-y-2">
<div className="flex items-center gap-2">
<span className="font-semibold text-white">{tx.description}</span>
<Badge variant={tx.status === 'executed' ? 'default' : tx.status === 'pending' ? 'secondary' : 'destructive'}>
{tx.status}
</Badge>
</div>
<div className="text-sm text-gray-400">
To: {tx.to} | Amount: {tx.amount} {tx.token}
</div>
<div className="flex items-center gap-4">
<Progress value={(tx.currentSignatures / tx.requiredSignatures) * 100} className="w-32" />
<span className="text-sm text-gray-400">
{tx.currentSignatures}/{tx.requiredSignatures} signatures
</span>
</div>
</div>
{tx.status === 'pending' && (
<Button size="sm" onClick={() => handleSign(tx.id)} className="bg-blue-600 hover:bg-blue-700">
<Key className="w-4 h-4 mr-1" />
Sign
</Button>
)}
</div>
</CardContent>
</Card>
))}
</CardContent>
</Card>
</div>
);
};
@@ -0,0 +1,197 @@
import React, { useState } from 'react';
import { Send, Loader2, CheckCircle, XCircle } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { useWallet } from '@/contexts/WalletContext';
import { Alert, AlertDescription } from '@/components/ui/alert';
interface TransactionModalProps {
isOpen: boolean;
onClose: () => void;
type: 'send' | 'vote' | 'delegate';
data?: any;
}
export const TransactionModal: React.FC<TransactionModalProps> = ({
isOpen,
onClose,
type,
data
}) => {
const { address, signTransaction, signMessage } = useWallet();
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const [message, setMessage] = useState('');
const [loading, setLoading] = useState(false);
const [txHash, setTxHash] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const handleSendTransaction = async () => {
if (!recipient || !amount) {
setError('Please fill in all fields');
return;
}
setLoading(true);
setError(null);
try {
const tx = {
to: recipient,
value: '0x' + (parseFloat(amount) * 1e18).toString(16),
data: '0x',
};
const hash = await signTransaction(tx);
setTxHash(hash);
} catch (err: any) {
setError(err.message || 'Transaction failed');
} finally {
setLoading(false);
}
};
const handleSignMessage = async () => {
if (!message) {
setError('Please enter a message to sign');
return;
}
setLoading(true);
setError(null);
try {
const signature = await signMessage(message);
setTxHash(signature);
} catch (err: any) {
setError(err.message || 'Failed to sign message');
} finally {
setLoading(false);
}
};
const resetForm = () => {
setRecipient('');
setAmount('');
setMessage('');
setTxHash(null);
setError(null);
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={resetForm}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Send className="h-5 w-5 text-kesk" />
{type === 'send' ? 'Send PZK' : type === 'vote' ? 'Cast Vote' : 'Delegate Voting Power'}
</DialogTitle>
<DialogDescription>
{type === 'send'
? 'Send PZK tokens to another address'
: type === 'vote'
? 'Submit your vote for the proposal'
: 'Delegate your voting power to another address'}
</DialogDescription>
</DialogHeader>
{!txHash ? (
<div className="space-y-4">
{type === 'send' && (
<>
<div>
<Label htmlFor="recipient">Recipient Address</Label>
<Input
id="recipient"
placeholder="0x..."
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
className="font-mono"
/>
</div>
<div>
<Label htmlFor="amount">Amount (PZK)</Label>
<Input
id="amount"
type="number"
placeholder="0.0"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
</>
)}
{type === 'vote' && (
<div>
<Label htmlFor="message">Vote Message</Label>
<Textarea
id="message"
placeholder="Enter your vote reason (optional)"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
</div>
)}
{error && (
<Alert variant="destructive">
<XCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="flex gap-2">
<Button
onClick={type === 'send' ? handleSendTransaction : handleSignMessage}
disabled={loading}
className="flex-1 bg-kesk hover:bg-kesk/90"
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Processing...
</>
) : (
<>
<Send className="mr-2 h-4 w-4" />
{type === 'send' ? 'Send Transaction' : 'Sign & Submit'}
</>
)}
</Button>
<Button variant="outline" onClick={resetForm} disabled={loading}>
Cancel
</Button>
</div>
</div>
) : (
<div className="space-y-4">
<Alert className="border-kesk/20">
<CheckCircle className="h-4 w-4 text-kesk" />
<AlertDescription>
Transaction submitted successfully!
</AlertDescription>
</Alert>
<div className="p-3 bg-muted rounded-lg">
<div className="text-sm text-muted-foreground">Transaction Hash</div>
<div className="font-mono text-xs break-all">{txHash}</div>
</div>
<Button onClick={resetForm} className="w-full">
Close
</Button>
</div>
)}
</DialogContent>
</Dialog>
);
};
+101
View File
@@ -0,0 +1,101 @@
import React from 'react';
import { Wallet, LogOut, AlertCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useWallet } from '@/contexts/WalletContext';
import { formatAddress, formatBalance } from '@/lib/wallet';
import { Badge } from '@/components/ui/badge';
export const WalletButton: React.FC = () => {
const {
isConnected,
address,
balance,
chainId,
error,
connectMetaMask,
disconnect,
switchNetwork
} = useWallet();
if (!isConnected) {
return (
<div className="flex items-center gap-2">
{error && (
<div className="flex items-center gap-2 text-sor text-sm">
<AlertCircle className="h-4 w-4" />
<span>{error}</span>
</div>
)}
<Button
onClick={connectMetaMask}
className="bg-kesk hover:bg-kesk/90 text-white"
>
<Wallet className="mr-2 h-4 w-4" />
Connect Wallet
</Button>
</div>
);
}
const isCorrectNetwork = chainId === '0x2329';
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="border-kesk/20 hover:border-kesk">
<div className="flex items-center gap-2">
<Wallet className="h-4 w-4 text-kesk" />
<div className="text-left">
<div className="text-sm font-medium">{formatAddress(address!)}</div>
<div className="text-xs text-muted-foreground">{formatBalance(balance)} PZK</div>
</div>
{!isCorrectNetwork && (
<Badge variant="destructive" className="ml-2 bg-sor">
Wrong Network
</Badge>
)}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>Wallet Details</DropdownMenuLabel>
<DropdownMenuSeparator />
<div className="px-2 py-1.5">
<div className="text-sm text-muted-foreground">Address</div>
<div className="text-sm font-mono">{formatAddress(address!)}</div>
</div>
<div className="px-2 py-1.5">
<div className="text-sm text-muted-foreground">Balance</div>
<div className="text-sm font-medium">{formatBalance(balance)} PZK</div>
</div>
<div className="px-2 py-1.5">
<div className="text-sm text-muted-foreground">Network</div>
<div className="text-sm font-medium">
{isCorrectNetwork ? 'PezkuwiChain' : 'Unknown Network'}
</div>
</div>
<DropdownMenuSeparator />
{!isCorrectNetwork && (
<>
<DropdownMenuItem onClick={switchNetwork} className="text-zer">
Switch to PezkuwiChain
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem onClick={disconnect} className="text-sor">
<LogOut className="mr-2 h-4 w-4" />
Disconnect
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};
+330
View File
@@ -0,0 +1,330 @@
import React, { useState, useEffect } from 'react';
import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut, Award, Users, TrendingUp, Shield } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { usePolkadot } from '@/contexts/PolkadotContext';
import { formatAddress } from '@/lib/wallet';
import { getAllScores, type UserScores } from '@/lib/scores';
interface WalletModalProps {
isOpen: boolean;
onClose: () => void;
}
export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) => {
const {
accounts,
selectedAccount,
setSelectedAccount,
connectWallet,
disconnectWallet,
api,
isApiReady,
error
} = usePolkadot();
const [copied, setCopied] = useState(false);
const [scores, setScores] = useState<UserScores>({
trustScore: 0,
referralScore: 0,
stakingScore: 0,
tikiScore: 0,
totalScore: 0
});
const [loadingScores, setLoadingScores] = useState(false);
const handleCopyAddress = () => {
if (selectedAccount?.address) {
navigator.clipboard.writeText(selectedAccount.address);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
const handleConnect = async () => {
await connectWallet();
};
const handleSelectAccount = (account: typeof accounts[0]) => {
setSelectedAccount(account);
onClose();
};
const handleDisconnect = () => {
disconnectWallet();
onClose();
};
// Fetch all scores from blockchain
useEffect(() => {
const fetchAllScores = async () => {
if (!api || !isApiReady || !selectedAccount?.address) {
setScores({
trustScore: 0,
referralScore: 0,
stakingScore: 0,
tikiScore: 0,
totalScore: 0
});
return;
}
setLoadingScores(true);
try {
const userScores = await getAllScores(api, selectedAccount.address);
setScores(userScores);
} catch (err) {
console.error('Failed to fetch scores:', err);
setScores({
trustScore: 0,
referralScore: 0,
stakingScore: 0,
tikiScore: 0,
totalScore: 0
});
} finally {
setLoadingScores(false);
}
};
fetchAllScores();
}, [api, isApiReady, selectedAccount]);
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Wallet className="h-5 w-5 text-purple-400" />
{selectedAccount ? 'Wallet Connected' : 'Connect Wallet'}
</DialogTitle>
<DialogDescription>
{selectedAccount
? 'Manage your Polkadot account'
: 'Connect your Polkadot.js extension to interact with PezkuwiChain'}
</DialogDescription>
</DialogHeader>
{/* No Extension Error */}
{error && error.includes('extension') && (
<div className="space-y-4">
<div className="p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<p className="text-sm text-yellow-300">
Polkadot.js extension not detected. Please install it to continue.
</p>
</div>
<div className="flex gap-3">
<a
href="https://polkadot.js.org/extension/"
target="_blank"
rel="noopener noreferrer"
className="flex-1"
>
<Button className="w-full bg-orange-600 hover:bg-orange-700">
<Chrome className="mr-2 h-4 w-4" />
Install Extension
</Button>
</a>
</div>
<p className="text-xs text-gray-400 text-center">
After installing, refresh the page and try again
</p>
</div>
)}
{/* Connected State */}
{selectedAccount && !error && (
<div className="space-y-4">
{/* Account Info */}
<div className="bg-gray-800/50 rounded-lg p-4 space-y-3">
<div>
<div className="text-xs text-gray-400 mb-1">Account Name</div>
<div className="font-medium">{selectedAccount.meta.name || 'Unnamed Account'}</div>
</div>
<div>
<div className="text-xs text-gray-400 mb-1">Address</div>
<div className="flex items-center justify-between gap-2">
<code className="text-sm font-mono text-gray-300 truncate">
{formatAddress(selectedAccount.address)}
</code>
<Button
size="icon"
variant="ghost"
onClick={handleCopyAddress}
className="shrink-0"
>
{copied ? (
<Check className="h-4 w-4 text-green-400" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
</div>
<div>
<div className="text-xs text-gray-400 mb-2">Scores from Blockchain</div>
{loadingScores ? (
<div className="text-sm text-gray-400">Loading scores...</div>
) : (
<div className="grid grid-cols-2 gap-3">
<div className="bg-gray-900/50 rounded p-2">
<div className="flex items-center gap-1 mb-1">
<Shield className="h-3 w-3 text-purple-400" />
<span className="text-xs text-gray-400">Trust</span>
</div>
<span className="text-sm font-bold text-purple-400">{scores.trustScore}</span>
</div>
<div className="bg-gray-900/50 rounded p-2">
<div className="flex items-center gap-1 mb-1">
<Users className="h-3 w-3 text-cyan-400" />
<span className="text-xs text-gray-400">Referral</span>
</div>
<span className="text-sm font-bold text-cyan-400">{scores.referralScore}</span>
</div>
<div className="bg-gray-900/50 rounded p-2">
<div className="flex items-center gap-1 mb-1">
<TrendingUp className="h-3 w-3 text-green-400" />
<span className="text-xs text-gray-400">Staking</span>
</div>
<span className="text-sm font-bold text-green-400">{scores.stakingScore}</span>
</div>
<div className="bg-gray-900/50 rounded p-2">
<div className="flex items-center gap-1 mb-1">
<Award className="h-3 w-3 text-pink-400" />
<span className="text-xs text-gray-400">Tiki</span>
</div>
<span className="text-sm font-bold text-pink-400">{scores.tikiScore}</span>
</div>
</div>
)}
<div className="mt-2 pt-2 border-t border-gray-700">
<div className="flex items-center justify-between">
<span className="text-xs text-gray-400">Total Score</span>
<span className="text-lg font-bold bg-gradient-to-r from-purple-400 to-cyan-400 bg-clip-text text-transparent">
{loadingScores ? '...' : scores.totalScore}
</span>
</div>
</div>
</div>
<div>
<div className="text-xs text-gray-400 mb-1">Source</div>
<div className="text-sm text-gray-300">
{selectedAccount.meta.source || 'polkadot-js'}
</div>
</div>
</div>
{/* Actions */}
<div className="flex gap-2">
<Button
variant="outline"
className="flex-1"
onClick={() => window.open(`https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9944#/explorer`, '_blank')}
>
<ExternalLink className="mr-2 h-4 w-4" />
View on Explorer
</Button>
<Button
variant="outline"
onClick={handleDisconnect}
className="text-red-400 border-red-400/30 hover:bg-red-400/10"
>
<LogOut className="mr-2 h-4 w-4" />
Disconnect
</Button>
</div>
{/* Switch Account */}
{accounts.length > 1 && (
<div className="space-y-2">
<div className="text-sm text-gray-400">Switch Account</div>
<div className="space-y-2 max-h-48 overflow-y-auto">
{accounts.map((account) => (
<button
key={account.address}
onClick={() => handleSelectAccount(account)}
className={`w-full p-3 rounded-lg border transition-all text-left ${
account.address === selectedAccount.address
? 'bg-purple-500/20 border-purple-500/50'
: 'bg-gray-800/30 border-gray-700 hover:border-gray-600'
}`}
>
<div className="font-medium text-sm">
{account.meta.name || 'Unnamed'}
</div>
<div className="text-xs text-gray-400 font-mono">
{formatAddress(account.address)}
</div>
</button>
))}
</div>
</div>
)}
</div>
)}
{/* Not Connected State */}
{!selectedAccount && !error && (
<div className="space-y-4">
{accounts.length > 0 ? (
// Has accounts, show selection
<div className="space-y-2">
<div className="text-sm text-gray-400">Select an account to connect:</div>
<div className="space-y-2">
{accounts.map((account) => (
<button
key={account.address}
onClick={() => handleSelectAccount(account)}
className="w-full p-4 rounded-lg border border-gray-700 bg-gray-800/50 hover:border-purple-500/50 hover:bg-gray-800 transition-all text-left"
>
<div className="font-medium mb-1">
{account.meta.name || 'Unnamed Account'}
</div>
<div className="text-sm text-gray-400 font-mono">
{account.address}
</div>
</button>
))}
</div>
</div>
) : (
// No accounts, show connect button
<div className="space-y-4">
<Button
onClick={handleConnect}
className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500"
>
<Wallet className="mr-2 h-4 w-4" />
Connect Polkadot.js
</Button>
<div className="text-sm text-gray-400 text-center">
Don't have Polkadot.js?{' '}
<a
href="https://polkadot.js.org/extension/"
target="_blank"
rel="noopener noreferrer"
className="text-purple-400 hover:underline"
>
Download here
</a>
</div>
</div>
)}
</div>
)}
</DialogContent>
</Dialog>
);
};