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
+306
View File
@@ -0,0 +1,306 @@
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 { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { X, Clock, CheckCircle, AlertCircle } from 'lucide-react';
interface LimitOrder {
id: string;
type: 'buy' | 'sell';
fromToken: string;
toToken: string;
fromAmount: number;
limitPrice: number;
currentPrice: number;
status: 'pending' | 'filled' | 'cancelled' | 'expired';
createdAt: number;
expiresAt: number;
}
interface LimitOrdersProps {
fromToken: string;
toToken: string;
currentPrice: number;
onCreateOrder?: (order: Omit<LimitOrder, 'id' | 'status' | 'createdAt' | 'expiresAt'>) => void;
}
export const LimitOrders: React.FC<LimitOrdersProps> = ({
fromToken,
toToken,
currentPrice,
onCreateOrder
}) => {
const [orderType, setOrderType] = useState<'buy' | 'sell'>('buy');
const [amount, setAmount] = useState('');
const [limitPrice, setLimitPrice] = useState('');
const [showCreateForm, setShowCreateForm] = useState(false);
// Mock orders (in production, fetch from blockchain)
const [orders, setOrders] = useState<LimitOrder[]>([
{
id: '1',
type: 'buy',
fromToken: 'PEZ',
toToken: 'HEZ',
fromAmount: 100,
limitPrice: 0.98,
currentPrice: 1.02,
status: 'pending',
createdAt: Date.now() - 3600000,
expiresAt: Date.now() + 82800000
},
{
id: '2',
type: 'sell',
fromToken: 'HEZ',
toToken: 'PEZ',
fromAmount: 50,
limitPrice: 1.05,
currentPrice: 1.02,
status: 'pending',
createdAt: Date.now() - 7200000,
expiresAt: Date.now() + 79200000
}
]);
const handleCreateOrder = () => {
const newOrder: Omit<LimitOrder, 'id' | 'status' | 'createdAt' | 'expiresAt'> = {
type: orderType,
fromToken: orderType === 'buy' ? toToken : fromToken,
toToken: orderType === 'buy' ? fromToken : toToken,
fromAmount: parseFloat(amount),
limitPrice: parseFloat(limitPrice),
currentPrice
};
console.log('Creating limit order:', newOrder);
// Add to orders list (mock)
const order: LimitOrder = {
...newOrder,
id: Date.now().toString(),
status: 'pending',
createdAt: Date.now(),
expiresAt: Date.now() + 86400000 // 24 hours
};
setOrders([order, ...orders]);
setShowCreateForm(false);
setAmount('');
setLimitPrice('');
if (onCreateOrder) {
onCreateOrder(newOrder);
}
};
const handleCancelOrder = (orderId: string) => {
setOrders(orders.map(order =>
order.id === orderId ? { ...order, status: 'cancelled' as const } : order
));
};
const getStatusBadge = (status: LimitOrder['status']) => {
switch (status) {
case 'pending':
return <Badge variant="outline" className="bg-yellow-500/10 text-yellow-400 border-yellow-500/30">
<Clock className="w-3 h-3 mr-1" />
Pending
</Badge>;
case 'filled':
return <Badge variant="outline" className="bg-green-500/10 text-green-400 border-green-500/30">
<CheckCircle className="w-3 h-3 mr-1" />
Filled
</Badge>;
case 'cancelled':
return <Badge variant="outline" className="bg-gray-500/10 text-gray-400 border-gray-500/30">
<X className="w-3 h-3 mr-1" />
Cancelled
</Badge>;
case 'expired':
return <Badge variant="outline" className="bg-red-500/10 text-red-400 border-red-500/30">
<AlertCircle className="w-3 h-3 mr-1" />
Expired
</Badge>;
}
};
const getPriceDistance = (order: LimitOrder) => {
const distance = ((order.limitPrice - order.currentPrice) / order.currentPrice) * 100;
return distance;
};
return (
<Card className="bg-gray-900 border-gray-800">
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle>Limit Orders</CardTitle>
<CardDescription>
Set orders to execute at your target price
</CardDescription>
</div>
<Button
onClick={() => setShowCreateForm(!showCreateForm)}
className="bg-blue-600 hover:bg-blue-700"
>
{showCreateForm ? 'Cancel' : '+ New Order'}
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
{showCreateForm && (
<Card className="bg-gray-800 border-gray-700 p-4">
<div className="space-y-4">
<div>
<Label>Order Type</Label>
<Tabs value={orderType} onValueChange={(v) => setOrderType(v as 'buy' | 'sell')}>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="buy">Buy {fromToken}</TabsTrigger>
<TabsTrigger value="sell">Sell {fromToken}</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div>
<Label>Amount ({orderType === 'buy' ? toToken : fromToken})</Label>
<Input
type="number"
placeholder="0.0"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="bg-gray-900 border-gray-700"
/>
</div>
<div>
<Label>Limit Price (1 {fromToken} = ? {toToken})</Label>
<Input
type="number"
placeholder="0.0"
value={limitPrice}
onChange={(e) => setLimitPrice(e.target.value)}
className="bg-gray-900 border-gray-700"
/>
<div className="text-xs text-gray-500 mt-1">
Current market price: ${currentPrice.toFixed(4)}
</div>
</div>
<div className="bg-gray-900 p-3 rounded-lg space-y-1">
<div className="flex justify-between text-sm">
<span className="text-gray-400">You will {orderType}</span>
<span className="text-white font-semibold">
{amount || '0'} {orderType === 'buy' ? fromToken : toToken}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">When price reaches</span>
<span className="text-white font-semibold">
${limitPrice || '0'} per {fromToken}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Estimated total</span>
<span className="text-white font-semibold">
{((parseFloat(amount || '0') * parseFloat(limitPrice || '0'))).toFixed(2)} {orderType === 'buy' ? toToken : fromToken}
</span>
</div>
</div>
<Button
onClick={handleCreateOrder}
disabled={!amount || !limitPrice}
className="w-full bg-green-600 hover:bg-green-700"
>
Create Limit Order
</Button>
<div className="text-xs text-gray-500 text-center">
Order will expire in 24 hours if not filled
</div>
</div>
</Card>
)}
{/* Orders List */}
<div className="space-y-3">
{orders.length === 0 ? (
<div className="text-center py-8 text-gray-400">
No limit orders yet. Create one to get started!
</div>
) : (
orders.map(order => {
const priceDistance = getPriceDistance(order);
return (
<Card key={order.id} className="bg-gray-800 border-gray-700 p-4">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<Badge variant={order.type === 'buy' ? 'default' : 'secondary'}>
{order.type.toUpperCase()}
</Badge>
<span className="font-semibold text-white">
{order.fromToken} {order.toToken}
</span>
</div>
{getStatusBadge(order.status)}
</div>
<div className="grid grid-cols-2 gap-4 text-sm mb-3">
<div>
<div className="text-gray-400">Amount</div>
<div className="text-white font-semibold">
{order.fromAmount} {order.fromToken}
</div>
</div>
<div>
<div className="text-gray-400">Limit Price</div>
<div className="text-white font-semibold">
${order.limitPrice.toFixed(4)}
</div>
</div>
<div>
<div className="text-gray-400">Current Price</div>
<div className="text-white">
${order.currentPrice.toFixed(4)}
</div>
</div>
<div>
<div className="text-gray-400">Distance</div>
<div className={priceDistance > 0 ? 'text-green-400' : 'text-red-400'}>
{priceDistance > 0 ? '+' : ''}{priceDistance.toFixed(2)}%
</div>
</div>
</div>
<div className="flex items-center justify-between text-xs text-gray-500">
<span>
Created {new Date(order.createdAt).toLocaleString()}
</span>
{order.status === 'pending' && (
<Button
variant="ghost"
size="sm"
onClick={() => handleCancelOrder(order.id)}
className="h-7 text-red-400 hover:text-red-300 hover:bg-red-500/10"
>
Cancel
</Button>
)}
</div>
</Card>
);
})
)}
</div>
<div className="text-xs text-gray-500 text-center pt-2">
Note: Limit orders require blockchain integration to execute automatically
</div>
</CardContent>
</Card>
);
};
+167
View File
@@ -0,0 +1,167 @@
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, Area, AreaChart } from 'recharts';
import { Card } from '@/components/ui/card';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { TrendingUp, TrendingDown } from 'lucide-react';
interface PriceChartProps {
fromToken: string;
toToken: string;
currentPrice: number;
}
// Helper: Convert backend token symbols to user-facing display names
const getDisplayName = (token: string): string => {
if (token === 'wUSDT') return 'USDT';
if (token === 'wHEZ') return 'HEZ';
return token; // HEZ, PEZ, etc. remain the same
};
export const PriceChart: React.FC<PriceChartProps> = ({ fromToken, toToken, currentPrice }) => {
const [timeframe, setTimeframe] = useState<'1H' | '24H' | '7D' | '30D'>('24H');
const [chartData, setChartData] = useState<any[]>([]);
const [priceChange, setPriceChange] = useState<{ value: number; percent: number }>({ value: 0, percent: 0 });
useEffect(() => {
// Generate mock historical data (in production, fetch from blockchain/oracle)
const generateMockData = () => {
const dataPoints = timeframe === '1H' ? 60 : timeframe === '24H' ? 24 : timeframe === '7D' ? 7 : 30;
const basePrice = currentPrice || 1.0;
const data = [];
let price = basePrice * 0.95; // Start 5% below current
for (let i = 0; i < dataPoints; i++) {
// Random walk with slight upward trend
const change = (Math.random() - 0.48) * 0.02; // Slight bullish bias
price = price * (1 + change);
let timeLabel = '';
const now = new Date();
if (timeframe === '1H') {
now.setMinutes(now.getMinutes() - (dataPoints - i));
timeLabel = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
} else if (timeframe === '24H') {
now.setHours(now.getHours() - (dataPoints - i));
timeLabel = now.toLocaleTimeString('en-US', { hour: '2-digit' });
} else if (timeframe === '7D') {
now.setDate(now.getDate() - (dataPoints - i));
timeLabel = now.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
} else {
now.setDate(now.getDate() - (dataPoints - i));
timeLabel = now.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
data.push({
time: timeLabel,
price: parseFloat(price.toFixed(4)),
timestamp: now.getTime()
});
}
// Add current price as last point
data.push({
time: 'Now',
price: basePrice,
timestamp: Date.now()
});
return data;
};
const data = generateMockData();
setChartData(data);
// Calculate price change
if (data.length > 1) {
const firstPrice = data[0].price;
const lastPrice = data[data.length - 1].price;
const change = lastPrice - firstPrice;
const changePercent = (change / firstPrice) * 100;
setPriceChange({ value: change, percent: changePercent });
}
}, [timeframe, currentPrice]);
const isPositive = priceChange.percent >= 0;
return (
<Card className="p-4 bg-gray-900 border-gray-800">
<div className="flex justify-between items-center mb-4">
<div>
<div className="text-sm text-gray-400 mb-1">
{getDisplayName(fromToken)}/{getDisplayName(toToken)} Price
</div>
<div className="flex items-center gap-3">
<span className="text-2xl font-bold text-white">
${currentPrice.toFixed(4)}
</span>
<div className={`flex items-center gap-1 text-sm font-semibold ${
isPositive ? 'text-green-400' : 'text-red-400'
}`}>
{isPositive ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
{isPositive ? '+' : ''}{priceChange.percent.toFixed(2)}%
</div>
</div>
</div>
<Tabs value={timeframe} onValueChange={(v) => setTimeframe(v as any)}>
<TabsList className="bg-gray-800">
<TabsTrigger value="1H" className="text-xs">1H</TabsTrigger>
<TabsTrigger value="24H" className="text-xs">24H</TabsTrigger>
<TabsTrigger value="7D" className="text-xs">7D</TabsTrigger>
<TabsTrigger value="30D" className="text-xs">30D</TabsTrigger>
</TabsList>
</Tabs>
</div>
<ResponsiveContainer width="100%" height={200}>
<AreaChart data={chartData}>
<defs>
<linearGradient id={`gradient-${isPositive ? 'green' : 'red'}`} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={isPositive ? '#10b981' : '#ef4444'} stopOpacity={0.3} />
<stop offset="100%" stopColor={isPositive ? '#10b981' : '#ef4444'} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis
dataKey="time"
stroke="#6b7280"
fontSize={10}
tickLine={false}
axisLine={false}
/>
<YAxis
stroke="#6b7280"
fontSize={10}
tickLine={false}
axisLine={false}
domain={['auto', 'auto']}
tickFormatter={(value) => `$${value.toFixed(3)}`}
/>
<Tooltip
contentStyle={{
backgroundColor: '#1f2937',
border: '1px solid #374151',
borderRadius: '8px',
padding: '8px'
}}
labelStyle={{ color: '#9ca3af' }}
itemStyle={{ color: '#fff' }}
formatter={(value: any) => [`$${value.toFixed(4)}`, 'Price']}
/>
<Area
type="monotone"
dataKey="price"
stroke={isPositive ? '#10b981' : '#ef4444'}
strokeWidth={2}
fill={`url(#gradient-${isPositive ? 'green' : 'red'})`}
/>
</AreaChart>
</ResponsiveContainer>
<div className="mt-3 text-xs text-gray-500 text-center">
Historical price data Updated in real-time
</div>
</Card>
);
};