# Dashboard API

Extend the DrakoBot dashboard with custom pages, navigation items, and API endpoints.

***

## ✨ Features

* **Custom Pages** — Add your own React pages to the dashboard
* **Navigation Integration** — Seamlessly add items to the sidebar
* **API Routes** — Create custom backend endpoints for your addon
* **Full React Support** — Use hooks, state, and all React features
* **Tailwind CSS** — Style with the dashboard's existing design system
* **Authentication** — Automatic auth handling for protected endpoints
* **Hot Reload** — Changes reflect instantly during development

***

## 🚀 Quick Start

### Folder Structure

```
addons/
└── my-addon/
    ├── my-addon.js          # Main addon file (events, etc.)
    └── dashboard/
        ├── config.js         # Dashboard configuration
        └── pages/
            ├── index.jsx     # Main page component
            └── settings.jsx  # Additional pages
```

### Example: Complete Dashboard Addon

**`addons/my-addon/dashboard/config.js`**

{% code expandable="true" %}

```javascript
module.exports = {
    // Register pages
    pages: [
        {
            path: '/addon/my-addon',
            component: 'index',
            title: 'My Addon'
        },
        {
            path: '/addon/my-addon/settings',
            component: 'settings',
            title: 'My Addon Settings'
        }
    ],
    
    // Add to sidebar navigation
    navItems: [
        {
            name: 'My Addon',
            path: '/addon/my-addon',
            emoji: '🚀',
            permission: null,  // null = any logged in user
            order: 50          // Lower = higher in sidebar
        }
    ],
    
    // Custom API endpoints
    apiRoutes: [
        {
            method: 'get',
            path: '/stats',
            handler: async (req, res) => {
                res.json({
                    message: 'Hello from My Addon!',
                    timestamp: new Date().toISOString()
                });
            }
        },
        {
            method: 'post',
            path: '/save',
            handler: async (req, res) => {
                const { setting } = req.body;
                // Save to database...
                res.json({ success: true, setting });
            }
        }
    ]
};
```

{% endcode %}

**`addons/my-addon/dashboard/pages/index.jsx`**

{% code expandable="true" %}

```jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function MyAddonPage() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const fetchData = async () => {
            try {
                // API path: /api/addons/{addonname}/{path}
                const response = await axios.get('/api/addons/myaddon/stats');
                setData(response.data);
            } catch (error) {
                console.error('Failed to fetch:', error);
            } finally {
                setLoading(false);
            }
        };
        fetchData();
    }, []);

    return (
        <div className="px-4 lg:px-6 space-y-6">
            <div className="glass-card p-6">
                <h1 className="text-xl font-bold text-foreground mb-4">
                    My Addon Dashboard
                </h1>
                
                {loading ? (
                    <p className="text-muted-foreground">Loading...</p>
                ) : (
                    <div className="glass-subtle rounded-xl p-4">
                        <p className="text-foreground">{data?.message}</p>
                        <p className="text-xs text-muted-foreground mt-2">
                            Last updated: {data?.timestamp}
                        </p>
                    </div>
                )}
            </div>
        </div>
    );
}
```

{% endcode %}

***

## 📄 Configuration Reference

### Pages

Register custom pages in your addon:

```javascript
pages: [
    {
        path: '/addon/my-addon',      // URL path (required)
        component: 'index',            // File in pages/ folder (required)
        title: 'My Addon',             // Page title (optional)
        requiredRoles: null            // Array of role IDs or null (optional)
    }
]
```

| Property        | Type            | Required | Description                              |
| --------------- | --------------- | -------- | ---------------------------------------- |
| `path`          | string          | ✅        | URL path for the page                    |
| `component`     | string          | ✅        | Filename in `pages/` (without extension) |
| `title`         | string          | ❌        | Browser tab title                        |
| `requiredRoles` | string\[]\|null | ❌        | Discord role IDs required to access      |

### Navigation Items

Add items to the dashboard sidebar:

```javascript
navItems: [
    {
        name: 'My Addon',              // Display name (required)
        path: '/addon/my-addon',       // Link destination (required)
        emoji: '🚀',                   // Emoji icon (optional)
        icon: 'faPuzzlePiece',         // FontAwesome icon (optional)
        requiredRoles: null,           // Array of role IDs or null (optional)
        order: 50                      // Sort order (optional)
    }
]
```

| Property        | Type            | Required | Description                           |
| --------------- | --------------- | -------- | ------------------------------------- |
| `name`          | string          | ✅        | Display name in sidebar               |
| `path`          | string          | ✅        | Navigation destination                |
| `emoji`         | string          | ❌        | Emoji to display                      |
| `icon`          | string          | ❌        | FontAwesome icon name                 |
| `requiredRoles` | string\[]\|null | ❌        | Discord role IDs required to see item |
| `order`         | number          | ❌        | Sort priority (lower = higher)        |

