Skip to content

Analytics Dashboard

Build a custom analytics dashboard using Pixlpay API data.

Overview

This pattern shows how to:

  1. Fetch analytics data from the API
  2. Aggregate and transform data
  3. Display in a dashboard

Data Collection

Fetch Dashboard Data

javascript
const { PixlpayClient } = require('./pixlpay');

const pixlpay = new PixlpayClient(
  process.env.PIXLPAY_STORE_URL,
  process.env.PIXLPAY_API_TOKEN
);

async function getDashboardData(startDate, endDate) {
  const [revenue, sales, recentOrders] = await Promise.all([
    pixlpay.getRevenueAnalytics({
      start_date: startDate,
      end_date: endDate,
      group_by: 'day'
    }),
    pixlpay.getSalesAnalytics({
      start_date: startDate,
      end_date: endDate
    }),
    pixlpay.getOrders({
      start_date: startDate,
      end_date: endDate,
      per_page: 10,
      sort_by: 'created_at',
      sort_order: 'desc'
    })
  ]);

  return {
    summary: {
      totalRevenue: parseFloat(revenue.data.summary.total_revenue),
      totalOrders: revenue.data.summary.total_orders,
      averageOrderValue: parseFloat(revenue.data.summary.average_order_value),
    },
    revenueChart: revenue.data.breakdown.map(d => ({
      date: d.period,
      revenue: parseFloat(d.total_revenue),
      orders: d.order_count
    })),
    topProducts: sales.data.top_products.slice(0, 5),
    recentOrders: recentOrders.data
  };
}

Compare Periods

javascript
async function getComparison(currentStart, currentEnd, previousStart, previousEnd) {
  const [current, previous] = await Promise.all([
    pixlpay.getRevenueAnalytics({ start_date: currentStart, end_date: currentEnd }),
    pixlpay.getRevenueAnalytics({ start_date: previousStart, end_date: previousEnd })
  ]);

  const currentRevenue = parseFloat(current.data.summary.total_revenue);
  const previousRevenue = parseFloat(previous.data.summary.total_revenue);

  return {
    current: current.data.summary,
    previous: previous.data.summary,
    revenueChange: ((currentRevenue - previousRevenue) / previousRevenue) * 100,
    ordersChange: ((current.data.summary.total_orders - previous.data.summary.total_orders)
      / previous.data.summary.total_orders) * 100
  };
}

API Endpoint

javascript
const express = require('express');
const app = express();

app.get('/api/dashboard', async (req, res) => {
  const { start_date, end_date } = req.query;

  try {
    const data = await getDashboardData(start_date, end_date);
    res.json(data);
  } catch (error) {
    console.error('Dashboard error:', error);
    res.status(500).json({ error: 'Failed to fetch dashboard data' });
  }
});

app.get('/api/dashboard/comparison', async (req, res) => {
  const today = new Date();
  const thirtyDaysAgo = new Date(today - 30 * 24 * 60 * 60 * 1000);
  const sixtyDaysAgo = new Date(today - 60 * 24 * 60 * 60 * 1000);

  const data = await getComparison(
    thirtyDaysAgo.toISOString().split('T')[0],
    today.toISOString().split('T')[0],
    sixtyDaysAgo.toISOString().split('T')[0],
    thirtyDaysAgo.toISOString().split('T')[0]
  );

  res.json(data);
});

app.listen(3000);

React Dashboard Component

jsx
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, Tooltip } from 'recharts';

function Dashboard() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [dateRange, setDateRange] = useState('30d');

  useEffect(() => {
    fetchDashboard();
  }, [dateRange]);

  async function fetchDashboard() {
    setLoading(true);
    const endDate = new Date().toISOString().split('T')[0];
    const days = dateRange === '7d' ? 7 : dateRange === '30d' ? 30 : 90;
    const startDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
      .toISOString().split('T')[0];

    const response = await fetch(
      `/api/dashboard?start_date=${startDate}&end_date=${endDate}`
    );
    const result = await response.json();
    setData(result);
    setLoading(false);
  }

  if (loading) return <div>Loading...</div>;

  return (
    <div className="dashboard">
      {/* Summary Cards */}
      <div className="summary-cards">
        <div className="card">
          <h3>Revenue</h3>
          <p className="value">${data.summary.totalRevenue.toFixed(2)}</p>
        </div>
        <div className="card">
          <h3>Orders</h3>
          <p className="value">{data.summary.totalOrders}</p>
        </div>
        <div className="card">
          <h3>Avg Order</h3>
          <p className="value">${data.summary.averageOrderValue.toFixed(2)}</p>
        </div>
      </div>

      {/* Revenue Chart */}
      <div className="chart-container">
        <h3>Revenue Over Time</h3>
        <LineChart width={800} height={300} data={data.revenueChart}>
          <XAxis dataKey="date" />
          <YAxis />
          <Tooltip />
          <Line type="monotone" dataKey="revenue" stroke="#10B981" />
        </LineChart>
      </div>

      {/* Top Products */}
      <div className="top-products">
        <h3>Top Products</h3>
        <table>
          <thead>
            <tr>
              <th>Product</th>
              <th>Units Sold</th>
              <th>Revenue</th>
            </tr>
          </thead>
          <tbody>
            {data.topProducts.map(product => (
              <tr key={product.id}>
                <td>{product.name}</td>
                <td>{product.units_sold}</td>
                <td>${parseFloat(product.total_revenue).toFixed(2)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Recent Orders */}
      <div className="recent-orders">
        <h3>Recent Orders</h3>
        <table>
          <thead>
            <tr>
              <th>Order</th>
              <th>Customer</th>
              <th>Total</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody>
            {data.recentOrders.map(order => (
              <tr key={order.id}>
                <td>{order.order_number}</td>
                <td>{order.customer_email}</td>
                <td>${order.total}</td>
                <td>{order.status}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Caching

Cache API responses to reduce load:

javascript
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5 minute cache

async function getCachedDashboard(startDate, endDate) {
  const cacheKey = `dashboard:${startDate}:${endDate}`;
  let data = cache.get(cacheKey);

  if (!data) {
    data = await getDashboardData(startDate, endDate);
    cache.set(cacheKey, data);
  }

  return data;
}

Real-Time Updates

Update dashboard when orders come in:

javascript
// Webhook handler
app.post('/webhooks/pixlpay', (req, res) => {
  const event = req.body;

  if (event.event_type === 'order.paid') {
    // Invalidate cache
    cache.flushAll();

    // Notify connected clients via WebSocket
    io.emit('order:new', {
      order_number: event.data.order_number,
      total: event.data.total
    });
  }

  res.status(200).json({ status: 'received' });
});

// Client-side
socket.on('order:new', (order) => {
  // Refresh dashboard data
  fetchDashboard();

  // Show notification
  showToast(`New order: ${order.order_number} - $${order.total}`);
});

Built for game developers, by game developers.