# React Sample MiniApp

### Table of Contents

* Basic Setup
* Simple Price Display
* Trading Form
* Complete Trading Dashboard
* Advanced Patterns

### Basic Setup

#### Installation

```bash
npx create-react-app my-miniapp --template typescript
cd my-miniapp
npm install @basedone/miniapp-sdk
```

#### App Structure

```tsx
// src/App.tsx
import React from 'react';
import { MiniAppProvider } from '@basedone/miniapp-sdk/react';
import { TradingApp } from './components/TradingApp';

function App() {
  return (
    <MiniAppProvider
      config={{
        appId: 'my-miniapp',
        permissions: ['read_market_data', 'place_orders'],
        debug: true,
        autoConnect: true
      }}
      autoRequestPermissions={true}
      defaultMarketSubscriptions={['ETH', 'BTC']}
    >
      <TradingApp />
    </MiniAppProvider>
  );
}

export default App;
```

### Simple Price Display

A minimal component that displays real-time prices.

```tsx
// src/components/PriceDisplay.tsx
import React from 'react';
import { useMiniApp, useMarketData } from '@basedone/miniapp-sdk/react';

export function PriceDisplay() {
  const { connected, sessionId } = useMiniApp();
  const marketData = useMarketData(['ETH', 'BTC', 'SOL']);

  if (!connected) {
    return <div>Connecting to Based Terminal...</div>;
  }

  return (
    <div style={{ padding: '20px', fontFamily: 'monospace' }}>
      <h2>Real-time Prices</h2>
      <p style={{ color: '#00ff88' }}>Session: {sessionId}</p>

      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
        gap: '20px',
        marginTop: '20px'
      }}>
        {Object.entries(marketData).map(([symbol, data]) => (
          <div key={symbol} style={{
            background: '#1a1a1a',
            padding: '20px',
            borderRadius: '12px',
            border: '1px solid #333'
          }}>
            <div style={{ fontSize: '18px', fontWeight: 'bold' }}>
              {symbol}
            </div>
            <div style={{ fontSize: '32px', color: '#00ff88', marginTop: '10px' }}>
              ${data.price?.toFixed(2) || '--'}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}
```

### Trading Form

A complete order form with all features.

```tsx
// src/components/TradingForm.tsx
import React, { useState } from 'react';
import { useMiniApp, useCommand } from '@basedone/miniapp-sdk/react';

export function TradingForm() {
  const { connected, hasPermission } = useMiniApp();
  const { execute: placeOrder, loading, error } = useCommand('order.submit');

  const [symbol, setSymbol] = useState('ETH');
  const [side, setSide] = useState<'buy' | 'sell'>('buy');
  const [orderType, setOrderType] = useState<'market' | 'limit'>('market');
  const [size, setSize] = useState('0.1');
  const [price, setPrice] = useState('');
  const [tpPrice, setTpPrice] = useState('');
  const [slPrice, setSlPrice] = useState('');

  const canTrade = hasPermission('place_orders');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!canTrade) {
      alert('Missing trading permissions');
      return;
    }

    try {
      const orderData: any = {
        symbol,
        side,
        orderType,
        size: parseFloat(size)
      };

      if (orderType === 'limit' && price) {
        orderData.price = parseFloat(price);
      }

      if (tpPrice || slPrice) {
        orderData.tpsl = {
          tpPrice: tpPrice ? parseFloat(tpPrice) : undefined,
          slPrice: slPrice ? parseFloat(slPrice) : undefined
        };
      }

      const result = await placeOrder(orderData);

      if (result.success) {
        alert('Order placed successfully!');
        // Reset form
        setSize('0.1');
        setPrice('');
        setTpPrice('');
        setSlPrice('');
      }
    } catch (err: any) {
      alert(`Order failed: ${err.message}`);
    }
  };

  if (!connected) {
    return <div>Connecting...</div>;
  }

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto', padding: '20px' }}>
      <h2>Place Order</h2>

      {!canTrade && (
        <div style={{
          padding: '10px',
          background: '#ff4444',
          borderRadius: '4px',
          marginBottom: '15px'
        }}>
          Trading permissions required
        </div>
      )}

      <form onSubmit={handleSubmit}>
        {/* Symbol */}
        <div style={{ marginBottom: '15px' }}>
          <label style={{ display: 'block', marginBottom: '5px' }}>
            Symbol
          </label>
          <select
            value={symbol}
            onChange={(e) => setSymbol(e.target.value)}
            style={{
              width: '100%',
              padding: '10px',
              background: '#1a1a1a',
              border: '1px solid #333',
              borderRadius: '4px',
              color: '#fff'
            }}
          >
            <option value="ETH">ETH</option>
            <option value="BTC">BTC</option>
            <option value="SOL">SOL</option>
          </select>
        </div>

        {/* Side */}
        <div style={{
          display: 'grid',
          gridTemplateColumns: '1fr 1fr',
          gap: '10px',
          marginBottom: '15px'
        }}>
          <button
            type="button"
            onClick={() => setSide('buy')}
            style={{
              padding: '12px',
              background: side === 'buy' ? '#00ff88' : '#333',
              color: side === 'buy' ? '#000' : '#fff',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer',
              fontWeight: 'bold'
            }}
          >
            Buy
          </button>
          <button
            type="button"
            onClick={() => setSide('sell')}
            style={{
              padding: '12px',
              background: side === 'sell' ? '#ff4444' : '#333',
              color: '#fff',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer',
              fontWeight: 'bold'
            }}
          >
            Sell
          </button>
        </div>

        {/* Order Type */}
        <div style={{ marginBottom: '15px' }}>
          <label style={{ display: 'block', marginBottom: '5px' }}>
            Order Type
          </label>
          <select
            value={orderType}
            onChange={(e) => setOrderType(e.target.value as 'market' | 'limit')}
            style={{
              width: '100%',
              padding: '10px',
              background: '#1a1a1a',
              border: '1px solid #333',
              borderRadius: '4px',
              color: '#fff'
            }}
          >
            <option value="market">Market</option>
            <option value="limit">Limit</option>
          </select>
        </div>

        {/* Size */}
        <div style={{ marginBottom: '15px' }}>
          <label style={{ display: 'block', marginBottom: '5px' }}>
            Size
          </label>
          <input
            type="number"
            value={size}
            onChange={(e) => setSize(e.target.value)}
            step="0.01"
            required
            style={{
              width: '100%',
              padding: '10px',
              background: '#1a1a1a',
              border: '1px solid #333',
              borderRadius: '4px',
              color: '#fff'
            }}
          />
        </div>

        {/* Limit Price */}
        {orderType === 'limit' && (
          <div style={{ marginBottom: '15px' }}>
            <label style={{ display: 'block', marginBottom: '5px' }}>
              Limit Price
            </label>
            <input
              type="number"
              value={price}
              onChange={(e) => setPrice(e.target.value)}
              step="0.01"
              required={orderType === 'limit'}
              style={{
                width: '100%',
                padding: '10px',
                background: '#1a1a1a',
                border: '1px solid #333',
                borderRadius: '4px',
                color: '#fff'
              }}
            />
          </div>
        )}

        {/* TP/SL */}
        <div style={{
          display: 'grid',
          gridTemplateColumns: '1fr 1fr',
          gap: '10px',
          marginBottom: '15px'
        }}>
          <div>
            <label style={{ display: 'block', marginBottom: '5px' }}>
              Take Profit
            </label>
            <input
              type="number"
              value={tpPrice}
              onChange={(e) => setTpPrice(e.target.value)}
              placeholder="Optional"
              step="0.01"
              style={{
                width: '100%',
                padding: '10px',
                background: '#1a1a1a',
                border: '1px solid #333',
                borderRadius: '4px',
                color: '#fff'
              }}
            />
          </div>
          <div>
            <label style={{ display: 'block', marginBottom: '5px' }}>
              Stop Loss
            </label>
            <input
              type="number"
              value={slPrice}
              onChange={(e) => setSlPrice(e.target.value)}
              placeholder="Optional"
              step="0.01"
              style={{
                width: '100%',
                padding: '10px',
                background: '#1a1a1a',
                border: '1px solid #333',
                borderRadius: '4px',
                color: '#fff'
              }}
            />
          </div>
        </div>

        {/* Submit Button */}
        <button
          type="submit"
          disabled={loading || !canTrade}
          style={{
            width: '100%',
            padding: '15px',
            background: canTrade && !loading ? '#00ff88' : '#333',
            color: canTrade && !loading ? '#000' : '#666',
            border: 'none',
            borderRadius: '4px',
            cursor: canTrade && !loading ? 'pointer' : 'not-allowed',
            fontWeight: 'bold',
            fontSize: '16px'
          }}
        >
          {loading ? 'Placing Order...' : `${side.toUpperCase()} ${symbol}`}
        </button>

        {error && (
          <div style={{
            marginTop: '15px',
            padding: '10px',
            background: '#ff4444',
            borderRadius: '4px',
            color: '#fff'
          }}>
            Error: {error.message}
          </div>
        )}
      </form>
    </div>
  );
}
```