### API Routes

Create custom backend endpoints:

```javascript
apiRoutes: [
    {
        method: 'get',                 // HTTP method (required)
        path: '/stats',                // Endpoint path (required)
        requiredRoles: null,           // Array of role IDs or null (optional)
        handler: async (req, res) => { // Handler function (required)
            res.json({ data: 'value' });
        }
    }
]
```

| Property        | Type            | Required | Description                                             |
| --------------- | --------------- | -------- | ------------------------------------------------------- |
| `method`        | string          | ✅        | `get`, `post`, `put`, `delete`, `patch`                 |
| `path`          | string          | ✅        | Endpoint path (prefixed with `/api/addons/{addonname}`) |
| `handler`       | function        | ✅        | Express route handler                                   |
| `middleware`    | array           | ❌        | Additional middleware functions                         |
| `requiredRoles` | string\[]\|null | ❌        | Discord role IDs required to access                     |

***

## 🎨 Styling Guide

### Available CSS Classes

The dashboard uses Tailwind CSS with custom utility classes:

{% code expandable="true" %}

```jsx
// Card styles
<div className="glass-card p-6">         {/* Main card container */}
<div className="glass-subtle p-4">       {/* Subtle inner card */}

// Text colors
<h1 className="text-foreground">         {/* Primary text */}
<p className="text-muted-foreground">    {/* Secondary text */}

// Buttons
<button className="px-4 py-2 rounded-xl bg-primary text-primary-foreground">
    Primary Button
</button>

// Inputs
<input className="w-full h-10 px-4 rounded-xl glass-input text-sm" />

// Gradients
<div className="bg-gradient-to-br from-blue-500 to-blue-600">
```

{% endcode %}

### Responsive Design

```jsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
    {/* Cards auto-adjust based on screen size */}
</div>
```

***

## 🔌 API Endpoint Patterns

### Accessing Your API

Your API routes are available at:

```
/api/addons/{addonname-lowercase}/{path}
```

**Example:**

* Addon name: `MyAddon`
* Route path: `/stats`
* Full URL: `/api/addons/myaddon/stats`

### Making API Calls

{% code expandable="true" %}

```jsx
import axios from 'axios';

// GET request
const response = await axios.get('/api/addons/myaddon/stats');

// POST request
const response = await axios.post('/api/addons/myaddon/save', {
    setting: 'value'
});

// With error handling
try {
    const { data } = await axios.get('/api/addons/myaddon/stats');
    console.log(data);
} catch (error) {
    if (error.response?.status === 401) {
        console.log('Not authenticated');
    }
}
```

{% endcode %}

### Handler Examples

{% code expandable="true" %}

```javascript
apiRoutes: [
    // Simple GET
    {
        method: 'get',
        path: '/info',
        handler: async (req, res) => {
            res.json({ version: '1.0.0' });
        }
    },
    
    // With request body
    {
        method: 'post',
        path: '/settings',
        handler: async (req, res) => {
            const { enabled, message } = req.body;
            // Validate
            if (typeof enabled !== 'boolean') {
                return res.status(400).json({ error: 'Invalid enabled value' });
            }
            // Save and respond
            res.json({ success: true, enabled, message });
        }
    },
    
    // With URL parameters
    {
        method: 'get',
        path: '/user/:userId',
        handler: async (req, res) => {
            const { userId } = req.params;
            // Fetch user data...
            res.json({ userId, data: {} });
        }
    },
    
    // Database integration
    {
        method: 'get',
        path: '/guild-stats',
        handler: async (req, res) => {
            const mongoose = require('mongoose');
            const stats = await mongoose.model('GuildStats').find({});
            res.json({ stats });
        }
    }
]
```

{% endcode %}

***

## 🔐 Authentication & Permissions

### Using requiredRoles

The `requiredRoles` property accepts an array of Discord role IDs. Users must have **at least one** of the specified roles to access the resource.

```javascript
// Anyone logged in (no role restriction)
requiredRoles: null

// Require specific role(s)
requiredRoles: ['123456789012345678']

// Require any of multiple roles (OR logic)
requiredRoles: ['ROLE_ID1', 'ROLE_ID2', 'ROLE_ID3']
```

### Complete Example with Permissions

{% code expandable="true" %}

