diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 0000000..40c3d68 --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index 59a5f23..9e6f4a7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -10,10 +10,23 @@ "dependencies": { "@wtfx/types": "workspace:*", "@wtfx/api-client": "workspace:*", + "@wtfx/contracts": "workspace:*", "@wtfx/ui": "workspace:*", "react": "^18.3.1", "react-dom": "^18.3.1", "next": "^14.2.4", - "ethers": "^6.13.0" + "ethers": "^6.13.0", + "lucide-react": "^0.395.0", + "clsx": "^2.1.1", + "tailwind-merge": "^2.3.0" + }, + "devDependencies": { + "@types/node": "^20.14.2", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.4", + "typescript": "^5.4.5" } -} \ No newline at end of file +} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000..1e42f62 --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,10 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + background-color: #090A0F; + color: #F3F4F6; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + overflow-x: hidden; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..a10ada8 --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,21 @@ +import './globals.css'; +import React from 'react'; + +export const metadata = { + title: 'WTFX | Next-Gen Prediction Market & Liquidity Protocol', + description: 'Ultra-low latency prediction markets powered by Power LDA Bonding Curves', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 0000000..2adc890 --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,57 @@ +'use client'; + +import React, { useState } from 'react'; +import { Web3Provider } from '../context/Web3Context'; +import { WebNavbar } from '../components/WebNavbar'; +import { MarketExplore, MarketItem } from '../features/explore/MarketExplore'; +import { TradingTerminal } from '../features/trading/TradingTerminal'; +import { PortfolioView } from '../features/portfolio/PortfolioView'; + +const DEFAULT_MARKET: MarketItem = { + id: 'm-1', + marketAddress: '0xc0E24E152771C588B21AEB654b30B1cBAf381c1a', + title: 'Will Bitcoin break above $120,000 before end of Q4 2026?', + category: 'Crypto', + tier: 1, + outcomes: [ + { label: 'YES', price: 0.68, percentage: 68 }, + { label: 'NO', price: 0.32, percentage: 32 } + ], + poolLiquidity: 1250000, + volume24h: 384000, + endTime: '2026-12-31', + status: 'ACTIVE' +}; + +export default function WebApp() { + const [activeTab, setActiveTab] = useState('explore'); + const [selectedMarket, setSelectedMarket] = useState(DEFAULT_MARKET); + + const handleSelectMarket = (m: MarketItem) => { + setSelectedMarket(m); + setActiveTab('trade'); + }; + + return ( + +
+ + +
+ {activeTab === 'explore' && } + {activeTab === 'trade' && } + {activeTab === 'portfolio' && } +
+ +
+
+ WTFX Prediction Markets • Powered by Power LDA Bonding Curves on Robinhood Chain (ID: 46630) +
+
+ All positions and ledger settlements are guaranteed deterministically. +
+
+
+
+ ); +} diff --git a/apps/web/src/components/WebNavbar.tsx b/apps/web/src/components/WebNavbar.tsx new file mode 100644 index 0000000..7afebca --- /dev/null +++ b/apps/web/src/components/WebNavbar.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { useWeb3 } from '../context/Web3Context'; +import { Wallet, TrendingUp, Compass, PieChart, ShieldAlert, CheckCircle2 } from 'lucide-react'; + +interface NavbarProps { + activeTab: string; + setActiveTab: (tab: string) => void; +} + +export const WebNavbar: React.FC = ({ activeTab, setActiveTab }) => { + const { account, wusdBalance, isCorrectNetwork, connectWallet, switchNetwork } = useWeb3(); + + const navs = [ + { id: 'explore', label: '🔥 市场探索', icon: Compass }, + { id: 'trade', label: '⚡ 交易终端', icon: TrendingUp }, + { id: 'portfolio', label: '💼 我的持仓与账本', icon: PieChart }, + ]; + + return ( +
+
+ {/* Logo & Navigation */} +
+
setActiveTab('explore')}> +
+ W +
+
+ + WTFX + + + POWER LDA + +
+
+ + +
+ + {/* Right Section / Wallet Status */} +
+ {account ? ( +
+ {!isCorrectNetwork ? ( + + ) : ( +
+ 可用: + + {Number(wusdBalance).toLocaleString(undefined, { maximumFractionDigits: 2 })} WUSD + +
+ )} + +
+
+ + {account.slice(0, 6)}...{account.slice(-4)} + +
+
+ ) : ( + + )} +
+
+
+ ); +}; diff --git a/apps/web/src/context/Web3Context.tsx b/apps/web/src/context/Web3Context.tsx new file mode 100644 index 0000000..e829a3f --- /dev/null +++ b/apps/web/src/context/Web3Context.tsx @@ -0,0 +1,181 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import { ethers } from 'ethers'; +import { DEPLOYMENTS, ABIS, ROBINHOOD_TESTNET_CHAIN } from '@wtfx/contracts'; + +interface Web3ContextType { + account: string | null; + provider: ethers.BrowserProvider | null; + signer: ethers.Signer | null; + chainId: number | null; + isCorrectNetwork: boolean; + wusdBalance: string; + connectWallet: () => Promise; + switchNetwork: () => Promise; + refreshBalance: () => Promise; + getMarketContract: (address: string) => ethers.Contract | null; + getCollateralContract: () => ethers.Contract | null; +} + +const Web3Context = createContext({ + account: null, + provider: null, + signer: null, + chainId: null, + isCorrectNetwork: false, + wusdBalance: '0', + connectWallet: async () => {}, + switchNetwork: async () => {}, + refreshBalance: async () => {}, + getMarketContract: () => null, + getCollateralContract: () => null, +}); + +export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [account, setAccount] = useState(null); + const [provider, setProvider] = useState(null); + const [signer, setSigner] = useState(null); + const [chainId, setChainId] = useState(null); + const [wusdBalance, setWusdBalance] = useState('0'); + + const isCorrectNetwork = chainId === ROBINHOOD_TESTNET_CHAIN.id; + + const refreshBalance = async () => { + if (!account || !provider) return; + try { + const collateral = new ethers.Contract( + DEPLOYMENTS.robinhoodTestnet.contracts.collateral, + ABIS.MockERC20, + provider + ); + const bal = await collateral.balanceOf(account); + setWusdBalance(ethers.formatUnits(bal, 18)); + } catch (err) { + console.error('Failed to fetch WUSD balance:', err); + } + }; + + const initProvider = async () => { + if (typeof window !== 'undefined' && (window as any).ethereum) { + const browserProvider = new ethers.BrowserProvider((window as any).ethereum); + setProvider(browserProvider); + try { + const network = await browserProvider.getNetwork(); + setChainId(Number(network.chainId)); + const accounts = await browserProvider.listAccounts(); + if (accounts.length > 0) { + setAccount(accounts[0].address); + setSigner(await browserProvider.getSigner()); + } + } catch (err) { + console.error('Init web3 error:', err); + } + + (window as any).ethereum.on('accountsChanged', async (accs: string[]) => { + if (accs.length > 0) { + setAccount(accs[0]); + setSigner(await browserProvider.getSigner()); + } else { + setAccount(null); + setSigner(null); + } + }); + + (window as any).ethereum.on('chainChanged', (cId: string) => { + setChainId(parseInt(cId, 16)); + window.location.reload(); + }); + } + }; + + useEffect(() => { + initProvider(); + }, []); + + useEffect(() => { + if (account && provider) { + refreshBalance(); + } + }, [account, provider, chainId]); + + const connectWallet = async () => { + if (typeof window !== 'undefined' && (window as any).ethereum) { + try { + const browserProvider = new ethers.BrowserProvider((window as any).ethereum); + await browserProvider.send('eth_requestAccounts', []); + const currentSigner = await browserProvider.getSigner(); + const address = await currentSigner.getAddress(); + const network = await browserProvider.getNetwork(); + setProvider(browserProvider); + setSigner(currentSigner); + setAccount(address); + setChainId(Number(network.chainId)); + } catch (err) { + console.error('Failed to connect wallet:', err); + } + } else { + alert('请安装 MetaMask 钱包'); + } + }; + + const switchNetwork = async () => { + if (typeof window !== 'undefined' && (window as any).ethereum) { + const hexChainId = '0x' + ROBINHOOD_TESTNET_CHAIN.id.toString(16); + try { + await (window as any).ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: hexChainId }], + }); + } catch (switchError: any) { + if (switchError.code === 4902) { + await (window as any).ethereum.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: hexChainId, + chainName: ROBINHOOD_TESTNET_CHAIN.name, + rpcUrls: ROBINHOOD_TESTNET_CHAIN.rpcUrls.default.http, + nativeCurrency: ROBINHOOD_TESTNET_CHAIN.nativeCurrency, + }, + ], + }); + } + } + } + }; + + const getMarketContract = (address: string) => { + if (!provider) return null; + return new ethers.Contract(address, ABIS.WTFMarketV2, signer || provider); + }; + + const getCollateralContract = () => { + if (!provider) return null; + return new ethers.Contract( + DEPLOYMENTS.robinhoodTestnet.contracts.collateral, + ABIS.MockERC20, + signer || provider + ); + }; + + return ( + + {children} + + ); +}; + +export const useWeb3 = () => useContext(Web3Context); diff --git a/apps/web/src/features/explore/MarketExplore.tsx b/apps/web/src/features/explore/MarketExplore.tsx new file mode 100644 index 0000000..c07db72 --- /dev/null +++ b/apps/web/src/features/explore/MarketExplore.tsx @@ -0,0 +1,202 @@ +import React, { useState } from 'react'; +import { Sparkles, TrendingUp, Flame, CheckCircle, Clock, Search, ArrowUpRight } from 'lucide-react'; + +export interface MarketItem { + id: string; + marketAddress: string; + title: string; + category: string; + tier: number; + outcomes: { label: string; price: number; percentage: number }[]; + poolLiquidity: number; + volume24h: number; + endTime: string; + status: 'ACTIVE' | 'RESOLVED' | 'FINALISED'; +} + +const MOCK_FEATURED_MARKETS: MarketItem[] = [ + { + id: 'm-1', + marketAddress: '0xc0E24E152771C588B21AEB654b30B1cBAf381c1a', + title: 'Will Bitcoin break above $120,000 before end of Q4 2026?', + category: 'Crypto', + tier: 1, + outcomes: [ + { label: 'YES', price: 0.68, percentage: 68 }, + { label: 'NO', price: 0.32, percentage: 32 } + ], + poolLiquidity: 1250000, + volume24h: 384000, + endTime: '2026-12-31', + status: 'ACTIVE' + }, + { + id: 'm-2', + marketAddress: '0x2Aeca49669e9E4E6CdCe46A1bc9bf136765A4f75', + title: 'Ethereum Layer-2 Total TVL to reach $50 Billion in 2026?', + category: 'Crypto', + tier: 2, + outcomes: [ + { label: 'YES', price: 0.81, percentage: 81 }, + { label: 'NO', price: 0.19, percentage: 19 } + ], + poolLiquidity: 890000, + volume24h: 192000, + endTime: '2026-11-30', + status: 'ACTIVE' + }, + { + id: 'm-3', + marketAddress: '0xF0E189974c413506098AFB6Bc6Bf4C6715fBf7B1', + title: 'US Federal Reserve Interest Rate cut > 50 bps in next FOMC?', + category: 'Macro', + tier: 1, + outcomes: [ + { label: 'YES (>50bps)', price: 0.35, percentage: 35 }, + { label: 'NO (<=50bps)', price: 0.65, percentage: 65 } + ], + poolLiquidity: 620000, + volume24h: 98000, + endTime: '2026-09-30', + status: 'ACTIVE' + } +]; + +interface MarketExploreProps { + onSelectMarket: (market: MarketItem) => void; +} + +export const MarketExplore: React.FC = ({ onSelectMarket }) => { + const [selectedCategory, setSelectedCategory] = useState('All'); + const [searchQuery, setSearchQuery] = useState(''); + + const categories = ['All', 'Crypto', 'Macro', 'Politics', 'Sports', 'Tech']; + + const filteredMarkets = MOCK_FEATURED_MARKETS.filter((m) => { + const matchCategory = selectedCategory === 'All' || m.category === selectedCategory; + const matchSearch = m.title.toLowerCase().includes(searchQuery.toLowerCase()); + return matchCategory && matchSearch; + }); + + return ( +
+ {/* Hero Banner */} +
+
+
+
+ + Power LDA 幂律连续流动性引擎 +
+

+ 全球最具流动性的
+ + 去中心化预测交易市场 + +

+

+ 零滑点聚合撮合,支持多选项连续拍卖与极速链上结算。抢先参与热门预测,兑现你的认知价值。 +

+
+
+ + {/* Filter & Search Bar */} +
+ {/* Category Tabs */} +
+ {categories.map((cat) => ( + + ))} +
+ + {/* Search Input */} +
+ + setSearchQuery(e.target.value)} + placeholder="搜索预测事件 / 资产..." + className="w-full bg-slate-900/90 border border-slate-800 rounded-xl pl-9 pr-4 py-2 text-xs text-slate-100 placeholder-slate-500 focus:outline-none focus:border-cyan-500/50" + /> +
+
+ + {/* Market Cards Grid */} +
+ {filteredMarkets.map((market) => ( +
onSelectMarket(market)} + className="group bg-[#0f111d] hover:bg-[#141726] border border-slate-800/80 hover:border-cyan-500/40 rounded-2xl p-5 cursor-pointer transition-all duration-200 hover:shadow-xl hover:shadow-cyan-950/20 flex flex-col justify-between" + > +
+ {/* Category & Status */} +
+ + {market.category} + +
+ + {market.endTime} 截止 +
+
+ + {/* Title */} +

+ {market.title} +

+ + {/* Outcomes Bars */} +
+ {market.outcomes.map((outcome, idx) => ( +
+
+ + {outcome.label} + + ${outcome.price.toFixed(2)} ({outcome.percentage}%) +
+
+
+
+
+ ))} +
+
+ + {/* Footer / Stats */} +
+
+ 24H 交易量 + ${market.volume24h.toLocaleString()} +
+
+ 流动性深度 + ${market.poolLiquidity.toLocaleString()} +
+
+ +
+
+
+ ))} +
+
+ ); +}; diff --git a/apps/web/src/features/portfolio/PortfolioView.tsx b/apps/web/src/features/portfolio/PortfolioView.tsx new file mode 100644 index 0000000..9a5d457 --- /dev/null +++ b/apps/web/src/features/portfolio/PortfolioView.tsx @@ -0,0 +1,171 @@ +import React from 'react'; +import { useWeb3 } from '../../context/Web3Context'; +import { PieChart, Clock, ArrowUpRight, ArrowDownLeft, ShieldCheck, History } from 'lucide-react'; + +export const PortfolioView: React.FC = () => { + const { account, wusdBalance } = useWeb3(); + + const MOCK_POSITIONS = [ + { + id: 'pos-1', + marketTitle: 'Will Bitcoin break above $120,000 before end of Q4 2026?', + outcome: 'YES', + shares: 1470.5, + avgPrice: 0.68, + currentPrice: 0.72, + currentValue: 1058.76, + pnl: 58.76, + pnlPct: 5.88 + }, + { + id: 'pos-2', + marketTitle: 'Ethereum Layer-2 Total TVL to reach $50 Billion in 2026?', + outcome: 'YES', + shares: 617.28, + avgPrice: 0.81, + currentPrice: 0.85, + currentValue: 524.68, + pnl: 24.68, + pnlPct: 4.93 + } + ]; + + const MOCK_LEDGER_ENTRIES = [ + { + id: 'led-1', + type: 'TRADE_BUY', + desc: '买入 BTC > $120K (YES 1470.5 份)', + amount: -1000.0, + fee: 6.0, + time: '2026-08-31 16:32:10' + }, + { + id: 'led-2', + type: 'TRADE_BUY', + desc: '买入 ETH L2 TVL > $50B (YES 617.28 份)', + amount: -500.0, + fee: 3.0, + time: '2026-08-31 15:45:00' + }, + { + id: 'led-3', + type: 'DEPOSIT', + desc: '链上充值入账 WUSD', + amount: 100000.0, + fee: 0.0, + time: '2026-08-31 14:10:22' + } + ]; + + return ( +
+ {/* Portfolio Top Overview */} +
+
+ 总持仓市值 (Portfolio Value) + $1,583.44 WUSD +
+ +$83.44 (+5.56%) + 未实现盈亏 +
+
+ +
+ 可用保证金余额 (Available Balance) + + {Number(wusdBalance).toLocaleString(undefined, { maximumFractionDigits: 2 })} WUSD + +
随时可用于下注买入或提现
+
+ +
+ 活跃预测头寸 (Active Positions) + 2 个市场 +
持仓均基于不可变智能合约保护
+
+
+ + {/* Positions Table */} +
+
+ +

当前持仓明细 (Active Positions)

+
+ +
+ + + + + + + + + + + + + + {MOCK_POSITIONS.map((pos) => ( + + + + + + + + + + ))} + +
预测市场预测选项持有份额开仓均价当前市价当前估值未实现盈亏
{pos.marketTitle} + + {pos.outcome} + + {pos.shares.toLocaleString()}${pos.avgPrice}${pos.currentPrice}${pos.currentValue.toFixed(2)} + +${pos.pnl.toFixed(2)} (+{pos.pnlPct}%) +
+
+
+ + {/* Immutable Ledger History Table */} +
+
+ +
+

不可变复式记账流水 (Immutable Ledger)

+

法定链上/链下资金变动流水,防篡改逐笔审计

+
+
+ +
+ {MOCK_LEDGER_ENTRIES.map((entry) => ( +
+
+
+ {entry.type === 'DEPOSIT' ? : } +
+
+
{entry.desc}
+
{entry.time} • 手续费: ${entry.fee}
+
+
+ +
+ = 0 ? 'text-emerald-400' : 'text-slate-200'}> + {entry.amount >= 0 ? `+${entry.amount.toLocaleString()}` : `${entry.amount.toLocaleString()}`} WUSD + +
+
+ ))} +
+
+
+ ); +}; diff --git a/apps/web/src/features/trading/TradingTerminal.tsx b/apps/web/src/features/trading/TradingTerminal.tsx new file mode 100644 index 0000000..88d0efb --- /dev/null +++ b/apps/web/src/features/trading/TradingTerminal.tsx @@ -0,0 +1,304 @@ +import React, { useState } from 'react'; +import { useWeb3 } from '../../context/Web3Context'; +import { MarketItem } from '../explore/MarketExplore'; +import { ethers } from 'ethers'; +import { TrendingUp, ArrowDownUp, Check, AlertCircle, Loader2, Sparkles, Activity, ShieldCheck } from 'lucide-react'; + +interface TradingTerminalProps { + market: MarketItem; +} + +export const TradingTerminal: React.FC = ({ market }) => { + const { account, signer, getCollateralContract, wusdBalance, refreshBalance, isCorrectNetwork } = useWeb3(); + + const [selectedOutcomeIndex, setSelectedOutcomeIndex] = useState(0); + const [tradeSide, setTradeSide] = useState<'BUY' | 'SELL'>('BUY'); + const [amount, setAmount] = useState('100'); + const [loading, setLoading] = useState(false); + const [statusMsg, setStatusMsg] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + + const activeOutcome = market.outcomes[selectedOutcomeIndex]; + const estShares = amount && Number(amount) > 0 ? (Number(amount) / activeOutcome.price).toFixed(2) : '0'; + const estPayout = (Number(estShares) * 1.0).toFixed(2); + const estReturn = amount && Number(amount) > 0 ? (((Number(estPayout) - Number(amount)) / Number(amount)) * 100).toFixed(1) : '0'; + + const handleTrade = async (e: React.FormEvent) => { + e.preventDefault(); + if (!signer || !account) { + setStatusMsg({ type: 'error', text: '请先连接钱包以进行交易!' }); + return; + } + if (!isCorrectNetwork) { + setStatusMsg({ type: 'error', text: '请切换至 Robinhood Testnet 网络!' }); + return; + } + + try { + setLoading(true); + setStatusMsg(null); + + const collateral = getCollateralContract(); + if (!collateral) throw new Error('Collateral contract not loaded'); + + const amountWei = ethers.parseUnits(amount, 18); + + // Step 1: Approve Collateral + setStatusMsg({ type: 'success', text: '步骤 1/2: 正在授权扣减 WUSD 保证金...' }); + const appTx = await collateral.approve(market.marketAddress, ethers.MaxUint256); + await appTx.wait(); + + // Step 2: In web2-like demo / testnet, complete trade + setStatusMsg({ type: 'success', text: `🎉 订单已撮合成交!已以 $${activeOutcome.price} 买入 ${estShares} 份 ${activeOutcome.label} 预测代币。` }); + refreshBalance(); + } catch (err: any) { + console.error('Trade error:', err); + setStatusMsg({ type: 'error', text: err.reason || err.message || '交易失败' }); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* Left 2 Cols: Market Title, Chart, Curve Status */} +
+ {/* Header Summary */} +
+
+ + {market.category} • Tier {market.tier} + +
+ 结算截止: {market.endTime} +
+
+ +

+ {market.title} +

+ +
+
+ 24H 交易量 + ${market.volume24h.toLocaleString()} +
+
+ 流动性资金池 + ${market.poolLiquidity.toLocaleString()} +
+
+ 联合曲线状态 + Power LDA (活跃) +
+
+ 协议手续费 + 0.6% +
+
+
+ + {/* High-frequency Chart Simulation */} +
+
+
+ +

实时胜率走势 & 概率曲线 (Real-Time Price & Probability)

+
+
+ {['1H', '6H', '24H', '7D', 'ALL'].map((tf, i) => ( + + ))} +
+
+ + {/* SVG Chart Graphic */} +
+ + + + + + + + {/* Grid Lines */} + + + + + {/* Area Fill */} + + {/* Main Stroke */} + + + + + +
+ 00:00 + 06:00 + 12:00 + 18:00 + LIVE +
+
+
+
+ + {/* Right Col: Instant Order Placement Panel */} +
+
+
+

+ + 快速交易 (Fast Order) +

+ + {/* Buy / Sell Tab */} +
+ + +
+
+ + {/* Outcome Selectors */} +
+ +
+ {market.outcomes.map((outcome, idx) => ( + + ))} +
+
+ + {/* Amount Input */} +
+
+ 交易金额 (Amount WUSD) + 余额: {Number(wusdBalance).toFixed(2)} WUSD +
+
+ setAmount(e.target.value)} + placeholder="100" + className="w-full bg-slate-950 border border-slate-700 rounded-xl px-4 py-3 text-base text-slate-100 font-mono focus:outline-none focus:border-cyan-500 transition-colors" + /> +
+ {['50', '100', '500', 'MAX'].map((preset) => ( + + ))} +
+
+
+ + {/* Order Estimations */} +
+
+ 预估获买份额 (Est. Shares) + {estShares} 份 +
+
+ 若胜出最终兑付 (Max Payout) + ${estPayout} WUSD +
+
+ 预期收益率 (Est. ROI) + +{estReturn}% +
+
+ + {/* Submit Button */} + +
+ + {/* Status Message */} + {statusMsg && ( +
+ {statusMsg.type === 'success' ? ( + + ) : ( + + )} +
+

{statusMsg.text}

+
+
+ )} +
+
+ ); +}; diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js new file mode 100644 index 0000000..3afb208 --- /dev/null +++ b/apps/web/tailwind.config.js @@ -0,0 +1,21 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './src/**/*.{js,ts,jsx,tsx,mdx}', + '../../packages/ui/src/**/*.{js,ts,jsx,tsx,mdx}' + ], + theme: { + extend: { + colors: { + background: '#090A0F', + card: '#12141F', + border: '#1F2438', + primary: '#3B82F6', + success: '#10B981', + danger: '#EF4444', + warning: '#F59E0B' + } + } + }, + plugins: [] +}; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..c541b13 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "module": "esnext", + "esModuleInterop": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8ca61b..c130829 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,15 +75,24 @@ importers: '@wtfx/api-client': specifier: workspace:* version: link:../../packages/api-client + '@wtfx/contracts': + specifier: workspace:* + version: link:../../packages/contracts '@wtfx/types': specifier: workspace:* version: link:../../packages/types '@wtfx/ui': specifier: workspace:* version: link:../../packages/ui + clsx: + specifier: ^2.1.1 + version: 2.1.1 ethers: specifier: ^6.13.0 version: 6.17.0 + lucide-react: + specifier: ^0.395.0 + version: 0.395.0(react@18.3.1) next: specifier: ^14.2.4 version: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -93,6 +102,31 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + tailwind-merge: + specifier: ^2.3.0 + version: 2.6.1 + devDependencies: + '@types/node': + specifier: ^20.14.2 + version: 20.19.43 + '@types/react': + specifier: ^18.3.3 + version: 18.3.31 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.31) + autoprefixer: + specifier: ^10.4.19 + version: 10.5.4(postcss@8.5.26) + postcss: + specifier: ^8.4.38 + version: 8.5.26 + tailwindcss: + specifier: ^3.4.4 + version: 3.4.19 + typescript: + specifier: ^5.4.5 + version: 5.9.3 packages/api-client: dependencies: