# Live Order Dashboard

> Build a live order dashboard with real-time updates. Database change subscriptions push INSERT and UPDATE events to all connected clients.

Subscribe to a D1 table and watch changes arrive in real time — no `sdk.socket.emit()` needed. When the server runs a SQL UPDATE, ChangeTracker detects it and broadcasts the row to all subscribers automatically.

**Demonstrates:** D1 table subscriptions, `channel('orders')` (no slash = ChangeTracker), `on('INSERT')`, `on('UPDATE')`

**Source:** `examples/socket-hub/src/examples/LiveOrderDashboard/`

---

## How it works

```
Server runs: UPDATE orders SET status = 'shipped' WHERE id = '123'
  → ChangeTracker polls D1 every 200ms
  → Detects the mutation
  → Broadcasts UPDATE event to all subscribers on channel('orders')
  → Dashboard UI updates in real time — no manual emit required
```

The key difference from pub/sub: the channel name has **no slash**. This activates ChangeTracker for the `orders` table.

## Client code

```tsx

type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'

interface Order {
  id: string
  customer_name: string
  product: string
  amount: number
  status: OrderStatus
  created_at: number
}

const STATUS_COLORS: Record = {
  pending:    'bg-yellow-500/10 text-yellow-400',
  processing: 'bg-blue-500/10 text-blue-400',
  shipped:    'bg-purple-500/10 text-purple-400',
  delivered:  'bg-green-500/10 text-green-400',
  cancelled:  'bg-red-500/10 text-red-400',
}

  const { realtime } = useAerostack()
  const [orders, setOrders] = useState([])
  const [recentUpdate, setRecentUpdate] = useState<string | null>(null)

  useEffect(() => {
    // Load initial data
    fetch('/api/examples/orders')
      .then(r => r.json())
      .then(data => setOrders(data.orders ?? []))

    // Subscribe to live changes
    // Note: no slash in topic name → ChangeTracker activates for 'orders' table
    const channel = realtime.channel('orders')

    channel
      .on('INSERT', ({ data }) => {
        setOrders(prev => [data, ...prev])
        setRecentUpdate(`New order: ${data.id}`)
        setTimeout(() => setRecentUpdate(null), 3000)
      })
      .on('UPDATE', ({ data }) => {
        setOrders(prev => prev.map(o => o.id === data.id ? { ...o, ...data } : o))
        setRecentUpdate(`Order ${data.id} → ${data.status}`)
        setTimeout(() => setRecentUpdate(null), 3000)
      })
      .subscribe()

    return () => channel.unsubscribe()
  }, [realtime])

  const updateOrderStatus = async (orderId: string, status: OrderStatus) => {
    await fetch('/api/examples/orders/update', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ orderId, status }),
    })
    // No need to update local state — the subscription will receive the UPDATE event
  }

  return (
    <div className="p-6">
      <div className="flex items-center justify-between mb-6">
        <h1 className="text-2xl font-bold text-white">Orders</h1>
        {recentUpdate && (
          <div className="text-sm text-green-400 bg-green-500/10 px-4 py-2 rounded-full">
            ↻ {recentUpdate}
          </div>
        )}
      </div>

      <div className="space-y-3">
        {orders.map(order => (
          <div
            key={order.id}
            className="flex items-center justify-between p-4 rounded-xl border border-gray-800 bg-gray-900/50"
          >
            <div>
              <div className="font-medium text-white">{order.customer_name}</div>
              <div className="text-sm text-gray-400">{order.product} · ${order.amount}</div>
            </div>
            <div className="flex items-center gap-3">
              <span className={`text-xs px-3 py-1 rounded-full font-medium ${STATUS_COLORS[order.status]}`}>
                {order.status}
              </span>
              <select
                value={order.status}
                onChange={e => updateOrderStatus(order.id, e.target.value as OrderStatus)}
                className="text-xs bg-gray-800 border border-gray-700 rounded-lg px-2 py-1 text-gray-300"
              >
                <option value="pending">pending</option>
                <option value="processing">processing</option>
                <option value="shipped">shipped</option>
                <option value="delivered">delivered</option>
                <option value="cancelled">cancelled</option>
              </select>
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}
```

## Server code

```ts

const app = new Hono()

// Setup: create orders table
app.post('/setup', async (c) => {
  await sdk.db.exec(`
    CREATE TABLE IF NOT EXISTS orders (
      id TEXT PRIMARY KEY,
      customer_name TEXT NOT NULL,
      product TEXT NOT NULL,
      amount REAL NOT NULL,
      status TEXT DEFAULT 'pending',
      created_at INTEGER NOT NULL
    )
  `)
  return c.json({ ok: true })
})

// List orders
app.get('/examples/orders', async (c) => {
  const { results } = await sdk.db.query(
    'SELECT * FROM orders ORDER BY created_at DESC LIMIT 50'
  )
  return c.json({ orders: results })
})

// Update order status — ChangeTracker broadcasts the UPDATE automatically
app.post('/examples/orders/update', async (c) => {
  const { orderId, status } = await c.req.json()

  await sdk.db.query(
    'UPDATE orders SET status = ? WHERE id = ?',
    [status, orderId]
  )

  // No sdk.socket.emit needed — ChangeTracker handles the broadcast
  return c.json({ ok: true })
})

```

ChangeTracker polls D1 every 200ms. For very high-frequency updates, consider debouncing on the client side to avoid UI thrashing.