```javascript
module.exports = {
    pages: [
        {
            path: '/addon/admin-panel',
            component: 'admin',
            title: 'Admin Panel',
            // Only users with Admin or Owner role can access
            requiredRoles: ['111111111111111111', '222222222222222222']
        },
        {
            path: '/addon/admin-panel/public',
            component: 'public',
            title: 'Public Info',
            // Anyone logged in can access
            requiredRoles: null
        }
    ],
    
    navItems: [
        {
            name: 'Admin Panel',
            path: '/addon/admin-panel',
            emoji: '⚙️',
            // Only show in sidebar for admins
            requiredRoles: ['111111111111111111', '222222222222222222']
        }
    ],
    
    apiRoutes: [
        {
            method: 'get',
            path: '/public-data',
            requiredRoles: null,  // Public endpoint
            handler: async (req, res) => {
                res.json({ public: true });
            }
        },
        {
            method: 'post',
            path: '/admin-action',
            requiredRoles: ['111111111111111111'],  // Admin only
            handler: async (req, res) => {
                // Only admins reach here
                res.json({ success: true });
            }
        }
    ]
};
```

{% endcode %}

### Getting Role IDs

{% stepper %}
{% step %}
Enable Developer Mode in Discord:

* Settings → Advanced → Developer Mode
  {% endstep %}

{% step %}
Copy the role ID:

* Right-click the role → Copy Role ID
  {% endstep %}
  {% endstepper %}

### Checking User in API Handler

{% code expandable="true" %}

```javascript
{
    method: 'get',
    path: '/user-info',
    handler: async (req, res) => {
        // req.user is available after auth
        const user = req.user || req.session?.userData;
        
        if (!user) {
            return res.status(401).json({ error: 'Not authenticated' });
        }
        
        // Access user data
        res.json({ 
            userId: user.id,
            username: user.username,
            roles: user.roles  // Array of role IDs
        });
    }
}
```

{% endcode %}

***

## 🧩 Complete Examples

### Stats Dashboard

{% code expandable="true" %}

```javascript
// config.js
module.exports = {
    pages: [
        { path: '/addon/stats', component: 'index', title: 'Server Stats' }
    ],
    navItems: [
        { name: 'Stats', path: '/addon/stats', emoji: '📊', order: 10 }
    ],
    apiRoutes: [
        {
            method: 'get',
            path: '/overview',
            handler: async (req, res) => {
                const client = require('../../bot').getDiscordClient();
                
                res.json({
                    guilds: client.guilds.cache.size,
                    users: client.users.cache.size,
                    channels: client.channels.cache.size,
                    uptime: process.uptime()
                });
            }
        }
    ]
};
```

{% endcode %}

{% code expandable="true" %}

```jsx
// pages/index.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function StatsPage() {
    const [stats, setStats] = useState(null);

    useEffect(() => {
        axios.get('/api/addons/stats/overview')
            .then(res => setStats(res.data))
            .catch(console.error);
    }, []);

    if (!stats) return <div className="text-muted-foreground">Loading...</div>;

    return (
        <div className="px-4 lg:px-6">
            <h1 className="text-2xl font-bold text-foreground mb-6">Server Stats</h1>
            
            <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
                <StatCard label="Guilds" value={stats.guilds} emoji="🏠" />
                <StatCard label="Users" value={stats.users} emoji="👥" />
                <StatCard label="Channels" value={stats.channels} emoji="📺" />
                <StatCard label="Uptime" value={formatUptime(stats.uptime)} emoji="⏱️" />
            </div>
        </div>
    );
}

function StatCard({ label, value, emoji }) {
    return (
        <div className="glass-card p-4">
            <div className="text-2xl mb-2">{emoji}</div>
            <div className="text-2xl font-bold text-foreground">{value}</div>
            <div className="text-sm text-muted-foreground">{label}</div>
        </div>
    );
}

function formatUptime(seconds) {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    return `${hours}h ${minutes}m`;
}
```

{% endcode %}

### Settings Panel

{% code expandable="true" %}

```javascript
// config.js
module.exports = {
    pages: [
        { path: '/addon/my-settings', component: 'index', title: 'My Settings' }
    ],
    navItems: [
        { name: 'My Settings', path: '/addon/my-settings', emoji: '⚙️' }
    ],
    apiRoutes: [
        {
            method: 'get',
            path: '/config',
            handler: async (req, res) => {
                // Load from database or file
                res.json({
                    enabled: true,
                    welcomeMessage: 'Welcome!',
                    notifyChannel: '123456789'
                });
            }
        },
        {
            method: 'post',
            path: '/config',
            handler: async (req, res) => {
                const { enabled, welcomeMessage, notifyChannel } = req.body;
                // Save to database...
                res.json({ success: true });
            }
        }
    ]
};
```

{% endcode %}

{% code expandable="true" %}

