mdxui

Features

Composable feature components for SaaS applications

Features

The Features system provides high-level feature components that compose multiple views and panels into complete functional areas of your application. Features auto-wire to Payload CMS collections and implement common SaaS patterns for developer portals, team management, billing, and more.

Type Signature

interface FeatureComponent {
  /** Feature configuration */
  config?: FeatureConfig
  /** Auto-wire to Payload collection */
  collection?: string
  /** Child content/views */
  children?: React.ReactNode
}

interface FeatureConfig {
  /** Show/hide individual views */
  [key: string]: boolean | string | number | object
}

DeveloperDashboard Feature

The DeveloperDashboard feature provides a complete developer portal with API keys, webhooks, usage metrics, and request logs.

Type Definition

From app-types.ts:

interface DeveloperAppConfig {
  /** Show API keys section */
  showAPIKeys?: boolean        // default: true
  /** Show webhooks section */
  showWebhooks?: boolean       // default: true
  /** Show usage metrics */
  showUsage?: boolean          // default: true
  /** Show API logs */
  showLogs?: boolean           // default: true
  /** Show documentation */
  showDocs?: boolean           // default: true
  /** API base URL */
  apiBaseUrl?: string
  /** Available API versions */
  apiVersions?: string[]
}

Usage

<App name="Developer Portal">
  <Shell>
    <DeveloperDashboard
      config={{
        showAPIKeys: true,
        showWebhooks: true,
        showUsage: true,
        showLogs: true,
        apiBaseUrl: 'https://api.example.com',
        apiVersions: ['v1', 'v2']
      }}
    />
  </Shell>
</App>

Included Views

The DeveloperDashboard feature includes:

ViewRouteDescriptionAuto-wire Collection
Overview/Dashboard with API metricsanalytics
API Keys/keysManage API authentication keysapiKeys
Webhooks/webhooksConfigure webhook endpointswebhooks
Usage/usageAPI usage metrics and analyticsusage
Logs/logsAPI request/response logslogs

Example Implementation

import { DeveloperDashboard } from '@mdxui/cockpit'

// Full feature with all views
<DeveloperDashboard
  config={{
    showAPIKeys: true,
    showWebhooks: true,
    showUsage: true,
    showLogs: true
  }}
/>

// Minimal configuration (keys only)
<DeveloperDashboard
  config={{
    showAPIKeys: true,
    showWebhooks: false,
    showUsage: false,
    showLogs: false
  }}
/>

Auto-Wiring

The DeveloperDashboard automatically queries these Payload collections:

// Auto-wired collections
apiKeys      // API authentication keys
webhooks     // Webhook endpoints
usage        // Usage metrics by key/endpoint
logs         // Request/response logs

Each view component handles its own data fetching via the DataProvider.

Team Management Feature

The Team feature provides complete team member management with roles, permissions, and invitations.

Type Definition

From app-types.ts:

interface SaaSAppConfig {
  /** Show team management */
  showTeam?: boolean           // default: true
  /** Enable multi-tenancy */
  multiTenant?: boolean        // default: true
  /** Show workspace switcher */
  showWorkspaceSwitcher?: boolean  // default: true
}

Usage

<App name="Acme SaaS">
  <Shell>
    <TeamManagement
      config={{
        showInvites: true,
        showRoles: true,
        showPermissions: true,
        multiTenant: true
      }}
    />
  </Shell>
</App>

Included Components

ComponentPurposeIntegration
UsersTableList team membersWorkOS Users Management widget
InviteDialogSend team invitationsWorkOS invitations API
RolesManagerManage user rolesWorkOS roles
PermissionsEditorConfigure permissionsWorkOS permissions

WorkOS Integration

The Team feature integrates with WorkOS for enterprise-ready user management:

import { UsersManagement } from '@workos-inc/widgets'
import { useAuth } from '@workos-inc/authkit-react'

export function TeamPage() {
  const { getAccessToken, organizationId } = useAuth()

  return (
    <div>
      <h1>Team</h1>
      <p>Manage your team members and their permissions.</p>

      <UsersManagement
        authToken={getAccessToken}
        organizationId={organizationId}
      />
    </div>
  )
}

