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].