React Sample MiniApp
This section showcases a sample React MiniApp:
/**
* React Usage Examples
* Shows how to use the Simple API with React hooks and components
*/
import React, { useEffect, useState } from 'react';
import {
// Simple React hooks
useSimpleMiniApp,
useSimpleTrade,
useSimpleMarketData,
useSimplePositions,
useSimpleBalance,
useSimplePermissions,
useSimpleConnection,
// Context provider and hooks
SimpleMiniAppProvider,
useSimpleMiniAppContext,
useSimpleTrading,
useSimpleAccount,
useSimplePermissionsContext,
// Types
SimpleOrderData,
SimpleMarketData,
SimplePermission
} from '../index';
// ============================================
// Example 1: Basic Hook Usage
// ============================================
/**
* Simple trading app using individual hooks
* This shows the most basic usage pattern
*/
export function BasicTradingApp() {
const { client, connected, sessionId, error } = useSimpleMiniApp({
appId: 'basic-trader',
debug: true
});
const { placeOrder, loading: tradingLoading, error: tradingError } = useSimpleTrade(client);
const { data: marketData, subscribe } = useSimpleMarketData(client);
const { permissions, requestTradingPermissions } = useSimplePermissions(client);
useEffect(() => {
if (connected) {
// Subscribe to market data on connection
subscribe(['ETH', 'BTC', 'SOL']);
// Request trading permissions
requestTradingPermissions();
}
}, [connected, subscribe, requestTradingPermissions]);
const handleQuickBuy = async (symbol: string, size: number) => {
try {
await placeOrder({
symbol,
side: 'buy',
orderType: 'market',
size
});
alert(`Bought ${size} ${symbol}!`);
} catch (err: any) {
alert(`Order failed: ${err.message}`);
}
};
if (error) {
return <div style={{ color: 'red' }}>Error: {error.message}</div>;
}
if (!connected) {
return <div>Connecting to Based.One Terminal...</div>;
}
return (
<div style={{ padding: '20px', fontFamily: 'monospace' }}>
<h2>🚀 Simple Trader</h2>
<p>Session: {sessionId}</p>
<p>Permissions: {permissions.join(', ')}</p>
<div style={{ marginBottom: '20px' }}>
<h3>📊 Market Data</h3>
{Object.entries(marketData).map(([symbol, data]) => (
<div key={symbol} style={{ margin: '10px 0' }}>
<strong>{symbol}:</strong> ${data.price.toFixed(2)}
<button
onClick={() => handleQuickBuy(symbol, 0.1)}
disabled={tradingLoading}
style={{ marginLeft: '10px', padding: '5px 10px' }}
>
{tradingLoading ? 'Placing...' : 'Quick Buy 0.1'}
</button>
</div>
))}
</div>
{tradingError && (
<div style={{ color: 'red' }}>Trading Error: {tradingError.message}</div>
)}
</div>
);
}
// ============================================
// Example 2: Context Provider Usage
// ============================================
/**
* App using the context provider for global state management
* This is the recommended pattern for larger applications
*/
export function ContextProviderApp() {
return (
<SimpleMiniAppProvider
config={{
appId: 'context-trader',
debug: true
}}
autoConnect={true}
autoRequestPermissions={true}
defaultMarketSubscriptions={['ETH', 'BTC', 'SOL']}
>
<TradingDashboard />
</SimpleMiniAppProvider>
);
}
function TradingDashboard() {
const {
connected,
sessionId,
marketData,
placeOrder,
hasPermission,
loading,
error
} = useSimpleMiniAppContext();
if (!connected) {
return <div>Connecting...</div>;
}
return (
<div style={{ padding: '20px' }}>
<h2>🏗️ Trading Dashboard</h2>
<p>Session: {sessionId}</p>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<MarketDataPanel />
<TradingPanel />
</div>
<AccountPanel />
{error && (
<div style={{ color: 'red', marginTop: '20px' }}>
Error: {error.message}
</div>
)}
</div>
);
}
function MarketDataPanel() {
const { marketData } = useSimpleMiniAppContext();
return (
<div style={{ border: '1px solid #ccc', padding: '15px', borderRadius: '8px' }}>
<h3>📊 Market Data</h3>
{Object.entries(marketData).map(([symbol, data]) => (
<div key={symbol} style={{
display: 'flex',
justifyContent: 'space-between',
padding: '8px 0',
borderBottom: '1px solid #eee'
}}>
<span><strong>{symbol}</strong></span>
<span>${data.price.toFixed(2)}</span>
{data.changePercent24h !== undefined && (
<span style={{
color: data.changePercent24h >= 0 ? 'green' : 'red'
}}>
{data.changePercent24h >= 0 ? '+' : ''}{data.changePercent24h.toFixed(2)}%
</span>
)}
</div>
))}
</div>
);
}
function TradingPanel() {
const { placeOrder, loading, hasPermission } = useSimpleMiniAppContext();
const [orderForm, setOrderForm] = useState<Partial<SimpleOrderData>>({
symbol: 'ETH',
side: 'buy',
orderType: 'market',
size: 0.1
});
const canTrade = hasPermission('place_orders');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!placeOrder || !orderForm.symbol || !orderForm.side || !orderForm.orderType || !orderForm.size) {
return;
}
try {
await placeOrder({
symbol: orderForm.symbol,
side: orderForm.side,
orderType: orderForm.orderType,
size: orderForm.size,
price: orderForm.price
});
alert('Order placed successfully!');
} catch (err: any) {
alert(`Order failed: ${err.message}`);
}
};
return (
<div style={{ border: '1px solid #ccc', padding: '15px', borderRadius: '8px' }}>
<h3>📈 Place Order</h3>
{!canTrade && (
<div style={{ color: 'orange', marginBottom: '10px' }}>
⚠️ Trading permissions required
</div>
)}
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '10px' }}>
<label>Symbol:</label>
<select
value={orderForm.symbol || ''}
onChange={(e) => setOrderForm(prev => ({ ...prev, symbol: e.target.value }))}
style={{ marginLeft: '10px', padding: '5px' }}
>
<option value="ETH">ETH</option>
<option value="BTC">BTC</option>
<option value="SOL">SOL</option>
</select>
</div>
<div style={{ marginBottom: '10px' }}>
<label>Side:</label>
<select
value={orderForm.side || ''}
onChange={(e) => setOrderForm(prev => ({ ...prev, side: e.target.value as 'buy' | 'sell' }))}
style={{ marginLeft: '10px', padding: '5px' }}
>
<option value="buy">Buy</option>
<option value="sell">Sell</option>
</select>
</div>
<div style={{ marginBottom: '10px' }}>
<label>Type:</label>
<select
value={orderForm.orderType || ''}
onChange={(e) => setOrderForm(prev => ({ ...prev, orderType: e.target.value as 'market' | 'limit' }))}
style={{ marginLeft: '10px', padding: '5px' }}
>
<option value="market">Market</option>
<option value="limit">Limit</option>
</select>
</div>
<div style={{ marginBottom: '10px' }}>
<label>Size:</label>
<input
type="number"
step="0.001"
value={orderForm.size || ''}
onChange={(e) => setOrderForm(prev => ({ ...prev, size: parseFloat(e.target.value) }))}
style={{ marginLeft: '10px', padding: '5px' }}
/>
</div>
{orderForm.orderType === 'limit' && (
<div style={{ marginBottom: '10px' }}>
<label>Price:</label>
<input
type="number"
step="0.01"
value={orderForm.price || ''}
onChange={(e) => setOrderForm(prev => ({ ...prev, price: parseFloat(e.target.value) }))}
style={{ marginLeft: '10px', padding: '5px' }}
/>
</div>
)}
<button
type="submit"
disabled={loading || !canTrade}
style={{
padding: '10px 20px',
backgroundColor: canTrade ? '#007bff' : '#ccc',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: canTrade ? 'pointer' : 'not-allowed'
}}
>
{loading ? 'Placing Order...' : 'Place Order'}
</button>
</form>
</div>
);
}
function AccountPanel() {
const { positions, balances, refreshPositions, refreshBalances } = useSimpleAccount();
useEffect(() => {
refreshPositions();
refreshBalances();
}, [refreshPositions, refreshBalances]);
return (
<div style={{ marginTop: '20px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<div style={{ border: '1px solid #ccc', padding: '15px', borderRadius: '8px' }}>
<h3>📋 Positions</h3>
{positions.length === 0 ? (
<p>No open positions</p>
) : (
positions.map(position => (
<div key={position.symbol} style={{
padding: '8px 0',
borderBottom: '1px solid #eee'
}}>
<div><strong>{position.symbol}</strong></div>
<div>Size: {position.size}</div>
<div>Entry: ${position.entryPrice.toFixed(2)}</div>
<div style={{
color: position.pnl >= 0 ? 'green' : 'red'
}}>
PnL: ${position.pnl.toFixed(2)} ({position.pnlPercent.toFixed(2)}%)
</div>
</div>
))
)}
<button
onClick={refreshPositions}
style={{ marginTop: '10px', padding: '5px 10px' }}
>
Refresh
</button>
</div>
<div style={{ border: '1px solid #ccc', padding: '15px', borderRadius: '8px' }}>
<h3>💰 Balances</h3>
{Object.keys(balances).length === 0 ? (
<p>No balances available</p>
) : (
Object.entries(balances).map(([asset, balance]) => (
<div key={asset} style={{
padding: '8px 0',
borderBottom: '1px solid #eee'
}}>
<div><strong>{asset}</strong></div>
<div>Free: {balance.free.toFixed(4)}</div>
<div>Locked: {balance.locked.toFixed(4)}</div>
<div>USD Value: ${balance.usdValue.toFixed(2)}</div>
</div>
))
)}
<button
onClick={() => refreshBalances()}
style={{ marginTop: '10px', padding: '5px 10px' }}
>
Refresh
</button>
</div>
</div>
</div>
);
}
// ============================================
// Example 3: Permission Gate Component
// ============================================
/**
* Component that manages permissions and gates access to features
*/
interface PermissionGateProps {
children: React.ReactNode;
requiredPermissions: SimplePermission[];
fallback?: React.ReactNode;
}
export function PermissionGate({
children,
requiredPermissions,
fallback
}: PermissionGateProps) {
const { hasPermission, requestPermission } = useSimplePermissionsContext();
const { loading } = useSimpleMiniAppContext();
const missingPermissions = requiredPermissions.filter(permission => !hasPermission(permission));
if (missingPermissions.length === 0) {
return <>{children}</>;
}
const handleRequestPermissions = async () => {
for (const permission of missingPermissions) {
await requestPermission(permission);
}
};
if (fallback) {
return <>{fallback}</>;
}
return (
<div style={{
padding: '20px',
border: '2px dashed #ffa500',
borderRadius: '8px',
textAlign: 'center'
}}>
<h4>🔐 Permissions Required</h4>
<p>This feature requires the following permissions:</p>
<ul style={{ textAlign: 'left', display: 'inline-block' }}>
{missingPermissions.map(permission => (
<li key={permission}>{permission.replace(/_/g, ' ')}</li>
))}
</ul>
<button
onClick={handleRequestPermissions}
disabled={loading}
style={{
padding: '10px 20px',
backgroundColor: '#ffa500',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
{loading ? 'Requesting...' : 'Grant Permissions'}
</button>
</div>
);
}
// ============================================
// Example 4: Connection Status Component
// ============================================
/**
* Component showing connection status with reconnect functionality
*/
export function ConnectionStatus() {
const config = { appId: 'connection-status-demo', debug: true };
const context = useSimpleMiniApp(config);
const { client, connected, sessionId, error } = context;
// Use the useSimpleConnection hook for connection management
const { connecting, connect, disconnect } = useSimpleConnection(client);
return (
<div style={{
padding: '10px',
backgroundColor: connected ? '#d4edda' : '#f8d7da',
border: `1px solid ${connected ? '#c3e6cb' : '#f5c6cb'}`,
borderRadius: '4px',
marginBottom: '20px'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<strong>Status:</strong> {
connected ? `Connected (${sessionId})` :
connecting ? 'Connecting...' :
'Disconnected'
}
</div>
<div>
{!connected && !connecting && (
<button onClick={connect} style={{ padding: '5px 10px' }}>
Connect
</button>
)}
{connected && (
<button onClick={disconnect} style={{ padding: '5px 10px' }}>
Disconnect
</button>
)}
</div>
</div>
{error && (
<div style={{ color: 'red', marginTop: '5px' }}>
Error: {error.message}
</div>
)}
</div>
);
}
// ============================================
// Example 5: Complete Mini-App
// ============================================
/**
* Complete mini-app example showing all patterns together
*/
export function CompleteMiniApp() {
return (
<SimpleMiniAppProvider
config={{
appId: 'complete-trader',
debug: true
}}
autoConnect={true}
autoRequestPermissions={true}
defaultMarketSubscriptions={['ETH', 'BTC', 'SOL']}
>
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '20px' }}>
<h1>🚀 Complete Trading Mini-App</h1>
<ConnectionStatus />
<PermissionGate requiredPermissions={['read_market_data']}>
<MarketDataPanel />
</PermissionGate>
<PermissionGate
requiredPermissions={['place_orders', 'cancel_orders']}
fallback={
<div style={{ padding: '20px', textAlign: 'center', color: '#666' }}>
Trading features require permissions
</div>
}
>
<TradingPanel />
</PermissionGate>
<PermissionGate requiredPermissions={['read_positions', 'read_balance']}>
<AccountPanel />
</PermissionGate>
</div>
</SimpleMiniAppProvider>
);
}
Last updated