### Complete Trading Dashboard

A comprehensive dashboard with multiple features.

```tsx
// src/components/TradingDashboard.tsx
import React, { useEffect } from 'react';
import { useMiniApp, usePositions, useWalletAddress } from '@basedone/miniapp-sdk/react';
import { PricePanel } from './PricePanel';
import { TradingForm } from './TradingForm';
import { PositionsPanel } from './PositionsPanel';
import { OrdersPanel } from './OrdersPanel';

export function TradingDashboard() {
  const {
    client,
    connected,
    sessionId,
    loading,
    error
  } = useMiniApp();

  if (error) {
    return (
      <div style={{
        padding: '20px',
        background: '#ff4444',
        borderRadius: '8px',
        color: '#fff'
      }}>
        Error: {error.message}
      </div>
    );
  }

  if (!connected || loading) {
    return (
      <div style={{
        padding: '40px',
        textAlign: 'center',
        fontSize: '18px'
      }}>
        Connecting to Based Terminal...
      </div>
    );
  }

  return (
    <div style={{
      maxWidth: '1400px',
      margin: '0 auto',
      padding: '20px'
    }}>
      <Header sessionId={sessionId} />

      <div style={{
        display: 'grid',
        gridTemplateColumns: '1fr 1fr',
        gap: '20px',
        marginTop: '20px'
      }}>
        <PricePanel />
        <TradingForm />
        <PositionsPanel />
        <OrdersPanel />
      </div>
    </div>
  );
}

function Header({ sessionId }: { sessionId?: string }) {
  const { address } = useWalletAddress();

  return (
    <div style={{
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'center',
      padding: '20px',
      background: '#1a1a1a',
      borderRadius: '12px',
      border: '1px solid #333'
    }}>
      <h1 style={{ margin: 0 }}>Trading Dashboard</h1>
      <div style={{ textAlign: 'right' }}>
        <div style={{ color: '#00ff88', fontSize: '14px' }}>
          Session: {sessionId}
        </div>
        {address && (
          <div style={{ color: '#aaa', fontSize: '12px', marginTop: '5px' }}>
            Wallet: {address.slice(0, 6)}...{address.slice(-4)}
          </div>
        )}
      </div>
    </div>
  );
}

// src/components/PricePanel.tsx
export function PricePanel() {
  const marketData = useMarketData(['ETH', 'BTC', 'SOL']);

  return (
    <div style={{
      background: '#1a1a1a',
      padding: '20px',
      borderRadius: '12px',
      border: '1px solid #333'
    }}>
      <h2 style={{ marginTop: 0 }}>Market Prices</h2>

      {Object.entries(marketData).map(([symbol, data]) => (
        <div key={symbol} style={{
          padding: '15px',
          background: '#000',
          borderRadius: '8px',
          marginBottom: '10px'
        }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <span style={{ fontSize: '18px', fontWeight: 'bold' }}>
              {symbol}
            </span>
            <span style={{ fontSize: '24px', color: '#00ff88' }}>
              ${data.price?.toFixed(2) || '--'}
            </span>
          </div>
        </div>
      ))}
    </div>
  );
}

// src/components/PositionsPanel.tsx
export function PositionsPanel() {
  const { client } = useMiniApp();
  const { positions, refresh, loading } = usePositions(client);

  useEffect(() => {
    refresh();
  }, [refresh]);

  return (
    <div style={{
      background: '#1a1a1a',
      padding: '20px',
      borderRadius: '12px',
      border: '1px solid #333'
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
        <h2 style={{ margin: 0 }}>Positions</h2>
        <button
          onClick={refresh}
          disabled={loading}
          style={{
            padding: '8px 16px',
            background: '#00ff88',
            color: '#000',
            border: 'none',
            borderRadius: '4px',
            cursor: loading ? 'not-allowed' : 'pointer',
            fontWeight: 'bold'
          }}
        >
          {loading ? 'Loading...' : 'Refresh'}
        </button>
      </div>

      {positions.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
          No open positions
        </div>
      ) : (
        <div>
          {positions.map((pos: any) => {
            const pnl = pos.unrealizedPnl || 0;
            const pnlColor = pnl >= 0 ? '#00ff88' : '#ff4444';

            return (
              <div key={pos.symbol} style={{
                padding: '15px',
                background: '#000',
                borderRadius: '8px',
                marginBottom: '10px'
              }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
                  <span style={{ fontSize: '18px', fontWeight: 'bold' }}>
                    {pos.symbol}
                  </span>
                  <span>Size: {pos.size}</span>
                </div>
                <div style={{ fontSize: '14px', color: '#aaa' }}>
                  Entry: ${pos.entryPrice.toFixed(2)}
                </div>
                <div style={{ fontSize: '18px', color: pnlColor, fontWeight: 'bold', marginTop: '8px' }}>
                  PnL: ${pnl.toFixed(2)}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// src/components/OrdersPanel.tsx
export function OrdersPanel() {
  const { client, orders, getOpenOrders, refreshOrders, loading } = useMiniApp();
  const { execute: cancelOrder } = useCommand('order.cancel');

  useEffect(() => {
    getOpenOrders();
  }, [getOpenOrders]);

  const handleCancel = async (orderId: string, symbol: string) => {
    try {
      await cancelOrder({ orderId, symbol });
      await refreshOrders();
    } catch (error: any) {
      alert(`Cancel failed: ${error.message}`);
    }
  };

  return (
    <div style={{
      background: '#1a1a1a',
      padding: '20px',
      borderRadius: '12px',
      border: '1px solid #333'
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
        <h2 style={{ margin: 0 }}>Open Orders</h2>
        <button
          onClick={refreshOrders}
          disabled={loading}
          style={{
            padding: '8px 16px',
            background: '#00ff88',
            color: '#000',
            border: 'none',
            borderRadius: '4px',
            cursor: loading ? 'not-allowed' : 'pointer',
            fontWeight: 'bold'
          }}
        >
          {loading ? 'Loading...' : 'Refresh'}
        </button>
      </div>

      {orders.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
          No open orders
        </div>
      ) : (
        <div>
          {orders.map((order: any) => (
            <div key={order.orderId} style={{
              padding: '15px',
              background: '#000',
              borderRadius: '8px',
              marginBottom: '10px',
              borderLeft: `3px solid ${order.side === 'buy' ? '#00ff88' : '#ff4444'}`
            }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '10px' }}>
                <span style={{ fontSize: '18px', fontWeight: 'bold' }}>
                  {order.symbol}
                </span>
                <span style={{
                  padding: '4px 8px',
                  background: order.side === 'buy' ? '#00ff8822' : '#ff444422',
                  color: order.side === 'buy' ? '#00ff88' : '#ff4444',
                  borderRadius: '4px',
                  fontSize: '12px',
                  fontWeight: 'bold'
                }}>
                  {order.side.toUpperCase()}
                </span>
              </div>
              <div style={{ fontSize: '14px', color: '#aaa', marginBottom: '10px' }}>
                {order.size} @ {order.price ? `$${order.price}` : 'Market'}
              </div>
              <button
                onClick={() => handleCancel(order.orderId, order.symbol)}
                style={{
                  padding: '6px 12px',
                  background: '#ff4444',
                  color: '#fff',
                  border: 'none',
                  borderRadius: '4px',
                  cursor: 'pointer',
                  fontSize: '12px',
                  fontWeight: 'bold'
                }}
              >
                Cancel Order
              </button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
```

### Advanced Patterns

#### Custom Hook for Batch Orders

```tsx
// src/hooks/useBatchTrading.ts
import { useState, useCallback } from 'react';
import { useMiniApp } from '@basedone/miniapp-sdk/react';
import type { SubmitOrderCommand } from '@basedone/miniapp-sdk';

export function useBatchTrading() {
  const { client, placeOrders } = useMiniApp();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const placeBatch = useCallback(async (orders: SubmitOrderCommand[]) => {
    if (!placeOrders) {
      throw new Error('SDK not initialized');
    }

    setLoading(true);
    setError(null);

    try {
      const result = await placeOrders(orders);

      if (result.success && result.data) {
        const { successCount, failureCount, results } = result.data;
        console.log(`Batch complete: ${successCount} success, ${failureCount} failed`);

        // Return detailed results
        return {
          success: true,
          successCount,
          failureCount,
          results
        };
      }

      throw new Error('Batch order failed');
    } catch (err) {
      setError(err as Error);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [placeOrders]);

  return {
    placeBatch,
    loading,
    error
  };
}

// Usage in component
export function BatchTradingPanel() {
  const { placeBatch, loading } = useBatchTrading();

  const handleGridOrders = async () => {
    const basePrice = 2500;
    const gridSize = 5;
    const stepSize = 50;

    // Create grid of limit orders
    const orders = Array.from({ length: gridSize }, (_, i) => ({
      symbol: 'ETH',
      side: 'buy' as const,
      orderType: 'limit' as const,
      price: basePrice - (i * stepSize),
      size: 0.1
    }));

    try {
      const result = await placeBatch(orders);
      alert(`Placed ${result.successCount} orders`);
    } catch (error: any) {
      alert(`Batch failed: ${error.message}`);
    }
  };

  return (
    <button
      onClick={handleGridOrders}
      disabled={loading}
      style={{
        padding: '12px 24px',
        background: '#00ff88',
        color: '#000',
        border: 'none',
        borderRadius: '4px',
        cursor: loading ? 'not-allowed' : 'pointer',
        fontWeight: 'bold'
      }}
    >
      {loading ? 'Placing...' : 'Place Grid Orders'}
    </button>
  );
}
```

#### Permission Gate Component

```tsx
// src/components/PermissionGate.tsx
import React, { ReactNode } from 'react';
import { useMiniApp } from '@basedone/miniapp-sdk/react';
import type { AppPermission } from '@basedone/miniapp-sdk';

interface PermissionGateProps {
  children: ReactNode;
  requiredPermissions: AppPermission[];
  fallback?: ReactNode;
}

export function PermissionGate({
  children,
  requiredPermissions,
  fallback
}: PermissionGateProps) {
  const { hasPermission, requestPermission, loading } = useMiniApp();

  const missingPermissions = requiredPermissions.filter(
    (perm) => !hasPermission(perm)
  );

  if (missingPermissions.length === 0) {
    return <>{children}</>;
  }

  const handleRequest = async () => {
    for (const perm of missingPermissions) {
      await requestPermission(perm);
    }
  };

  if (fallback) {
    return <>{fallback}</>;
  }

  return (
    <div style={{
      padding: '30px',
      background: '#1a1a1a',
      borderRadius: '12px',
      border: '2px dashed #ffa500',
      textAlign: 'center'
    }}>
      <h3 style={{ color: '#ffa500', marginTop: 0 }}>
        Permissions Required
      </h3>
      <p>This feature requires additional permissions:</p>
      <ul style={{ listStyle: 'none', padding: 0, margin: '20px 0' }}>
        {missingPermissions.map((perm) => (
          <li key={perm} style={{
            padding: '8px',
            background: '#000',
            margin: '5px 0',
            borderRadius: '4px'
          }}>
            {perm.replace(/_/g, ' ')}
          </li>
        ))}
      </ul>
      <button
        onClick={handleRequest}
        disabled={loading}
        style={{
          padding: '12px 24px',
          background: '#ffa500',
          color: '#000',
          border: 'none',
          borderRadius: '4px',
          cursor: loading ? 'not-allowed' : 'pointer',
          fontWeight: 'bold',
          fontSize: '16px'
        }}
      >
        {loading ? 'Requesting...' : 'Grant Permissions'}
      </button>
    </div>
  );
}

// Usage
export function ProtectedTradingForm() {
  return (
    <PermissionGate requiredPermissions={['place_orders', 'cancel_orders']}>
      <TradingForm />
    </PermissionGate>
  );
}
```

