Transform the current prototype market details page into a production-ready, contract-integrated component that serves as the cornerstone of the PakoMarket prediction market platform. This plan combines architectural excellence, Web3 integration, and user experience optimization into a systematic 4-week implementation roadmap.
- Contract-Ready Trading: Full integration with smart contracts and USDC
- Production Architecture: Scalable, maintainable, and testable codebase
- Exceptional UX: Intuitive, accessible, and performant user experience
- Security-First: Robust validation, error handling, and safety measures
- Zero precision errors in financial calculations
- Sub-2s page load times with proper caching
- 100% accessibility compliance (WCAG 2.1 AA)
- Full trading flow from approval to settlement
- Start with Mock Data: Get UI working before contract integration
- Incremental Testing: Test each component as you build it
- Fallback Strategies: Graceful degradation when Web3 features fail
- Phase 1: Mock data + UI components (Week 1)
- Phase 2: Contract integration with fallbacks (Week 2)
- Phase 3: Real-time features + optimization (Week 3)
- Phase 4: Testing + production polish (Week 4)
- Web3 Unavailable: Show "Connect Wallet" prompts with mock data
- Contract Errors: Display user-friendly error messages with retry options
- Network Issues: Graceful degradation to read-only mode
- Transaction Failures: Clear error messages with suggested actions
apps/web/src/app/market/[id]/
├── page.tsx # Server component with data fetching
├── loading.tsx # Loading UI (Next.js convention)
├── error.tsx # Error boundary (Next.js convention)
├── not-found.tsx # 404 page (Next.js convention)
├── components/
│ ├── MarketHeader.tsx # Title, volume, close time, status
│ ├── MarketBreadcrumb.tsx # Navigation breadcrumb
│ ├── ProbabilityChart.tsx # Historical price chart
│ ├── OutcomeSelector.tsx # Yes/No outcome selection
│ ├── TradePanel.tsx # Trading interface
│ ├── RelatedMarkets.tsx # Suggested markets
│ ├── MarketDetails.tsx # Market description, rules
│ └── skeletons/
│ ├── MarketHeaderSkeleton.tsx
│ ├── ChartSkeleton.tsx
│ └── TradePanelSkeleton.tsx
├── hooks/
│ ├── useMarketData.ts # Market data fetching & caching
│ ├── useTrading.ts # Trading state management
│ ├── useUSDCAllowance.ts # USDC approval flow
│ ├── usePriceQuotes.ts # Real-time price quotes
│ └── useMarketValidation.ts # Market ID & state validation
├── utils/
│ ├── formatting.ts # Number, currency, date formatters
│ ├── validation.ts # Zod schemas for all inputs
│ ├── calculations.ts # Financial math with bigint
│ └── constants.ts # Trading constants & limits
└── types/
├── market.ts # Market data interfaces
├── trading.ts # Trading-related types
└── api.ts # API response types// types/market.ts
export interface Market {
id: string;
title: string;
description: string;
endDate: Date;
volume: bigint; // USDC volume in wei (6 decimals)
liquidity: bigint; // Total liquidity
status: MarketStatus;
outcomes: Outcome[];
metadata: {
creator: Address;
createdAt: Date;
category: string;
tags: string[];
ipfsHash?: string;
};
}
export interface Outcome {
id: string;
name: string;
probability: number; // 0-1 float for display only
price: bigint; // Actual price in wei
volume: bigint; // Volume in USDC wei
shares: bigint; // Total shares outstanding
}
export type MarketStatus = 'Open' | 'Closed' | 'Paused' | 'Resolved' | 'Invalid';// app/market/[id]/page.tsx
import { Suspense } from 'react';
import { notFound } from 'next/navigation';
import { MarketHeader } from './components/MarketHeader';
import { MarketContent } from './components/MarketContent';
import { validateMarketId } from './utils/validation';
export default async function MarketPage({
params
}: {
params: { id: string }
}) {
// Server-side validation
const { success, data: marketId } = validateMarketId(params.id);
if (!success) notFound();
// Server-side data fetching (eventually from API)
const market = await getMarketData(marketId);
if (!market) notFound();
return (
<div className="min-h-screen bg-gray-50 dark:bg-[#0b1220]">
<MarketHeader market={market} />
<Suspense fallback={<MarketContentSkeleton />}>
<MarketContent marketId={marketId} initialData={market} />
</Suspense>
</div>
);
}// components/MarketBreadcrumb.tsx
export function MarketBreadcrumb({ market }: { market: Market }) {
return (
<nav className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-4">
<Link
href="/"
className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
>
Markets
</Link>
<ChevronRight className="w-4 h-4" />
<Link
href={`/?category=${market.metadata.category}`}
className="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
>
{market.metadata.category}
</Link>
<ChevronRight className="w-4 h-4" />
<span className="text-gray-900 dark:text-gray-100 font-medium truncate">
{market.title}
</span>
</nav>
);
}// components/MarketStatusBanner.tsx
export function MarketStatusBanner({ market }: { market: Market }) {
const statusConfig = {
Open: {
bg: 'bg-green-50 dark:bg-green-900/20',
text: 'text-green-700 dark:text-green-400',
icon: <TrendingUp className="w-4 h-4" />
},
Closed: {
bg: 'bg-gray-50 dark:bg-gray-800',
text: 'text-gray-700 dark:text-gray-300',
icon: <Clock className="w-4 h-4" />
},
Resolved: {
bg: 'bg-blue-50 dark:bg-blue-900/20',
text: 'text-blue-700 dark:text-blue-400',
icon: <CheckCircle className="w-4 h-4" />
},
// ... other statuses
};
const config = statusConfig[market.status];
const timeLeft = getTimeLeft(market.endDate);
return (
<div className={`${config.bg} border border-opacity-20 rounded-lg p-4 mb-6`}>
<div className="flex items-center justify-between">
<div className={`flex items-center gap-2 ${config.text}`}>
{config.icon}
<span className="font-medium">{market.status}</span>
{market.status === 'Open' && timeLeft && (
<span className="text-sm">• Closes {timeLeft}</span>
)}
</div>
{market.status === 'Resolved' && (
<div className="text-sm">
Resolved: <strong>{getResolutionOutcome(market)}</strong>
</div>
)}
</div>
</div>
);
}// components/skeletons/MarketHeaderSkeleton.tsx
export function MarketHeaderSkeleton() {
return (
<div className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<div className="max-w-7xl mx-auto px-4 py-6">
<div className="animate-pulse">
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/3 mb-4"></div>
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-2/3 mb-4"></div>
<div className="flex gap-6 mb-4">
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-24"></div>
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-32"></div>
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-28"></div>
</div>
</div>
</div>
</div>
);
}## ✅ **PHASE 1 COMPLETION CHECKLIST**
### **File Structure & Architecture**
- [ ] Created `/apps/web/src/app/market/[id]/` directory structure
- [ ] Implemented `page.tsx` as server component
- [ ] Created `loading.tsx`, `error.tsx`, `not-found.tsx` pages
- [ ] Set up component directory with proper organization
- [ ] Created hooks directory for custom hooks
- [ ] Set up utils directory for helper functions
- [ ] Created types directory for TypeScript interfaces
### **Component Extraction**
- [ ] Extracted `MarketHeader.tsx` from monolithic file
- [ ] Extracted `ProbabilityChart.tsx` (with placeholder)
- [ ] Extracted `OutcomeSelector.tsx` for Yes/No selection
- [ ] Extracted `TradePanel.tsx` for trading interface
- [ ] Extracted `RelatedMarkets.tsx` for suggestions
- [ ] Created skeleton components for loading states
- [ ] All components have proper TypeScript interfaces
### **Decimal Precision & Math**
- [ ] Created `FinancialMath` utility class
- [ ] Implemented `toUSDCWei()` and `fromUSDCWei()` functions
- [ ] Added `calculateSharesOut()` with slippage protection
- [ ] Added `calculateUSDCIn()` for reverse calculations
- [ ] Implemented `formatUSDC()` and `formatShares()` helpers
- [ ] All money calculations use `bigint` instead of `number`
- [ ] Added input validation with Zod schemas
### **Mock Data Integration**
- [ ] Created comprehensive mock market data
- [ ] Implemented proper TypeScript interfaces for all data
- [ ] Added mock historical data for charts
- [ ] Created mock related markets data
- [ ] All mock data uses proper `bigint` values
- [ ] Mock data structure matches real API structure
### **Basic UI/UX**
- [ ] Implemented responsive design (mobile-first)
- [ ] Added dark/light mode support throughout
- [ ] Created proper loading states and skeletons
- [ ] Added basic error handling and display
- [ ] Implemented proper navigation breadcrumbs
- [ ] Added market status banners (Open/Closed/Resolved)
- [ ] All components are accessible (basic a11y)
### **Testing Foundation**
- [ ] Set up testing environment (Vitest + React Testing Library)
- [ ] Created unit tests for `FinancialMath` utility
- [ ] Added component tests for basic rendering
- [ ] Implemented mock data testing
- [ ] Added input validation testing
- [ ] All critical paths have test coverage
### **Code Quality**
- [ ] All components under 200 lines
- [ ] Proper TypeScript types throughout
- [ ] No `any` types used
- [ ] Consistent naming conventions
- [ ] Proper error boundaries
- [ ] Clean, readable code structure
### **Performance Basics**
- [ ] Components are properly memoized where needed
- [ ] No unnecessary re-renders
- [ ] Proper key props for lists
- [ ] Images are optimized
- [ ] Bundle size is reasonable (< 200KB for this phase)
### **Documentation**
- [ ] All components have JSDoc comments
- [ ] README updated with new structure
- [ ] TypeScript interfaces are well documented
- [ ] Utility functions have usage examples
- [ ] Component props are documented
### **Phase 1 Success Criteria**
- [ ] Market details page loads with mock data
- [ ] All components render without errors
- [ ] Responsive design works on mobile and desktop
- [ ] Dark/light mode switching works
- [ ] Basic trading interface is functional (UI only)
- [ ] All tests pass
- [ ] No TypeScript errors
- [ ] Performance is acceptable (< 2s load time)// utils/calculations.ts
import { parseUnits, formatUnits } from 'viem';
export const USDC_DECIMALS = 6;
export const SHARE_DECIMALS = 18;
export const PRECISION_MULTIPLIER = 10n ** 18n;
export class FinancialMath {
// Convert display amount to blockchain amount
static toUSDCWei(amount: string | number): bigint {
if (typeof amount === 'number') {
if (!Number.isFinite(amount) || amount < 0) {
throw new Error('Invalid amount');
}
amount = amount.toString();
}
return parseUnits(amount, USDC_DECIMALS);
}
// Convert blockchain amount to display amount
static fromUSDCWei(wei: bigint): string {
return formatUnits(wei, USDC_DECIMALS);
}
// Calculate shares received for USDC amount
static calculateSharesOut(
usdcIn: bigint,
currentPrice: bigint,
slippageTolerance: number = 0.01 // 1%
): { sharesOut: bigint; minSharesOut: bigint } {
const sharesOut = (usdcIn * PRECISION_MULTIPLIER) / currentPrice;
const slippageAmount = (sharesOut * BigInt(Math.floor(slippageTolerance * 10000))) / 10000n;
const minSharesOut = sharesOut - slippageAmount;
return { sharesOut, minSharesOut };
}
// Calculate USDC needed for desired shares
static calculateUSDCIn(
sharesDesired: bigint,
currentPrice: bigint
): bigint {
return (sharesDesired * currentPrice) / PRECISION_MULTIPLIER;
}
// Price impact calculation
static calculatePriceImpact(
usdcAmount: bigint,
liquidity: bigint
): number {
if (liquidity === 0n) return 0;
const impact = Number(usdcAmount * 10000n / liquidity) / 100; // Convert to percentage
return Math.min(impact, 100); // Cap at 100%
}
// Format for display with proper decimal places
static formatUSDC(wei: bigint, decimals: number = 2): string {
const amount = Number(formatUnits(wei, USDC_DECIMALS));
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(amount);
}
static formatShares(shares: bigint, decimals: number = 4): string {
const amount = Number(formatUnits(shares, SHARE_DECIMALS));
return new Intl.NumberFormat('en-US', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(amount);
}
static formatPercentage(value: number, decimals: number = 1): string {
return new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
}).format(value / 100);
}
}// utils/validation.ts
import { z } from 'zod';
import { isAddress } from 'viem';
export const MarketIdSchema = z.string().uuid().or(z.string().min(1));
export const TradeAmountSchema = z.object({
amount: z.string()
.refine((val) => !isNaN(Number(val)) && Number(val) > 0, "Amount must be positive")
.refine((val) => Number(val) >= 0.01, "Minimum amount is $0.01")
.refine((val) => Number(val) <= 10000, "Maximum amount is $10,000")
.transform((val) => FinancialMath.toUSDCWei(val)),
slippage: z.number()
.min(0.001, "Minimum slippage is 0.1%")
.max(0.5, "Maximum slippage is 50%")
.default(0.01),
deadline: z.number()
.min(60, "Minimum deadline is 1 minute")
.max(3600, "Maximum deadline is 1 hour")
.default(1200), // 20 minutes
});
export const AddressSchema = z.string().refine(isAddress, "Invalid Ethereum address");
export function validateMarketId(id: string) {
return MarketIdSchema.safeParse(id);
}
export function validateTradeParams(params: unknown) {
return TradeAmountSchema.safeParse(params);
}// hooks/useUSDCAllowance.ts
import { useAccount, useReadContract, useWriteContract } from 'wagmi';
import { erc20Abi } from 'viem';
import { config } from '@/lib/config';
export function useUSDCAllowance(spender: Address) {
const { address } = useAccount();
// Read current allowance
const { data: allowance, isLoading, refetch } = useReadContract({
address: config.contracts.USDC as Address,
abi: erc20Abi,
functionName: 'allowance',
args: address && spender ? [address, spender] : undefined,
query: { enabled: Boolean(address && spender) }
});
// Write contract for approval
const { writeContract: approve, isPending: isApproving } = useWriteContract();
const approveUSDC = useCallback(async (amount: bigint) => {
if (!address || !spender) throw new Error('No address or spender');
return approve({
address: config.contracts.USDC as Address,
abi: erc20Abi,
functionName: 'approve',
args: [spender, amount],
});
}, [approve, address, spender]);
const needsApproval = useCallback((amount: bigint) => {
return !allowance || allowance < amount;
}, [allowance]);
return {
allowance: allowance || 0n,
approveUSDC,
isApproving,
needsApproval,
refetch,
isLoading,
};
}// hooks/useTrading.ts
import { useState, useCallback } from 'react';
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { toast } from 'sonner';
export type TradeState = 'idle' | 'approving' | 'trading' | 'confirming' | 'success' | 'error';
export function useTrading(marketAddress: Address) {
const { address, chainId } = useAccount();
const [tradeState, setTradeState] = useState<TradeState>('idle');
const [txHash, setTxHash] = useState<Hash>();
const [error, setError] = useState<string>();
const { allowance, approveUSDC, needsApproval } = useUSDCAllowance(marketAddress);
const { writeContract: trade } = useWriteContract();
const { isLoading: isConfirming } = useWaitForTransactionReceipt({
hash: txHash,
onSuccess: () => {
setTradeState('success');
toast.success('Trade completed successfully!');
},
onError: (error) => {
setTradeState('error');
setError(error.message);
toast.error('Trade failed');
}
});
const executeTrade = useCallback(async (params: {
outcome: 'yes' | 'no';
amount: bigint;
minSharesOut: bigint;
deadline: number;
}) => {
try {
setError(undefined);
// Validate chain
if (chainId !== config.local.chainId) {
throw new Error('Please switch to the correct network');
}
// Check approval first
if (needsApproval(params.amount)) {
setTradeState('approving');
toast.info('Approving USDC...');
await approveUSDC(params.amount);
// Wait for approval to be mined before continuing
await new Promise(resolve => setTimeout(resolve, 2000));
}
// Execute trade
setTradeState('trading');
toast.info('Executing trade...');
const hash = await trade({
address: marketAddress,
abi: marketAbi, // Import your market ABI
functionName: 'buyShares',
args: [
params.outcome === 'yes' ? 0 : 1, // outcome index
params.amount,
params.minSharesOut,
BigInt(Math.floor(Date.now() / 1000) + params.deadline)
],
});
setTxHash(hash);
setTradeState('confirming');
toast.info('Confirming transaction...');
} catch (err) {
setTradeState('error');
const errorMessage = err instanceof Error ? err.message : 'Trade failed';
setError(errorMessage);
toast.error(errorMessage);
}
}, [chainId, marketAddress, needsApproval, approveUSDC, trade]);
const reset = useCallback(() => {
setTradeState('idle');
setError(undefined);
setTxHash(undefined);
}, []);
return {
tradeState,
error,
txHash,
isConfirming,
executeTrade,
reset,
canTrade: address && chainId === config.local.chainId,
};
}// hooks/usePriceQuotes.ts
import { useEffect, useState } from 'react';
import { useBlockNumber } from 'wagmi';
export function usePriceQuotes(marketAddress: Address, refreshInterval: number = 30000) {
const [quotes, setQuotes] = useState<{
yes: { price: bigint; priceImpact: number };
no: { price: bigint; priceImpact: number };
}>();
const { data: blockNumber } = useBlockNumber({ watch: true });
const fetchQuotes = useCallback(async (amount: bigint) => {
if (!marketAddress || amount === 0n) return;
try {
// This would call your contract's getQuote function
const [yesQuote, noQuote] = await Promise.all([
getQuote(marketAddress, 'yes', amount),
getQuote(marketAddress, 'no', amount),
]);
setQuotes({
yes: yesQuote,
no: noQuote,
});
} catch (error) {
console.error('Failed to fetch quotes:', error);
}
}, [marketAddress]);
// Refresh quotes on new blocks
useEffect(() => {
if (blockNumber) {
fetchQuotes(parseUnits('100', 6)); // Default quote for $100
}
}, [blockNumber, fetchQuotes]);
return { quotes, fetchQuotes };
}// hooks/useChainValidation.ts
export function useChainValidation() {
const { chainId, switchChain } = useSwitchChain();
const targetChainId = config.local.chainId;
const isCorrectChain = chainId === targetChainId;
const isUnsupported = chainId && !isCorrectChain;
const switchToCorrectChain = useCallback(async () => {
try {
await switchChain({ chainId: targetChainId });
toast.success('Switched to correct network');
} catch (error) {
toast.error('Failed to switch network');
throw error;
}
}, [switchChain, targetChainId]);
return {
isCorrectChain,
isUnsupported,
switchToCorrectChain,
currentChainId: chainId,
targetChainId,
};
}// components/NetworkStatus.tsx
export function NetworkStatus() {
const { isCorrectChain, isUnsupported, switchToCorrectChain } = useChainValidation();
const { isConnected } = useAccount();
if (!isConnected) return null;
if (isUnsupported) {
return (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-lg p-4 mb-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-yellow-700 dark:text-yellow-400">
<AlertTriangle className="w-5 h-5" />
<span className="font-medium">Wrong Network</span>
<span className="text-sm">Please switch to Localhost 8545</span>
</div>
<button
onClick={switchToCorrectChain}
className="px-4 py-2 bg-yellow-600 hover:bg-yellow-700 text-white rounded-lg text-sm font-medium transition-colors"
>
Switch Network
</button>
</div>
</div>
);
}
if (isCorrectChain) {
return (
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-700 rounded-lg p-3 mb-6">
<div className="flex items-center gap-2 text-green-700 dark:text-green-400">
<CheckCircle className="w-4 h-4" />
<span className="text-sm">Connected to Localhost 8545</span>
</div>
</div>
);
}
return null;
}## ✅ **PHASE 2 COMPLETION CHECKLIST**
### **Web3 Integration**
- [ ] Implemented `useUSDCAllowance` hook
- [ ] Created `useTrading` hook with full trading flow
- [ ] Added `usePriceQuotes` hook for real-time quotes
- [ ] Implemented `useChainValidation` hook
- [ ] Added proper error handling for all Web3 operations
- [ ] Created fallback strategies for Web3 failures
- [ ] All hooks have proper TypeScript types
### **Trading Flow Implementation**
- [ ] USDC approval flow works correctly
- [ ] Buy/Sell transactions execute properly
- [ ] Slippage protection is implemented
- [ ] Transaction monitoring and status updates
- [ ] Error handling for failed transactions
- [ ] Success/failure notifications to users
- [ ] Proper loading states during transactions
### **Chain Management**
- [ ] Network validation works correctly
- [ ] Network switching functionality
- [ ] Wrong network warnings and prompts
- [ ] Chain ID validation for all operations
- [ ] Graceful handling of unsupported networks
- [ ] User-friendly network status indicators
### **Real-time Data**
- [ ] Price quotes update in real-time
- [ ] Market data refreshes automatically
- [ ] Proper caching with React Query
- [ ] Optimistic updates for better UX
- [ ] Error handling for data fetch failures
- [ ] Fallback to cached data when needed
### **Contract Integration**
- [ ] All contract calls use proper ABI
- [ ] Gas estimation for transactions
- [ ] Transaction confirmation handling
- [ ] Event listening for real-time updates
- [ ] Proper error parsing from contract calls
- [ ] Fallback strategies for contract failures
### **Security & Validation**
- [ ] All user inputs validated with Zod
- [ ] Decimal precision handled with bigint
- [ ] No client-side private key exposure
- [ ] Proper error handling without data leakage
- [ ] XSS protection on all dynamic content
- [ ] Input sanitization for all user data
### **Testing & Quality**
- [ ] Unit tests for all Web3 hooks
- [ ] Integration tests for trading flow
- [ ] Error handling tests for all failure scenarios
- [ ] Mock Web3 provider for testing
- [ ] Contract interaction tests
- [ ] Performance tests for real-time updates
### **Fallback Strategies**
- [ ] Web3 unavailable: Show connect wallet prompts
- [ ] Contract errors: Display user-friendly messages
- [ ] Network issues: Graceful degradation to read-only
- [ ] Transaction failures: Clear error messages with retry
- [ ] Data fetch failures: Show cached data with refresh option
- [ ] All fallbacks tested and working
### **Phase 2 Success Criteria**
- [ ] Full trading flow works end-to-end
- [ ] USDC approval and trading execute correctly
- [ ] Real-time price updates work
- [ ] Network switching functions properly
- [ ] Error handling is comprehensive
- [ ] All fallback strategies work
- [ ] Performance is maintained (< 2s load time)
- [ ] All tests pass
- [ ] No TypeScript errors// hooks/useMarketData.ts
import { useQuery, useQueryClient } from '@tanstack/react-query';
export function useMarketData(marketId: string) {
return useQuery({
queryKey: ['market', marketId],
queryFn: async () => {
// For now, use mock data but structure for API integration
const market = mockMarkets.find(m => m.id === marketId);
if (!market) throw new Error('Market not found');
// Transform mock data to proper format
return {
...market,
volume: FinancialMath.toUSDCWei(market.poolUsd.toString()),
outcomes: market.outcomes?.map(outcome => ({
...outcome,
price: FinancialMath.toUSDCWei((outcome.price || 0.5).toString()),
volume: FinancialMath.toUSDCWei((outcome.volume || 0).toString()),
shares: parseUnits('1000000', SHARE_DECIMALS), // Mock shares
})) || [],
};
},
staleTime: 1000 * 60 * 5, // 5 minutes
refetchInterval: 1000 * 30, // 30 seconds
retry: 3,
});
}
export function useMarketList() {
return useQuery({
queryKey: ['markets'],
queryFn: async () => {
// Transform all mock markets
return mockMarkets.map(market => ({
...market,
volume: FinancialMath.toUSDCWei(market.poolUsd.toString()),
}));
},
staleTime: 1000 * 60 * 2, // 2 minutes
});
}// components/TradePanel.tsx
export function TradePanel({ market }: { market: Market }) {
const [selectedOutcome, setSelectedOutcome] = useState<'yes' | 'no'>('yes');
const [amount, setAmount] = useState<string>('');
const [slippage, setSlippage] = useState<number>(0.01); // 1%
const [showAdvanced, setShowAdvanced] = useState<boolean>(false);
const { isConnected, address } = useAccount();
const { isCorrectChain, switchToCorrectChain } = useChainValidation();
const { executeTrade, tradeState, error, canTrade } = useTrading(market.address);
const { quotes, fetchQuotes } = usePriceQuotes(market.address);
// Real-time quote updates
const debouncedAmount = useDebounce(amount, 500);
useEffect(() => {
if (debouncedAmount && parseFloat(debouncedAmount) > 0) {
fetchQuotes(FinancialMath.toUSDCWei(debouncedAmount));
}
}, [debouncedAmount, fetchQuotes]);
const handleTrade = useCallback(async () => {
if (!amount || !quotes) return;
try {
const parsedAmount = FinancialMath.toUSDCWei(amount);
const quote = quotes[selectedOutcome];
const { minSharesOut } = FinancialMath.calculateSharesOut(
parsedAmount,
quote.price,
slippage
);
await executeTrade({
outcome: selectedOutcome,
amount: parsedAmount,
minSharesOut,
deadline: 1200, // 20 minutes
});
} catch (err) {
console.error('Trade failed:', err);
}
}, [amount, quotes, selectedOutcome, slippage, executeTrade]);
const isValidAmount = amount && parseFloat(amount) >= 0.01 && parseFloat(amount) <= 10000;
const quote = quotes?.[selectedOutcome];
return (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<div className="flex justify-between items-center mb-6">
<h3 className="text-lg font-semibold">Trade</h3>
<button
onClick={() => setShowAdvanced(!showAdvanced)}
className="text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
>
{showAdvanced ? 'Simple' : 'Advanced'}
</button>
</div>
{/* Network Status */}
{isConnected && !isCorrectChain && (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-lg p-3 mb-4">
<div className="flex items-center justify-between">
<span className="text-yellow-700 dark:text-yellow-400 text-sm">Wrong network</span>
<button
onClick={switchToCorrectChain}
className="text-yellow-700 dark:text-yellow-400 text-sm underline hover:no-underline"
>
Switch
</button>
</div>
</div>
)}
{/* Outcome Selection */}
<div className="grid grid-cols-2 gap-2 mb-6">
{['yes', 'no'].map((outcome) => (
<button
key={outcome}
onClick={() => setSelectedOutcome(outcome as 'yes' | 'no')}
className={`p-3 rounded-lg border-2 transition-all ${
selectedOutcome === outcome
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500'
}`}
>
<div className="text-sm font-medium capitalize">{outcome}</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{FinancialMath.formatUSDC(
market.outcomes.find(o => o.name.toLowerCase() === outcome)?.price || 0n
)}
</div>
</button>
))}
</div>
{/* Amount Input */}
<div className="mb-4">
<label className="block text-sm font-medium mb-2">Amount (USDC)</label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">$</span>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.00"
min="0.01"
max="10000"
step="0.01"
className="w-full pl-8 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-700"
/>
</div>
<div className="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-1">
<span>Min: $0.01</span>
<span>Max: $10,000</span>
</div>
</div>
{/* Quick Amount Buttons */}
<div className="grid grid-cols-4 gap-2 mb-6">
{['10', '50', '100', '500'].map((quickAmount) => (
<button
key={quickAmount}
onClick={() => setAmount(quickAmount)}
className="py-2 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
${quickAmount}
</button>
))}
</div>
{/* Advanced Settings */}
{showAdvanced && (
<div className="mb-6 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<div className="mb-4">
<label className="block text-sm font-medium mb-2">
Slippage Tolerance: {FinancialMath.formatPercentage(slippage * 100)}
</label>
<input
type="range"
min="0.001"
max="0.1"
step="0.001"
value={slippage}
onChange={(e) => setSlippage(parseFloat(e.target.value))}
className="w-full"
/>
<div className="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-1">
<span>0.1%</span>
<span>10%</span>
</div>
</div>
</div>
)}
{/* Trade Preview */}
{isValidAmount && quote && (
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 mb-6">
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span>You pay</span>
<span className="font-medium">${amount} USDC</span>
</div>
<div className="flex justify-between">
<span>You receive</span>
<span className="font-medium">
{FinancialMath.formatShares(
FinancialMath.calculateSharesOut(
FinancialMath.toUSDCWei(amount),
quote.price
).sharesOut
)} shares
</span>
</div>
<div className="flex justify-between">
<span>Price impact</span>
<span className={quote.priceImpact > 5 ? 'text-red-500' : 'text-gray-600 dark:text-gray-400'}>
{FinancialMath.formatPercentage(quote.priceImpact)}
</span>
</div>
</div>
</div>
)}
{/* Trade Button */}
<button
onClick={handleTrade}
disabled={!canTrade || !isValidAmount || tradeState !== 'idle'}
className="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
>
{!isConnected && 'Connect Wallet'}
{isConnected && !isCorrectChain && 'Switch Network'}
{canTrade && tradeState === 'idle' && 'Place Trade'}
{tradeState === 'approving' && 'Approving USDC...'}
{tradeState === 'trading' && 'Trading...'}
{tradeState === 'confirming' && 'Confirming...'}
{tradeState === 'success' && 'Trade Complete!'}
{tradeState === 'error' && 'Try Again'}
</button>
{/* Error Display */}
{error && (
<div className="mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700 rounded-lg">
<p className="text-red-700 dark:text-red-400 text-sm">{error}</p>
</div>
)}
{/* Connect Wallet CTA */}
{!isConnected && (
<div className="mt-4 text-center">
<WalletConnect />
</div>
)}
</div>
);
}// components/ProbabilityChart.tsx
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
export function ProbabilityChart({ market }: { market: Market }) {
const [timeRange, setTimeRange] = useState<'1H' | '6H' | '1D' | '7D' | '1M' | 'ALL'>('1D');
// Mock historical data - replace with real API call
const historicalData = generateMockHistoricalData(market, timeRange);
return (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<div className="flex justify-between items-center mb-6">
<h3 className="text-lg font-semibold">Probability Over Time</h3>
<div className="flex gap-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-1">
{(['1H', '6H', '1D', '7D', '1M', 'ALL'] as const).map((range) => (
<button
key={range}
onClick={() => setTimeRange(range)}
className={`px-3 py-1 text-xs rounded-md transition-colors ${
timeRange === range
? 'bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
>
{range}
</button>
))}
</div>
</div>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={historicalData}>
<CartesianGrid strokeDasharray="3 3" className="opacity-30" />
<XAxis
dataKey="time"
tickFormatter={(time) => new Date(time).toLocaleDateString()}
className="text-xs"
/>
<YAxis
domain={[0, 100]}
tickFormatter={(value) => `${value}%`}
className="text-xs"
/>
<Tooltip
labelFormatter={(time) => new Date(time).toLocaleString()}
formatter={(value: number, name: string) => [
`${value.toFixed(1)}%`,
name === 'yes' ? 'Yes' : 'No'
]}
contentStyle={{
backgroundColor: 'rgb(31 41 55)',
border: 'none',
borderRadius: '8px',
color: 'white'
}}
/>
<Line
type="monotone"
dataKey="yes"
stroke="#3b82f6"
strokeWidth={2}
dot={false}
name="yes"
/>
<Line
type="monotone"
dataKey="no"
stroke="#ef4444"
strokeWidth={2}
dot={false}
name="no"
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
);
}// components/MobileTradePanel.tsx
export function MobileTradePanel({ market }: { market: Market }) {
const [isOpen, setIsOpen] = useState(false);
const [selectedOutcome, setSelectedOutcome] = useState<'yes' | 'no'>('yes');
return (
<>
{/* Mobile Trade Button */}
<div className="md:hidden fixed bottom-20 left-4 right-4 z-40">
<button
onClick={() => setIsOpen(true)}
className="w-full py-4 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-semibold shadow-lg transition-colors"
>
Trade on {selectedOutcome === 'yes' ? 'Yes' : 'No'}
</button>
</div>
{/* Mobile Trade Modal */}
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
<Dialog.Content className="fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 rounded-t-xl p-6 z-50 max-h-[90vh] overflow-y-auto">
<div className="flex justify-between items-center mb-6">
<Dialog.Title className="text-lg font-semibold">
Trade: {market.title}
</Dialog.Title>
<Dialog.Close className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full">
<X className="w-5 h-5" />
</Dialog.Close>
</div>
{/* Mobile-optimized trade interface */}
<TradePanel market={market} isMobile />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</>
);
}// utils/accessibility.ts
export const a11yProps = {
// Button accessibility
button: (label: string, pressed?: boolean) => ({
'aria-label': label,
'aria-pressed': pressed,
role: 'button',
tabIndex: 0,
}),
// Input accessibility
input: (label: string, error?: string, describedBy?: string) => ({
'aria-label': label,
'aria-invalid': !!error,
'aria-describedby': error ? `${describedBy}-error` : describedBy,
}),
// Status announcements
status: (message: string, priority: 'polite' | 'assertive' = 'polite') => ({
'aria-live': priority,
'aria-atomic': true,
role: 'status',
}),
};
// Screen reader announcements
export function announceToScreenReader(message: string, priority: 'polite' | 'assertive' = 'polite') {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', priority);
announcement.setAttribute('aria-atomic', 'true');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
setTimeout(() => document.body.removeChild(announcement), 1000);
}## ✅ **PHASE 3 COMPLETION CHECKLIST**
### **Advanced UI Components**
- [ ] Complete `TradePanel` component with all features
- [ ] Implemented `ProbabilityChart` with Recharts
- [ ] Added time range selector (1H, 6H, 1D, 1W, 1M, ALL)
- [ ] Created mobile-optimized trading interface
- [ ] Implemented advanced settings (slippage, deadline)
- [ ] Added trade preview with calculations
- [ ] All components are fully responsive
### **Chart Integration**
- [ ] Recharts library installed and configured
- [ ] Historical data visualization working
- [ ] Interactive tooltips and legends
- [ ] Time range switching functionality
- [ ] Proper data formatting for charts
- [ ] Chart performance optimized
- [ ] Mobile-friendly chart interactions
### **Mobile Optimization**
- [ ] Mobile-first responsive design
- [ ] Touch-friendly interface elements
- [ ] Mobile trade modal implementation
- [ ] Swipe gestures for navigation
- [ ] Optimized font sizes and spacing
- [ ] Mobile-specific loading states
- [ ] Performance optimized for mobile
### **Accessibility (WCAG 2.1 AA)**
- [ ] Screen reader compatibility
- [ ] Keyboard navigation support
- [ ] Focus management and indicators
- [ ] Color contrast compliance
- [ ] ARIA labels and descriptions
- [ ] Status announcements for dynamic content
- [ ] Alternative text for all images
- [ ] Form labels and error messages
### **Performance Optimization**
- [ ] Component memoization implemented
- [ ] Image optimization with Next.js Image
- [ ] Bundle size optimization
- [ ] Lazy loading for non-critical components
- [ ] Virtual scrolling for long lists
- [ ] Debounced input handling
- [ ] Optimized re-renders
### **Advanced Features**
- [ ] Real-time price updates
- [ ] Advanced trading settings
- [ ] Price impact calculations
- [ ] Slippage protection
- [ ] Transaction history
- [ ] User position tracking
- [ ] Market statistics display
### **Error Handling & UX**
- [ ] Comprehensive error states
- [ ] Empty state components
- [ ] Loading animations and skeletons
- [ ] User-friendly error messages
- [ ] Retry mechanisms for failed operations
- [ ] Graceful degradation strategies
- [ ] Toast notifications for user feedback
### **Testing & Quality**
- [ ] Component tests for all new features
- [ ] Accessibility testing with automated tools
- [ ] Performance testing and optimization
- [ ] Mobile device testing
- [ ] Cross-browser compatibility testing
- [ ] User acceptance testing
### **Phase 3 Success Criteria**
- [ ] All UI components are polished and functional
- [ ] Charts display data correctly and interactively
- [ ] Mobile experience is excellent
- [ ] Accessibility compliance achieved
- [ ] Performance targets met (< 2s load time)
- [ ] All advanced features working
- [ ] Error handling is comprehensive
- [ ] All tests pass
- [ ] No TypeScript errors// components/OptimizedMarketCard.tsx
const MarketCard = memo(({ market, onTrade }: MarketCardProps) => {
const memoizedOutcomes = useMemo(() =>
market.outcomes.map(outcome => ({
...outcome,
formattedPrice: FinancialMath.formatUSDC(outcome.price),
formattedVolume: FinancialMath.formatUSDC(outcome.volume),
}))
, [market.outcomes]);
return (
<article className="...">
{/* Optimized rendering */}
</article>
);
});
MarketCard.displayName = 'MarketCard';// components/OptimizedImage.tsx
import Image from 'next.js';
export function OptimizedMarketImage({ market }: { market: Market }) {
return (
<div className="relative w-full h-48 rounded-lg overflow-hidden">
<Image
src={market.imageUrl || '/images/default-market.jpg'}
alt={market.title}
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority={false}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
/>
</div>
);
}// __tests__/utils/calculations.test.ts
import { describe, it, expect } from 'vitest';
import { FinancialMath } from '@/utils/calculations';
describe('FinancialMath', () => {
describe('toUSDCWei', () => {
it('should convert string amounts correctly', () => {
expect(FinancialMath.toUSDCWei('100')).toBe(100_000_000n);
expect(FinancialMath.toUSDCWei('0.01')).toBe(10_000n);
});
it('should throw on invalid amounts', () => {
expect(() => FinancialMath.toUSDCWei('-1')).toThrow();
expect(() => FinancialMath.toUSDCWei('abc')).toThrow();
});
});
describe('calculateSharesOut', () => {
it('should calculate shares correctly', () => {
const usdcIn = 1_000_000n; // $1
const price = 500_000_000_000_000_000n; // $0.50
const result = FinancialMath.calculateSharesOut(usdcIn, price);
expect(result.sharesOut).toBe(2_000_000_000_000_000_000n); // 2 shares
});
});
});// __tests__/components/TradePanel.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { TradePanel } from '@/components/TradePanel';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const mockMarket = {
id: '1',
title: 'Test Market',
// ... other properties
};
const TestWrapper = ({ children }: { children: React.ReactNode }) => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } }
});
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
};
describe('TradePanel', () => {
it('should render trade options', () => {
render(<TradePanel market={mockMarket} />, { wrapper: TestWrapper });
expect(screen.getByText('Yes')).toBeInTheDocument();
expect(screen.getByText('No')).toBeInTheDocument();
});
it('should validate trade amounts', async () => {
render(<TradePanel market={mockMarket} />, { wrapper: TestWrapper });
const amountInput = screen.getByPlaceholderText('0.00');
fireEvent.change(amountInput, { target: { value: '0.001' } });
await waitFor(() => {
expect(screen.getByText(/minimum amount/i)).toBeInTheDocument();
});
});
});// components/ErrorStates.tsx
export function MarketNotFound({ marketId }: { marketId: string }) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="mb-4">
<SearchX className="w-16 h-16 mx-auto text-gray-400" />
</div>
<h1 className="text-2xl font-bold mb-2">Market Not Found</h1>
<p className="text-gray-600 dark:text-gray-400 mb-6">
The market with ID "{marketId}" could not be found.
</p>
<Link
href="/"
className="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
Back to Markets
</Link>
</div>
</div>
);
}
export function NetworkError({ onRetry }: { onRetry: () => void }) {
return (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700 rounded-lg p-6 text-center">
<WifiOff className="w-12 h-12 mx-auto text-red-500 mb-4" />
<h3 className="text-lg font-semibold mb-2">Connection Error</h3>
<p className="text-red-700 dark:text-red-400 mb-4">
Unable to load market data. Please check your connection.
</p>
<button
onClick={onRetry}
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
>
Try Again
</button>
</div>
);
}// components/animations/LoadingSpinner.tsx
export function LoadingSpinner({ size = 'medium' }: { size?: 'small' | 'medium' | 'large' }) {
const sizeClasses = {
small: 'w-4 h-4',
medium: 'w-8 h-8',
large: 'w-12 h-12',
};
return (
<div className="flex justify-center">
<div
className={`${sizeClasses[size]} animate-spin rounded-full border-2 border-gray-300 border-t-blue-600`}
role="status"
aria-label="Loading"
>
<span className="sr-only">Loading...</span>
</div>
</div>
);
}## ✅ **PHASE 4 COMPLETION CHECKLIST**
### **Testing Implementation**
- [ ] Unit tests for all utility functions (90%+ coverage)
- [ ] Component tests for all major components
- [ ] Integration tests for trading flow
- [ ] End-to-end tests for critical user journeys
- [ ] Accessibility tests with automated tools
- [ ] Performance tests and optimization
- [ ] Cross-browser compatibility tests
- [ ] Mobile device testing
### **Error Handling & Recovery**
- [ ] Comprehensive error boundaries implemented
- [ ] User-friendly error messages for all scenarios
- [ ] Retry mechanisms for failed operations
- [ ] Graceful degradation strategies
- [ ] Network error handling
- [ ] Transaction failure recovery
- [ ] Data loading error states
- [ ] 404 and not found pages
### **Performance & Optimization**
- [ ] Bundle size under 500KB gzipped
- [ ] Page load time under 2 seconds
- [ ] First Contentful Paint under 1.5 seconds
- [ ] Largest Contentful Paint under 2.5 seconds
- [ ] Cumulative Layout Shift under 0.1
- [ ] Lighthouse score 90+ for all categories
- [ ] Image optimization implemented
- [ ] Code splitting and lazy loading
### **SEO & Meta Tags**
- [ ] Dynamic meta tags for each market
- [ ] Open Graph tags for social sharing
- [ ] Twitter Card tags
- [ ] Structured data markup
- [ ] Sitemap generation
- [ ] Robots.txt configuration
- [ ] Canonical URLs
- [ ] Meta descriptions for all pages
### **Analytics & Monitoring**
- [ ] Google Analytics integration
- [ ] Custom event tracking
- [ ] Performance monitoring
- [ ] Error tracking with Sentry
- [ ] User behavior analytics
- [ ] Conversion tracking
- [ ] A/B testing setup
- [ ] Real-time monitoring dashboard
### **Security & Compliance**
- [ ] XSS protection implemented
- [ ] CSRF protection enabled
- [ ] Input sanitization throughout
- [ ] Content Security Policy headers
- [ ] HTTPS enforcement
- [ ] Privacy policy compliance
- [ ] GDPR compliance measures
- [ ] Security audit completed
### **Production Deployment**
- [ ] Environment configuration
- [ ] Build optimization
- [ ] CDN configuration
- [ ] Caching strategy
- [ ] Database optimization
- [ ] API rate limiting
- [ ] Monitoring and alerting
- [ ] Backup and recovery procedures
### **Documentation & Maintenance**
- [ ] API documentation updated
- [ ] Component documentation complete
- [ ] Deployment guide written
- [ ] Troubleshooting guide created
- [ ] Maintenance procedures documented
- [ ] Code comments and JSDoc
- [ ] README updated
- [ ] Changelog maintained
### **Final Quality Assurance**
- [ ] All features working as expected
- [ ] No critical bugs or issues
- [ ] Performance targets met
- [ ] Accessibility compliance achieved
- [ ] Security measures in place
- [ ] All tests passing
- [ ] Code review completed
- [ ] User acceptance testing passed
### **Phase 4 Success Criteria**
- [ ] Production-ready application
- [ ] All performance targets met
- [ ] Comprehensive test coverage
- [ ] Security audit passed
- [ ] SEO optimization complete
- [ ] Analytics and monitoring active
- [ ] Documentation complete
- [ ] Ready for public launch// app/market/[id]/page.tsx
import { Metadata } from 'next';
export async function generateMetadata({
params
}: {
params: { id: string }
}): Promise<Metadata> {
const market = await getMarketData(params.id);
if (!market) {
return {
title: 'Market Not Found - PakoMarket',
};
}
return {
title: `${market.title} - PakoMarket`,
description: `Trade on "${market.title}" - Volume: ${FinancialMath.formatUSDC(market.volume)}`,
openGraph: {
title: market.title,
description: market.description,
images: [market.imageUrl || '/images/default-og.jpg'],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: market.title,
description: market.description,
images: [market.imageUrl || '/images/default-og.jpg'],
},
};
}// utils/analytics.ts
export function trackMarketView(marketId: string, marketTitle: string) {
// Analytics tracking
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'market_view', {
market_id: marketId,
market_title: marketTitle,
});
}
}
export function trackTrade(marketId: string, outcome: string, amount: string) {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'trade_initiated', {
market_id: marketId,
outcome,
amount: parseFloat(amount),
});
}
}-
Complete Market Details Page
- ✅ Server-side rendered with proper SEO
- ✅ Mobile-responsive design
- ✅ Dark/light mode support
- ✅ Accessibility compliant (WCAG 2.1 AA)
-
Full Trading Integration
- ✅ USDC approval workflow
- ✅ Real-time price quotes
- ✅ Slippage protection
- ✅ Chain validation
- ✅ Transaction monitoring
-
Robust Architecture
- ✅ Component-based architecture
- ✅ Custom hooks for Web3 integration
- ✅ Proper error handling
- ✅ Type-safe throughout
- ✅ Performance optimized
-
Production Features
- ✅ Loading states and skeletons
- ✅ Error boundaries
- ✅ 404 handling
- ✅ Analytics integration
- ✅ SEO optimization
| Metric | Target | Measurement |
|---|---|---|
| Page Load Time | < 2s | Lighthouse |
| First Contentful Paint | < 1.5s | Lighthouse |
| Largest Contentful Paint | < 2.5s | Lighthouse |
| Cumulative Layout Shift | < 0.1 | Lighthouse |
| Bundle Size | < 500KB gzipped | webpack-bundle-analyzer |
| Accessibility Score | 100/100 | Lighthouse |
- Unit Tests: 90%+ coverage for utilities and hooks
- Component Tests: Key components tested with React Testing Library
- Integration Tests: End-to-end trading flow
- Accessibility Tests: Automated a11y testing
- Performance Tests: Bundle size and runtime performance
- All user inputs validated with Zod
- Decimal precision handled with bigint
- No client-side private key exposure
- Proper error handling without data leakage
- XSS protection on all dynamic content
- CSRF protection on state-changing operations
- ✅ Chrome 90+
- ✅ Firefox 88+
- ✅ Safari 14+
- ✅ Edge 90+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
- Real-time Updates: WebSocket integration for live price updates
- Advanced Charts: Candlestick charts, volume indicators
- Social Features: Comments, sharing, market creator profiles
- Portfolio View: User's position tracking and P&L
- Caching Strategy: Redis integration for API responses
- CDN Integration: Image and static asset optimization
- Database Optimization: Query optimization and indexing
- Advanced Analytics: User behavior tracking and insights
- Multi-chain Support: Extend beyond local development
- Advanced Order Types: Limit orders, stop-loss
- Liquidity Incentives: LP rewards and staking
- Mobile App: React Native or PWA implementation
- Page Load Speed: < 2 seconds average
- Bounce Rate: < 30% on market detail pages
- Trade Completion Rate: > 85% of initiated trades complete
- Error Rate: < 1% of user interactions result in errors
- Uptime: 99.9% availability
- API Response Time: < 200ms average
- Bundle Size: < 500KB compressed
- Test Coverage: > 90% for critical paths
- Trading Volume: Track USDC volume per market
- User Retention: Weekly active users on market pages
- Conversion Rate: Visitors to traders conversion
- Market Engagement: Time spent on market detail pages
- Development environment configured
- All dependencies installed and working
- Git repository set up with proper branching
- CI/CD pipeline configured
- Testing environment ready
- Design system and components library
- Project documentation structure
- File Structure: Complete directory setup
- Component Extraction: All components extracted and working
- Decimal Precision: Financial math utilities implemented
- Mock Data: Comprehensive mock data system
- Basic UI/UX: Responsive design and theming
- Testing Foundation: Basic testing setup
- Code Quality: TypeScript and linting
- Performance Basics: Initial optimization
- Web3 Hooks: All trading hooks implemented
- Trading Flow: Complete buy/sell functionality
- Chain Management: Network validation and switching
- Real-time Data: Live price updates and caching
- Contract Integration: All contract calls working
- Security & Validation: Input validation and security
- Testing & Quality: Web3 testing implementation
- Fallback Strategies: All fallbacks working
- Advanced UI: Complete trading interface
- Chart Integration: Recharts implementation
- Mobile Optimization: Mobile-first design
- Accessibility: WCAG 2.1 AA compliance
- Performance: Advanced optimization
- Advanced Features: Real-time updates, etc.
- Error Handling: Comprehensive error states
- Testing & Quality: Advanced testing
- Testing Implementation: Complete test suite
- Error Handling: All error scenarios covered
- Performance: All targets met
- SEO & Meta: Complete SEO optimization
- Analytics: Monitoring and tracking
- Security: Security audit and compliance
- Production: Deployment readiness
- Documentation: Complete documentation
- Performance: All benchmarks met
- Security: Security audit passed
- Testing: 90%+ test coverage
- Accessibility: WCAG 2.1 AA compliant
- SEO: Complete SEO optimization
- Monitoring: Analytics and error tracking
- Documentation: Complete and up-to-date
- Launch: Ready for public release
- Performance: Page load < 2s, Lighthouse 90+
- Accessibility: 100% WCAG compliance
- Testing: 90%+ coverage, all tests passing
- Security: No critical vulnerabilities
- UX: User acceptance testing passed
- Code Quality: No TypeScript errors, clean code
- Documentation: Complete and maintained
- Launch: Production deployment successful
This mega plan transforms the current prototype into a production-ready, contract-integrated market details page that serves as the foundation for the entire PakoMarket platform. The systematic approach ensures security, scalability, and exceptional user experience while building toward full Web3 integration.
Remember: Use this plan as a guide, not a rigid checklist. Adapt and iterate based on your team's progress and changing requirements. The key is to maintain momentum while ensuring quality at each step.