```jsx
// pages/index.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function SettingsPage() {
    const [settings, setSettings] = useState({
        enabled: false,
        welcomeMessage: '',
        notifyChannel: ''
    });
    const [saving, setSaving] = useState(false);

    useEffect(() => {
        axios.get('/api/addons/my-settings/config')
            .then(res => setSettings(res.data))
            .catch(console.error);
    }, []);

    const handleSave = async () => {
        setSaving(true);
        try {
            await axios.post('/api/addons/my-settings/config', settings);
            alert('Settings saved!');
        } catch (error) {
            alert('Failed to save');
        } finally {
            setSaving(false);
        }
    };

    return (
        <div className="px-4 lg:px-6 space-y-6">
            <div className="glass-card p-6">
                <h1 className="text-xl font-bold text-foreground mb-6">Settings</h1>
                
                {/* Toggle */}
                <div className="flex items-center justify-between p-4 glass-subtle rounded-xl mb-4">
                    <div>
                        <h3 className="text-sm font-medium text-foreground">Enable Feature</h3>
                        <p className="text-xs text-muted-foreground">Toggle the feature on/off</p>
                    </div>
                    <button
                        onClick={() => setSettings(s => ({ ...s, enabled: !s.enabled }))}
                        className={`w-12 h-6 rounded-full transition-colors ${
                            settings.enabled ? 'bg-primary' : 'bg-secondary'
                        }`}
                    >
                        <span className={`block w-4 h-4 rounded-full bg-white transition-transform ${
                            settings.enabled ? 'translate-x-7' : 'translate-x-1'
                        }`} />
                    </button>
                </div>
                
                {/* Text Input */}
                <div className="p-4 glass-subtle rounded-xl mb-4">
                    <label className="text-sm font-medium text-foreground block mb-2">
                        Welcome Message
                    </label>
                    <input
                        type="text"
                        value={settings.welcomeMessage}
                        onChange={e => setSettings(s => ({ ...s, welcomeMessage: e.target.value }))}
                        className="w-full h-10 px-4 rounded-xl glass-input text-sm"
                        placeholder="Enter message..."
                    />
                </div>
                
                {/* Save Button */}
                <div className="flex justify-end">
                    <button
                        onClick={handleSave}
                        disabled={saving}
                        className="px-6 py-2.5 rounded-xl bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
                    >
                        {saving ? 'Saving...' : 'Save Settings'}
                    </button>
                </div>
            </div>
        </div>
    );
}
```

{% endcode %}

***

## 📋 Requirements Summary

### Minimum Required

```
addons/my-addon/
└── dashboard/
    ├── config.js      # Must export { pages, navItems, apiRoutes }
    └── pages/
        └── index.jsx  # At least one page component
```

### config.js Template

```javascript
module.exports = {
    pages: [],      // Required (can be empty)
    navItems: [],   // Required (can be empty)
    apiRoutes: []   // Required (can be empty)
};
```

### Page Component Template

```jsx
import React from 'react';

export default function MyPage() {
    return (
        <div className="px-4 lg:px-6">
            <div className="glass-card p-6">
                <h1 className="text-xl font-bold text-foreground">My Page</h1>
            </div>
        </div>
    );
}
```

***

## 🔄 Integration with Event API

Combine dashboard with event handlers:

**`addons/my-addon/my-addon.js`**

{% code expandable="true" %}

```javascript
module.exports = {
    name: 'MyAddon',
    version: '1.0.0',
    
    // Event handlers
    events: {
        'discord:messageCreate': async (eventData, context) => {
            // Handle Discord events
        }
    },
    
    // Initialization
    run: async (client, eventManager) => {
        console.log('[MyAddon] Loaded with dashboard support!');
    }
};
```

{% endcode %}

The dashboard config is automatically loaded from `dashboard/config.js` when the addon initializes.

***

## 💡 Tips & Best Practices

{% stepper %}
{% step %}
Use meaningful addon names — The addon name becomes part of the API URL
{% endstep %}

{% step %}
Handle loading states — Always show loading indicators
{% endstep %}

{% step %}
Error handling — Catch and display errors gracefully
{% endstep %}

{% step %}
API validation — Always validate request data on the backend
{% endstep %}

{% step %}
Permission checks — Use permissions for sensitive features
{% endstep %}
{% endstepper %}

***

## Troubleshooting

<details>

<summary>Page not loading</summary>

* Check browser console for errors
* Verify file exists in `dashboard/pages/`
* Ensure component has `export default`

</details>

<details>

<summary>API returns 404</summary>

* Check addon name is lowercase in URL
* Verify route is registered in config.js
* Check server logs for registration messages

</details>

<details>

<summary>Styles not applying</summary>

* Use existing Tailwind classes
* Check class names are correct
* Verify you're using the right CSS variable names

</details>

<details>

<summary>Authentication issues</summary>

* Ensure you're logged into the dashboard
* Check if route requires specific permissions
* Verify session is active

</details>

***