Permission Checks

Team management requires specific permissions:

const canManageTeam =
  hasOrganization &&
  permissions?.includes('widgets:users-table:manage')

Empty States

Handle different states appropriately:

{!user ? (
  <EmptyState
    message="Please sign in to manage your team."
  />
) : !hasOrganization ? (
  <EmptyState
    message="You need to be part of an organization to manage team members."
  />
) : !canManageTeam ? (
  <EmptyState
    message="You need admin permissions to manage team members."
  />
) : (
  <UsersManagement authToken={getAccessToken} />
)}

Billing Management Feature

The Billing feature provides subscription management, usage tracking, payment methods, and invoice history.

Type Definition

From app-types.ts:

interface SaaSAppConfig {
  /** Show billing section */
  showBilling?: boolean        // default: true
  /** Pricing tiers */
  pricingTiers?: string[]      // e.g., ['free', 'pro', 'enterprise']
}

Usage

<App name="Acme SaaS">
  <Shell>
    <BillingManagement
      config={{
        showSubscription: true,
        showUsage: true,
        showInvoices: true,
        showPaymentMethods: true,
        pricingTiers: ['starter', 'professional', 'enterprise']
      }}
    />
  </Shell>
</App>

Billing Sections

The Billing feature includes these sections:

1. Current Plan

Display subscription details:

<Card>
  <CardHeader>
    <div className="flex items-center justify-between">
      <CardTitle>Current Plan</CardTitle>
      <Badge variant={status === 'active' ? 'default' : 'secondary'}>
        {status}
      </Badge>
    </div>
    <CardDescription>
      Renews {formatDate(renewalDate)}
    </CardDescription>
  </CardHeader>
  <CardContent>
    <div className="flex items-baseline gap-2">
      <p className="text-3xl font-bold">{planName}</p>
      <p className="text-lg text-muted-foreground">
        ${planPrice}/mo
      </p>
    </div>
    <div className="space-y-2">
      {features.map(feature => (
        <div className="flex items-center gap-2">
          <Check className="h-4 w-4 text-primary" />
          {feature}
        </div>
      ))}
    </div>
    <Button variant="outline" className="w-full">
      Manage Plan
    </Button>
  </CardContent>
</Card>

2. Usage Breakdown

Track resource consumption:

<Card>
  <CardHeader>
    <CardTitle>Usage Breakdown</CardTitle>
    <CardDescription>
      Resource usage for the current billing period
    </CardDescription>
  </CardHeader>
  <CardContent>
    {usageBreakdown.map(item => {
      const percentage = (item.usage / item.limit) * 100

      return (
        <div key={item.resource} className="space-y-2">
          <div className="flex items-center justify-between">
            <div>
              <p className="font-medium">{item.resource}</p>
              <p className="text-sm text-muted-foreground">
                {formatUsage(item.usage, item.unit)} of {formatUsage(item.limit, item.unit)}
              </p>
            </div>
            <div className="text-right">
              <p className="font-medium">${item.cost.toFixed(2)}</p>
              <p className="text-sm text-muted-foreground">
                {percentage.toFixed(0)}% used
              </p>
            </div>
          </div>
          <Progress
            value={percentage}
            className={getUsageColor(percentage)}
          />
        </div>
      )
    })}
  </CardContent>
</Card>

3. Payment Method

Manage payment information:

<Card>
  <CardHeader>
    <CardTitle>
      <CreditCard className="h-4 w-4" />
      Payment Method
    </CardTitle>
  </CardHeader>
  <CardContent>
    <div className="flex items-center justify-between p-3 border rounded-md">
      <div className="flex items-center gap-3">
        <div className="h-8 w-12 bg-muted rounded flex items-center justify-center">
          {brand}
        </div>
        <div>
          <p className="font-medium">•••• {last4}</p>
          <p className="text-xs text-muted-foreground">
            Expires {expiryMonth}/{expiryYear}
          </p>
        </div>
      </div>
      <Badge variant="secondary">Default</Badge>
    </div>
    <Button variant="outline" className="w-full">
      Update
    </Button>
  </CardContent>
</Card>

4. Billing Alerts

Configure usage notifications:

<Card>
  <CardHeader>
    <CardTitle>
      <Bell className="h-4 w-4" />
      Billing Alerts
    </CardTitle>
  </CardHeader>
  <CardContent>
    <div className="flex items-start justify-between gap-4">
      <div className="space-y-1">
        <Label htmlFor="alerts">Usage alerts</Label>
        <p className="text-xs text-muted-foreground">
          Notify at {threshold}% usage
        </p>
      </div>
      <Switch
        id="alerts"
        checked={alertsEnabled}
        onCheckedChange={handleAlertsToggle}
      />
    </div>
  </CardContent>
</Card>

5. Invoice History

Display past invoices:

<Card>
  <CardHeader>
    <CardTitle>
      <Receipt className="h-5 w-5" />
      Invoice History
    </CardTitle>
  </CardHeader>
  <CardContent>
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Invoice</TableHead>
          <TableHead>Date</TableHead>
          <TableHead>Amount</TableHead>
          <TableHead>Status</TableHead>
          <TableHead></TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {invoices.map(invoice => (
          <TableRow key={invoice.id}>
            <TableCell className="font-mono">
              {invoice.invoiceNumber}
            </TableCell>
            <TableCell>{formatDate(invoice.date)}</TableCell>
            <TableCell className="font-medium">
              ${invoice.amount.toFixed(2)}
            </TableCell>
            <TableCell>
              <Badge variant={invoice.status === 'paid' ? 'default' : 'secondary'}>
                {invoice.status}
              </Badge>
            </TableCell>
            <TableCell>
              <Button
                variant="ghost"
                size="icon"
                onClick={() => handleDownloadInvoice(invoice.invoiceNumber)}
              >
                <Download className="h-4 w-4" />
              </Button>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  </CardContent>
</Card>

Usage Helpers

Utility functions for formatting billing data:

// Format usage numbers
function formatUsage(usage: number, unit: string): string {
  if (usage >= 1000) {
    return `${(usage / 1000).toFixed(1)}k ${unit}`
  }
  return `${usage} ${unit}`
}

// Calculate usage percentage
function getUsagePercentage(usage: number, limit: number): number {
  return Math.min((usage / limit) * 100, 100)
}

// Get color based on usage
function getUsageColor(percentage: number): string {
  if (percentage >= 90) return 'bg-destructive'
  if (percentage >= 75) return 'bg-yellow-500'
  return ''
}

// Format dates
function formatRelativeDate(dateString: string): string {
  const date = new Date(dateString)
  return date.toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric'
  })
}

Settings Sections Feature

The Settings feature provides comprehensive application configuration organized into logical sections.

Type Definition

interface SettingsConfig {
  /** Settings sections to show */
  sections?: SettingsSection[]
  /** Default section */
  defaultSection?: string
}

interface SettingsSection {
  id: string
  label: string
  icon?: string
  component: React.ComponentType
}

Common Settings Sections

1. General Settings

<SettingsSection id="general" label="General">
  <Card>
    <CardHeader>
      <CardTitle>Organization Name</CardTitle>
      <CardDescription>
        Update your organization's display name
      </CardDescription>
    </CardHeader>
    <CardContent>
      <Input value={orgName} onChange={handleNameChange} />
    </CardContent>
  </Card>
</SettingsSection>

2. Profile Settings

<SettingsSection id="profile" label="Profile">
  <Card>
    <CardHeader>
      <CardTitle>Personal Information</CardTitle>
    </CardHeader>
    <CardContent className="space-y-4">
      <FormField label="Name" name="name" />
      <FormField label="Email" name="email" type="email" />
      <FormField label="Avatar" name="avatar" type="file" />
    </CardContent>
  </Card>
</SettingsSection>

3. Security Settings

<SettingsSection id="security" label="Security">
  <Card>
    <CardHeader>
      <CardTitle>Two-Factor Authentication</CardTitle>
      <CardDescription>
        Add an extra layer of security to your account
      </CardDescription>
    </CardHeader>
    <CardContent>
      <Switch
        checked={twoFactorEnabled}
        onCheckedChange={handleToggle2FA}
      />
    </CardContent>
  </Card>
</SettingsSection>

4. Notifications Settings

<SettingsSection id="notifications" label="Notifications">
  <Card>
    <CardHeader>
      <CardTitle>Email Notifications</CardTitle>
    </CardHeader>
    <CardContent className="space-y-4">
      <div className="flex items-center justify-between">
        <Label>Product updates</Label>
        <Switch />
      </div>
      <div className="flex items-center justify-between">
        <Label>Security alerts</Label>
        <Switch defaultChecked />
      </div>
      <div className="flex items-center justify-between">
        <Label>Billing notifications</Label>
        <Switch defaultChecked />
      </div>
    </CardContent>
  </Card>
</SettingsSection>

5. API Settings

<SettingsSection id="api" label="API">
  <Card>
    <CardHeader>
      <CardTitle>API Configuration</CardTitle>
      <CardDescription>
        Manage API access and webhooks
      </CardDescription>
    </CardHeader>
    <CardContent>
      <Link to="/api-keys">
        <Button variant="outline" className="w-full">
          Manage API Keys
        </Button>
      </Link>
      <Link to="/webhooks">
        <Button variant="outline" className="w-full">
          Configure Webhooks
        </Button>
      </Link>
    </CardContent>
  </Card>
</SettingsSection>

Settings Layout

Use a sidebar navigation for multiple sections:

<SettingsView layout="column">
  <div className="flex gap-6">
    {/* Sidebar */}
    <nav className="w-48 space-y-1">
      {sections.map(section => (
        <Button
          key={section.id}
          variant={activeSection === section.id ? 'secondary' : 'ghost'}
          className="w-full justify-start"
          onClick={() => setActiveSection(section.id)}
        >
          {section.icon && <Icon name={section.icon} />}
          {section.label}
        </Button>
      ))}
    </nav>

    {/* Content */}
    <div className="flex-1">
      {renderSection(activeSection)}
    </div>
  </div>
</SettingsView>

Auto-Wiring to Payload Collections

Features automatically connect to Payload CMS collections for data management.

Collection Mapping

Features map to Payload collections:

const FEATURE_COLLECTIONS = {
  DeveloperDashboard: {
    apiKeys: 'apiKeys',
    webhooks: 'webhooks',
    usage: 'usage',
    logs: 'logs'
  },
  TeamManagement: {
    users: 'users',
    invites: 'invites',
    roles: 'roles',
    permissions: 'permissions'
  },
  BillingManagement: {
    subscriptions: 'subscriptions',
    invoices: 'invoices',
    paymentMethods: 'paymentMethods',
    usage: 'usage'
  }
}

DataProvider Pattern

Features use the DataProvider to fetch collection data:

import { useDataProvider } from '@mdxui/core'

export function APIKeysView() {
  const dataProvider = useDataProvider()
  const [keys, setKeys] = useState([])

  useEffect(() => {
    dataProvider.getList('apiKeys', {
      pagination: { page: 1, perPage: 10 },
      sort: { field: 'created', order: 'DESC' }
    }).then(({ data }) => setKeys(data))
  }, [])

  return (
    <div>
      {keys.map(key => (
        <KeyCard key={key.id} {...key} />
      ))}
    </div>
  )
}

Explicit Collection Override

Override the default collection:

<DeveloperDashboard
  config={{
    showAPIKeys: true,
    apiKeysCollection: 'custom_api_keys'  // Override default
  }}
/>

Feature Composition Patterns

Compose multiple features in your application:

1. Side-by-Side Features

Display features in columns:

<App name="Full Stack Dashboard">
  <Shell>
    <View layout="column">
      <div className="grid grid-cols-2 gap-6">
        <DeveloperDashboard
          config={{ showAPIKeys: true, showWebhooks: true }}
        />
        <TeamManagement
          config={{ showInvites: true }}
        />
      </div>
    </View>
  </Shell>
</App>

2. Tabbed Features

Organize features in tabs:

<App name="Admin Portal">
  <Shell>
    <Tabs defaultValue="developer">
      <TabsList>
        <TabsTrigger value="developer">Developer</TabsTrigger>
        <TabsTrigger value="team">Team</TabsTrigger>
        <TabsTrigger value="billing">Billing</TabsTrigger>
      </TabsList>

      <TabsContent value="developer">
        <DeveloperDashboard />
      </TabsContent>

      <TabsContent value="team">
        <TeamManagement />
      </TabsContent>

      <TabsContent value="billing">
        <BillingManagement />
      </TabsContent>
    </Tabs>
  </Shell>
</App>

3. Routed Features

Use React Router for feature navigation:

import { Routes, Route } from 'react-router-dom'

<App name="SaaS Platform">
  <Shell>
    <Routes>
      <Route path="/" element={<DashboardView />} />
      <Route path="/api/*" element={<DeveloperDashboard />} />
      <Route path="/team" element={<TeamManagement />} />
      <Route path="/billing" element={<BillingManagement />} />
      <Route path="/settings/*" element={<SettingsView />} />
    </Routes>
  </Shell>
</App>

4. Conditional Features

Show features based on permissions:

<App name="Multi-Tenant SaaS">
  <Shell>
    {hasPermission('developer:view') && (
      <DeveloperDashboard />
    )}

    {hasPermission('team:manage') && (
      <TeamManagement />
    )}

    {hasPermission('billing:view') && (
      <BillingManagement />
    )}
  </Shell>
</App>

5. Nested Features

Embed features within other features:

<App name="Enterprise Platform">
  <Shell>
    <DashboardView>
      <div className="grid gap-6">
        {/* Quick access to features */}
        <Card>
          <CardHeader>
            <CardTitle>API Overview</CardTitle>
          </CardHeader>
          <CardContent>
            <DeveloperDashboard
              config={{
                showAPIKeys: true,
                showWebhooks: false,
                compact: true
              }}
            />
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>Team Status</CardTitle>
          </CardHeader>
          <CardContent>
            <TeamManagement
              config={{
                showInvites: true,
                compact: true
              }}
            />
          </CardContent>
        </Card>
      </div>
    </DashboardView>
  </Shell>
</App>

Feature Extensions

Extend features with custom views:

import { DeveloperDashboard } from '@mdxui/cockpit'

// Extend with custom view
<DeveloperDashboard
  config={{
    showAPIKeys: true,
    showWebhooks: true
  }}
>
  {/* Add custom views */}
  <CustomAnalyticsView />
  <CustomReportsView />
</DeveloperDashboard>

Best Practices

1. Progressive Disclosure

Start with essential features, add more as needed:

// Good - Start simple
<DeveloperDashboard
  config={{
    showAPIKeys: true,
    showWebhooks: false,  // Add later when needed
    showLogs: false
  }}
/>

2. Consistent Navigation

Place related features together:

<Sidebar>
  <NavSection label="Developer">
    <NavItem href="/api-keys">API Keys</NavItem>
    <NavItem href="/webhooks">Webhooks</NavItem>
    <NavItem href="/usage">Usage</NavItem>
  </NavSection>

  <NavSection label="Organization">
    <NavItem href="/team">Team</NavItem>
    <NavItem href="/billing">Billing</NavItem>
  </NavSection>
</Sidebar>

3. Permission Gating

Always check permissions before showing features:

{hasPermission('billing:view') ? (
  <BillingManagement />
) : (
  <EmptyState
    message="You don't have access to billing."
  />
)}

4. Loading States

Handle async data loading:

{isLoading ? (
  <FeatureSkeleton />
) : (
  <DeveloperDashboard />
)}

5. Error Handling

Gracefully handle feature errors:

<ErrorBoundary
  fallback={<FeatureError message="Failed to load developer dashboard" />}
>
  <DeveloperDashboard />
</ErrorBoundary>

On this page