#### Real-time Event Listener

```tsx
// src/components/EventMonitor.tsx
import React, { useState, useEffect } from 'react';
import { useMiniApp, useEvent } from '@basedone/miniapp-sdk/react';

export function EventMonitor() {
  const { client } = useMiniApp();
  const [events, setEvents] = useState<string[]>([]);

  // Listen to order updates
  useEvent(client, 'order.update', (order: any) => {
    const msg = `Order ${order.orderId}: ${order.status}`;
    setEvents(prev => [msg, ...prev].slice(0, 20)); // Keep last 20 events
  });

  // Listen to position updates
  useEvent(client, 'position.update', (position: any) => {
    const msg = `Position ${position.symbol} updated`;
    setEvents(prev => [msg, ...prev].slice(0, 20));
  });

  // Listen to wallet switches
  useEvent(client, 'wallet.switch', (event: any) => {
    const msg = `Wallet switched to ${event.address}`;
    setEvents(prev => [msg, ...prev].slice(0, 20));
  });

  return (
    <div style={{
      background: '#1a1a1a',
      padding: '20px',
      borderRadius: '12px',
      border: '1px solid #333'
    }}>
      <h3>Event Monitor</h3>
      <div style={{
        background: '#000',
        padding: '15px',
        borderRadius: '8px',
        maxHeight: '300px',
        overflowY: 'auto',
        fontFamily: 'monospace',
        fontSize: '12px'
      }}>
        {events.length === 0 ? (
          <div style={{ color: '#666' }}>No events yet...</div>
        ) : (
          events.map((event, i) => (
            <div key={i} style={{ padding: '5px 0', borderBottom: '1px solid #222' }}>
              [{new Date().toLocaleTimeString()}] {event}
            </div>
          ))
        )}
      </div>
    </div>
  );
}
```

***

**That's it!** You now have comprehensive examples for building MiniApps with React. Check out the [API Reference ](https://basedapp.gitbook.io/docs/integrations/based-miniapp/api)for complete documentation.

```tsx
      }}
    >
      <div className="App">
        <TradingBot />
      </div>
    </BasedMiniAppProvider>
  );
}

export default App;

```
