Skip to main content

Frontend Components

Build React components for your plugin's admin UI.

Structure

frontend/
├── index.ts # Exports
├── pages/
│ └── MyPluginPage.tsx # Main page
├── components/
│ └── MyWidget.tsx # Reusable components
└── hooks/
└── useMyPlugin.ts # Custom hooks

Page Component

// pages/MyPluginPage.tsx
import React from 'react';
import { Box, Typography, Card, Button } from '@mui/material';
import { useQuery } from '@apollo/client';
import { MY_PLUGIN_QUERY } from '../queries';

export const MyPluginPage: React.FC = () => {
const { data, loading } = useQuery(MY_PLUGIN_QUERY);

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

return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
My Plugin
</Typography>
<Card sx={{ p: 2 }}>
{data?.myPluginItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</Card>
</Box>
);
};

GraphQL Queries

// queries.ts
import { gql } from '@apollo/client';

export const MY_PLUGIN_QUERY = gql`
query MyPluginItems {
myPluginItems {
id
name
value
}
}
`;

export const CREATE_ITEM = gql`
mutation CreateItem($name: String!, $value: Int!) {
createMyPluginItem(name: $name, value: $value) {
id
name
}
}
`;

Custom Hooks

// hooks/useMyPlugin.ts
import { useQuery, useMutation } from '@apollo/client';
import { MY_PLUGIN_QUERY, CREATE_ITEM } from '../queries';

export const useMyPlugin = () => {
const { data, loading, refetch } = useQuery(MY_PLUGIN_QUERY);
const [createItem] = useMutation(CREATE_ITEM, {
onCompleted: () => refetch()
});

return {
items: data?.myPluginItems ?? [],
loading,
createItem: (name: string, value: number) =>
createItem({ variables: { name, value } })
};
};

Using MUI Components

Follow the existing design system:

import {
Box,
Card,
Typography,
Button,
TextField,
Table,
TableBody,
TableCell,
TableHead,
TableRow
} from '@mui/material';

Routing

Plugin pages are automatically routed to /admin/plugins/[plugin-name].